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 |
@@ -54,6 +54,13 @@ const localDevConfig = ({
|
|||||||
uniacid: 115,
|
uniacid: 115,
|
||||||
domain: 'https://5g.aigc-quickapp.com/',
|
domain: 'https://5g.aigc-quickapp.com/',
|
||||||
},
|
},
|
||||||
})['2811']; // 选择要使用的环境配置
|
'47': { // 修改弹窗
|
||||||
|
uniacid: 47,
|
||||||
|
domain: 'https://5g.aigc-quickapp.com/',
|
||||||
|
},
|
||||||
|
'2075': { //
|
||||||
|
uniacid: 2075,
|
||||||
|
domain: 'https://xcx.aigc-quickapp.com/',
|
||||||
|
},
|
||||||
|
})['1']; // 选择要使用的环境配置
|
||||||
export default localDevConfig;
|
export default localDevConfig;
|
||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,20 +20,13 @@ try {
|
|||||||
// 默认域名, 自定义发行时可以修改
|
// 默认域名, 自定义发行时可以修改
|
||||||
let defaultDomain = '';
|
let defaultDomain = '';
|
||||||
|
|
||||||
// #ifdef H5_XCX_5G_QUICKAPP_COM
|
// #ifdef H5
|
||||||
defaultDomain = 'https://xcx20.5g-quickapp.com';
|
// 域名由 vue.config.js 中 DefinePlugin 注入的 process.env.UNI_APP_DOMAIN 传入
|
||||||
// #endif
|
try {
|
||||||
|
if (process.env.UNI_APP_DOMAIN) {
|
||||||
// #ifdef H5_XCX_AIGC_QUICKAPP_COM
|
defaultDomain = process.env.UNI_APP_DOMAIN;
|
||||||
defaultDomain = 'https://xcx.aigc-quickapp.com/';
|
}
|
||||||
// #endif
|
} catch (e) {}
|
||||||
|
|
||||||
// #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/';
|
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 确保域名以 '/' 结尾
|
// 确保域名以 '/' 结尾
|
||||||
|
|||||||
@@ -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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,11 +54,14 @@ function loadLangPackSync(lang, path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得当前本地语言
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
function getCurrentLocale() {
|
function getCurrentLocale() {
|
||||||
return uni.getStorageSync('lang') || "zh-cn";
|
return uni.getStorageSync('lang') || "zh-cn";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
langList: langConfig.langList,
|
langList: langConfig.langList,
|
||||||
|
|
||||||
|
|||||||
@@ -622,6 +622,7 @@ export default {
|
|||||||
success: () => {
|
success: () => {
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
fallbackFunc && fallbackFunc();
|
fallbackFunc && fallbackFunc();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -1,9 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"mp-weixin": "node scripts/mp-weixin.patch.js",
|
"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"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dart-sass": "^1.25.0",
|
"dart-sass": "^1.25.0",
|
||||||
@@ -28,44 +25,40 @@
|
|||||||
"h5-xcx20.5g-quickapp.com": {
|
"h5-xcx20.5g-quickapp.com": {
|
||||||
"title": "H5-xcx20.5g-quickapp.com",
|
"title": "H5-xcx20.5g-quickapp.com",
|
||||||
"env": {
|
"env": {
|
||||||
"UNI_PLATFORM": "h5"
|
"UNI_PLATFORM": "h5",
|
||||||
|
"UNI_APP_DOMAIN": "https://xcx20.5g-quickapp.com/"
|
||||||
},
|
},
|
||||||
"define": {
|
"define": {
|
||||||
"H5_XCX_5G_QUICKAPP_COM": true,
|
|
||||||
"H5_PRODUCTION": true,
|
|
||||||
"PRODUCTION": true
|
"PRODUCTION": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"h5-xcx.aigc-quickapp.com": {
|
"h5-xcx.aigc-quickapp.com": {
|
||||||
"title": "H5-xcx.aigc-quickapp.com",
|
"title": "H5-xcx.aigc-quickapp.com",
|
||||||
"env": {
|
"env": {
|
||||||
"UNI_PLATFORM": "h5"
|
"UNI_PLATFORM": "h5",
|
||||||
|
"UNI_APP_DOMAIN": "https://xcx.aigc-quickapp.com/"
|
||||||
},
|
},
|
||||||
"define": {
|
"define": {
|
||||||
"H5_XCX_AIGC_QUICKAPP_COM": true,
|
|
||||||
"H5_PRODUCTION": true,
|
|
||||||
"PRODUCTION": true
|
"PRODUCTION": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"h5-5g.aigc-quickapp.com": {
|
"h5-5g.aigc-quickapp.com": {
|
||||||
"title": "H5-5g.aigc-quickapp.com",
|
"title": "H5-5g.aigc-quickapp.com",
|
||||||
"env": {
|
"env": {
|
||||||
"UNI_PLATFORM": "h5"
|
"UNI_PLATFORM": "h5",
|
||||||
|
"UNI_APP_DOMAIN": "https://5g.aigc-quickapp.com/"
|
||||||
},
|
},
|
||||||
"define": {
|
"define": {
|
||||||
"H5_5G_AIGC_QUICKAPP_COM": true,
|
|
||||||
"H5_PRODUCTION": true,
|
|
||||||
"PRODUCTION": true
|
"PRODUCTION": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"h5-test.aigc-quickapp.com": {
|
"h5-test.aigc-quickapp.com": {
|
||||||
"title": "H5-test.aigc-quickapp.com",
|
"title": "H5-test.aigc-quickapp.com",
|
||||||
"env": {
|
"env": {
|
||||||
"UNI_PLATFORM": "h5"
|
"UNI_PLATFORM": "h5",
|
||||||
|
"UNI_APP_DOMAIN": "https://test.aigc-quickapp.com/"
|
||||||
},
|
},
|
||||||
"define": {
|
"define": {
|
||||||
"H5_TEST_AIGC_QUICKAPP_COM": true,
|
|
||||||
"H5_PRODUCTION": true,
|
|
||||||
"PRODUCTION": true
|
"PRODUCTION": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,7 +318,7 @@
|
|||||||
{
|
{
|
||||||
"path": "ai-chat/index",
|
"path": "ai-chat/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "AI 客服"
|
"navigationBarTitleText": "AI智能客服"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<image :src="$util.img(siteInfo.logo_square)" mode="aspectFill"></image>
|
<image :src="$util.img(siteInfo.logo_square)" mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-box" :style="{ color: '#ffffff' }">
|
<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>
|
<text>{{ followOfficialAccount.welcomeMsg }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -141,6 +141,8 @@
|
|||||||
<privacy-popup ref="privacyPopup"></privacy-popup>
|
<privacy-popup ref="privacyPopup"></privacy-popup>
|
||||||
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
|
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
|
||||||
<ns-login ref="login"></ns-login>
|
<ns-login ref="login"></ns-login>
|
||||||
|
<!-- 版本信息触发区(固定定位,不可见,始终可点击) -->
|
||||||
|
<view class="version-trigger" @tap="onTitleTap"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -243,4 +245,14 @@ export default {
|
|||||||
bottom: 180rpx !important;
|
bottom: 180rpx !important;
|
||||||
/* #endif */
|
/* #endif */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-trigger {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 50rpx;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
z-index: 999;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -91,6 +91,39 @@ export default {
|
|||||||
|
|
||||||
// 阻止事件继续传播
|
// 阻止事件继续传播
|
||||||
return false;
|
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>
|
<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;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: white; /* 白色 */
|
|
||||||
overflow: hidden;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 48px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* 自定义导航头部 */
|
/* 自定义导航头部:固定在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占位样式 */
|
||||||
|
|||||||
109
readme.md
109
readme.md
@@ -40,22 +40,20 @@
|
|||||||
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
|
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
|
||||||
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
|
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
|
||||||
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
|
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
|
||||||
3. 打开资源管理器→项目根目录,右键选择「在终端中打开」,执行命令:
|
3. 构建完成后,**自动执行**补丁脚本(拷贝 site.js / config 文件 + 生成 ZIP 包),无需手动操作
|
||||||
```bash
|
|
||||||
npm run mp-weixin
|
|
||||||
```
|
|
||||||
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-weixin 压缩包
|
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-weixin 压缩包
|
||||||
💡 **示例**:`mp-weixin-2026-01-23-1769152056146.zip`
|
💡 **示例**:`mp-weixin-2026-05-11-3bff91e.zip`
|
||||||
|
*压缩包文件名末尾为 Git 提交 ID*
|
||||||
*同时该目录下会生成未压缩的 `mp-weixin` 目录*
|
*同时该目录下会生成未压缩的 `mp-weixin` 目录*
|
||||||
|
|
||||||
##### 4.1.1.3 【验证与重命名】
|
##### 4.1.1.3 【验证与重命名】
|
||||||
1. 打开未压缩的 `mp-weixin` 目录,找到 `site.js` 文件,将文件内的 `uniacid` 值改为当前客户编号(如:2812)并保存
|
1. 打开未压缩的 `mp-weixin` 目录,找到 `site.js` 文件,将文件内的 `uniacid` 值改为当前客户编号(如:2812)并保存
|
||||||
2. 打开微信开发者工具,导入「mp-weixin」目录,点击「编译」,在开发者工具控制台验证有无报错,以及能否返回对应客户的业务数据
|
2. 打开微信开发者工具,导入「mp-weixin」目录,点击「编译」,在开发者工具控制台验证有无报错,以及能否返回对应客户的业务数据
|
||||||
3. 确保无误后将生成的 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`
|
||||||
|
|
||||||
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
|
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
|
||||||
|
|
||||||
@@ -85,66 +83,93 @@
|
|||||||
📌 **测试版**保留原有 `/hwappx/test/`
|
📌 **测试版**保留原有 `/hwappx/test/`
|
||||||
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
|
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
|
||||||
|
|
||||||
3. 选择菜单栏「发行」 → 「自定义发行」 →(「H5-xcx.aigc-quickapp.com」查看.local.config.js中站点的网址前缀,站点的网址前缀是什么就选自定义发行中相同的格式)
|
3. 选择菜单栏「发行」 → 「自定义发行」 → 选择对应的站点脚本
|
||||||
例如:网址是5g.aigc-quickapp.com,自定义发行就选H5-5g.aigc-quickapp.com
|
💡 例如:网址是 `5g.aigc-quickapp.com` 选 `H5-5g.aigc-quickapp.com`
|
||||||
网址是dev.aigc-quickapp.com,自定义发行就选H5-xcx.aigc-quickapp.com
|
|
||||||
4. 修改以下配置:
|
4. 修改以下配置:
|
||||||
1. **网站标题**:快应用
|
1. **网站标题**:快应用
|
||||||
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
|
2. **网站域名**:当前客户域名
|
||||||
3. 确认后点击「发行」等待构建完成
|
3. 确认后点击「发行」等待构建完成
|
||||||
|
|
||||||
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
|
⚠️ **注意**:构建完成后自动输出到 `unpackage/dist/build/h5/`,格式为 `{域名}-{base路径}.zip`
|
||||||
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
|
💡 **示例**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
|
||||||
5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
|
|
||||||
*仅打包文件,不包含外层 web 目录*
|
|
||||||
|
|
||||||
##### 4.2.1.2 【重命名】
|
##### 4.2.1.2 【交付与最终发布】🔍
|
||||||
1. 按版本类型规范重命名压缩包:
|
1. 将生成的 `.zip` 文件发送给运维人员
|
||||||
- **通用版**:`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. 将重命名后的压缩包发送给运维人员
|
|
||||||
2. **运维人员操作流程**:
|
2. **运维人员操作流程**:
|
||||||
1. 解压压缩包
|
1. 解压压缩包
|
||||||
2. 打开快应用开发者工具,导入解压后的代码目录
|
2. 打开快应用开发者工具,导入解压后的代码目录
|
||||||
3. 验证代码无报错后,执行上传发布操作
|
3. 验证代码无报错后,执行上传发布操作
|
||||||
|
|
||||||
##### 4.2.1.3 【测试流程】
|
##### 4.2.1.3 【测试流程】
|
||||||
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
|
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
|
||||||
如果是test就把hwappx/common/改为hwappx/test/----------改站点
|
如果是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文件) |
|
| 重命名输出目录 | `{域名}-{base路径用.连接}`(如 `xcx.aigc-quickapp.com-hwappx.common`) |
|
||||||
| `npm run mp-weixin:patch` | 只打补丁(production模式,不创建ZIP文件) |
|
| 创建 ZIP 文件 | 文件名格式:`{目录名}-{Git提交ID}-{上海时区时间戳}.zip` |
|
||||||
| `npm run mp-weixin:dev` | 开发模式构建(development模式,包含ZIP文件) |
|
| 清理 | ZIP 创建完成后自动删除源目录 |
|
||||||
| `npm run mp-weixin:dev:patch` | 只打补丁(development模式,不创建ZIP文件) |
|
|
||||||
|
|
||||||
### 5.2 构建脚本功能
|
💡 **示例 ZIP**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
|
||||||
|
|
||||||
- 复制 `project.config.json` 和 `project.private.config.json` 文件到构建目录
|
**微信小程序**生产构建时自动执行:
|
||||||
- 复制 `site.js` 文件到构建目录
|
- 拷贝 `site.js` / `project.config.json` / `project.private.config.json` 到构建目录
|
||||||
- 在 `vendor.js` 文件开头追加 `import site from "../site.js";` 语句
|
- 创建 ZIP 包(文件名包含 Git 提交 ID)
|
||||||
- 支持创建构建结果的 ZIP 压缩包
|
|
||||||
- 自动打开 ZIP 文件所在目录
|
### 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. 🔄 版本历史
|
||||||
|
|
||||||
### 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` 文件
|
- 修复微信小程序构建脚本,支持复制 `project.config.json` 和 `project.private.config.json` 文件
|
||||||
- 增强构建脚本功能,添加命令行参数支持
|
- 增强构建脚本功能,添加命令行参数支持
|
||||||
- 在 `package.json` 中添加相关 npm 脚本命令
|
- 在 `package.json` 中添加相关 npm 脚本命令
|
||||||
|
|||||||
@@ -18,9 +18,18 @@ async function createZipWithSystemCommand(sourceDir, outputDir) {
|
|||||||
throw new Error(`源目录不存在: ${sourceDir}`);
|
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 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);
|
const zipFilePath = path.join(outputDir, zipFileName);
|
||||||
|
|
||||||
let command;
|
let command;
|
||||||
|
|||||||
@@ -2,21 +2,16 @@
|
|||||||
/**
|
/**
|
||||||
* fix-wechat-miniapp.js
|
* fix-wechat-miniapp.js
|
||||||
*
|
*
|
||||||
* 该脚本用于修复微信小程序的一些问题:
|
* 该脚本用于微信小程序的构建后处理(仅在生产构建时由 vue.config.js 触发):
|
||||||
* 1. 将项目根目录下的 site.js 文件拷贝到 unpackage/dist/build/mp-weixin 目录下,如果文件存在则覆盖
|
* 1. 将 project.config.json 及 project.private.config.json 拷贝到构建产物目录
|
||||||
* 2. 将 `const site = require("../site.js");` 这段代码追加到
|
* 2. 将 site.js 拷贝到构建产物目录
|
||||||
* unpackage/dist/build/mp-weixin/common/vendor.js 文件内容开头部分,
|
* 3. 创建 ZIP 压缩包
|
||||||
* 如果这个文件开头已经有了这行代码,则不追加
|
*
|
||||||
|
* 注意:version.js 生成和 vendor.js 注入已由 webpack 插件 (VersionInjectPlugin) 在 emit 钩子中自动处理。
|
||||||
*
|
*
|
||||||
* 使用:
|
* 使用:
|
||||||
* node fix-wechat-miniapp.js # 打补丁并创建 ZIP 文件(默认 mode=production)
|
* node fix-wechat-miniapp.js # 拷贝文件并创建 ZIP(默认 mode=production)
|
||||||
* node fix-wechat-miniapp.js --no-zip # 只打补丁,不创建 ZIP 文件
|
* 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 在当前工作目录下)。
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -26,37 +21,19 @@ const path = require('path');
|
|||||||
const { openFileDirectory } = require('./open-directory');
|
const { openFileDirectory } = require('./open-directory');
|
||||||
const { createZipWithSystemCommand } = require('./create-zip');
|
const { createZipWithSystemCommand } = require('./create-zip');
|
||||||
|
|
||||||
async function commonPatch(mode = 'production') {
|
async function copyAssets(mode = 'production') {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 根据当前脚本所在目录(scripts),定位到项目根目录
|
|
||||||
const cwd = path.join(__dirname, '..');
|
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 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);
|
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'];
|
const configFiles = ['project.config.json', 'project.private.config.json'];
|
||||||
for (const fileName of configFiles) {
|
for (const fileName of configFiles) {
|
||||||
const srcPath = path.join(cwd, fileName);
|
const srcPath = path.join(cwd, fileName);
|
||||||
const destPath = path.join(destDir, fileName);
|
const destPath = path.join(destDir, fileName);
|
||||||
|
if (await exists(srcPath)) {
|
||||||
// 检查源文件是否存在
|
|
||||||
const fileExists = await exists(srcPath);
|
|
||||||
if (fileExists) {
|
|
||||||
await fsp.copyFile(srcPath, destPath);
|
await fsp.copyFile(srcPath, destPath);
|
||||||
console.log(`已拷贝: ${srcPath} -> ${destPath}`);
|
console.log(`已拷贝: ${srcPath} -> ${destPath}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -64,47 +41,15 @@ async function commonPatch(mode = 'production') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制 site.js 到目标目录(覆盖)
|
// 复制 site.js
|
||||||
await fsp.copyFile(srcSitePath, destSitePath);
|
const srcSitePath = path.join(cwd, 'site.js');
|
||||||
console.log(`已拷贝: ${srcSitePath} -> ${destSitePath}`);
|
if (await exists(srcSitePath)) {
|
||||||
|
await fsp.copyFile(srcSitePath, path.join(destDir, 'site.js'));
|
||||||
// 2) 处理 vendor.js:在开头追加 require 语句(如果还没有的话)
|
console.log(`已拷贝: ${srcSitePath} -> ${destDir}`);
|
||||||
const vendorExists = await exists(vendorPath);
|
} else {
|
||||||
if (!vendorExists) {
|
console.error(`源文件不存在: ${srcSitePath}`);
|
||||||
console.warn(`目标 vendor.js 不存在,跳过追加 require。路径: ${vendorPath}`);
|
process.exitCode = 2;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
} catch (err) {
|
||||||
console.error('发生错误:', err);
|
console.error('发生错误:', err);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
@@ -112,33 +57,27 @@ async function commonPatch(mode = 'production') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// 解析命令行参数
|
|
||||||
const argv = process.argv.slice(2);
|
const argv = process.argv.slice(2);
|
||||||
const options = {
|
const options = {
|
||||||
noZip: argv.includes('--no-zip'),
|
noZip: argv.includes('--no-zip'),
|
||||||
mode: 'production' // 默认值
|
mode: 'production'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 解析 --mode 参数
|
|
||||||
const modeIndex = argv.indexOf('--mode');
|
const modeIndex = argv.indexOf('--mode');
|
||||||
if (modeIndex !== -1 && modeIndex + 1 < argv.length) {
|
if (modeIndex !== -1 && modeIndex + 1 < argv.length) {
|
||||||
options.mode = argv[modeIndex + 1];
|
options.mode = argv[modeIndex + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) 打补丁
|
// 1) 拷贝文件
|
||||||
await commonPatch(options.mode);
|
await copyAssets(options.mode);
|
||||||
// await commonPatch('development');
|
|
||||||
|
|
||||||
// 2) 创建 ZIP 文件(如果未指定 --no-zip)
|
// 2) 创建 ZIP(仅 production 模式,且未指定 --no-zip)
|
||||||
if (!options.noZip) {
|
if (!options.noZip && options.mode === 'production') {
|
||||||
const cwd = path.join(__dirname, '..');
|
const cwd = path.join(__dirname, '..');
|
||||||
const buildDir = options.mode === 'production' ? 'build' : 'dev';
|
const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
|
||||||
const sourceDir = path.join(cwd, 'unpackage', 'dist', buildDir, 'mp-weixin');
|
const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
|
||||||
const destDir = path.join(cwd, 'unpackage', 'dist', buildDir);
|
|
||||||
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
|
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
|
||||||
console.log(`ZIP 文件路径: ${zipFilePath}`);
|
console.log(`ZIP 文件路径: ${zipFilePath}`);
|
||||||
|
|
||||||
// 3) 自动打开zip所在的目录
|
|
||||||
await openFileDirectory(zipFilePath);
|
await openFileDirectory(zipFilePath);
|
||||||
} else {
|
} else {
|
||||||
console.log('跳过创建 ZIP 文件和打开目录');
|
console.log('跳过创建 ZIP 文件和打开目录');
|
||||||
@@ -158,7 +97,6 @@ async function ensureDir(dirPath) {
|
|||||||
try {
|
try {
|
||||||
await fsp.mkdir(dirPath, { recursive: true });
|
await fsp.mkdir(dirPath, { recursive: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore if exists
|
|
||||||
if (e.code !== 'EEXIST') throw e;
|
if (e.code !== 'EEXIST') throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,13 @@ async function openDirectoryWindows(directoryPath) {
|
|||||||
|
|
||||||
console.log(`📁 在文件资源管理器中打开: ${normalizedPath}`);
|
console.log(`📁 在文件资源管理器中打开: ${normalizedPath}`);
|
||||||
|
|
||||||
// 方法1: 使用 explorer 命令
|
// 方法1: 使用 explorer 命令(spawn + detached,不等待 GUI 进程)
|
||||||
try {
|
try {
|
||||||
const command = `explorer "${normalizedPath}"`;
|
const child = spawn('explorer', [normalizedPath], {
|
||||||
await execAsync(command);
|
stdio: 'ignore',
|
||||||
|
detached: true
|
||||||
|
});
|
||||||
|
child.unref();
|
||||||
console.log('✅ 已启动文件资源管理器');
|
console.log('✅ 已启动文件资源管理器');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
260
vue.config.js
260
vue.config.js
@@ -1,27 +1,65 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { execSync } = require('child_process');
|
||||||
const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webpack');
|
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
|
// 打印出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]);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
// 定义UNI_OUTPUT_DIR环境变量
|
// 定义UNI_OUTPUT_DIR环境变量(H5 自定义发行脚本使用)
|
||||||
try {
|
try {
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
throw new Error('NODE_ENV 环境变量未设置');
|
throw new Error('NODE_ENV 环境变量未设置');
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildDir = process.env.NODE_ENV === 'production' ? 'build' : 'dev';
|
const buildDir = process.env.NODE_ENV === 'production' ? 'build' : 'dev';
|
||||||
|
const platform = process.env.UNI_PLATFORM || '';
|
||||||
const UNI_SCRIPT = process.env.UNI_SCRIPT ?? '';
|
const UNI_SCRIPT = process.env.UNI_SCRIPT ?? '';
|
||||||
|
|
||||||
if ([
|
// 所有 H5 自定义脚本统一处理,无需在 vue.config.js 中硬编码脚本名
|
||||||
// 参照 package.json 中定义的 "uni-app" --> "scripts"
|
if (platform === 'h5' && UNI_SCRIPT) {
|
||||||
'h5-xcx20.5g-quickapp.com', 'h5-xcx.aigc-quickapp.com', 'h5-5g.aigc-quickapp.com', 'h5-test.aigc-quickapp.com'
|
// 从 manifest.json 读取 h5.router.base,拼接到输出路径
|
||||||
].includes(UNI_SCRIPT)) {
|
let baseSuffix = '';
|
||||||
process.env.UNI_OUTPUT_DIR = path.join(__dirname, `unpackage/dist/${buildDir}/h5/${UNI_SCRIPT}`);
|
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);
|
console.log('UNI_OUTPUT_DIR = ', process.env.UNI_OUTPUT_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +67,211 @@ try {
|
|||||||
console.log('NODE_ENV 环境变量未设置,默认使用开发模式');
|
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 = {
|
module.exports = {
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
@@ -118,5 +361,8 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 所有模式下添加版本注入插件(适用于所有平台)
|
||||||
|
config.plugins.push(new VersionInjectPlugin());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user