Compare commits
45 Commits
tmp/tmp-01
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 695d27c5cb | |||
| cd82ba2f27 | |||
| 698d419525 | |||
| a252d42b62 | |||
| 69466f410c | |||
| 39245dfcac | |||
| 80b18cee0f | |||
| b2688dc8a1 | |||
| d5ca77e2d8 | |||
| e789ab2ca3 | |||
| 5e83771193 | |||
| 0d18041037 | |||
| 875cf0b428 | |||
| e16402c9c1 | |||
| dee2887026 | |||
| 6033cc2fa0 | |||
| c1fbabe07b | |||
| 221ef7bb6d | |||
| 55c1e80ed7 | |||
| 79202c4542 | |||
| 3bff91e421 | |||
| 979384c2d6 | |||
| 504e5322fb | |||
| 25fc0a59a8 | |||
| 34e27d0621 | |||
| 913fc90176 | |||
| c1bac6d9e8 | |||
| 31382a2388 | |||
| d009077183 | |||
| 133eb29a71 | |||
| 4e480d35df | |||
| bc6d243362 | |||
| a285706112 | |||
| 9c04c031ab | |||
| 0ce929f8a1 | |||
| 61ab9f11bd | |||
| 2747cb49ae | |||
| 9c8ebf8d74 | |||
| e19fef785f | |||
| 92ae114c5c | |||
| b25c366a5b | |||
| b23c21d05b | |||
| 65a24f25bb | |||
| 2a0935b581 | |||
| b31197a8b4 |
@@ -42,18 +42,25 @@ const localDevConfig = ({
|
||||
uniacid: 2,
|
||||
domain: 'http://localhost:8050/',
|
||||
},
|
||||
'100': {
|
||||
uniacid: 100,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'1': {
|
||||
uniacid: 1,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'115': {
|
||||
uniacid: 115,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
})['2811']; // 选择要使用的环境配置
|
||||
|
||||
'100': {
|
||||
uniacid: 100,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'1': {
|
||||
uniacid: 1,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'115': {
|
||||
uniacid: 115,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'47': { // 修改弹窗
|
||||
uniacid: 47,
|
||||
domain: 'https://5g.aigc-quickapp.com/',
|
||||
},
|
||||
'2075': { //
|
||||
uniacid: 2075,
|
||||
domain: 'https://xcx.aigc-quickapp.com/',
|
||||
},
|
||||
})['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
|
||||
},
|
||||
|
||||
@@ -18,22 +18,15 @@ try {
|
||||
|
||||
// 线上版本部署域名, 注意: 必须以 '/' 结尾,适用于小程序/H5/快应用等场景
|
||||
// 默认域名, 自定义发行时可以修改
|
||||
let defaultDomain = '';
|
||||
let defaultDomain = '';
|
||||
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM
|
||||
defaultDomain = 'https://xcx20.5g-quickapp.com';
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_XCX_AIGC_QUICKAPP_COM
|
||||
defaultDomain = 'https://xcx.aigc-quickapp.com/';
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_5G_AIGC_QUICKAPP_COM
|
||||
defaultDomain = 'https://5g.aigc-quickapp.com/';
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_TEST_AIGC_QUICKAPP_COM
|
||||
defaultDomain = 'https://test.aigc-quickapp.com/';
|
||||
// #ifdef H5
|
||||
// 域名由 vue.config.js 中 DefinePlugin 注入的 process.env.UNI_APP_DOMAIN 传入
|
||||
try {
|
||||
if (process.env.UNI_APP_DOMAIN) {
|
||||
defaultDomain = process.env.UNI_APP_DOMAIN;
|
||||
}
|
||||
} catch (e) {}
|
||||
// #endif
|
||||
|
||||
// 确保域名以 '/' 结尾
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ function processRoutePath(route) {
|
||||
}
|
||||
}
|
||||
// ---- 处理页面目录映射 <end>----
|
||||
|
||||
|
||||
// 去掉pages目录,只保留子目录
|
||||
if (routeParts[0] === 'pages') {
|
||||
routeParts = routeParts.slice(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return routeParts.join("/");
|
||||
}
|
||||
|
||||
@@ -54,11 +54,14 @@ function loadLangPackSync(lang, path) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前本地语言
|
||||
* @returns
|
||||
*/
|
||||
function getCurrentLocale() {
|
||||
return uni.getStorageSync('lang') || "zh-cn";
|
||||
return uni.getStorageSync('lang') || "zh-cn";
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
langList: langConfig.langList,
|
||||
|
||||
@@ -77,18 +80,18 @@ export default {
|
||||
if (!_page) return;
|
||||
|
||||
const locale = getCurrentLocale(); // 获得当前本地语言
|
||||
|
||||
|
||||
let value = ''; // 存放解析后的语言值
|
||||
let langPath = ''; // 存放当前页面语言包路径
|
||||
|
||||
try {
|
||||
//公共语言包(同步加载)
|
||||
var lang = loadLangPackSync(locale, 'common');
|
||||
|
||||
|
||||
//当前页面语言包(同步加载)
|
||||
let route = _page.route;
|
||||
langPath = processRoutePath(route);
|
||||
|
||||
|
||||
// 加载当前页面语言包
|
||||
let currentPageLang = loadLangPackSync(locale, langPath);
|
||||
|
||||
@@ -113,7 +116,7 @@ export default {
|
||||
} else {
|
||||
value = mergedLang[field] !== undefined ? mergedLang[field] : field;
|
||||
}
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error('解析语言包失败:', e, { langPath, field, locale });
|
||||
value = field;
|
||||
@@ -129,7 +132,7 @@ export default {
|
||||
|
||||
// 多语言调试,注释后可以关闭控制台输出
|
||||
if (field == value) {
|
||||
console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`);
|
||||
console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`);
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -165,7 +168,7 @@ export default {
|
||||
if (!_page) return;
|
||||
|
||||
const locale = getCurrentLocale();
|
||||
|
||||
|
||||
this.title(this.lang("title"));
|
||||
|
||||
//设置tabbar的文字语言
|
||||
@@ -215,4 +218,4 @@ export default {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +622,7 @@ export default {
|
||||
success: () => {
|
||||
},
|
||||
fail: (err) => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fallbackFunc && fallbackFunc();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
25
package.json
25
package.json
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"scripts": {
|
||||
"mp-weixin": "node scripts/mp-weixin.patch.js",
|
||||
"mp-weixin:patch": "node scripts/mp-weixin.patch.js --no-zip",
|
||||
"mp-weixin:dev": "node scripts/mp-weixin.patch.js --mode development",
|
||||
"mp-weixin:dev:patch": "node scripts/mp-weixin.patch.js --mode development --no-zip"
|
||||
"mp-weixin": "node scripts/mp-weixin.patch.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dart-sass": "^1.25.0",
|
||||
@@ -28,44 +25,40 @@
|
||||
"h5-xcx20.5g-quickapp.com": {
|
||||
"title": "H5-xcx20.5g-quickapp.com",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5"
|
||||
"UNI_PLATFORM": "h5",
|
||||
"UNI_APP_DOMAIN": "https://xcx20.5g-quickapp.com/"
|
||||
},
|
||||
"define": {
|
||||
"H5_XCX_5G_QUICKAPP_COM": true,
|
||||
"H5_PRODUCTION": true,
|
||||
"PRODUCTION": true
|
||||
}
|
||||
},
|
||||
"h5-xcx.aigc-quickapp.com": {
|
||||
"title": "H5-xcx.aigc-quickapp.com",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5"
|
||||
"UNI_PLATFORM": "h5",
|
||||
"UNI_APP_DOMAIN": "https://xcx.aigc-quickapp.com/"
|
||||
},
|
||||
"define": {
|
||||
"H5_XCX_AIGC_QUICKAPP_COM": true,
|
||||
"H5_PRODUCTION": true,
|
||||
"PRODUCTION": true
|
||||
}
|
||||
},
|
||||
"h5-5g.aigc-quickapp.com": {
|
||||
"title": "H5-5g.aigc-quickapp.com",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5"
|
||||
"UNI_PLATFORM": "h5",
|
||||
"UNI_APP_DOMAIN": "https://5g.aigc-quickapp.com/"
|
||||
},
|
||||
"define": {
|
||||
"H5_5G_AIGC_QUICKAPP_COM": true,
|
||||
"H5_PRODUCTION": true,
|
||||
"PRODUCTION": true
|
||||
}
|
||||
},
|
||||
"h5-test.aigc-quickapp.com": {
|
||||
"title": "H5-test.aigc-quickapp.com",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5"
|
||||
"UNI_PLATFORM": "h5",
|
||||
"UNI_APP_DOMAIN": "https://test.aigc-quickapp.com/"
|
||||
},
|
||||
"define": {
|
||||
"H5_TEST_AIGC_QUICKAPP_COM": true,
|
||||
"H5_PRODUCTION": true,
|
||||
"PRODUCTION": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
{
|
||||
"path": "ai-chat/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI 客服"
|
||||
"navigationBarTitleText": "AI智能客服"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<image :src="$util.img(siteInfo.logo_square)" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="info-box" :style="{ color: '#ffffff' }">
|
||||
<text class="font-size-base">{{ siteInfo.site_name }}</text>
|
||||
<text class="font-size-base" @tap="onTitleTap">{{ siteInfo.site_name }}</text>
|
||||
<text>{{ followOfficialAccount.welcomeMsg }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -141,6 +141,8 @@
|
||||
<privacy-popup ref="privacyPopup"></privacy-popup>
|
||||
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
|
||||
<ns-login ref="login"></ns-login>
|
||||
<!-- 版本信息触发区(固定定位,不可见,始终可点击) -->
|
||||
<view class="version-trigger" @tap="onTitleTap"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -243,4 +245,14 @@ export default {
|
||||
bottom: 180rpx !important;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.version-trigger {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 50rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
z-index: 999;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -91,6 +91,39 @@ export default {
|
||||
|
||||
// 阻止事件继续传播
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* 标题连击 5 次显示构建版本信息(供测试人员使用)
|
||||
*/
|
||||
onTitleTap() {
|
||||
this._titleTapCount = (this._titleTapCount || 0) + 1;
|
||||
if (this._titleTapTimer) {
|
||||
clearTimeout(this._titleTapTimer);
|
||||
}
|
||||
if (this._titleTapCount >= 5) {
|
||||
this._titleTapCount = 0;
|
||||
this._showVersionInfo();
|
||||
return;
|
||||
}
|
||||
// 2 秒内未达到 5 次则重置计数
|
||||
this._titleTapTimer = setTimeout(() => {
|
||||
this._titleTapCount = 0;
|
||||
}, 2000);
|
||||
},
|
||||
_showVersionInfo() {
|
||||
let v = null;
|
||||
try {
|
||||
v = typeof __app_version !== 'undefined' ? __app_version : null;
|
||||
} catch (e) {}
|
||||
if (v && v.commitId) {
|
||||
uni.showModal({
|
||||
title: '构建版本信息',
|
||||
content: `提交 ID: ${v.commitId}\n构建时间: ${v.buildTime}`,
|
||||
showCancel: false
|
||||
});
|
||||
} else {
|
||||
console.log('未获取到构建版本信息');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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占位样式 */
|
||||
|
||||
119
readme.md
119
readme.md
@@ -40,22 +40,20 @@
|
||||
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
|
||||
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
|
||||
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
|
||||
3. 打开资源管理器→项目根目录,右键选择「在终端中打开」,执行命令:
|
||||
```bash
|
||||
npm run mp-weixin
|
||||
```
|
||||
3. 构建完成后,**自动执行**补丁脚本(拷贝 site.js / config 文件 + 生成 ZIP 包),无需手动操作
|
||||
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-weixin 压缩包
|
||||
💡 **示例**:`mp-weixin-2026-01-23-1769152056146.zip`
|
||||
💡 **示例**:`mp-weixin-2026-05-11-3bff91e.zip`
|
||||
*压缩包文件名末尾为 Git 提交 ID*
|
||||
*同时该目录下会生成未压缩的 `mp-weixin` 目录*
|
||||
|
||||
##### 4.1.1.3 【验证与重命名】
|
||||
1. 打开未压缩的 `mp-weixin` 目录,找到 `site.js` 文件,将文件内的 `uniacid` 值改为当前客户编号(如:2812)并保存
|
||||
2. 打开微信开发者工具,导入「mp-weixin」目录,点击「编译」,在开发者工具控制台验证有无报错,以及能否返回对应客户的业务数据
|
||||
3. 确保无误后将生成的 mp-weixin 压缩包重命名
|
||||
- **定制化版**格式:`定制化-客户编号-域名-mp-weixin-当前日期-生成编号.zip`
|
||||
- **定制化版**格式:`定制化-客户编号-域名-mp-weixin-当前日期-Git提交ID.zip`
|
||||
- **通用版**无需重命名
|
||||
|
||||
💡 **示例**:`custom-2812-xcx.aigc-quickapp.com-mp-weixin-2026-01-22-1769152056146.zip`
|
||||
💡 **示例**:`custom-2812-xcx.aigc-quickapp.com-mp-weixin-2026-05-11-3bff91e.zip`
|
||||
|
||||
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
|
||||
|
||||
@@ -63,10 +61,10 @@
|
||||
1. 将重命名后的文件发送给技术人员
|
||||
*📌通用版直接将生成的 mp-weixin 压缩包发送给技术人员*
|
||||
2. **技术人员操作流程**:
|
||||
- 解压压缩包
|
||||
- 确认 `site.js` 中的 `uniacid` 为客户编号
|
||||
- 用微信开发者工具导入解压后的代码目录
|
||||
- 编译验证无误后,上传发布
|
||||
- 解压压缩包
|
||||
- 确认 `site.js` 中的 `uniacid` 为客户编号
|
||||
- 用微信开发者工具导入解压后的代码目录
|
||||
- 编译验证无误后,上传发布
|
||||
##### 4.1.1.5 【本地设置】
|
||||

|
||||
#####
|
||||
@@ -85,66 +83,93 @@
|
||||
📌 **测试版**保留原有 `/hwappx/test/`
|
||||
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
|
||||
|
||||
3. 选择菜单栏「发行」 → 「自定义发行」 →(「H5-xcx.aigc-quickapp.com」查看.local.config.js中站点的网址前缀,站点的网址前缀是什么就选自定义发行中相同的格式)
|
||||
例如:网址是5g.aigc-quickapp.com,自定义发行就选H5-5g.aigc-quickapp.com
|
||||
网址是dev.aigc-quickapp.com,自定义发行就选H5-xcx.aigc-quickapp.com
|
||||
3. 选择菜单栏「发行」 → 「自定义发行」 → 选择对应的站点脚本
|
||||
💡 例如:网址是 `5g.aigc-quickapp.com` 选 `H5-5g.aigc-quickapp.com`
|
||||
4. 修改以下配置:
|
||||
1. **网站标题**:快应用
|
||||
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
|
||||
2. **网站域名**:当前客户域名
|
||||
3. 确认后点击「发行」等待构建完成
|
||||
|
||||
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
|
||||
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
|
||||
5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
|
||||
*仅打包文件,不包含外层 web 目录*
|
||||
⚠️ **注意**:构建完成后自动输出到 `unpackage/dist/build/h5/`,格式为 `{域名}-{base路径}.zip`
|
||||
💡 **示例**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
|
||||
|
||||
##### 4.2.1.2 【重命名】
|
||||
1. 按版本类型规范重命名压缩包:
|
||||
- **通用版**:`hwappx-common-域名-时间.zip`
|
||||
💡 **示例**:`hwappx-common-xcx.aigc-quickapp.com-2026-01-24.zip`
|
||||
|
||||
- **定制化版**:`客户名称-定制化---hwappx-客户编号-域名-时间.zip`
|
||||
💡 **示例**:`POCT检测分析平台-定制化---hwappx-2811-xcx.aigc-quickapp.com-2026-01-24.zip`
|
||||
|
||||
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
|
||||
|
||||
##### 4.2.1.3 【交付与最终发布】🔍
|
||||
|
||||
1. 将重命名后的压缩包发送给运维人员
|
||||
##### 4.2.1.2 【交付与最终发布】🔍
|
||||
1. 将生成的 `.zip` 文件发送给运维人员
|
||||
2. **运维人员操作流程**:
|
||||
1. 解压压缩包
|
||||
2. 打开快应用开发者工具,导入解压后的代码目录
|
||||
3. 验证代码无报错后,执行上传发布操作
|
||||
##### 4.2.1.3 【测试流程】
|
||||
|
||||
##### 4.2.1.3 【测试流程】
|
||||
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
|
||||
如果是test就把hwappx/common/改为hwappx/test/----------改站点
|
||||
|
||||
---
|
||||
|
||||
## 5. 📝 构建脚本说明
|
||||
## 5. 📝 构建与版本信息
|
||||
|
||||
### 5.1 可用的 npm 脚本命令
|
||||
### 5.1 自动注入机制
|
||||
|
||||
| 命令 | 说明 |
|
||||
生产构建时,`vue.config.js` 中的 `VersionInjectPlugin` 会自动执行以下操作:
|
||||
|
||||
| 操作 | 适用平台 | 说明 |
|
||||
|------|---------|------|
|
||||
| `__app_version` 全局变量注入 | 所有平台 | 构建产物的 `vendor.js`(小程序)或 `index.html`(H5)中自动内联版本数据 |
|
||||
| 构建版本信息查看 | 所有平台 | 首页右下角连击 5 次弹出模态框显示 `commitId` 和 `buildTime` |
|
||||
|
||||
版本数据包含:
|
||||
- **commitId**:当前构建的 Git 短提交 ID
|
||||
- **buildTime**:构建时间
|
||||
|
||||
> **开发模式**:HMR 热更新时由 webpack emit 钩子自动处理,不影响开发体验。
|
||||
|
||||
### 5.2 自动 ZIP 打包
|
||||
|
||||
**H5 自定义发行**构建完成后自动执行:
|
||||
|
||||
| 步骤 | 说明 |
|
||||
|------|------|
|
||||
| `npm run mp-weixin` | 默认构建(production模式,包含ZIP文件) |
|
||||
| `npm run mp-weixin:patch` | 只打补丁(production模式,不创建ZIP文件) |
|
||||
| `npm run mp-weixin:dev` | 开发模式构建(development模式,包含ZIP文件) |
|
||||
| `npm run mp-weixin:dev:patch` | 只打补丁(development模式,不创建ZIP文件) |
|
||||
| 重命名输出目录 | `{域名}-{base路径用.连接}`(如 `xcx.aigc-quickapp.com-hwappx.common`) |
|
||||
| 创建 ZIP 文件 | 文件名格式:`{目录名}-{Git提交ID}-{上海时区时间戳}.zip` |
|
||||
| 清理 | ZIP 创建完成后自动删除源目录 |
|
||||
|
||||
### 5.2 构建脚本功能
|
||||
💡 **示例 ZIP**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
|
||||
|
||||
- 复制 `project.config.json` 和 `project.private.config.json` 文件到构建目录
|
||||
- 复制 `site.js` 文件到构建目录
|
||||
- 在 `vendor.js` 文件开头追加 `import site from "../site.js";` 语句
|
||||
- 支持创建构建结果的 ZIP 压缩包
|
||||
- 自动打开 ZIP 文件所在目录
|
||||
**微信小程序**生产构建时自动执行:
|
||||
- 拷贝 `site.js` / `project.config.json` / `project.private.config.json` 到构建目录
|
||||
- 创建 ZIP 包(文件名包含 Git 提交 ID)
|
||||
|
||||
### 5.3 环境配置
|
||||
|
||||
域名配置统一通过 `package.json` 中各脚本的 `env.UNI_APP_DOMAIN` 传入:
|
||||
|
||||
```json
|
||||
"h5-xcx.aigc-quickapp.com": {
|
||||
"title": "...",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5",
|
||||
"UNI_APP_DOMAIN": "https://xcx.aigc-quickapp.com/"
|
||||
},
|
||||
"define": { "PRODUCTION": true }
|
||||
}
|
||||
```
|
||||
|
||||
`config.js` 和 `vue.config.js` 均通过 `process.env.UNI_APP_DOMAIN` 读取,**新增/删除脚本无需改动代码**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 🔄 版本历史
|
||||
|
||||
### 6.1 v1.3
|
||||
### 6.1 v1.5.1
|
||||
|
||||
- `VersionInjectPlugin`:webpack emit 钩子自动注入 `__app_version` 到所有平台产物
|
||||
- 首页右下角连击 5 次显示构建版本信息(commitId + buildTime)
|
||||
- `mp-weixin.patch.js` 精简为仅文件拷贝和 ZIP 打包
|
||||
- 修复 `openWxWorkServiceChat` 方法中 fail 回调语法错误
|
||||
- ZIP 文件名末尾使用 Git 提交 ID 替换时间戳
|
||||
- 构建脚本幂等性修复,支持多次运行不产生重复注入
|
||||
|
||||
### 6.2 v1.3
|
||||
- 修复微信小程序构建脚本,支持复制 `project.config.json` 和 `project.private.config.json` 文件
|
||||
- 增强构建脚本功能,添加命令行参数支持
|
||||
- 在 `package.json` 中添加相关 npm 脚本命令
|
||||
|
||||
@@ -18,9 +18,18 @@ async function createZipWithSystemCommand(sourceDir, outputDir) {
|
||||
throw new Error(`源目录不存在: ${sourceDir}`);
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
// 获取当前 Git 提交 ID(短 hash)
|
||||
let commitId = 'unknown';
|
||||
try {
|
||||
const { stdout } = await execAsync('git rev-parse --short HEAD', { cwd: path.join(__dirname, '..') });
|
||||
commitId = stdout.trim();
|
||||
} catch (e) {
|
||||
console.warn('无法获取 Git 提交 ID,使用 unknown 代替');
|
||||
}
|
||||
|
||||
// 生成文件名:mp-weixin-<日期>-<提交ID>.zip
|
||||
const dateString = new Date().toISOString().split('T')[0];
|
||||
const zipFileName = `mp-weixin-${dateString}-${new Date().getTime()}.zip`;
|
||||
const zipFileName = `mp-weixin-${dateString}-${commitId}.zip`;
|
||||
const zipFilePath = path.join(outputDir, zipFileName);
|
||||
|
||||
let command;
|
||||
|
||||
@@ -2,21 +2,16 @@
|
||||
/**
|
||||
* fix-wechat-miniapp.js
|
||||
*
|
||||
* 该脚本用于修复微信小程序的一些问题:
|
||||
* 1. 将项目根目录下的 site.js 文件拷贝到 unpackage/dist/build/mp-weixin 目录下,如果文件存在则覆盖
|
||||
* 2. 将 `const site = require("../site.js");` 这段代码追加到
|
||||
* unpackage/dist/build/mp-weixin/common/vendor.js 文件内容开头部分,
|
||||
* 如果这个文件开头已经有了这行代码,则不追加
|
||||
* 该脚本用于微信小程序的构建后处理(仅在生产构建时由 vue.config.js 触发):
|
||||
* 1. 将 project.config.json 及 project.private.config.json 拷贝到构建产物目录
|
||||
* 2. 将 site.js 拷贝到构建产物目录
|
||||
* 3. 创建 ZIP 压缩包
|
||||
*
|
||||
* 注意:version.js 生成和 vendor.js 注入已由 webpack 插件 (VersionInjectPlugin) 在 emit 钩子中自动处理。
|
||||
*
|
||||
* 使用:
|
||||
* node fix-wechat-miniapp.js # 打补丁并创建 ZIP 文件(默认 mode=production)
|
||||
* node fix-wechat-miniapp.js --no-zip # 只打补丁,不创建 ZIP 文件
|
||||
* node fix-wechat-miniapp.js --mode development # 使用 development 模式打补丁
|
||||
* node fix-wechat-miniapp.js --mode production # 使用 production 模式打补丁(默认)
|
||||
*
|
||||
* 注意:
|
||||
* - 在 Windows 上路径使用反斜杠也是可以的;脚本使用 path.join 来兼容不同平台。
|
||||
* - 运行前请确认在项目根目录运行(site.js 在当前工作目录下)。
|
||||
* node fix-wechat-miniapp.js # 拷贝文件并创建 ZIP(默认 mode=production)
|
||||
* node fix-wechat-miniapp.js --no-zip # 只拷贝文件,不创建 ZIP
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -26,37 +21,19 @@ const path = require('path');
|
||||
const { openFileDirectory } = require('./open-directory');
|
||||
const { createZipWithSystemCommand } = require('./create-zip');
|
||||
|
||||
async function commonPatch(mode = 'production') {
|
||||
async function copyAssets(mode = 'production') {
|
||||
try {
|
||||
|
||||
// 根据当前脚本所在目录(scripts),定位到项目根目录
|
||||
const cwd = path.join(__dirname, '..');
|
||||
|
||||
const srcSitePath = path.join(cwd, 'site.js');
|
||||
const destDir = path.join(cwd, 'unpackage', 'dist', mode === 'production' ? 'build' : 'dev', 'mp-weixin');
|
||||
const destSitePath = path.join(destDir, 'site.js');
|
||||
const vendorPath = path.join(destDir, 'common', 'vendor.js');
|
||||
|
||||
// 1) 检查源文件是否存在
|
||||
const srcExists = await exists(srcSitePath);
|
||||
if (!srcExists) {
|
||||
console.error(`源文件不存在: ${srcSitePath}`);
|
||||
process.exitCode = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保目标目录存在
|
||||
await ensureDir(destDir);
|
||||
|
||||
// 复制 project.config.json 及 project.private.config.json 文件到 destDir 下面
|
||||
// 复制 project.config.json 和 project.private.config.json
|
||||
const configFiles = ['project.config.json', 'project.private.config.json'];
|
||||
for (const fileName of configFiles) {
|
||||
const srcPath = path.join(cwd, fileName);
|
||||
const destPath = path.join(destDir, fileName);
|
||||
|
||||
// 检查源文件是否存在
|
||||
const fileExists = await exists(srcPath);
|
||||
if (fileExists) {
|
||||
if (await exists(srcPath)) {
|
||||
await fsp.copyFile(srcPath, destPath);
|
||||
console.log(`已拷贝: ${srcPath} -> ${destPath}`);
|
||||
} else {
|
||||
@@ -64,47 +41,15 @@ async function commonPatch(mode = 'production') {
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 site.js 到目标目录(覆盖)
|
||||
await fsp.copyFile(srcSitePath, destSitePath);
|
||||
console.log(`已拷贝: ${srcSitePath} -> ${destSitePath}`);
|
||||
|
||||
// 2) 处理 vendor.js:在开头追加 require 语句(如果还没有的话)
|
||||
const vendorExists = await exists(vendorPath);
|
||||
if (!vendorExists) {
|
||||
console.warn(`目标 vendor.js 不存在,跳过追加 require。路径: ${vendorPath}`);
|
||||
return;
|
||||
// 复制 site.js
|
||||
const srcSitePath = path.join(cwd, 'site.js');
|
||||
if (await exists(srcSitePath)) {
|
||||
await fsp.copyFile(srcSitePath, path.join(destDir, 'site.js'));
|
||||
console.log(`已拷贝: ${srcSitePath} -> ${destDir}`);
|
||||
} else {
|
||||
console.error(`源文件不存在: ${srcSitePath}`);
|
||||
process.exitCode = 2;
|
||||
}
|
||||
|
||||
let vendorContent = await fsp.readFile(vendorPath, 'utf8');
|
||||
|
||||
const requireLine = 'const site = require("../site");';
|
||||
// 检查文件开头(例如前 5 行或前 400 字符)是否已经包含该 require 语句
|
||||
const inspectPrefix = vendorContent.slice(0, 400);
|
||||
const requireRegex = new RegExp(
|
||||
// 匹配可能存在的引号或分号差异
|
||||
'^\\s*const\\s+site\\s*=\\s*require\\s*\\([\'"]\\.\\./site[\'"]\\);?',
|
||||
'm'
|
||||
);
|
||||
|
||||
if (requireRegex.test(inspectPrefix)) {
|
||||
console.log('vendor.js 开头已包含 require("../site") 语句,跳过追加。');
|
||||
return;
|
||||
}
|
||||
|
||||
// 把 requireLine + newline 插入文件最前面。
|
||||
// 保留原始换行风格(若可能)
|
||||
const eol = vendorContent.includes('\r\n') ? '\r\n' : '\n';
|
||||
let prefix = '';
|
||||
|
||||
// 保留可能的 BOM
|
||||
if (vendorContent.charCodeAt(0) === 0xfeff) {
|
||||
prefix = '\uFEFF';
|
||||
vendorContent = vendorContent.slice(1);
|
||||
}
|
||||
|
||||
const newContent = prefix + requireLine + eol + vendorContent;
|
||||
await fsp.writeFile(vendorPath, newContent, 'utf8');
|
||||
console.log(`已在 vendor.js 开头追加: ${requireLine}`);
|
||||
} catch (err) {
|
||||
console.error('发生错误:', err);
|
||||
process.exitCode = 1;
|
||||
@@ -112,33 +57,27 @@ async function commonPatch(mode = 'production') {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 解析命令行参数
|
||||
const argv = process.argv.slice(2);
|
||||
const options = {
|
||||
noZip: argv.includes('--no-zip'),
|
||||
mode: 'production' // 默认值
|
||||
mode: 'production'
|
||||
};
|
||||
|
||||
// 解析 --mode 参数
|
||||
const modeIndex = argv.indexOf('--mode');
|
||||
if (modeIndex !== -1 && modeIndex + 1 < argv.length) {
|
||||
options.mode = argv[modeIndex + 1];
|
||||
}
|
||||
|
||||
// 1) 打补丁
|
||||
await commonPatch(options.mode);
|
||||
// await commonPatch('development');
|
||||
// 1) 拷贝文件
|
||||
await copyAssets(options.mode);
|
||||
|
||||
// 2) 创建 ZIP 文件(如果未指定 --no-zip)
|
||||
if (!options.noZip) {
|
||||
// 2) 创建 ZIP(仅 production 模式,且未指定 --no-zip)
|
||||
if (!options.noZip && options.mode === 'production') {
|
||||
const cwd = path.join(__dirname, '..');
|
||||
const buildDir = options.mode === 'production' ? 'build' : 'dev';
|
||||
const sourceDir = path.join(cwd, 'unpackage', 'dist', buildDir, 'mp-weixin');
|
||||
const destDir = path.join(cwd, 'unpackage', 'dist', buildDir);
|
||||
const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
|
||||
const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
|
||||
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
|
||||
console.log(`ZIP 文件路径: ${zipFilePath}`);
|
||||
|
||||
// 3) 自动打开zip所在的目录
|
||||
await openFileDirectory(zipFilePath);
|
||||
} else {
|
||||
console.log('跳过创建 ZIP 文件和打开目录');
|
||||
@@ -158,7 +97,6 @@ async function ensureDir(dirPath) {
|
||||
try {
|
||||
await fsp.mkdir(dirPath, { recursive: true });
|
||||
} catch (e) {
|
||||
// ignore if exists
|
||||
if (e.code !== 'EEXIST') throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,13 @@ async function openDirectoryWindows(directoryPath) {
|
||||
|
||||
console.log(`📁 在文件资源管理器中打开: ${normalizedPath}`);
|
||||
|
||||
// 方法1: 使用 explorer 命令
|
||||
// 方法1: 使用 explorer 命令(spawn + detached,不等待 GUI 进程)
|
||||
try {
|
||||
const command = `explorer "${normalizedPath}"`;
|
||||
await execAsync(command);
|
||||
const child = spawn('explorer', [normalizedPath], {
|
||||
stdio: 'ignore',
|
||||
detached: true
|
||||
});
|
||||
child.unref();
|
||||
console.log('✅ 已启动文件资源管理器');
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
260
vue.config.js
260
vue.config.js
@@ -1,27 +1,65 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webpack');
|
||||
|
||||
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]);
|
||||
// }
|
||||
|
||||
|
||||
// 定义UNI_OUTPUT_DIR环境变量
|
||||
// 定义UNI_OUTPUT_DIR环境变量(H5 自定义发行脚本使用)
|
||||
try {
|
||||
if (!process.env.NODE_ENV) {
|
||||
throw new Error('NODE_ENV 环境变量未设置');
|
||||
}
|
||||
|
||||
const buildDir = process.env.NODE_ENV === 'production' ? 'build' : 'dev';
|
||||
|
||||
const platform = process.env.UNI_PLATFORM || '';
|
||||
const UNI_SCRIPT = process.env.UNI_SCRIPT ?? '';
|
||||
|
||||
if ([
|
||||
// 参照 package.json 中定义的 "uni-app" --> "scripts"
|
||||
'h5-xcx20.5g-quickapp.com', 'h5-xcx.aigc-quickapp.com', 'h5-5g.aigc-quickapp.com', 'h5-test.aigc-quickapp.com'
|
||||
].includes(UNI_SCRIPT)) {
|
||||
process.env.UNI_OUTPUT_DIR = path.join(__dirname, `unpackage/dist/${buildDir}/h5/${UNI_SCRIPT}`);
|
||||
// 所有 H5 自定义脚本统一处理,无需在 vue.config.js 中硬编码脚本名
|
||||
if (platform === 'h5' && UNI_SCRIPT) {
|
||||
// 从 manifest.json 读取 h5.router.base,拼接到输出路径
|
||||
let baseSuffix = '';
|
||||
try {
|
||||
const manifestPath = path.join(__dirname, 'manifest.json');
|
||||
baseSuffix = _readManifestBase(manifestPath);
|
||||
} catch (e) {}
|
||||
// 从脚本 env 中获取域名(优先使用 UNI_APP_DOMAIN,否则从 UNI_SCRIPT 提取)
|
||||
let domain = '';
|
||||
if (process.env.UNI_APP_DOMAIN) {
|
||||
try {
|
||||
domain = new URL(process.env.UNI_APP_DOMAIN).hostname;
|
||||
} catch (e) {
|
||||
domain = process.env.UNI_APP_DOMAIN.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
|
||||
}
|
||||
} else {
|
||||
domain = UNI_SCRIPT.replace(/^h5-/, '');
|
||||
}
|
||||
// 格式: {域名}-{base用.连接}
|
||||
const outputDirName = baseSuffix ? `${domain}-${baseSuffix}` : domain;
|
||||
process.env.UNI_OUTPUT_DIR = path.join(__dirname, `unpackage/dist/${buildDir}/h5/${outputDirName}`);
|
||||
console.log('UNI_OUTPUT_DIR = ', process.env.UNI_OUTPUT_DIR);
|
||||
}
|
||||
|
||||
@@ -29,6 +67,211 @@ try {
|
||||
console.log('NODE_ENV 环境变量未设置,默认使用开发模式');
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 manifest.json 中的 h5.router.base,去掉注释后解析
|
||||
* 返回 base 路径的 . 连接形式(如 /hwappx/common/ → hwappx.common)
|
||||
*/
|
||||
function _readManifestBase(manifestPath) {
|
||||
try {
|
||||
const raw = fs.readFileSync(manifestPath, 'utf8');
|
||||
// 去掉 /* */ 和 // 注释(HBuilder 生成的 JSON 允许注释)
|
||||
const clean = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '');
|
||||
const manifest = JSON.parse(clean);
|
||||
if (manifest?.h5?.router?.base) {
|
||||
return manifest.h5.router.base.replace(/^\/+|\/+$/g, '').replace(/\//g, '.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[manifest] 读取 h5.router.base 失败:', e.message);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义 webpack 插件:在通用产物(vendor.js)中内联注入版本数据
|
||||
* 利用 emit 钩子在资源写入磁盘前处理,支持 HMR(每次重编译都会自动注入)
|
||||
* 适用于所有平台(mp-weixin / h5 等)
|
||||
*/
|
||||
class VersionInjectPlugin {
|
||||
constructor() {
|
||||
this._cachedCommitId = null;
|
||||
}
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('VersionInjectPlugin', (compilation, callback) => {
|
||||
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
||||
const platform = process.env.UNI_PLATFORM || '';
|
||||
|
||||
// 获取 Git 提交 ID(缓存,避免每次 HMR 都执行)
|
||||
if (!this._cachedCommitId) {
|
||||
try {
|
||||
this._cachedCommitId = execSync('git rev-parse --short HEAD', { cwd: __dirname }).toString().trim();
|
||||
} catch (e) {
|
||||
this._cachedCommitId = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
const versionData = { commitId: this._cachedCommitId, buildTime: new Date().toISOString() };
|
||||
const inlineVersion = JSON.stringify(versionData);
|
||||
|
||||
// 尝试注入到 vendor.js(mp-weixin / mp-* 等平台)
|
||||
const vendorKey = 'common/vendor.js';
|
||||
const vendorAsset = compilation.assets[vendorKey];
|
||||
if (vendorAsset) {
|
||||
let content = typeof vendorAsset.source === 'function' ? vendorAsset.source() : vendorAsset._value;
|
||||
if (typeof content !== 'string') content = content.toString();
|
||||
|
||||
// 清理旧注入
|
||||
content = content.replace(/^\s*const\s+site\s*=\s*require\s*\(['"]\.\.\/site(\.js)?['"]\);?\s*\r?\n/gm, '');
|
||||
content = content.replace(/^\s*const\s+__app_version\s*=\s*(require\s*\(['"]\.\.\/version(\.json|\.js)?['"]\)|.+);?\s*\r?\n/gm, '');
|
||||
|
||||
// 只注入版本信息(通用),site.js require 仅在 mp-weixin 生产构建时注入
|
||||
const lines = [`const __app_version = ${inlineVersion};`];
|
||||
if (platform === 'mp-weixin' && mode === 'production') {
|
||||
lines.unshift('const site = require("../site.js");');
|
||||
}
|
||||
|
||||
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
||||
let prefix = '';
|
||||
if (content.charCodeAt(0) === 0xfeff) {
|
||||
prefix = '\uFEFF';
|
||||
content = content.slice(1);
|
||||
}
|
||||
|
||||
content = prefix + lines.join(eol) + eol + content;
|
||||
|
||||
compilation.assets[vendorKey] = {
|
||||
source: () => content,
|
||||
size: () => content.length
|
||||
};
|
||||
|
||||
console.log(`[VersionInject] ✅ vendor.js 已注入 (platform=${platform}, mode=${mode}, commit=${this._cachedCommitId})`);
|
||||
} else {
|
||||
// H5 等平台可能没有 common/vendor.js,改为尝试注入到 index.html
|
||||
// #ifdef H5
|
||||
const htmlKey = 'index.html';
|
||||
const htmlAsset = compilation.assets[htmlKey];
|
||||
if (htmlAsset) {
|
||||
let html = typeof htmlAsset.source === 'function' ? htmlAsset.source() : htmlAsset._value;
|
||||
if (typeof html !== 'string') html = html.toString();
|
||||
// 在 </head> 前注入 script 标签
|
||||
const scriptTag = `<script>var __app_version = ${inlineVersion};</script>`;
|
||||
if (!html.includes('__app_version')) {
|
||||
html = html.replace('</head>', `${scriptTag}\n</head>`);
|
||||
compilation.assets[htmlKey] = {
|
||||
source: () => html,
|
||||
size: () => html.length
|
||||
};
|
||||
console.log(`[VersionInject] ✅ index.html 已注入 (platform=${platform}, commit=${this._cachedCommitId})`);
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
// 生产构建后处理:mp-weixin 拷贝 site.js + ZIP;H5 自动打包 ZIP
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
compiler.hooks.afterEmit.tapAsync('ProductionZipPack', (compilation, callback) => {
|
||||
const platform = process.env.UNI_PLATFORM || '';
|
||||
const UNI_SCRIPT = process.env.UNI_SCRIPT ?? '';
|
||||
|
||||
if (platform === 'mp-weixin') {
|
||||
console.log('\n🚀 mp-weixin 生产构建完成,运行补丁脚本(site.js 拷贝 + ZIP 打包)...');
|
||||
try {
|
||||
execSync(`"${process.execPath}" scripts/mp-weixin.patch.js --mode production`, { cwd: __dirname, stdio: 'inherit' });
|
||||
} catch (e) {
|
||||
console.error('补丁脚本执行失败:', e.message);
|
||||
}
|
||||
} else if (platform === 'h5' && UNI_SCRIPT) {
|
||||
this._zipH5Build(compilation);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_computeH5OutputName() {
|
||||
// 计算输出目录名: {域名}-{base用.连接}
|
||||
let baseSuffix = _readManifestBase(path.join(__dirname, 'manifest.json'));
|
||||
let domain = '';
|
||||
if (process.env.UNI_APP_DOMAIN) {
|
||||
try {
|
||||
domain = new URL(process.env.UNI_APP_DOMAIN).hostname;
|
||||
} catch (e) {
|
||||
domain = process.env.UNI_APP_DOMAIN.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
|
||||
}
|
||||
} else {
|
||||
domain = (process.env.UNI_SCRIPT || '').replace(/^h5-/, '');
|
||||
}
|
||||
const result = baseSuffix ? `${domain}-${baseSuffix}` : domain;
|
||||
console.log('[H5Zip] 输出目录名:', result);
|
||||
return result;
|
||||
}
|
||||
|
||||
_zipH5Build(compilation) {
|
||||
const outputDirName = this._computeH5OutputName();
|
||||
const buildDir = process.env.NODE_ENV === 'production' ? 'build' : 'dev';
|
||||
const h5Dir = path.join(__dirname, 'unpackage', 'dist', buildDir, 'h5');
|
||||
|
||||
// 实际构建输出目录(由 webpack 决定)
|
||||
const actualOutput = compilation.compiler.outputPath;
|
||||
const targetDir = path.join(h5Dir, outputDirName);
|
||||
|
||||
// 如果路径一致,跳过重命名
|
||||
if (actualOutput !== targetDir) {
|
||||
try {
|
||||
if (fs.existsSync(targetDir)) {
|
||||
fs.rmSync(targetDir, { recursive: true });
|
||||
}
|
||||
fs.renameSync(actualOutput, targetDir);
|
||||
console.log(`[H5Zip] 输出目录已重命名为: ${outputDirName}`);
|
||||
} catch (e) {
|
||||
console.error('[H5Zip] 目录重命名失败:', e.message);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log(`[H5Zip] 输出目录名正确: ${outputDirName}`);
|
||||
}
|
||||
|
||||
// 标准时区时间戳格式: 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);
|
||||
|
||||
console.log(`\n📦 H5 ZIP: ${zipFileName}...`);
|
||||
|
||||
let command;
|
||||
const sourcePath = targetDir.replace(/'/g, "''");
|
||||
const destPath = zipFilePath.replace(/'/g, "''");
|
||||
if (process.platform === 'win32') {
|
||||
command = `powershell -Command "Compress-Archive -Path '${sourcePath}\\*' -DestinationPath '${destPath}' -Force"`;
|
||||
} else {
|
||||
const sourceBase = path.dirname(targetDir);
|
||||
const sourceName = path.basename(targetDir);
|
||||
command = `cd '${sourceBase}' && zip -r '${zipFilePath}' '${sourceName}' -x '*/node_modules/*' '*.git/*' '*.DS_Store'`;
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(command, { stdio: 'inherit' });
|
||||
console.log(`✅ H5 ZIP: ${zipFileName}`);
|
||||
// 删除已打包的源目录
|
||||
// fs.rmSync(targetDir, { recursive: true, force: true });
|
||||
// console.log(`[H5Zip] 已删除源目录: ${outputDirName}`);
|
||||
} catch (e) {
|
||||
console.error('H5 ZIP 打包失败:', e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出配置
|
||||
module.exports = {
|
||||
productionSourceMap: false,
|
||||
@@ -118,5 +361,8 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有模式下添加版本注入插件(适用于所有平台)
|
||||
config.plugins.push(new VersionInjectPlugin());
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user