Compare commits

..

1 Commits

Author SHA1 Message Date
1cfd2db49e feat: 集成华为快应用帐号登录 2026-05-08 15:49:29 +08:00
78 changed files with 1718 additions and 1324 deletions

View File

@@ -42,25 +42,18 @@ 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/',
},
'47': { // 修改弹窗
uniacid: 47,
domain: 'https://5g.aigc-quickapp.com/',
},
'2075': { //
uniacid: 2075,
domain: 'https://xcx.aigc-quickapp.com/',
},
'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/',
},
})['1']; // 选择要使用的环境配置
export default localDevConfig;

View File

@@ -266,6 +266,7 @@
imgUrl: this.$util.img(share_data.imgUrl)
},
res => {
console.log(res);
}
);
}

View File

@@ -40,6 +40,15 @@ export default {
*/
async sendMessage(message, options = {}) {
try {
// 调试:打印关键状态
console.log('[AI Service] sendMessage 开始执行');
console.log('[AI Service] store.state.memberInfo:', store.state.memberInfo);
console.log('[AI Service] uniacid:', this.getUniacid());
console.log('[AI Service] currentConversationId:', this.getConversationId());
const aiConfig = store.getters.globalAIKefuConfig;
console.log('[AI Service] aiConfig:', aiConfig);
const params = {
url: '/api/kefu/chat',
data: {
@@ -57,18 +66,23 @@ export default {
}
};
console.log('[AI Service] 发送请求到:', params.url);
console.log('[AI Service] 请求参数:', JSON.stringify(params.data, null, 2));
const response = await http.sendRequest({
...params,
async: false
});
console.log('[AI Service] 收到响应:', response);
const result = this.handleResponse(response);
if (result.conversationId) {
this.setConversationId(result.conversationId);
}
return result;
} catch (error) {
console.error('[AI Service] AiChat API请求失败,详细错误:', error);
console.error('[AI Service] Dify API请求失败,详细错误:', error);
console.error('[AI Service] 错误堆栈:', error.stack);
// 打印更多信息帮助调试
console.error('[AI Service] store 状态:', {
@@ -88,17 +102,8 @@ 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`;
console.log(`wssUrl = ${wssUrl}`);
const socketTask = wx.connectSocket({
url: wssUrl,
header: {}
@@ -107,31 +112,9 @@ 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(() => {
console.log('WebSocket 连接成功,开始认证...');
socketTask.send({
data: JSON.stringify({
action: 'auth',
@@ -145,8 +128,10 @@ export default {
socketTask.onMessage((res) => {
try {
const data = JSON.parse(res.data);
console.log('收到 WebSocket 消息:', data);
if (data.type === 'auth_success') {
console.log('认证成功,发送聊天消息...');
isAuthenticated = true;
socketTask.send({
data: JSON.stringify({
@@ -161,8 +146,8 @@ export default {
} else if (data.type === 'auth_failed') {
const errorMsg = '认证失败,请重新登录';
console.error(errorMsg, data);
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
socketTask.close();
}
@@ -179,14 +164,16 @@ export default {
if (conversationId) {
this.setConversationId(conversationId);
}
closedNormally = true;
safeComplete({ content, conversation_id: conversationId });
safeResolve({ content, conversation_id: conversationId });
if (onComplete) {
onComplete({ content, conversation_id: conversationId });
}
resolve({ content, conversation_id: conversationId });
socketTask.close();
}
// 可选:处理 done
else if (data.type === 'done') {
console.log('对话完成:', data);
}
} catch (e) {
@@ -197,19 +184,22 @@ export default {
socketTask.onError((err) => {
const errorMsg = 'WebSocket 连接失败';
console.error(errorMsg, err);
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
});
socketTask.onClose(() => {
if (!closedNormally && !settled) {
const errorMsg = 'WebSocket 连接意外关闭';
console.error(errorMsg);
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
}
console.log('WebSocket 连接已关闭');
});
const timeout = setTimeout(() => {
if (!isAuthenticated || content === '') {
console.warn('WebSocket 超时,强制关闭');
socketTask.close();
reject(new Error('AI服务响应超时'));
if (onComplete) onComplete({ error: 'AI服务响应超时' });
}
}, 10000);
});
// #endif
},
@@ -398,6 +388,7 @@ export default {
async: false,
data: {}
});
console.log('/api/kefu/health response', response);
const available = [response?.data?.status, response?.data?.components?.ai_service_config?.status]
.filter(item => item === 'healthy').length > 0;
return {

View File

@@ -98,6 +98,54 @@ export default {
});
}
// #endif
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
this._huaweiSignIn(callback);
// #endif
},
/**
* 华为快应用帐号登录
*/
async _huaweiSignIn(callback) {
try {
const HwService = require('@/common/js/hw-jssdk.js').default;
// 确保 HwService 已初始化
if (!HwService.isQuickAppEnv()) {
console.warn('[Auth] 非快应用环境,跳过华为帐号登录');
return;
}
const userInfo = await HwService.Account.signIn();
if (userInfo && userInfo.openId) {
// 将华为 openId 传给后端换取系统 token
this.$api.sendRequest({
url: '/wechat/api/quickapp/huawei_auth',
data: {
openid: userInfo.openId,
unionid: userInfo.unionId,
nickname: userInfo.displayName,
avatar: userInfo.avatarUrl,
gender: userInfo.gender
},
success: res => {
if (res.code >= 0) {
if (res.data.openid) this.authInfo.hw_openid = res.data.openid;
if (res.data.unionid) this.authInfo.hw_unionid = res.data.unionid;
typeof callback == 'function' && callback(this.authInfo);
} else {
this.$util.showToast({
title: res.message ? res.message : '华为帐号登录失败'
});
}
}
});
}
} catch (error) {
console.error('[Auth] 华为帐号登录失败:', error);
this.$util.showToast({
title: '华为帐号登录失败'
});
}
}
}
}

View File

@@ -20,13 +20,20 @@ try {
// 默认域名, 自定义发行时可以修改
let defaultDomain = '';
// #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) {}
// #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/';
// #endif
// 确保域名以 '/' 结尾
@@ -52,6 +59,7 @@ if (process?.env?.NODE_ENV === 'development') {
// 如果本地配置文件不存在或加载失败,保持使用默认配置
// 只在开发模式下输出提示信息
// #ifdef WEB
console.log('本地配置文件 .local.config.js 不存在,使用默认配置');
// #endif
}
}
@@ -112,5 +120,16 @@ export default {
pingInterval: 1500,
// 版本号
version: '1.0'
version: '1.0',
// 华为快应用 HwService 配置
// 需在 AppGallery Connect 创建应用后获取
huawei: {
// AGC 应用配置(替换为实际的 AGC 配置值)
appId: '', // 应用ID
clientId: '', // 客户端ID
clientSecret: '', // 客户端密钥(注意安全,建议由后端下发)
// 华为帐号登录回调地址
redirectUri: window.location.origin + '/hwappx/common/',
}
};

View File

@@ -32,6 +32,7 @@ export class CustomerService {
// 优先级:外部传入 > vuex store > 空对象
const servicerConfig = this.externalConfig || this.vm.$store.state.servicerConfig || {};
console.log(`【实时客服配置】`, servicerConfig);
let platformConfig = null;
@@ -119,9 +120,9 @@ export class CustomerService {
}
/**
* 跳转到 AI智能客服页面(AiChat)
* 跳转到 AI 客服页面(Dify)
*/
openAiChatService() {
openDifyService() {
try {
// 清除未读数(如果存在)
if (typeof this.vm.setAiUnreadCount === 'function') {
@@ -135,7 +136,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 +145,7 @@ export class CustomerService {
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
console.error('跳转 AI 客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
@@ -166,6 +167,8 @@ export class CustomerService {
}
const config = this.getPlatformConfig();
console.log('【当前客服配置】', config);
console.log('【客服类型】', config.type);
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
@@ -177,24 +180,31 @@ export class CustomerService {
// 核心路由:根据 type 决定行为
switch (config.type) {
case 'aikefu':
this.openAiChatService();
console.log('【跳转 AI 客服】目标路径: /pages_tool/ai-chat/index');
this.openDifyService();
break;
case 'wxwork':
console.log('【跳转企业微信客服】');
this.openWxworkService(false, config, options);
break;
case 'third':
console.log('【跳转第三方客服】');
this.openThirdService(config);
break;
case 'miniprogram':
console.log('【跳转第三方小程序客服】');
this.openThirdService(config);
break;
case 'niushop':
console.log('【跳转牛商客服】');
this.openNiushopService(niushop);
break;
case 'weapp':
console.log('【跳转微信官方客服】');
this.openWeappService(config, options);
break;
case 'aliapp':
console.log('【跳转支付宝客服】');
this.openAliappService(config);
break;
default:
@@ -215,7 +225,7 @@ export class CustomerService {
wx.navigateToMiniProgram({
appId: 'wxeb490c6f9b154ef9',
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig.contact_url)}`,
success: () => {},
success: () => console.log('跳转企业微信成功'),
fail: (err) => {
console.error('跳转企业微信失败:', err);
this.fallbackToOriginalService();
@@ -252,20 +262,24 @@ export class CustomerService {
}
openThirdService(config) {
console.log('【第三方客服配置】', config);
console.log('【配置字段】', Object.keys(config));
// 支持多种可能的字段名
const miniAppId = config.mini_app_id || config.miniAppId || config.appid || config.appId || config.app_id;
const miniAppPath = config.mini_app_path || config.miniAppPath || config.path || config.page_path || '';
console.log('【解析后的小程序配置】AppID:', miniAppId, 'Path:', miniAppPath);
// 优先处理第三方微信小程序客服
if (miniAppId) {
console.log('【跳转第三方小程序】AppID:', miniAppId, 'Path:', miniAppPath);
// #ifdef MP-WEIXIN
wx.navigateToMiniProgram({
appId: miniAppId,
path: miniAppPath,
success: () => {
console.log('【跳转第三方小程序成功】');
},
fail: (err) => {
console.error('【跳转第三方小程序失败】', err);
@@ -281,6 +295,7 @@ export class CustomerService {
// 处理第三方链接客服
if (config.third_url) {
console.log('【跳转第三方链接】', config.third_url);
// #ifdef H5
window.location.href = config.third_url;
// #endif
@@ -311,7 +326,7 @@ export class CustomerService {
// 此方法仅用于自定义跳转(如 useOfficial: false)
if (config.useOfficial !== false) {
// 不做任何事,应由 <button open-type="contact"> 触发
console.log('使用微信官方客服,请确保按钮为 <button open-type="contact">');
return;
}
this.handleCustomWeappService(config, options);
@@ -364,12 +379,12 @@ export class CustomerService {
openAliappService(config) {
if (config.type === 'aikefu') {
this.openAiChatService();
this.openDifyService();
} else if (config.type === 'third') {
this.openThirdService(config);
} else {
// 支付宝原生客服由 button open-type="contact" 触发,此处不处理
console.log('使用支付宝官方客服');
}
}
@@ -565,7 +580,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 +588,7 @@ export class CustomerService {
}
});
} catch (e) {
console.error('跳转 AI智能客服异常:', e);
console.error('跳转 AI 客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
@@ -629,6 +644,8 @@ export class CustomerService {
handleUnifiedKefuClick(options = {}) {
const validation = this.validateConfig();
console.log('【客服配置验证】', validation);
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
@@ -640,10 +657,12 @@ export class CustomerService {
}
const platformConfig = this.getPlatformConfig();
console.log('【当前客服配置】', platformConfig);
// 检查企业微信配置
if (platformConfig.type === 'wxwork') {
const wxworkConfig = this.getWxworkConfig();
console.log('【企业微信配置】', wxworkConfig);
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {

View File

@@ -200,6 +200,7 @@ export default {
},
methods: {
play(){
console.log(123)
},
async init() {

View File

@@ -61,6 +61,9 @@ class EventBus {
return 'xhs'
// #endif
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
return 'quickapp'
// #endif
return 'unknown'
}

View File

@@ -164,6 +164,7 @@ export default {
// 商品详情
if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = (this.goodsSkuDetail.goods_content);
console.log(this.goodsSkuDetail.goods_content)
// if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = htmlParser(this.goodsSkuDetail.goods_content);
//商品服务

View File

@@ -316,9 +316,11 @@ function replaceVideo(html){
let rep = `<video style="width:100% !important;display:block;max-width: ${screenWidth} !important;"`;
var html = html.replace(/\\/g, '').replace(/<video/g, rep);
console.log(html)
html = html.replace(/<video [^>]*src=['"]([^'"]+)[^>]*>/gi, (match, capture) => {
return rep + ' src="' + util.img(capture) + '"/>';
});
// console.log(html)
return html;
}

View File

@@ -37,6 +37,11 @@ const app_type = 'app';
const app_type_name = 'APP';
// #endif
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
const app_type = 'quickapp';
const app_type_name = '快应用';
// #endif
export default {
sendRequest(params) {
if (!Config.baseUrl) {

361
common/js/hw-jssdk.js Normal file
View File

@@ -0,0 +1,361 @@
/**
* 华为快应用 HwService JS SDK 统一封装
* 用于 H5 壳模式下的快应用环境,通过 hms-js-base 桥接层调用 HMS Core 服务
*
* 架构说明:
* - 所有 HMS 服务通过 @hw-hmscore/hms-js-base 的 hmsjsb.invoke() 统一桥接
* - Analytics Kit 有独立封装 @hw-hmscore/analytics-sdk-quickapp(可选安装)
* - Account Kit 无独立 quickapp npm 包,直接通过 hmsjsb.invoke 调用
* - Push Kit 无独立 quickapp npm 包,直接通过 hmsjsb.invoke 调用
*
* 使用方式:
* import HwService from '@/common/js/hw-jssdk.js'
*
* // 初始化
* await HwService.init()
*
* // 华为帐号登录
* const userInfo = await HwService.Account.signIn()
*
* // 分析服务埋点
* HwService.Analytics.onEvent('event_name', { key: 'value' })
*
* @version 1.1.0
*/
// hms-js-base 提供底层 JS Bridge 桥接能力
let hmsjsb = null
let hmsbase = null
// Analytics SDK 独立包(可选安装)
let HiAnalytics = null
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
try {
// 加载基础桥接库(必装)
const hmsBase = require('@hw-hmscore/hms-js-base')
hmsjsb = hmsBase.hmsjsb
hmsbase = hmsBase.hmsbase
// 尝试加载 Analytics SDK(可选)
try {
HiAnalytics = require('@hw-hmscore/analytics-sdk-quickapp')
} catch (e) {
console.log('[HwService] Analytics SDK 未安装,将使用 invoke 桥接方式')
}
} catch (e) {
console.log('[HwService] hms-js-base 未安装或不在快应用环境')
}
// #endif
class HwService {
constructor() {
this._initialized = false
this._config = null
// 子服务实例
this.Account = null
this.Analytics = null
this.Push = null
}
/**
* 判断当前是否运行在快应用引擎环境
*/
isQuickAppEnv() {
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
if (typeof window !== 'undefined') {
return !!(
window.qa ||
window.__HwService__ ||
window.hms ||
hmsjsb || // hms-js-base 加载成功
navigator.userAgent.indexOf('QuickApp') > -1
)
}
// #endif
return false
}
/**
* 初始化 HwService
* @param {Object} config { appId, clientId, clientSecret }
*/
async init(config = {}) {
if (this._initialized) {
console.log('[HwService] 已初始化,跳过')
return
}
this._config = config
console.log('[HwService] 开始初始化...')
try {
// 子服务实例化
this.Account = new AccountService(hmsjsb)
this.Analytics = new AnalyticsService(hmsjsb, HiAnalytics)
this.Push = new PushService(hmsjsb)
this._initialized = true
console.log('[HwService] 初始化完成')
} catch (error) {
console.error('[HwService] 初始化失败:', error)
throw error
}
}
}
// ============================================================
// 底层 invoke 封装
// ============================================================
/**
* 通过 hmsjsb.invoke 调用 HMS Core API 的 Promise 封装
* @param {Object} hmsjsbRef hmsjsb 引用
* @param {string} apiName API 名称
* @param {Object} args 参数
* @returns {Promise<Object>}
*/
function invokeHmsApi(hmsjsbRef, apiName, args = {}) {
return new Promise((resolve, reject) => {
if (!hmsjsbRef || typeof hmsjsbRef.invoke !== 'function') {
reject(new Error('[HwService] hmsjsb.invoke 不可用'))
return
}
hmsjsbRef.invoke({
apiName,
args,
success: (res) => resolve(res),
fail: (err) => reject(err)
})
})
}
// ============================================================
// 华为帐号服务 (Account Kit)
// 通过 hmsjsb.invoke 桥接(无独立 npm 包)
// ============================================================
class AccountService {
constructor(hmsjsbRef) {
this._hmsjsb = hmsjsbRef
}
/**
* 华为帐号登录
* @param {Array} scopes 授权范围
* @returns {Promise<Object>} 用户信息
*/
async signIn(scopes = ['openid', 'profile']) {
console.log('[HwService.Account] signIn...')
// apiName 需根据实际华为文档确认,常见值有:
// 'accountSignIn' | 'com.huawei.hms.api.Account.signIn'
const result = await invokeHmsApi(this._hmsjsb, 'accountSignIn', { scopes })
return this._formatUserInfo(result)
}
/**
* 静默获取华为帐号用户信息
*/
async getUserInfo() {
const result = await invokeHmsApi(this._hmsjsb, 'accountGetUserInfo', {})
return this._formatUserInfo(result)
}
/**
* 退出登录
*/
async signOut() {
try {
await invokeHmsApi(this._hmsjsb, 'accountSignOut', {})
} catch (e) {
console.warn('[HwService.Account] signOut 失败:', e)
}
}
/**
* 统一格式化用户信息
*/
_formatUserInfo(rawInfo) {
if (!rawInfo) return null
return {
openId: rawInfo.openId || rawInfo.openid || '',
unionId: rawInfo.unionId || rawInfo.unionid || '',
displayName: rawInfo.displayName || rawInfo.nickName || rawInfo.nickname || '',
avatarUrl: rawInfo.avatarUrl || rawInfo.avatar || rawInfo.headPictureUrl || '',
gender: rawInfo.gender || 0,
country: rawInfo.country || '',
province: rawInfo.province || '',
city: rawInfo.city || '',
raw: rawInfo
}
}
}
// ============================================================
// 华为分析服务 (Analytics Kit)
// 支持 @hw-hmscore/analytics-sdk-quickapp(优先)或 invoke 桥接
// ============================================================
class AnalyticsService {
constructor(hmsjsbRef, hiAnalyticsSdk) {
this._hmsjsb = hmsjsbRef
this._sdk = hiAnalyticsSdk
}
/**
* 上报事件
* @param {string} eventName 事件名称
* @param {Object} params 事件参数
*/
onEvent(eventName, params = {}) {
// 方案一:Analytics SDK(独立封装包)
if (this._sdk && typeof this._sdk.onEvent === 'function') {
this._sdk.onEvent(eventName, params)
return
}
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
try {
const instance = this._sdk.HiAnalytics.getInstance()
instance.onEvent(eventName, params)
return
} catch (e) {
// fallthrough
}
}
// 方案二:invoke 桥接
if (this._hmsjsb) {
this._hmsjsb.invoke({
apiName: 'analyticsOnEvent',
args: { eventName, params },
success: () => {},
fail: (err) => console.warn('[HwService.Analytics] onEvent 失败:', err)
})
return
}
// fallback
console.log('[HwService.Analytics] onEvent (fallback):', eventName, params)
}
/**
* 设置用户属性
* @param {Object} profile
*/
setUserProfile(profile = {}) {
if (this._sdk && typeof this._sdk.setUserProfile === 'function') {
this._sdk.setUserProfile(profile)
return
}
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
try {
const instance = this._sdk.HiAnalytics.getInstance()
instance.setUserProfile(profile)
return
} catch (e) {}
}
if (this._hmsjsb) {
this._hmsjsb.invoke({
apiName: 'analyticsSetUserProfile',
args: { profile },
success: () => {},
fail: (err) => console.warn('[HwService.Analytics] setUserProfile 失败:', err)
})
return
}
console.log('[HwService.Analytics] setUserProfile (fallback):', profile)
}
/**
* 设置用户 ID
* @param {string} userId
*/
setUserId(userId) {
if (this._sdk && typeof this._sdk.setUserId === 'function') {
this._sdk.setUserId(userId)
return
}
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
try {
const instance = this._sdk.HiAnalytics.getInstance()
instance.setUserId(userId)
return
} catch (e) {}
}
if (this._hmsjsb) {
this._hmsjsb.invoke({
apiName: 'analyticsSetUserId',
args: { userId },
success: () => {},
fail: (err) => console.warn('[HwService.Analytics] setUserId 失败:', err)
})
return
}
console.log('[HwService.Analytics] setUserId (fallback):', userId)
}
/**
* 页面浏览事件
*/
onPageView(pageName, pageClassOverride = '') {
this.onEvent('$PageView', {
pageName,
pageClassOverride: pageClassOverride || pageName
})
}
}
// ============================================================
// 华为推送服务 (Push Kit)
// 通过 hmsjsb.invoke 桥接(无独立 npm 包)
// ============================================================
class PushService {
constructor(hmsjsbRef) {
this._hmsjsb = hmsjsbRef
this._token = null
}
/**
* 获取推送令牌
*/
async getToken() {
if (this._token) return this._token
try {
const result = await invokeHmsApi(this._hmsjsb, 'pushGetToken', {})
this._token = result.token || result
return this._token
} catch (e) {
console.warn('[HwService.Push] getToken 不可用:', e)
return null
}
}
/**
* 监听推送令牌变更
*/
onTokenReceived(callback) {
if (!this._hmsjsb) return
this._hmsjsb.on('pushOnToken', callback)
}
/**
* 监听消息到达
*/
onMessageReceived(callback) {
if (!this._hmsjsb) return
this._hmsjsb.on('pushOnMessageReceived', callback)
}
/**
* 监听通知点击
*/
onNotificationClicked(callback) {
if (!this._hmsjsb) return
this._hmsjsb.on('pushOnNotificationClicked', callback)
}
}
// ============================================================
// 导出单例
// ============================================================
const hwService = new HwService()
export default hwService
export { AccountService, AnalyticsService, PushService }

View File

@@ -54,14 +54,11 @@ function loadLangPackSync(lang, path) {
}
}
/**
* 获得当前本地语言
* @returns
*/
function getCurrentLocale() {
return uni.getStorageSync('lang') || "zh-cn";
}
export default {
langList: langConfig.langList,

View File

@@ -89,13 +89,16 @@ export default {
let platform = util.getDeviceInfo().platform;
switch (platform) {
case 'android':
console.log('运行Android上')
openMapByAndroid(arr[1], arr[0], name)
break;
case 'ios':
console.log('运行iOS上')
openMapByIos(arr[1], arr[0], name)
break;
default:
openMapByDefault(arr[1], arr[0], name)
console.log('运行在开发者工具上')
break;
}
// #endif

View File

@@ -43,6 +43,7 @@ export class NavigationHelper {
query.select('.uni-page-head').boundingClientRect((rect) => {
if (rect && rect.height > 0) {
console.log('直接查询导航栏高度成功:', rect.height)
resolve(rect.height)
} else {
console.warn('未找到 uni-page-head 元素或高度为0')
@@ -63,6 +64,7 @@ export class NavigationHelper {
const height = menuButtonInfo.bottom +
(menuButtonInfo.top - systemInfo.statusBarHeight)
console.log('微信小程序导航栏高度:', height)
resolve(height)
} catch (error) {
console.error('微信小程序高度计算失败:', error)
@@ -172,6 +174,7 @@ export class NavigationHelper {
// 处理服务请求
async handleServiceRequest(event, component) {
console.log('处理导航相关服务请求:', event.type)
// 安全检查事件目标
if (this.shouldProcessNavigationRequest(event)) {
@@ -275,6 +278,7 @@ export class NavigationHelper {
// 清理缓存
clearCache() {
this.navigationCache.clear()
console.log('导航缓存已清理')
}
}

View File

@@ -21,35 +21,53 @@ export default {
methods: {
// 判断是否已连接
checkOpenSocket() {
console.log('判断是否已连接');
// alert('判断是否已连接')
let self = this;
uni.sendSocketMessage({
data: 'ping',
success: (res) => {
console.log('连接成功,检查');
// alert('连接成功,检查')
// self.getChatList();
},
fail: (err) => { // 未连接打开websocket连接
console.log('连接失败');
// alert('连接失败')
self.openConnection();
}
});
},
openConnection() { // 打开连接
console.log('打开连接');
// alert('打开连接')
// uni.closeSocket(); // 确保已经关闭后再重新打开
uni.connectSocket({
url: config.webSocket,
method: 'POST',
success(res) {
console.log('连接成功 connectSocket=', res);
// alert('连接成功 connectSocket=', res);
},
fail(err) {
console.log('连接失败 connectSocket=', err);
}
});
// uni.onSocketOpen((res) => {
// console.log('连接成功', res);
// });
this.onSocketMessage(); // 打开成功监听服务器返回的消息
},
// 打开成功监听服务器返回的消息
onSocketMessage() { // 消息
console.log("开始监听");
let that = this;
this.pingInterval = config.pingInterval;
this.timeoutObj = null;
uni.onSocketMessage((res) => { //type:init,connect,close,string,order,goods
let msg = JSON.parse(res.data);
console.log("监听该服务器消息", res);
if (msg.type == 'close') {
clearInterval(that.timeoutObj);
that.timeoutObj = null;
@@ -123,18 +141,22 @@ export default {
},
// 检测心跳reset
reset() {
console.log("检测心跳");
clearInterval(this.timeoutObj);
this.start(); // 启动心跳
},
// 启动心跳 start
start() {
console.log("启动心跳");
let self = this;
this.timeoutObj = setInterval(function () {
uni.sendSocketMessage({
data: 'ping',
success: (res) => {
console.log('连接中....');
},
fail: (err) => {
console.log('连接失败重新连接....');
self.openConnection();
}
});

View File

@@ -34,6 +34,7 @@ export const makePhoneCall = (mobile) => {
uni.makePhoneCall({
phoneNumber: `${mobile}`,
success(e) {
console.log(e);
}
});
} catch (err) {

View File

@@ -132,7 +132,9 @@ export default {
let url = to;
// 替换url中的前缀
console.log('页面跳转 redirectTo', to, param, mode);
url = adaptSubpackageUrl(url);
console.log('adaptSubpackageUrl', url);
if (param != undefined) {
Object.keys(param).forEach(function (key) {
@@ -176,6 +178,7 @@ export default {
break;
default:
// 保留当前页面,跳转到应用内的某个页面,新增页面到栈顶,长度+1(上限10层)
console.log('保留当前页面,navigateTo跳转到应用内的某个页面', url);
uni.navigateTo({
url
});
@@ -620,8 +623,12 @@ export default {
appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID(固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面)
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig?.contact_url)}`,
success: () => {
console.log('跳转企业微信成功');
},
fail: (err) => {
console.error('跳转企业微信失败:', err);
// 降级处理:使用原有客服方式
fallbackFunc && fallbackFunc();
}
});
} else {
@@ -902,11 +909,12 @@ export default {
},
success: res => {
if (res.code >= 0) {
uni.removeStorage({
key: 'source_member',
success: res => {
}
})
uni.removeStorage({
key: 'source_member',
success: res => {
console.log('删除成功', res)
}
})
}
}
})
@@ -930,23 +938,27 @@ export default {
success: res => {
if (res.data.length) {
// #ifdef MP-WEIXIN
uni.requestSubscribeMessage({
tmplIds: res.data,
success: (res) => {
},
fail: (res) => {
}
})
uni.requestSubscribeMessage({
tmplIds: res.data,
success: (res) => {
console.log("res", res)
},
fail: (res) => {
console.log('fail', res)
}
})
// #endif
// #ifdef MP-ALIPAY
my.requestSubscribeMessage({
entityIds: res.data,
success: res => {
},
fail: res => {
}
});
my.requestSubscribeMessage({
entityIds: res.data,
success: res => {
console.log("res", res)
},
fail: res => {
console.log('fail', res)
}
});
// #endif
}
}
@@ -1201,6 +1213,7 @@ export default {
let _isQuickApp = false;
try {
const ua = navigator?.userAgent?.toLowerCase();
// console.log('ua = ', ua);
_isQuickApp = ua.indexOf('quickapp') !== -1;
if (!_isQuickApp) {

View File

@@ -55,6 +55,7 @@ let Weixin = function () {
typeof callback == 'function' && callback(res);
},
fail: (res) => {
console.log('获取收货地址 fail',res);
alert(JSON.stringify(res))
}
});

View File

@@ -98,7 +98,9 @@ export default {
uni.setStorageSync('audio_list', audio_list)
}
}
console.log(uni.getStorageSync('audio_list'))
var n = this;
// console.log(a)
a.onPlay(function () {
var e = setInterval(function () {
var i = a.currentTime / a.duration * 100 + "%", s = Math.floor(Math.ceil(a.currentTime) / 60), o = (Math.ceil(a.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(a.currentTime);
@@ -111,12 +113,13 @@ export default {
var s = n.$util.img(n.value.audioUrl), o = n.audios[n.value.id].seconds || 0, r = 0, u = 1;
0 == u && a.onEnded(function (e) {
c[t].status = !1, n.status = !1, c[t].seconds = 0, n.audios = c;
c[t].status = !1, n.status = !1, c[t].seconds = 0, console.log(c), n.audios = c;
});
var c = n.audios;
c[t] || (c[t] = {}), a.paused && 0 == o ? (a.src = s, a.play(), 1 == u && (a.loop = !0),
c[t].status = !0, n.status = !0, n.pauseOther(t)) : a.paused && o > 0 ? (a.play(), 0 == r ? a.seek(o) : a.seek(0),
c[t].status = !0, n.status = !0, n.pauseOther(t)) : (a.pause(), c[t].status = !1, n.status = !1), n.audios = c;
console.log(n.audios)
},
pauseOther: function (e) {
var t = this;
@@ -187,14 +190,18 @@ export default {
var u = s + ":" + o, c = that.audios;
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r
that.audios = c
// console.log(c)
console.log(that.audios[that.value.id].audiotime)
that.audiotime = that.audios[that.value.id].audiotime
that.audiowidth = that.audios[that.value.id].audiowidth
console.log(i)
that.lyg = i
}, 1e3);
});
this.audioContext.onEnded(() => {
console.log('播放结束');
this.status = 0
});
}

View File

@@ -256,6 +256,7 @@ export default {
try {
// 发送视频被点击播放事件
this.$emit('channel-video-click-play', item);
console.log('播放视频:', item);
// #ifdef MP-WEIXIN
// 在微信小程序环境中调用视频播放接口
@@ -288,6 +289,7 @@ export default {
*/
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
// 为失败的图片设置默认图片
const item = this.value.list[index];
if (item) {
@@ -304,6 +306,7 @@ export default {
addMouseDragScroll() {
// 只在Web环境中添加
// #ifndef MP
console.log('addMouseDragScroll called');
let isDragging = false;
let startX = 0;
@@ -326,9 +329,11 @@ export default {
const handleMouseDown = (e) => {
// 检查是否点击在组件内
if (this.$el.contains(e.target)) {
console.log('mousedown event in component:', e);
// 查找可滚动元素
currentScrollElement = findScrollableParent(e.target);
if (currentScrollElement) {
console.log('Found scrollable element:', currentScrollElement);
isDragging = true;
startX = e.pageX;
startScrollLeft = currentScrollElement.scrollLeft;
@@ -340,14 +345,17 @@ export default {
// 鼠标移动事件
const handleMouseMove = (e) => {
if (!isDragging || !currentScrollElement) return;
console.log('mousemove event:', e);
e.preventDefault();
const dx = e.pageX - startX;
currentScrollElement.scrollLeft = startScrollLeft - dx;
console.log('scrollLeft:', currentScrollElement.scrollLeft);
};
// 鼠标释放事件
const handleMouseUp = () => {
if (isDragging && currentScrollElement) {
console.log('mouseup event');
currentScrollElement.style.cursor = 'grab';
}
isDragging = false;
@@ -360,17 +368,21 @@ export default {
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mouseleave', handleMouseUp);
console.log('Global mouse event listeners added');
// 组件销毁时移除事件监听器
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('mousedown', handleMouseDown);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mouseleave', handleMouseUp);
console.log('Global mouse event listeners removed');
});
// 为所有.channel-nav元素添加必要的样式
setTimeout(() => {
const channelNavs = document.querySelectorAll('.channel-nav');
console.log('Found channel-nav elements:', channelNavs.length);
channelNavs.forEach(element => {
element.style.overflowX = 'auto';
element.style.overflowY = 'hidden';
@@ -379,9 +391,11 @@ export default {
element.style.maxWidth = '100%';
element.style.cursor = 'grab';
element.style.userSelect = 'none'; // 防止文本选择
console.log('Added styles to channel-nav:', element);
});
}, 100); // 延迟执行,确保DOM已完全渲染
console.log('Mouse drag scroll setup completed');
// #endif
}
}

View File

@@ -139,6 +139,7 @@ export default {
const enableEmbedMode = Boolean(this.value?.embedMode)
&& Boolean(this.value?.feedToken)
&& wechatChannelUtil.isEmbedModeSupported();
console.log('enableEmbedMode', enableEmbedMode);
return enableEmbedMode;
// #endif
return false

View File

@@ -91,6 +91,7 @@ export default {
// 页面跳转
redirectTo(item) {
if (!item.wap_url || this.$util.getCurrRoute() != this.$util.MEMBER_PAGE_URL || this.storeToken) {
console.log(item);
this.$util.diyRedirectTo(item);
} else {
this.$refs.login.open(item.wap_url);

View File

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

View File

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

View File

@@ -164,6 +164,7 @@ export default {
return;
}
}
console.log(link)
this.$util.diyRedirectTo(link);
},
swiperChange(e) {

View File

@@ -358,6 +358,11 @@ export default {
});
} else data = this.setPagestyle;
console.log(`diy-group ['diyDataArray'] = `, {
data: data,
diyData: this.diyData,
diyGlobalData: this.diyGlobalData,
})
return data;
}
},

View File

@@ -109,6 +109,7 @@ export default {
}
})
.catch(err => {
console.log('get live status', err);
});
// 往后间隔1分钟或更慢的频率去轮询获取直播状态
@@ -126,7 +127,8 @@ export default {
clearInterval(timer);
}
})
.catch(err => {
.catch(err => {
console.log('get live status', err);
});
}, 60000);
// #endif

View File

@@ -119,6 +119,7 @@ export default {
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
console.log(obj)
return obj;
},
// 子项样式
@@ -139,12 +140,14 @@ export default {
}
return obj;
}
},
},
methods: {
getList() {
console.log(121)
var data = {
num: this.value.count
};
console.log(this.value)
if (this.value.sources == 'diy') {
data.num = 0;
data.merch_id_arr = this.value.merchIds.toString();

View File

@@ -75,6 +75,8 @@ export default {
// 允许从相机和相册扫码,h5端不起作用
uni.scanCode({
success: function (res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
}
});
// #endif

View File

@@ -19,7 +19,7 @@
<template v-for="(item, index) in list">
<template v-if="index > 0">
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)"
:style="{ marginBottom: index < list.length - 1 ? value.imageGap * 2 + 'rpx' : 0, width: item.imgWidth, height: item.imgHeight + 'px' }">
:style="{ marginBottom: value.imageGap * 2 + 'rpx', 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,6 +235,8 @@ 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) => {
@@ -244,14 +246,12 @@ 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,6 +311,7 @@ export default {
this.list.forEach((item, index) => {
item.imgWidth += 'px';
item.imgHeight;
});
}
});
@@ -481,7 +482,7 @@ export default {
// 一左两右
.rubik-cube .template-left,
.rubik-cube .template-right {
width: 50%;
// width: 50%;
box-sizing: border-box;
}

View File

@@ -221,7 +221,10 @@ export default {
// 组件创建钩子
created() {
console.log(`diy-tab-create`, {
value: this.mergedValue,
tabs: this.mergedValue.tabs
});
},
// 组件挂载钩子

View File

@@ -114,6 +114,7 @@ export default {
style += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
style += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
console.log(this.value);
return style;
},
@@ -173,6 +174,7 @@ export default {
// 图片加载错误处理
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
},
// 弹窗视频加载完成事件
@@ -200,6 +202,7 @@ export default {
if (width && height) {
this.popupVideoRatio = width >= height ? 'landscape' : 'portrait';
console.log(`弹窗视频宽高比检测: ${width}x${height}, 比例: ${this.popupVideoRatio}`);
} else {
// 获取失败,默认竖屏
this.popupVideoRatio = 'portrait';
@@ -230,6 +233,10 @@ export default {
width: calc(33.3333333% - 14rpx);
box-sizing: border-box;
&:nth-child(3n + 3) {
width: calc(33.33% - 15rpx);
}
&:nth-of-type(1),
&:nth-of-type(2),
&:nth-of-type(3) {

View File

@@ -127,6 +127,8 @@ export default {
const videoUrl = this.videoUrl;
if (!videoUrl) return;
console.log('H5端预加载视频获取比例:', videoUrl);
const video = document.createElement('video');
video.preload = 'metadata';
video.src = videoUrl;
@@ -136,9 +138,11 @@ export default {
video.addEventListener('loadedmetadata', () => {
const width = video.videoWidth;
const height = video.videoHeight;
console.log('H5端预加载获取视频宽高:', width, height);
if (width && height) {
this.videoRatio = width >= height ? 'landscape' : 'portrait';
console.log('H5端视频比例判定:', this.videoRatio, width >= height ? '横屏' : '竖屏');
}
});
@@ -152,6 +156,8 @@ export default {
// 视频加载完成事件(组件内video标签)
onVideoLoaded(event) {
console.log('视频组件 loadedmetadata 事件触发');
let width = 0;
let height = 0;
@@ -159,6 +165,7 @@ export default {
if (event && event.target) {
width = event.target.videoWidth || 0;
height = event.target.videoHeight || 0;
console.log('H5端组件视频宽高:', width, height);
}
// #endif
@@ -166,20 +173,24 @@ export default {
if (event && event.detail) {
width = event.detail.width || 0;
height = event.detail.height || 0;
console.log('微信小程序端视频宽高:', width, height);
}
// #endif
if (width && height) {
this.videoRatio = width >= height ? 'landscape' : 'portrait';
console.log('视频比例最终判定:', this.videoRatio);
}
},
// 视频播放事件
onVideoPlay() {
console.log('视频开始播放');
},
// 视频暂停事件
onVideoPause() {
console.log('视频暂停');
},
// 视频错误事件

View File

@@ -149,8 +149,9 @@ export const wechatChannelUtil = {
wx.openChannelsActivity({
feedId: item.feedId,
finderUserName: item.finderUserName,
success: (res) => {
resolve(res);
success: (res) => {
console.log('打开视频号成功', res);
resolve(res);
},
fail: (err) => {
this.handleError(err, item);

View File

@@ -2,6 +2,7 @@ export default {
methods: {
// 异步触发事件
async __$emitEvent(payload = {eventName: '__unnamedEvent', data: {}, promiseCallback: null}) {
// console.log('__$emitEvent', payload)
await this.$eventBus.emit(payload.eventName, payload.data, payload.promiseCallback)
},

View File

@@ -186,6 +186,7 @@ export default {
uni.getStorage({
key: 'shopInfo',
success: (e) => {
console.log('【调试】当前 shopInfo:', e.data);
this.shopInfo = e.data;
},
fail: () => {

View File

@@ -53,29 +53,40 @@ export default {
}
},
mounted() {
this.listenPrivacySettingChange()
this.curPageShow()
this.proactive()
// 检查配置,决定是否显示隐私协议
const shopInfo = uni.getStorageSync('shopInfo');
if (shopInfo?.ischina == 1 && shopInfo?.index_login_auth == 1) {
this.listenPrivacySettingChange()
this.curPageShow()
} else {
// 如果不需要显示隐私协议,直接触发同意事件
this.$emit('agree');
}
},
created() {
try {
// #ifdef MP-WEIXIN
//查询微信侧记录的用户是否有待同意的隐私政策信息
wx.getPrivacySetting({
success: (res) => {
// console.log('隐私政策信息', res);
// console.log(res.privacyContractName);
this.privacyContractName = res.privacyContractName
}
});
// #endif
// #ifdef QUICKAPP-WEBVIEW || H5
if (this.$util.isQuickApp() && typeof has != 'undefined' && has?.getPrivacySetting) {
has.getPrivacySetting({
success: (res) => {
if (res.privacyContractName) {
this.privacyContractName = res.privacyContractName
}
},
})
if (this.$util.isQuickApp()) {
if (typeof has != 'undefined' && has?.getPrivacySetting) {
has.getPrivacySetting({
success: (res) => {
if (res.privacyContractName) {
this.privacyContractName = res.privacyContractName
}
},
})
}
}
// #endif
} catch (e) {
@@ -83,50 +94,39 @@ export default {
}
},
methods: {
/**
* 统一弹窗 + 关闭其他页面的弹窗
*/
showPrivacyPopup() {
this.popUp()
this.closeOtherPagePopUp(this.disPopUp)
},
/**
* 根据是否需要授权决定弹窗或 emit agree
*/
checkAndShow(needAuthorization) {
if (needAuthorization) {
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree')
} else {
this.showPrivacyPopup()
}
} else {
this.$emit('agree')
}
},
getPrivacySettingByQuickApp() {
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
if (!this.$util.isQuickApp()) return;
if (typeof has != 'undefined' && has?.getPrivacySetting) {
has.getPrivacySetting({
success: (res) => {
this.checkAndShow(res.needAuthorization)
},
})
} else {
this.checkAndShow(!uni.getStorageSync('privacyAgreed'))
if (this.$util.isQuickApp()) {
if (typeof has != 'undefined' && has?.getPrivacySetting) {
has.getPrivacySetting({
success: (res) => {
// console.log(res)
if (res.needAuthorization) {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
} else {
this.$emit('agree')
}
},
})
} else {
// 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理,判断是否已经授权
if (uni.getStorageSync('privacyAgreed')) {
this.$emit('agree')
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
}
}
// #endif
},
/**
* 监听平台侧的隐私授权事件
*/
listenPrivacySettingChange() {
// #ifdef MP-WEIXIN
// 监听微信侧隐私政策授权变化事件
if (wx.onNeedPrivacyAuthorization) {
wx.onNeedPrivacyAuthorization((resolve) => {
if (typeof this.privacyAuthorization === 'function') {
@@ -137,28 +137,66 @@ export default {
// #endif
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
if (this.$util.isQuickApp() && typeof has != 'undefined' && has?.onPrivacySettingChange) {
has.onPrivacySettingChange((res) => {
this.checkAndShow(res.needAuthorization)
})
if (this.$util.isQuickApp()) {
// 监听快速应用侧隐私政策授权变化事件
if (typeof has != 'undefined' && has?.onPrivacySettingChange) {
has.onPrivacySettingChange((res) => {
// console.log(res)
if (res.needAuthorization) {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
} else {
this.$emit('agree')
}
})
}
}
// #endif
},
/**
* 主动查询隐私授权状态并弹窗
*/
// 主动查询用户隐私政策授权状态,针对快速应用
this.getPrivacySettingByQuickApp();
},
// 主动查询用户隐私政策授权状态
proactive() {
// 检查配置,决定是否显示隐私协议
const shopInfo = uni.getStorageSync('shopInfo');
if (!(shopInfo?.ischina == 1 && shopInfo?.index_login_auth == 1)) {
// 如果不需要显示隐私协议,直接触发同意事件
this.$emit('agree');
return;
}
// #ifdef MP-WEIXIN
if (wx.getPrivacySetting) {
wx.getPrivacySetting({
success: (res) => {
this.checkAndShow(res.needAuthorization)
// console.log(res)
if (res.needAuthorization) {
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (!privacyAgreed) {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
} else {
this.$emit('agree')
}
} else {
this.$emit('agree')
}
},
});
} else {
// 低版本基础库直接检查本地存储
this.checkAndShow(!uni.getStorageSync('privacyAgreed'))
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
}
// #endif
@@ -167,24 +205,29 @@ export default {
// #endif
// #ifdef H5 || WEB
this.checkAndShow(!uni.getStorageSync('privacyAgreed'))
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
// #endif
},
/**
* 初始化监听程序(页面级)
*/
//初始化监听程序
curPageShow() {
closeOtherPagePopUpHooks.add(this.disPopUp)
this.privacyAuthorization = resolve => {
this.privacyResolves.add(resolve)
this.showPrivacyPopup()
//打开弹窗
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
},
/**
* 弹窗时关掉其他页面的隐私弹窗,避免重叠
*/
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
closeOtherPagePopUp(closePopUp) {
closeOtherPagePopUpHooks.forEach(hook => {
if (closePopUp !== hook) {
@@ -192,12 +235,16 @@ export default {
}
})
},
//打开隐私协议
openPrivacyContract() {
// #ifdef MP-WEIXIN
wx.openPrivacyContract({
success(res) {},
fail(err) {}
success(res) {
// console.log('打开隐私协议', res);
},
fail(err) {
// console.error('打开隐私协议失败', err)
}
});
// #endif
@@ -205,44 +252,59 @@ export default {
if (this.$util.isQuickApp()) {
if (typeof has != 'undefined' && has?.openPrivacySetting) {
has.openPrivacySetting({
success: (res) => {},
fail: (err) => {}
success: (res) => {
// console.log('打开隐私协议', res);
},
fail: (err) => {
// console.error('打开隐私协议失败', err)
}
});
} else {
// 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理
this.$util.redirectTo(privacyContractPage);
}
} else {
// H5 环境下的处理逻辑
this.$util.redirectTo(privacyContractPage);
}
// #endif
},
//打开用户服务协议
openPrivacyService() {
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
if (this.$util.isQuickApp()) {
if (typeof has != 'undefined' && has?.openPrivacySetting) {
has.openPrivacySetting({
success: (res) => {},
fail: (err) => {}
success: (res) => {
// console.log('打开用户服务协议', res);
},
fail: (err) => {
// console.error('打开用户服务协议失败', err)
}
});
} else {
// 属于嵌入到快应用Webview组件中的情况,按照H5的方式来处理
this.$util.redirectTo(privacyServicePage);
}
} else {
// H5 环境下的处理逻辑
this.$util.redirectTo(privacyServicePage);
}
// #endif
},
// 不同意
handleDisagree() {
this.privacyResolves.forEach(resolve => {
resolve({ event: 'disagree' })
resolve({
event: 'disagree',
})
})
this.privacyResolves.clear()
//关闭弹窗
this.disPopUp()
// 保存用户授权状态到本地存储,无论同意还是不同意,都不再重复弹出
uni.setStorageSync('privacyAgreed', true);
//退出小程序
uni.showModal({
content: '未同意隐私协议,无法使用相关功能',
success: () => {
@@ -250,7 +312,7 @@ export default {
}
})
},
// 同意并继续
handleAgree() {
this.privacyResolves.forEach(resolve => {
resolve({
@@ -259,23 +321,28 @@ export default {
})
})
this.privacyResolves.clear()
//关闭弹窗
this.disPopUp()
// 保存用户授权状态到本地存储,用于所有环境
uni.setStorageSync('privacyAgreed', true);
this.$emit('agree')
},
//打开弹窗
popUp() {
this.showPop = true;
},
//关闭弹窗
disPopUp() {
this.showPop = false;
},
},
beforeDestroy() {
// 清理事件监听器和集合
this.privacyResolves.clear()
closeOtherPagePopUpHooks.delete(this.disPopUp)
// 注意:这里需要根据实际情况清理微信和快速应用的事件监听器
// 由于微信的 wx.onNeedPrivacyAuthorization 没有对应的 off 方法,这里可能需要其他方式处理
},
}
</script>

View File

@@ -89,6 +89,7 @@ export default {
}
},
endVal() {
console.log(123)
if (this.autoplay) {
this.start()
}

View File

@@ -164,6 +164,7 @@
appId: 'wxeb490c6f9b154ef9', // 企业微信小程序AppID
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(contactUrl)}`,
success: () => {
console.log('跳转企业微信成功');
this.$util.showToast({
title: '跳转成功',
icon: 'success'
@@ -188,6 +189,7 @@
this.wxWorkSDK.addContact({
userId: contactId
}, (res) => {
console.log('打开用户资料成功:', res);
}, (err) => {
console.error('打开用户资料失败:', err);
this.showError('打开用户资料失败');

View File

@@ -0,0 +1,158 @@
# 华为快应用 HwService JS SDK 集成指南
## 概述
本文档介绍如何将该 uni-app 项目以 H5 壳模式部署到华为快应用平台,并集成华为 HwService JS SDK(帐号服务 + 分析服务)。
## 项目架构
```
H5 壳模式快应用架构:
┌─────────────────────────────────────┐
│ 华为快应用引擎 (QuickApp Runtime) │
│ ┌─────────────────────────────────┐ │
│ │ WebView (H5 页面) │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ uni-app 应用 │ │ │
│ │ │ ├─ common/js/hw-jssdk.js │ │ │
│ │ │ ├─ common/js/auth.js │ │ │
│ │ │ └─ store/index.js │ │ │
│ │ └───────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ │ HwService Bridge │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ HMS Core (华为移动服务) │ │
│ │ ├─ Account Kit (帐号服务) │ │
│ │ ├─ Analytics Kit (分析服务) │ │
│ │ └─ Push Kit (推送服务,可选) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
```
## 部署前准备
### 1. 华为 AppGallery Connect 配置
1. 登录 [AppGallery Connect](https://developer.huawei.com/consumer/cn/service/josp/agc/index.html)
2. 创建应用(选择「快应用」类型)
3. 开启以下服务:
- **华为帐号服务 (Account Kit)**
- **华为分析服务 (Analytics Kit)**(可选)
- **华为推送服务 (Push Kit)**(可选)
4. 下载 `agconnect-services.json` 配置文件
5. 将配置文件放入项目根目录
### 2. 安装 npm 依赖
```bash
# 基础桥接库(必装,提供 hmsjsb.invoke 桥接能力)
npm install --save @hw-hmscore/hms-js-base
# 华为分析服务封装包(可选,提供更完善的 API 封装)
npm install --save @hw-hmscore/analytics-sdk-quickapp
```
**注意:**
- **Account Kit(帐号服务)** 和 **Push Kit(推送服务)** 没有独立的 quickapp npm 包,统一通过 `@hw-hmscore/hms-js-base` 的 `hmsjsb.invoke()` 桥接调用
- `@hw-hmscore/analytics-sdk-quickapp` 版本 `6.10.0-301`(ISC 许可证),**可选安装**——不装则走 invoke 桥接
- 相关 npm 包由华为官方发布(`hmscore@huawei.com`)
### 3. 配置 HUAWEI 条件编译
本项目的 `package.json` 已为各快应用域名定义自定义发行脚本:
| 脚本名 | 条件编译标记 | 用途 |
|--------|------------|------|
| `h5-xcx20.5g-quickapp.com` | `H5_XCX_5G_QUICKAPP_COM` | 5G 快应用 |
| `h5-xcx.aigc-quickapp.com` | `H5_XCX_AIGC_QUICKAPP_COM` | AIGC 快应用 |
| `h5-5g.aigc-quickapp.com` | `H5_5G_AIGC_QUICKAPP_COM` | 5G AIGC 快应用 |
| `h5-test.aigc-quickapp.com` | `H5_TEST_AIGC_QUICKAPP_COM` | 测试快应用 |
当使用 HBuilderX → 发行 → 自定义发行 时,对应的 `#ifdef` 条件编译块会被激活。
### 4. 配置华为参数
编辑 `common/js/config.js` 中的 `huawei` 配置项:
```javascript
huawei: {
appId: '你的AGC应用ID',
clientId: '你的客户端ID',
clientSecret: '你的客户端密钥(建议由后端下发)',
redirectUri: window.location.origin + '/hwappx/common/',
}
```
**注意**:`clientSecret` 敏感信息建议由后端接口动态下发,避免在前端硬编码。
## 后端配合
### 新增接口
华为帐号登录需要后端增加以下接口:
| 接口路径 | 方法 | 说明 |
|---------|------|------|
| `/wechat/api/quickapp/huawei_auth` | POST | 华为 openId 换 token |
**请求参数**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `openid` | string | 华为帐号 openId |
| `unionid` | string | 华为帐号 unionId(可选) |
| `nickname` | string | 用户昵称 |
| `avatar` | string | 头像 URL |
| `gender` | int | 性别(0:未知 1:男 2:女) |
**返回格式**(需与现有登录接口保持一致):
```json
{
"code": 0,
"data": {
"openid": "华为openId",
"unionid": "华为unionId"
}
}
```
## 已修改的文件清单
| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `common/js/hw-jssdk.js` | **新增** | HwService 统一封装(Account/Analytics/Push) |
| `common/js/auth.js` | 修改 | 新增 `_huaweiSignIn` 方法,快应用环境下使用华为帐号登录 |
| `common/js/http.js` | 修改 | 新增 `H5_XCX_*_QUICKAPP_COM` 条件编译,请求类型为 `quickapp` |
| `common/js/event-bus.js` | 修改 | 新增 `quickapp` 平台检测分支 |
| `common/js/config.js` | 修改 | 新增 `huawei` 配置项 |
| `store/index.js` | 修改 | 新增 `huaweiAccountInfo` 状态/`initHwService` action |
## 发布流程
1. 使用 HBuilderX 打开项目
2. 修改 `manifest.json` → Web 配置 → 基础路径(`/hwappx/common/`)
3. 选择「发行」→「自定义发行」→ 选择对应的快应用脚本
4. 构建完成后,将 `unpackage/dist/build/web/` 目录打包为 zip
5. 使用华为快应用 IDE 导入并发布
## 验证方式
### 检查 HwService 是否可用
```javascript
const HwService = require('@/common/js/hw-jssdk.js').default;
console.log('是否快应用环境:', HwService.isQuickAppEnv());
```
### 验证华为帐号登录
在华为手机上打开快应用,点击登录,应弹出华为帐号授权窗口。
### 验证分析服务
集成后可在 AppGallery Connect 控制台查看埋点数据。
## 注意事项
1. **H5 壳模式限制**:HwService 依赖华为快应用引擎注入的 Bridge,在普通浏览器中不可用
2. **真机测试**:必须在华为手机上测试,模拟器可能无法完整模拟 Bridge
3. **条件编译生效**:必须通过 HBuilderX 的自定义发行脚本构建,`#ifdef` 标记才会生效
4. **clientSecret 安全**:建议由后端接口动态下发,不要前端硬编码
5. **登录降级**:如果华为帐号服务不可用,建议提供短信验证码或其他登录方式作为降级方案

View File

@@ -107,7 +107,7 @@
},
"router" : {
"mode" : "history",
"base" : "/hwappx/common/"
"base" : "/hwappx/test/"
},
"title" : "",
"devServer" : {

View File

@@ -1,6 +1,9 @@
{
"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": {
"dart-sass": "^1.25.0",
@@ -25,40 +28,44 @@
"h5-xcx20.5g-quickapp.com": {
"title": "H5-xcx20.5g-quickapp.com",
"env": {
"UNI_PLATFORM": "h5",
"UNI_APP_DOMAIN": "https://xcx20.5g-quickapp.com/"
"UNI_PLATFORM": "h5"
},
"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_APP_DOMAIN": "https://xcx.aigc-quickapp.com/"
"UNI_PLATFORM": "h5"
},
"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_APP_DOMAIN": "https://5g.aigc-quickapp.com/"
"UNI_PLATFORM": "h5"
},
"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_APP_DOMAIN": "https://test.aigc-quickapp.com/"
"UNI_PLATFORM": "h5"
},
"define": {
"H5_TEST_AIGC_QUICKAPP_COM": true,
"H5_PRODUCTION": true,
"PRODUCTION": true
}
}

View File

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

View File

@@ -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" @tap="onTitleTap">{{ siteInfo.site_name }}</text>
<text class="font-size-base">{{ siteInfo.site_name }}</text>
<text>{{ followOfficialAccount.welcomeMsg }}</text>
</view>
</view>
@@ -141,8 +141,6 @@
<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>
@@ -245,14 +243,4 @@ export default {
bottom: 180rpx !important;
/* #endif */
}
.version-trigger {
position: fixed;
right: 0;
bottom: 50rpx;
width: 100rpx;
height: 100rpx;
z-index: 999;
background: transparent;
}
</style>

View File

@@ -87,43 +87,12 @@ export default {
return true;
}
// 打开登录弹窗
// console.log('==> _handleDiyGroupInteractionEvent, 未登录,打开登录弹窗', { payload })
this.$refs.login?.open();
// 阻止事件继续传播
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('未获取到构建版本信息');
}
}
}
}

View File

@@ -413,8 +413,10 @@ export default {
}
},
success: function (res) {
console.log('success', res);
},
fail: function (res) {
console.log('fail', res);
}
})
}

View File

@@ -151,7 +151,8 @@ export default {
// #endif
}, this.wait);
// console.log('this.top', this.top);
// console.log('this.radius', this.radius);
setTimeout(() => {
this.animationData = uni

View File

@@ -566,6 +566,7 @@ export default {
// 手指触摸事件 用于菜单左滑
touchS(e) {
this.startX = e.touches[0].clientX;
// console.log('开始' + e.touches[0].clientX);
},
touchE(e, cartIndex) {
this.endX = e.changedTouches[0].clientX;

View File

@@ -72,6 +72,7 @@ export default {
url: '/api/lucky/status',
data: {},
success: res => {
console.log(res)
this.langstatus = res.status
if(this.langstatus == 1){
// this.$langConfig.refresh();

View File

@@ -762,10 +762,13 @@ export default {
}
this.$forceUpdate();
// this.orderCreateData.coupon[merch_id].coupon_id = data.coupon_id
console.log(this.selectCouponId)
},
//确定使用优惠券
useCpopon() {
console.log(this.merchCoupon.merch_id)
this.orderCreateData.coupon[this.merchCoupon.merch_id].coupon_id = this.selectCouponId;
console.log(this.orderCreateData.coupon)
this.$refs.couponPopup.close();
this.calculate();
},

View File

@@ -137,6 +137,7 @@ export default {
}
},
fail: function(res) {
console.log('fail', res);
}
})
} else {

View File

@@ -359,8 +359,9 @@ export default {
getFenxiaoDetail() {
this.$api.sendRequest({
url: '/fenxiao/api/fenxiao/detail',
success: res => {
if (res.data) {
success: res => {
console.log(res)
if (res.data) {
if (res.data.status == -1) {
this.$util.showToast({
title: '当前分销商已冻结'

View File

@@ -136,10 +136,12 @@ export default {
image_list: this.$util.img(this.goodsList[this.currIndex].goods_image)
}
},
success: function (res) {
},
fail: function (res) {
}
success: function (res) {
console.log('success', res);
},
fail: function (res) {
console.log('fail', res);
}
})
}
},

View File

@@ -312,8 +312,9 @@
if (res.code == 0 && res.data.length) {
uni.requestSubscribeMessage({
tmplIds: res.data,
fail: res => {
},
fail: res => {
console.log('fail', res);
},
complete: () => {
callback();
}

View File

@@ -118,6 +118,7 @@
this.categoryId = 'category-' + index;
// 阻止切换分类之后滚动事件也立即执行
this.scrollLock = true;
console.log(index)
// this.goodsList = []
// this.getgoodslist()
@@ -173,8 +174,9 @@
data: {
is_type_show:1,
},
success: res => {
this.categoryTree = res.data
success: res => {
console.log( res.data)
this.categoryTree = res.data
},
fail: () => {
this.$util.showToast({

View File

@@ -164,8 +164,11 @@ export default {
id:this.goodsData.id
},
success: res => {
console.log(res)
if(res.result.detail){
this.goodsData = res.result.detail
console.log(this.goodsData.goods_image)
}else{
this.$util.showToast({
title: '商品不存在',
});
@@ -734,6 +737,7 @@ export default {
})
// 清空规格的图片
console.log(this.goodsData)
return false
for (var i = 0; i < this.goodsData.goods_sku_data.length; i++) {
if (this.goodsData.goods_sku_data[i].sku_images.length == 0) this.goodsData.goods_sku_data[i].sku_image = '';
@@ -792,6 +796,9 @@ export default {
} else if (this.goodsData.verify_validity_type == 2) {
data.virtual_indate = this.virtualTime;
}
// console.log(this.goodsData.goods_spec_format,'format')
// if(this.goodsData.goods_spec_format == '[]'){
// data.carmichael= data.carmichael
// }
var save = null;

View File

@@ -150,8 +150,9 @@ export default {
data: {
merch_id:this.merch_id
},
success: res => {
this.merch = res.data
success: res => {
console.log(res)
this.merch = res.data
}
});
},

View File

@@ -54,8 +54,9 @@ export default {
this.$api.sendRequest({
url: '/api/config/agreement',
data: data,
success: res => {
uni.setNavigationBarTitle({
success: res => {
console.log(res.data.title)
uni.setNavigationBarTitle({
title: res.data.title
})
this.content = res.data.content

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,23 @@
<template>
<view class="wrapper">
<!--自定义导航头部 - 绝对定位在顶部,不随键盘上移 -->
<view class="container" :style="wrapperPageStyle">
<!--自定义导航头部 - 只在微信小程序显示,H5端隐藏 -->
<!-- #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">
<!-- 聊天内容区域 -->
<view class="chat-content"
:style="wrapperChatContentStyle"
>
<!-- AI聊天组件 -->
<ai-chat-message
ref="chat"
:initial-messages="initialMessages"
@@ -127,6 +131,37 @@ 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) {
@@ -148,6 +183,7 @@ export default {
onShow() {
// 监听键盘高度变化
uni.onKeyboardHeightChange((res) => {
console.log('键盘高度变化:', res.height);
this.keyboardHeight = res.height;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(res.height);
@@ -197,6 +233,7 @@ export default {
setupNavigationEvents() {
uni.onWindowResize((res) => {
console.log('窗口大小变化:', res.size);
this.calculateScrollViewHeight();
});
},
@@ -204,6 +241,8 @@ export default {
// ========== 事件处理方法 ==========
async handleServiceRequest(event) {
console.log('处理服务请求:', EventSafety.extractEventData(event));
if (event.matches('.service-component') ||
event.detail?.componentType === 'service') {
await this.processServiceRequest(event);
@@ -211,10 +250,12 @@ export default {
},
async handleNavigationRequest(event) {
console.log('处理导航请求:', event.type);
this.emitNavigationInfo(event);
},
handleComponentInteraction(event) {
console.log('处理组件交互:', event.detail);
this.processComponentInteraction(event);
},
@@ -230,11 +271,16 @@ export default {
// ========== 初始化页面 ==========
initChat() {
console.log('AI聊天页面初始化');
// 检查是否有从商品分类传递过来的商品信息
const selectedProduct = uni.getStorageSync('selectedProduct')
if (selectedProduct) {
console.log('检测到选择的商品:', selectedProduct)
// 清除存储的商品信息,避免重复发送
uni.removeStorageSync('selectedProduct')
// 延迟发送商品信息,确保聊天组件已经初始化
setTimeout(() => {
this.sendProductToAI(selectedProduct)
@@ -283,6 +329,7 @@ export default {
this. $off(eventType, handler);
});
this.safeEventHandlers.clear();
console.log('组件清理完成');
},
showError(message) {
@@ -396,9 +443,11 @@ export default {
},
onMessageSent(message) {
console.log('用户发送消息:', message);
},
onAIResponse(message) {
console.log('AI回复消息:', message);
},
generateAIResponse(userMessage) {
@@ -440,6 +489,7 @@ export default {
},
onHistoryLoaded(messages) {
console.log('历史消息加载完成:', messages.length);
},
onFilePreview(message) {
@@ -447,15 +497,19 @@ export default {
},
onAudioPlay(message) {
console.log('音频播放:', message);
},
onAudioPause(message) {
console.log('音频暂停:', message);
},
onVideoPlay(message) {
console.log('视频播放:', message);
},
onVideoPause(message) {
console.log('视频暂停:', message);
},
sendProductToAI(product) {
@@ -466,6 +520,8 @@ export default {
const productContent = `我想了解以下商品的详细信息:\n商品名称:${product.goods_name}\n价格:¥${product.price}\n商品ID:${product.goods_id}`
console.log('商品信息已发送到AI客服:', productContent)
// 使用sendMessageWithContent方法发送商品信息
this.$refs.chat.sendMessageWithContent(productContent)
},
@@ -492,6 +548,7 @@ export default {
},
onInputChange(value) {
console.log('输入内容:', value);
},
// ✅ 新增:权限校验(核心修复)
@@ -539,22 +596,20 @@ export default {
@import url('/common/css/iconfont.css');
/* 页面样式 */
.wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
background-color: white; /* 白色 */
overflow: hidden;
position: fixed;
top: 48px;
left: 0;
right: 0;
bottom: 0;
}
/* 自定义导航头部:固定在wrapper顶部,不随键盘上移 */
/* 自定义导航头部 */
.custom-navbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
@@ -634,18 +689,12 @@ export default {
}
}
/* 聊天内容区域:从导航栏下方到容器底部 */
/* 聊天内容区域 */
.chat-content {
position: absolute;
/* #ifdef MP-WEIXIN */
top: 0;
/* #endif */
/* #ifndef MP-WEIXIN */
top: 44px;
/* #endif */
left: 0;
right: 0;
bottom: 0;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 底部tabBar占位样式 */

View File

@@ -130,10 +130,12 @@ export default {
}
},
fail: (err) => {
console.log(err);
}
});
},
toDetail(item) {
console.log(item.files_url);
const url = item.files_url;
uni.showLoading({ title: "正在加载" });
try {
@@ -145,6 +147,7 @@ export default {
uni.openDocument({
filePath: filePath,
success: () => {
console.log("打开成功");
},
fail: () => {
},
@@ -158,12 +161,14 @@ export default {
uni.hideLoading();
},
complete: (res) => {
console.log("下载完成", res);
if (res.statusCode === 404) {
uni.showToast({ title: "文件不存在", icon: "none" });
}
}
});
} catch (e) {
console.log("CatchClause", e);
uni.hideLoading();
}
},
@@ -191,6 +196,7 @@ export default {
uni.openDocument({
filePath: filePath,
success: () => {
console.log("打开成功");
},
fail: () => {
},
@@ -204,12 +210,14 @@ export default {
uni.hideLoading();
},
complete: (res) => {
console.log("下载完成", res);
if (res.statusCode === 404) {
uni.showToast({ title: "文件不存在", icon: "none" });
}
}
});
} catch (e) {
console.log("CatchClause", e);
uni.hideLoading();
}
},
@@ -241,6 +249,7 @@ export default {
}
});
} catch (e) {
console.log("CatchClause", e);
uni.hideLoading();
}
},
@@ -256,10 +265,12 @@ export default {
this.$util.showToast({ title: res.message });
},
fail: (err) => {
console.log(err);
}
});
},
change(e) {
console.log(e);
this.ismessage = e.show ? 1 : 0;
},
tapMessage() {
@@ -274,6 +285,7 @@ export default {
uni.makePhoneCall({
phoneNumber: phone + "",
success: (res) => {
console.log(res);
}
});
},
@@ -281,9 +293,11 @@ export default {
uni.setClipboardData({
data: text,
success: () => {
console.log("复制成功");
uni.showToast({ title: "复制成功", icon: "success", duration: 2000 });
},
fail: () => {
console.log("复制失败");
}
});
},

View File

@@ -110,6 +110,7 @@ export default {
})
},
toDetail(item) {
console.log(item.files_url)
uni.showLoading({
title: '正在加载'
})
@@ -117,10 +118,12 @@ export default {
uni.downloadFile({
url: item.files_url,
success: res => {
console.log(res)
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开成功')
},
fail: () => {
},
@@ -134,6 +137,7 @@ export default {
uni.hideLoading()
},
complete: res => {
console.log('下载完成', res)
if (res.statusCode === 404) {
uni.showToast({
title: '文件不存在',
@@ -143,6 +147,7 @@ export default {
}
})
} catch (e) {
console.log('CatchClause', e)
uni.hideLoading()
}
},

View File

@@ -324,8 +324,9 @@ export default {
});
}
},
fail: res => {
}
fail: res => {
console.log('fail', res);
}
});
// #endif
},

View File

@@ -265,8 +265,9 @@
if (res.code == 0 && res.data.length) {
uni.requestSubscribeMessage({
tmplIds: res.data,
fail: res => {
},
fail: res => {
console.log('fail', res);
},
complete: () => {
callback();
}

View File

@@ -114,6 +114,8 @@ export default {
let msg = res.message;
if (res.code == 0 && res.data) {
newArr = res.data.list;
} else {
console.log(`后台服务器返回的信息:${msg}`);
}
mescroll.endSuccess(newArr.length);
//设置列表数据

View File

@@ -2,6 +2,7 @@ export default {
methods: {
// 跳转至登录页
toLogin(url) {
console.log('跳转至登录页', {url, currentRoute: this.$util.getCurrentRoute()});
this.$refs.login.open(url ?? this.$util.getCurrentRoute());
}
}

View File

@@ -264,6 +264,7 @@ export default {
uni.requestSubscribeMessage({
tmplIds: res.data,
fail: (res) => {
console.log('fail', res)
},
complete: ()=> {
callback();

View File

@@ -266,6 +266,7 @@ export default {
uni.requestSubscribeMessage({
tmplIds: res.data,
fail: (res) => {
console.log('fail', res)
},
complete: ()=> {
callback();

View File

@@ -75,6 +75,7 @@ export default {
}
});
} catch (error) {
console.log("CatchClause", error);
uni.hideLoading();
}
},
@@ -102,6 +103,7 @@ export default {
}
},
fail: (err) => {
console.log(err);
uni.hideLoading();
this.$util.showToast({ title: '下载失败' });
}
@@ -112,6 +114,7 @@ export default {
url: '/api/seal/info',
data: { keyword: this.keyword },
success: (res) => {
console.log(res);
this.list = res.value;
this.info = res;
this.title = res.name;

View File

@@ -64,6 +64,7 @@ export default {
uni.openDocument({
filePath: res.tempFilePath,
success: (res) => {
console.log('打开文档成功');
}
});
}

View File

@@ -18,6 +18,7 @@ export default {
this.$api.sendRequest({
url: '/api/config/defaultvr',
success: res => {
console.log(res)
if (res.code == 0) {
uni.setNavigationBarTitle({
title:res.data.title

119
readme.md
View File

@@ -40,20 +40,22 @@
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
3. 构建完成后,**自动执行**补丁脚本(拷贝 site.js / config 文件 + 生成 ZIP 包),无需手动操作
3. 打开资源管理器→项目根目录,右键选择「在终端中打开」,执行命令:
```bash
npm run mp-weixin
```
4. 找到项目根目录 `/unpackage/dist/build` 下生成的 mp-weixin 压缩包
💡 **示例**:`mp-weixin-2026-05-11-3bff91e.zip`
*压缩包文件名末尾为 Git 提交 ID*
💡 **示例**:`mp-weixin-2026-01-23-1769152056146.zip`
*同时该目录下会生成未压缩的 `mp-weixin` 目录*
##### 4.1.1.3 【验证与重命名】
1. 打开未压缩的 `mp-weixin` 目录,找到 `site.js` 文件,将文件内的 `uniacid` 值改为当前客户编号(如:2812)并保存
2. 打开微信开发者工具,导入「mp-weixin」目录,点击「编译」,在开发者工具控制台验证有无报错,以及能否返回对应客户的业务数据
3. 确保无误后将生成的 mp-weixin 压缩包重命名
- **定制化版**格式:`定制化-客户编号-域名-mp-weixin-当前日期-Git提交ID.zip`
- **定制化版**格式:`定制化-客户编号-域名-mp-weixin-当前日期-生成编号.zip`
- **通用版**无需重命名
💡 **示例**:`custom-2812-xcx.aigc-quickapp.com-mp-weixin-2026-05-11-3bff91e.zip`
💡 **示例**:`custom-2812-xcx.aigc-quickapp.com-mp-weixin-2026-01-22-1769152056146.zip`
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
@@ -61,10 +63,10 @@
1. 将重命名后的文件发送给技术人员
*📌通用版直接将生成的 mp-weixin 压缩包发送给技术人员*
2. **技术人员操作流程**:
- 解压压缩包
- 确认 `site.js` 中的 `uniacid` 为客户编号
- 用微信开发者工具导入解压后的代码目录
- 编译验证无误后,上传发布
- 解压压缩包
- 确认 `site.js` 中的 `uniacid` 为客户编号
- 用微信开发者工具导入解压后的代码目录
- 编译验证无误后,上传发布
##### 4.1.1.5 【本地设置】
![](readme_files/1.jpg)
#####
@@ -83,93 +85,66 @@
📌 **测试版**保留原有 `/hwappx/test/`
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
3. 选择菜单栏「发行」 → 「自定义发行」 → 选择对应的站点脚本
💡 例如:网址是 `5g.aigc-quickapp.com` 选 `H5-5g.aigc-quickapp.com`
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
4. 修改以下配置:
1. **网站标题**:快应用
2. **网站域名**:当前客户域名
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
3. 确认后点击「发行」等待构建完成
⚠️ **注意**:构建完成后自动输出到 `unpackage/dist/build/h5/`,格式为 `{域名}-{base路径}.zip`
💡 **示例**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
*仅打包文件,不包含外层 web 目录*
##### 4.2.1.2 【交付与最终发布】🔍
1. 将生成的 `.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. 将重命名后的压缩包发送给运维人员
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 自动注入机制
### 5.1 可用的 npm 脚本命令
生产构建时,`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 自定义发行**构建完成后自动执行:
| 步骤 | 说明 |
| 命令 | 说明 |
|------|------|
| 重命名输出目录 | `{域名}-{base路径用.连接}`(如 `xcx.aigc-quickapp.com-hwappx.common`) |
| 创建 ZIP 文件 | 文件名格式:`{目录名}-{Git提交ID}-{上海时区时间戳}.zip` |
| 清理 | ZIP 创建完成后自动删除源目录 |
| `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文件) |
💡 **示例 ZIP**:`xcx.aigc-quickapp.com-hwappx.common-3bff91e-20260511_1058.zip`
### 5.2 构建脚本功能
**微信小程序**生产构建时自动执行:
- 拷贝 `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` 读取,**新增/删除脚本无需改动代码**。
- 复制 `project.config.json` 和 `project.private.config.json` 文件到构建目录
- 复制 `site.js` 文件到构建目录
- 在 `vendor.js` 文件开头追加 `import site from "../site.js";` 语句
- 支持创建构建结果的 ZIP 压缩包
- 自动打开 ZIP 文件所在目录
---
## 6. 🔄 版本历史
### 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
### 6.1 v1.3
- 修复微信小程序构建脚本,支持复制 `project.config.json` 和 `project.private.config.json` 文件
- 增强构建脚本功能,添加命令行参数支持
- 在 `package.json` 中添加相关 npm 脚本命令

View File

@@ -18,18 +18,9 @@ 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}-${commitId}.zip`;
const zipFileName = `mp-weixin-${dateString}-${new Date().getTime()}.zip`;
const zipFilePath = path.join(outputDir, zipFileName);
let command;

View File

@@ -2,16 +2,21 @@
/**
* fix-wechat-miniapp.js
*
* 该脚本用于微信小程序的构建后处理(仅在生产构建时由 vue.config.js 触发):
* 1. 将 project.config.json 及 project.private.config.json 拷贝到构建产物目录
* 2. 将 site.js 拷贝到构建产物目录
* 3. 创建 ZIP 压缩包
*
* 注意:version.js 生成和 vendor.js 注入已由 webpack 插件 (VersionInjectPlugin) 在 emit 钩子中自动处理。
* 该脚本用于修复微信小程序的一些问题:
* 1. 将项目根目录下的 site.js 文件拷贝到 unpackage/dist/build/mp-weixin 目录下,如果文件存在则覆盖
* 2. 将 `const site = require("../site.js");` 这段代码追加到
* unpackage/dist/build/mp-weixin/common/vendor.js 文件内容开头部分,
* 如果这个文件开头已经有了这行代码,则不追加
*
* 使用:
* node fix-wechat-miniapp.js # 拷贝文件并创建 ZIP(默认 mode=production)
* node fix-wechat-miniapp.js --no-zip # 只拷贝文件,不创建 ZIP
* 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 在当前工作目录下)。
*/
const fs = require('fs');
@@ -21,19 +26,37 @@ const path = require('path');
const { openFileDirectory } = require('./open-directory');
const { createZipWithSystemCommand } = require('./create-zip');
async function copyAssets(mode = 'production') {
async function commonPatch(mode = 'production') {
try {
const cwd = path.join(__dirname, '..');
const destDir = path.join(cwd, 'unpackage', 'dist', mode === 'production' ? 'build' : 'dev', 'mp-weixin');
// 根据当前脚本所在目录(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
// 复制 project.config.json 及 project.private.config.json 文件到 destDir 下面
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);
if (await exists(srcPath)) {
// 检查源文件是否存在
const fileExists = await exists(srcPath);
if (fileExists) {
await fsp.copyFile(srcPath, destPath);
console.log(`已拷贝: ${srcPath} -> ${destPath}`);
} else {
@@ -41,15 +64,47 @@ async function copyAssets(mode = 'production') {
}
}
// 复制 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;
// 复制 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;
}
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;
@@ -57,27 +112,33 @@ async function copyAssets(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 copyAssets(options.mode);
// 1) 打补丁
await commonPatch(options.mode);
// await commonPatch('development');
// 2) 创建 ZIP(仅 production 模式,且未指定 --no-zip)
if (!options.noZip && options.mode === 'production') {
// 2) 创建 ZIP 文件(如果未指定 --no-zip)
if (!options.noZip) {
const cwd = path.join(__dirname, '..');
const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
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 zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
console.log(`ZIP 文件路径: ${zipFilePath}`);
// 3) 自动打开zip所在的目录
await openFileDirectory(zipFilePath);
} else {
console.log('跳过创建 ZIP 文件和打开目录');
@@ -97,6 +158,7 @@ async function ensureDir(dirPath) {
try {
await fsp.mkdir(dirPath, { recursive: true });
} catch (e) {
// ignore if exists
if (e.code !== 'EEXIST') throw e;
}
}

View File

@@ -55,13 +55,10 @@ async function openDirectoryWindows(directoryPath) {
console.log(`📁 在文件资源管理器中打开: ${normalizedPath}`);
// 方法1: 使用 explorer 命令(spawn + detached,不等待 GUI 进程)
// 方法1: 使用 explorer 命令
try {
const child = spawn('explorer', [normalizedPath], {
stdio: 'ignore',
detached: true
});
child.unref();
const command = `explorer "${normalizedPath}"`;
await execAsync(command);
console.log('✅ 已启动文件资源管理器');
return true;
} catch (error) {

View File

@@ -89,6 +89,8 @@ const store = new Vuex.Store({
status: false,
path: ''
},
// 华为快应用帐号信息
huaweiAccountInfo: null,
},
mutations: {
// 设置是否可以领取新人礼
@@ -246,7 +248,16 @@ const store = new Vuex.Store({
// 设置主题颜色
setThemeColor(state, value) {
state.themeColor = value;
}
},
// 华为快应用帐号信息
setHuaweiAccountInfo(state, value) {
state.huaweiAccountInfo = value;
if (value) {
uni.setStorageSync('huaweiAccountInfo', value);
} else {
uni.removeStorageSync('huaweiAccountInfo');
}
},
},
getters: {
// AI智能助手配置
@@ -303,6 +314,8 @@ const store = new Vuex.Store({
this.commit('setGlobalStoreInfo', null);
}
this.commit('setInitStatus', true)
// 初始化华为快应用 HwService(H5 壳模式下的快应用环境)
this.dispatch('initHwService');
resolve(data);
}
}
@@ -416,6 +429,7 @@ const store = new Vuex.Store({
},
// 生成主题颜色CSS变量
themeColorSet() {
// console.log('样式颜色设置...');
let theme = this.state.themeStyle;
if (!theme?.main_color || !theme?.aux_color) return;
try {
@@ -439,6 +453,31 @@ const store = new Vuex.Store({
} catch (e) {
console.error('设置主题颜色失败', e);
}
// console.log('themeColor => ', this.state.themeColor);
},
// 初始化华为快应用 HwService
initHwService() {
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
try {
const HwService = require('@/common/js/hw-jssdk.js').default;
if (HwService.isQuickAppEnv()) {
HwService.init().then(() => {
console.log('[Store] HwService 初始化成功');
// 检查是否有缓存的华为帐号信息
const cachedInfo = uni.getStorageSync('huaweiAccountInfo');
if (cachedInfo) {
this.commit('setHuaweiAccountInfo', cachedInfo);
}
}).catch(err => {
console.error('[Store] HwService 初始化失败:', err);
});
} else {
console.log('[Store] 非快应用环境,跳过 HwService 初始化');
}
} catch (e) {
console.log('[Store] HwService 未配置,跳过初始化');
}
// #endif
}
}
})

View File

@@ -1,65 +1,27 @@
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环境变量(H5 自定义发行脚本使用)
// 定义UNI_OUTPUT_DIR环境变量
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 ?? '';
// 所有 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}`);
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}`);
console.log('UNI_OUTPUT_DIR = ', process.env.UNI_OUTPUT_DIR);
}
@@ -67,211 +29,6 @@ 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,
@@ -361,8 +118,5 @@ module.exports = {
}
}
}
// 所有模式下添加版本注入插件(适用于所有平台)
config.plugins.push(new VersionInjectPlugin());
}
};