Compare commits

..

4 Commits

Author SHA1 Message Date
b23c21d05b Merge pull request 'fix/lang' (#4) from fix/lang into main
Reviewed-on: #4
2026-02-04 09:12:58 +00:00
65a24f25bb fix(lang): 提取getCurrentLocale为独立函数并优化代码格式
将getCurrentLocale方法提取为独立函数以避免重复代码
移除多余的空格并保持代码风格一致
2026-02-04 17:10:12 +08:00
2a0935b581 Merge pull request 'docs: 更新readme文档结构和内容' (#2) from dev/1.0 into main
Reviewed-on: #2
2026-01-24 09:41:36 +00:00
b31197a8b4 Merge pull request 'dev/1.0' (#1) from dev/1.0 into main
Reviewed-on: #1
2026-01-24 09:24:05 +00:00
39 changed files with 2685 additions and 5277 deletions

View File

@@ -42,18 +42,6 @@ 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/',
},
})['1']; // 选择要使用的环境配置
})['2811']; // 选择要使用的环境配置
export default localDevConfig;

View File

@@ -5,17 +5,6 @@ import store from '@/store/index.js'
let currentConversationId = null;
const CONVERSATION_KEY = 'ai_conversation_id';
/**
* 获取域名配置参数(避免重复代码)
*/
function getDomainParams() {
return {
imgDomain: Config.imgDomain,
h5Domain: Config.h5Domain,
baseUrl: Config.baseUrl
};
}
// 初始化时从本地读取会话ID
try {
const saved = uni.getStorageSync(CONVERSATION_KEY);
@@ -27,28 +16,12 @@ try {
}
export default {
/**
* 获取uniacid
* @returns {number} uniacid
*/
getUniacid() {
return uni.getStorageSync('uniacid') || Config.uniacid; // 从缓存中获取uniacid,或使用默认uniacid
},
/**
* 发送普通消息(blocking 模式)
*/
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: {
@@ -58,38 +31,28 @@ export default {
query: message,
response_mode: 'blocking', // 强制 blocking
user: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.getConversationId() || '',
...getDomainParams()
conversation_id: this.getConversationId() || ''
},
header: {
'Content-Type': 'application/json'
}
};
console.log('[AI Service] 发送请求到:', params.url);
console.log('[AI Service] 请求参数:', JSON.stringify(params.data, null, 2));
console.log('Sending blocking request to:', params.url);
console.log('Request data:', params.data);
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] Dify API请求失败,详细错误:', error);
console.error('[AI Service] 错误堆栈:', error.stack);
// 打印更多信息帮助调试
console.error('[AI Service] store 状态:', {
memberInfo: store.state.memberInfo,
uniacid: this.getUniacid(),
token: store.state.token
});
console.error('Dify API请求失败:', error);
throw new Error('AI服务暂时不可用,请稍后重试');
}
},
@@ -99,110 +62,53 @@ export default {
*/
async sendStreamMessage(message, onChunk, onComplete) {
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/);
const domain = match ? match[1] : '';
const wssUrl = `wss://${domain}/ws/aikefu`;
console.log(`wssUrl = ${wssUrl}`);
const socketTask = wx.connectSocket({
url: wssUrl,
header: {}
});
let content = '';
let conversationId = '';
let isAuthenticated = false;
socketTask.onOpen(() => {
console.log('WebSocket 连接成功,开始认证...');
socketTask.send({
data: JSON.stringify({
action: 'auth',
uniacid: this.getUniacid(),
token: store.state.token || 'test_token',
user_id: store.state.memberInfo?.id || 'anonymous'
})
});
});
socketTask.onMessage((res) => {
// 微信小程序:降级为普通请求 + 前端打字模拟
try {
const data = JSON.parse(res.data);
console.log('收到 WebSocket 消息:', data);
const result = await this.sendMessage(message);
const content = result.content || '';
const conversationId = result.conversationId || '';
if (data.type === 'auth_success') {
console.log('认证成功,发送聊天消息...');
isAuthenticated = true;
socketTask.send({
data: JSON.stringify({
action: 'chat',
uniacid: this.getUniacid(),
query: message,
user_id: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.getConversationId() || '',
...getDomainParams()
})
});
} else if (data.type === 'auth_failed') {
const errorMsg = '认证失败,请重新登录';
console.error(errorMsg, data);
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
socketTask.close();
}
// 处理流式消息块
else if (data.event === 'message') {
const text = data.answer || data.content || data.text || '';
content += text;
if (onChunk) onChunk(text);
}
// 处理流结束
else if (data.event === 'message_end') {
conversationId = data.conversation_id || '';
// 保存会话ID(确保连续对话)
if (conversationId) {
this.setConversationId(conversationId);
}
// 模拟打字效果
let index = 0;
const chunkSize = 2; // 每次显示2个字符
return new Promise((resolve) => {
const timer = setInterval(() => {
if (index < content.length) {
const chunk = content.substring(index, index + chunkSize);
index += chunkSize;
if (onChunk) onChunk(chunk);
} else {
clearInterval(timer);
if (onComplete) {
onComplete({ content, conversation_id: conversationId });
onComplete({
content: content,
conversation_id: conversationId
});
}
resolve({ content, conversation_id: conversationId });
socketTask.close();
}
// 可选:处理 done
else if (data.type === 'done') {
console.log('对话完成:', data);
}, 80); // 打字速度:80ms/次
});
} catch (error) {
console.error('小程序流式消息降级失败:', error);
if (onComplete) {
onComplete({ error: error.message || '发送失败' });
}
} catch (e) {
console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data);
throw error;
}
});
// #endif
socketTask.onError((err) => {
const errorMsg = 'WebSocket 连接失败';
console.error(errorMsg, err);
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
});
socketTask.onClose(() => {
console.log('WebSocket 连接已关闭');
});
const timeout = setTimeout(() => {
if (!isAuthenticated || content === '') {
console.warn('WebSocket 超时,强制关闭');
socketTask.close();
reject(new Error('AI服务响应超时'));
if (onComplete) onComplete({ error: 'AI服务响应超时' });
}
}, 10000);
});
// #ifdef H5
// H5:使用真实流式(EventSource / Fetch)
return this.sendHttpStream(message, onChunk, onComplete);
// #endif
},
/**
* HTTP 流式请求(仅 H5 使用)
*/
@@ -217,19 +123,20 @@ export default {
'Accept': 'text/event-stream',
},
body: JSON.stringify({
uniacid: this.getUniacid(),
token: store.state.token || 'test_token',
uniacid: store.state.uniacid || '1',
inputs: {},
query: message,
response_mode: 'streaming',
user_id: store.state.memberInfo?.id || 'anonymous',
conversation_id: this.getConversationId() || '',
...getDomainParams()
conversation_id: this.getConversationId() || ''
})
});
if (!response.ok || !response.body) {
throw new Error('无效响应');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
if (!response.body) {
throw new Error('响应体不可用');
}
const reader = response.body.getReader();
@@ -238,64 +145,50 @@ export default {
let content = '';
let conversationId = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 按行分割,保留不完整的最后一行
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 未完成的行留到下次
function processBuffer(buf, callback) {
const lines = buf.split('\n');
buf = lines.pop() || '';
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('data:')) {
try {
const jsonStr = trimmed.slice(5).trim();
if (jsonStr && jsonStr !== '[DONE]') {
if (jsonStr) {
try {
const data = JSON.parse(jsonStr);
if (data.event === 'message') {
const text = data.answer || data.text || '';
content += text;
if (onChunk) onChunk(text);
callback(text);
}
if (data.conversation_id) {
conversationId = data.conversation_id;
}
if (data.event === 'message_end') {
// 可提前结束
}
// 可选:提前完成
}
} catch (e) {
console.warn('解析失败:', e, line);
console.warn('解析流数据失败:', e);
}
}
}
}
return buf;
}
// 处理最后残留的 buffer(如果有)
if (buffer.trim().startsWith('data:')) {
try {
const jsonStr = buffer.trim().slice(5);
if (jsonStr) {
const data = JSON.parse(jsonStr);
if (data.event === 'message') {
const text = data.answer || '';
content += text;
if (onChunk) onChunk(text);
}
if (data.conversation_id) {
conversationId = data.conversation_id;
}
}
} catch (e) {
console.warn('最后 buffer 解析失败:', e);
}
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
buffer = processBuffer(buffer, (chunk) => {
if (onChunk) onChunk(chunk);
});
}
if (onComplete) {
onComplete({ content, conversation_id: conversationId });
onComplete({
content,
conversation_id: conversationId
});
}
return { content, conversation_id: conversationId };
} catch (error) {
@@ -303,6 +196,11 @@ export default {
throw error;
}
// #endif
// #ifdef MP-WEIXIN
// 理论上不会执行到这里,但防止 fallback
return this.sendStreamMessage(message, onChunk, onComplete);
// #endif
},
/**
@@ -361,11 +259,9 @@ export default {
const params = {
url: '/api/kefu/createConversation',
data: {
uniacid: this.getUniacid(),
token: store.state.token || 'test_token',
uniacid: store.state.uniacid,
user_id: store.state.memberInfo?.id || 'anonymous',
member_id: store.state.memberInfo?.id || '',
...getDomainParams()
},
header: {
'Content-Type': 'application/json'
@@ -388,7 +284,6 @@ 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 {
@@ -410,10 +305,7 @@ export default {
try {
const response = await http.sendRequest({
url: '/api/kefu/clear-conversation',
data: {
conversation_id: conversationId,
...getDomainParams()
},
data: { conversation_id: conversationId },
async: false
});
return response.success;
@@ -432,14 +324,13 @@ export default {
throw new Error('会话ID(conversation_id)是必填参数');
}
const requestData = {
uniacid: params.uniacid || this.getUniacid(),
uniacid: params.uniacid || store.state.uniacid || '1',
conversation_id: params.conversation_id,
user_id: params.user_id || store.state.memberInfo?.id || 'anonymous',
limit: params.limit || 20,
offset: params.offset || 0,
member_id: params.member_id || store.state.memberInfo?.id || '',
token: params.token || '',
...getDomainParams()
token: params.token || ''
};
const response = await http.sendRequest({
url: '/api/kefu/getHistory',
@@ -478,15 +369,13 @@ export default {
if (window.currentEventSource) {
window.currentEventSource.close();
}
const paramsObj = {
uniacid: this.getUniacid(),
user_id: store.state.memberInfo?.id,
const params = new URLSearchParams({
uniacid: store.state.uniacid || '1',
user_id: store.state.memberInfo?.id || '123456',
query: message,
conversation_id: conversationId || '',
stream: 'true',
...getDomainParams()
};
const params = new URLSearchParams(paramsObj);
stream: 'true'
});
const url = `${Config.baseUrl}/api/kefu/chat?${params.toString()}`;
try {
const eventSource = new EventSource(url);

View File

@@ -2,15 +2,68 @@
* 客服统一处理服务
* 整合各种客服方式,提供统一的调用接口
*/
import IMAGE_CONFIG from './image-config.js';
class CustomerService {
constructor(vueInstance, externalConfig = {}) {
if (!vueInstance.$lang) {
throw new Error('CustomerService 必须在 Vue 实例中初始化');
}
export class CustomerService {
constructor(vueInstance, externalConfig = null) {
this.vm = vueInstance;
this.externalConfig = externalConfig; // 外部传入的最新配置(优先级最高)
this.latestPlatformConfig = null;
}
getSupoortKeFuList() {
if (!this.vm) return [];
const vm = this.vm;
return [
{
id: 'weixin-official',
name: vm.$lang('customer.weChatKefu'),
isOfficial: true,
type: 'weapp'
},
{
id: 'custom-kefu',
name: vm.$lang('customer.systemKefu'),
isOfficial: false
},
{
id: 'qyweixin-kefu',
name: vm.$lang('customer.weChatWorkKefu'),
isOfficial: false
},
]
}
/**
* 打开客服选择弹窗
*/
openCustomerSelectPopupDialog() {
const kefu_list = this.getSupoortKeFuList();
const kefuNames = kefu_list.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
const kefu = kefu_list[res.tapIndex];
this.externalConfig = kefu ?? this.externalConfig ?? {};
if (kefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (kefu.id === 'custom-kefu') {
this.handleCustomerClick();
} else if (kefu.id === 'qyweixin-kefu') {
this.handleQyWeixinKefuClick();
}
}
});
}
/**
* 强制刷新配置(支持传入外部配置)
* @param {Object} externalConfig 外部最新配置
@@ -30,29 +83,28 @@ export class CustomerService {
return this.latestPlatformConfig;
}
// 优先级:外部传入 > vuex store > 空对象
// 优先级:外部传入的最新配置 > vuex配置 > 空对象
const servicerConfig = this.externalConfig || this.vm.$store.state.servicerConfig || {};
console.log(`【实时客服配置】`, servicerConfig);
let platformConfig = null;
// #ifdef H5
platformConfig = servicerConfig.h5 && typeof servicerConfig.h5 === 'object' ? servicerConfig.h5 : null;
platformConfig = servicerConfig.h5 ? (typeof servicerConfig.h5 === 'object' ? servicerConfig.h5 : null) : null;
// #endif
// #ifdef MP-WEIXIN
platformConfig = servicerConfig.weapp && typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null;
platformConfig = servicerConfig.weapp ? (typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null) : null;
// #endif
// #ifdef MP-ALIPAY
platformConfig = servicerConfig.aliapp && typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null;
platformConfig = servicerConfig.aliapp ? (typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null) : null;
// #endif
// #ifdef PC
platformConfig = servicerConfig.pc && typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null;
platformConfig = servicerConfig.pc ? (typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null) : null;
// #endif
// 防止空数组被当作有效配置
// 处理空数组情况(你的配置中pc/aliapp是空数组,转为null)
if (Array.isArray(platformConfig)) {
platformConfig = null;
}
@@ -78,15 +130,6 @@ export class CustomerService {
return config && typeof config === 'object' && config.type;
}
/**
* 检查是否有有效的客服配置(用于判断是否显示客服按钮)
* @returns {Boolean}
*/
hasValidKefuConfig() {
const config = this.getPlatformConfig();
return config && typeof config === 'object' && config.type && config.type !== 'none';
}
/**
* 验证客服配置完整性
* @returns {Object} 验证结果
@@ -101,18 +144,32 @@ export class CustomerService {
warnings: []
};
if (!config || !config.type) {
if (!config) {
result.isValid = false;
result.errors.push('客服配置不存在');
return result;
}
if (config.type === 'aikefu') {
return result;
}
if (!config.type) {
result.isValid = false;
result.errors.push('客服类型未配置');
return result;
}
if (config.type === 'wxwork') {
if (!wxworkConfig || !wxworkConfig.enable) {
result.warnings.push('企业微信未启用');
if (!wxworkConfig) {
result.isValid = false;
result.errors.push('企业微信配置不存在');
} else {
if (!wxworkConfig.enable) {
result.warnings.push('企业微信功能未启用');
}
if (!wxworkConfig.contact_url) {
result.warnings.push('企业微信活码链接未配置');
result.warnings.push('企业微信活码链接未配置,将使用原有客服方式');
}
}
}
@@ -120,39 +177,16 @@ export class CustomerService {
}
/**
* 跳转到 AI 客服页面(Dify)
* 跳转到AI客服页面
*/
openDifyService() {
try {
// 清除未读数(如果存在)
if (typeof this.vm.setAiUnreadCount === 'function') {
this.vm.setAiUnreadCount(0);
}
// ✅ 修正路径:必须与 pages.json 中注册的路径一致
const aiChatUrl = '/pages_tool/ai-chat/index';
// ✅ 使用 navigateTo 保留返回栈(体验更好)
uni.navigateTo({
url: aiChatUrl,
fail: (err) => {
console.error('跳转 AI 客服失败:', err);
// H5 兜底
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI 客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
openAIKeFuService() {
const vm = this.vm;
vm.$util.redirectTo(vm.$util.AI_CHAT_PAGE_URL);
}
/**
* 处理客服点击事件(统一入口)
* @param {Object} options 选项参数(用于消息卡片等)
* 处理客服点击事件
* @param {Object} options 选项参数
*/
handleCustomerClick(options = {}) {
const validation = this.validateConfig();
@@ -167,61 +201,51 @@ export class CustomerService {
}
const config = this.getPlatformConfig();
console.log('【当前客服配置】', config);
console.log('【客服类型】', config.type);
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
if (config.type === 'none') {
this.showNoServicePopup();
return;
}
// 核心路由:根据 type 决定行为
// 核心分支:根据最新的type处理
switch (config.type) {
case 'aikefu':
console.log('【跳转 AI 客服】目标路径: /pages_tool/ai-chat/index');
this.openDifyService();
this.openAIKeFuService();
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:
console.error('【未知客服类型】', config.type);
this.makePhoneCall();
}
}
// ================== 各类型客服实现 ==================
/**
* 打开企业微信客服
* @param {boolean} useOriginalService 是否使用原有客服方式
* @param {Object} servicerConfig 客服配置
* @param {Object} options 选项参数
*/
openWxworkService(useOriginalService = false, servicerConfig = null, options = {}) {
const config = servicerConfig || this.getPlatformConfig();
const wxworkConfig = this.getWxworkConfig();
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
// #ifdef MP-WEIXIN
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
if (wxworkConfig?.enable && wxworkConfig?.contact_url && !useOriginalService) {
wx.navigateToMiniProgram({
appId: 'wxeb490c6f9b154ef9',
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig.contact_url)}`,
@@ -232,16 +256,9 @@ export class CustomerService {
}
});
} else {
// 检查是否有企业微信配置
if (!config.wxwork_url && !config.corpid) {
console.error('企业微信配置不完整,缺少 wxwork_url 或 corpid');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
this.fallbackToPhoneCall();
return;
}
wx.openCustomerServiceChat({
extInfo: { url: config.wxwork_url || '' },
corpId: config.corpid || '',
extInfo: { url: config.wxwork_url },
corpId: config.corpid,
showMessageCard: true,
sendMessageTitle,
sendMessagePath,
@@ -251,125 +268,120 @@ export class CustomerService {
// #endif
// #ifdef H5
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
if (wxworkConfig?.enable && wxworkConfig?.contact_url) {
window.location.href = wxworkConfig.contact_url;
} else if (config.wxwork_url) {
window.location.href = config.wxwork_url;
location.href = config.wxwork_url;
} else {
this.fallbackToPhoneCall();
}
// #endif
}
/**
* 打开第三方客服
* @param {Object} config 客服配置
*/
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);
uni.showToast({ title: '跳转失败,请稍后重试', icon: 'none' });
}
});
// #endif
// #ifdef H5
uni.showToast({ title: '第三方小程序客服仅在微信小程序中可用', icon: 'none' });
// #endif
return;
}
// 处理第三方链接客服
if (config.third_url) {
console.log('【跳转第三方链接】', config.third_url);
// #ifdef H5
window.location.href = config.third_url;
// #endif
// #ifdef MP-WEIXIN
uni.setClipboardData({
data: config.third_url,
success: () => {
uni.showToast({ title: '链接已复制,请在浏览器打开', icon: 'none' });
}
});
// #endif
} else {
console.error('【第三方客服配置不完整】缺少 mini_app_id 或 third_url');
this.fallbackToPhoneCall();
}
}
/**
* 打开牛商客服
* @param {Object} niushop 牛商参数
*/
openNiushopService(niushop) {
if (Object.keys(niushop).length > 0 && this.vm.$util?.redirectTo) {
if (Object.keys(niushop).length > 0) {
this.vm.$util.redirectTo('/pages_tool/chat/room', niushop);
} else {
this.makePhoneCall();
}
}
/**
* 打开微信小程序客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
openWeappService(config, options = {}) {
// 如果 useOfficial 为 true 或 undefined,则使用原生系统客服(由 button open-type="contact" 触发)
// 此方法仅用于自定义跳转(如 useOfficial: false)
if (config.useOfficial !== false) {
// 不做任何事,应由 <button open-type="contact"> 触发
console.log('使用微信官方客服,请确保按钮为 <button open-type="contact">');
if (!this.shouldUseCustomService(config)) {
console.log('使用官方微信小程序客服');
return;
}
console.log('使用自定义微信小程序客服');
this.handleCustomWeappService(config, options);
}
/**
* 处理自定义微信小程序客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
handleCustomWeappService(config, options = {}) {
const { sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options || {};
if (config.customServiceUrl) {
let url = config.customServiceUrl;
const params = [];
const { sendMessageTitle, sendMessagePath, sendMessageImg } = options;
if (sendMessageTitle) params.push(`title=${encodeURIComponent(sendMessageTitle)}`);
if (sendMessagePath) params.push(`path=${encodeURIComponent(sendMessagePath)}`);
if (sendMessageImg) params.push(`img=${encodeURIComponent(sendMessageImg)}`);
if (params.length > 0) {
url += (url.includes('?') ? '&' : '?') + params.join('&');
}
uni.navigateTo({ url });
uni.navigateTo({
url: url,
fail: (err) => {
console.error('跳转自定义客服页面失败:', err);
this.tryThirdPartyService(config, options);
}
});
return;
}
this.tryThirdPartyService(config, options);
}
/**
* 尝试使用第三方客服
* @param {Object} config 客服配置
* @param {Object} options 选项参数
*/
tryThirdPartyService(config, options = {}) {
// 支持第三方微信小程序客服
if (config.thirdPartyMiniAppId || config.mini_app_id) {
// #ifdef MP-WEIXIN
wx.navigateToMiniProgram({
appId: config.thirdPartyMiniAppId || config.mini_app_id,
path: config.thirdPartyMiniAppPath || config.mini_app_path || ''
});
// #endif
return;
}
// 支持第三方链接客服
if (config.thirdPartyServiceUrl || config.third_url) {
const serviceUrl = config.thirdPartyServiceUrl || config.third_url;
if (config.thirdPartyServiceUrl) {
// #ifdef H5
window.open(serviceUrl, '_blank');
window.open(config.thirdPartyServiceUrl, '_blank');
// #endif
// #ifdef MP-WEIXIN
uni.setClipboardData({ data: serviceUrl });
uni.showToast({ title: '客服链接已复制', icon: 'none' });
if (config.thirdPartyMiniAppId) {
wx.navigateToMiniProgram({
appId: config.thirdPartyMiniAppId,
path: config.thirdPartyMiniAppPath || '',
fail: (err) => {
console.error('跳转第三方小程序失败:', err);
this.fallbackToPhoneCall();
}
});
} else {
uni.setClipboardData({
data: config.thirdPartyServiceUrl,
success: () => {
uni.showModal({
title: '客服链接已复制',
content: '客服链接已复制到剪贴板,请在浏览器中粘贴访问',
showCancel: false
});
}
});
}
// #endif
return;
}
@@ -377,55 +389,106 @@ export class CustomerService {
this.fallbackToPhoneCall();
}
openAliappService(config) {
if (config.type === 'aikefu') {
this.openDifyService();
} else if (config.type === 'third') {
this.openThirdService(config);
} else {
// 支付宝原生客服由 button open-type="contact" 触发,此处不处理
console.log('使用支付宝官方客服');
/**
* 降级到电话客服
*/
fallbackToPhoneCall() {
uni.showModal({
title: '联系客服',
content: '在线客服暂时不可用,是否拨打电话联系客服?',
success: (res) => {
if (res.confirm) {
this.makePhoneCall();
}
}
// ================== 辅助方法 ==================
makePhoneCall() {
this.vm.$api.sendRequest({
url: '/api/site/shopcontact',
success: res => {
if (res.code === 0 && res.data?.mobile) {
uni.makePhoneCall({ phoneNumber: res.data.mobile });
} else {
uni.showToast({ title: '暂无客服电话', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '获取客服电话失败', icon: 'none' });
}
});
}
/**
* 打开支付宝小程序客服
* @param {Object} config 客服配置
*/
openAliappService(config) {
console.log('支付宝小程序客服', config);
switch (config.type) {
case 'aikefu':
this.openAIKeFuService();
break;
case 'third':
this.openThirdService(config);
break;
default:
console.log('使用支付宝官方客服');
break;
}
}
/**
* 拨打电话
*/
makePhoneCall(mobileNumber) {
if (mobileNumber) {
return uni.makePhoneCall({
phoneNumber: mobileNumber
});
}
// 从缓存中获取电话信息
uni.getStorage({
key: 'shopInfo',
success: (res) => {
const shopInfo = res.data;
const mobile = shopInfo?.mobile ?? '';
if (mobile) {
uni.makePhoneCall({
phoneNumber: mobile
});
} else {
uni.showToast({
title: '暂无客服电话',
icon: 'none'
});
}
}
});
}
/**
* 显示无客服弹窗
*/
showNoServicePopup() {
const siteInfo = this.vm.$store.state.siteInfo || {};
const message = siteInfo?.site_tel
? `请联系客服,客服电话是${siteInfo.site_tel}`
: '抱歉,商家暂无客服,请线下联系';
uni.showModal({ title: '联系客服', content: message, showCancel: false });
}
showConfigErrorPopup(errors) {
uni.showModal({
title: '客服配置错误',
content: `配置有误:\n${errors.join('\n')}`,
title: '联系客服',
content: message,
showCancel: false
});
}
/**
* 显示配置错误弹窗
* @param {Array} errors 错误列表
*/
showConfigErrorPopup(errors) {
const message = errors.join('\n');
uni.showModal({
title: '配置错误',
content: `客服配置有误:\n${message}`,
showCancel: false
});
}
/**
* 降级处理:使用原有客服方式
*/
fallbackToOriginalService() {
uni.showModal({
title: '提示',
content: '无法直接添加企业微信,是否使用其他方式联系客服?',
content: '无法直接添加企业微信客服,是否使用其他方式联系客服?',
success: (res) => {
if (res.confirm) {
this.openWxworkService(true);
@@ -434,19 +497,9 @@ export class CustomerService {
});
}
fallbackToPhoneCall() {
uni.showModal({
title: '提示',
content: '在线客服不可用,是否拨打电话联系客服?',
success: (res) => {
if (res.confirm) this.makePhoneCall();
}
});
}
/**
* 获取按钮配置(用于 template 中 v-if / open-type 判断)
* @returns {Object}
* 获取客服按钮配置
* @returns {Object} 按钮配置
*/
getButtonConfig() {
const config = this.getPlatformConfig();
@@ -454,10 +507,11 @@ export class CustomerService {
let openType = '';
// #ifdef MP-WEIXIN
if (config.type === 'weapp' && config.useOfficial !== false) {
openType = 'contact';
if (config.type === 'weapp') {
openType = config.useOfficial !== false ? 'contact' : '';
}
// #endif
// #ifdef MP-ALIPAY
if (config.type === 'aliapp') openType = 'contact';
// #endif
@@ -466,239 +520,26 @@ export class CustomerService {
}
/**
* 获取客服按钮图标
* @returns {String} 图标URL
* 判断是否应该使用自定义客服处理
* @param {Object} config 客服配置
* @returns {boolean} 是否使用自定义客服
*/
getKefuButtonIcon() {
const shopInfo = this.vm.shopInfo || {};
const config = this.getPlatformConfig();
// 优先使用指定的智能客服头像
if (config?.type === 'aikefu' || config?.type === 'wxwork' || config?.type === 'qyweixin') {
return this.vm.$util.img(IMAGE_CONFIG.aiAvatar);
}
return shopInfo.kefuimg || this.vm.$util?.getDefaultImage()?.kefu || '';
}
/**
* 判断是否为微信官方客服
* @returns {Boolean}
*/
isWeappOfficialKefu() {
const config = this.getPlatformConfig();
return config?.type === 'weapp';
}
/**
* 判断是否为智能客服
* @returns {Boolean}
*/
isAIKefu() {
const config = this.getPlatformConfig();
return config?.type === 'aikefu';
}
/**
* 判断是否为企业微信客服
* @returns {Boolean}
*/
isWxworkKefu() {
const config = this.getPlatformConfig();
return config?.type === 'wxwork' || config?.type === 'qyweixin';
}
/**
* 判断是否为第三方客服
* @returns {Boolean}
*/
isThirdKefu() {
const config = this.getPlatformConfig();
return config?.type === 'third';
}
/**
* 判断是否需要同时显示小程序系统客服
* @returns {Boolean}
*/
shouldShowExtraWeappSystemKefu() {
const config = this.getPlatformConfig();
return (config?.show_system_service === true || config?.show_system_service === '1') && config?.type !== 'weapp';
}
/**
* 计算容器高度
* @param {Boolean} isLanguageSwitchEnabled 是否启用语言切换
* @param {Boolean} showWeappSystemKefu 是否显示小程序系统客服
* @param {Boolean} fixBtnShow 是否显示按钮
* @returns {String} 高度值(px)
*/
getContainerHeight(isLanguageSwitchEnabled, showWeappSystemKefu, fixBtnShow) {
if (!fixBtnShow) return '160px';
let buttonCount = 1;
if (isLanguageSwitchEnabled) buttonCount++;
if (showWeappSystemKefu) buttonCount++;
const totalRpx = 94 * buttonCount - 14;
const pxValue = Math.round(totalRpx * 0.5);
return `${pxValue}px`;
}
/**
* 获取未读消息数量
* @returns {Number}
*/
getUnreadCount() {
return this.vm.$store?.state?.aiUnreadCount || 0;
}
/**
* 拨打电话联系客服
* @param {String} phoneNumber 电话号码
*/
makePhoneCallByNumber(phoneNumber) {
if (phoneNumber) {
uni.makePhoneCall({ phoneNumber });
} else {
this.makePhoneCall();
}
}
/**
* 打开 AI 智能助手
*/
openAIChat() {
try {
if (typeof this.vm.setAiUnreadCount === 'function') {
this.vm.setAiUnreadCount(0);
}
const aiChatUrl = '/pages_tool/ai-chat/index';
uni.navigateTo({
url: aiChatUrl,
fail: (err) => {
console.error('跳转 AI 客服失败:', err);
// #ifdef H5
window.location.href = aiChatUrl;
// #endif
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
});
} catch (e) {
console.error('跳转 AI 客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
}
/**
* 打开客服选择对话框(预留方法)
*/
openCustomerSelectPopupDialog() {
uni.showToast({ title: '客服选择功能开发中', icon: 'none' });
}
/**
* 打开客服选择弹出层(ActionSheet)
* @param {Array} kefuList 客服列表
*/
openKefuSelectPopup(kefuList) {
const kefuNames = kefuList.map(item => item.name);
uni.showActionSheet({
itemList: kefuNames,
success: (res) => {
const selectedKefu = kefuList[res.tapIndex];
const cs = createCustomerService(this.vm, selectedKefu);
if (selectedKefu.isOfficial) {
uni.openCustomerServiceConversation({
sessionFrom: 'weapp',
showMessageCard: true
});
} else if (selectedKefu.id === 'qyweixin-kefu') {
if (uni.getSystemInfoSync().platform === 'wechat') {
uni.navigateTo({
url: '/pages_tool/qyweixin-kefu/index'
});
} else {
const qyweixinUrl = this.vm.shopInfo?.qyweixinUrl;
if (qyweixinUrl) {
window.location.href = qyweixinUrl;
} else {
uni.showToast({ title: '企业微信客服未配置', icon: 'none' });
}
}
} else {
cs.handleCustomerClick();
}
}
});
}
/**
* 核心方法:统一客服入口(带验证和配置检查)
* @param {Object} options 选项参数
*/
handleUnifiedKefuClick(options = {}) {
const validation = this.validateConfig();
console.log('【客服配置验证】', validation);
if (!validation.isValid) {
console.error('客服配置无效:', validation.errors);
uni.showToast({ title: '客服暂不可用', icon: 'none' });
return;
}
if (validation.warnings.length > 0) {
console.warn('客服配置警告:', validation.warnings);
}
const platformConfig = this.getPlatformConfig();
console.log('【当前客服配置】', platformConfig);
// 检查企业微信配置
if (platformConfig.type === 'wxwork') {
const wxworkConfig = this.getWxworkConfig();
console.log('【企业微信配置】', wxworkConfig);
shouldUseCustomService(config) {
// #ifdef MP-WEIXIN
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
console.warn('企业微信配置不完整,使用原生客服');
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
if (config?.type === 'weapp') {
return config.useOfficial === false;
}
// #endif
// #ifdef H5
if (!wxworkConfig?.contact_url && !platformConfig.wxwork_url) {
console.error('企业微信链接未配置');
uni.showToast({ title: '企业微信链接未配置', icon: 'none' });
return;
}
// #endif
}
// 直接调用统一处理方法,由 CustomerService 内部根据配置路由
try {
this.handleCustomerClick({
sendMessageTitle: options.sendMessageTitle || '来自悬浮按钮的咨询',
sendMessagePath: options.sendMessagePath || '/pages/index/index'
});
} catch (error) {
console.error('客服处理失败:', error);
uni.showToast({ title: '打开客服失败', icon: 'none' });
}
return true;
}
}
/**
* 创建客服服务实例
* @param {Object} vueInstance Vue 实例(通常是 this)
* @param {Object} externalConfig 可选:外部传入的最新配置(如从 DIY 数据中提取)
* @returns {CustomerService}
* @param {Object} vueInstance Vue实例
* @param {Object} externalConfig 外部最新配置
* @returns {CustomerService} 客服服务实例
*/
export function createCustomerService(vueInstance, externalConfig = null) {
export function createCustomerService(vueInstance, externalConfig = {}) {
return new CustomerService(vueInstance, externalConfig);
}

View File

@@ -65,10 +65,6 @@ export default {
componentRefresh() {
return this.$store.state.componentRefresh;
},
// AI客服配置
globalAIKefuConfig() {
return this.$store.state.globalAIKefuConfig;
},
// 客服配置
servicerConfig() {
return this.$store.state.servicerConfig;

View File

@@ -1,15 +0,0 @@
/**
* 图片配置文件
* 将大图片上传到服务器后,请在此处配置网络URL
*/
// 替换为你上传后的实际图片URL
const IMAGE_CONFIG = {
// AI客服头像
aiAvatar: 'public/static/img/s-agent.jpg',
// 用户默认头像
userAvatar: 'public/static/img/s-user.jpg'
};
export default IMAGE_CONFIG;

View File

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

View File

@@ -82,7 +82,7 @@
<block v-if="goodsList.length">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true" />
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}
</view>
</view>
@@ -99,6 +99,9 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
<block v-else>
<text class="unit price-style small">{{ $lang('Make') ? $lang('Make') : '询底价' }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
@@ -117,12 +120,6 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @tap.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
<!-- <view class="right-wrap" v-if="value.template == 2 || value.template == 4">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
@@ -178,7 +175,7 @@
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"
:lazy-load="true" :show-menu-by-longpress="true" />
:lazy-load="true" />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
@@ -194,6 +191,10 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
<block v-else>
<text class="unit price-style small">{{ $lang('Make') ? $lang('Make') : '询底价' }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
@@ -211,12 +212,6 @@
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @tap.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
<view class="right-wrap" v-if="value.template == 2">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
@@ -723,15 +718,6 @@ export default {
}
}
},
/**
* 跳转到商品详情
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
diyRedirectTo(link) {
if (!link) return;
this.$util.diyRedirectTo(JSON.parse(link));
@@ -1398,25 +1384,4 @@ export default {
color: var(--main-color);
}
}
.ai-chat-btn-wrap {
display: flex;
justify-content: flex-end;
margin-top: auto;
padding-top: 10rpx;
}
.ai-chat-btn {
background-color: #ff0000;
color: #fff;
padding: 12rpx 30rpx;
border-radius: 40rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1.2;
text-align: center;
white-space: nowrap;
display: inline-block;
}
</style>

View File

@@ -14,7 +14,7 @@
</view>
<view class="video-info">
<!-- 视频标题,支持多行显示控制 -->
<view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }}
</view>
<!-- 视频统计信息 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view>
@@ -34,7 +34,7 @@
</view>
<view class="video-info">
<!-- 视频标题,支持多行显示控制 -->
<view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
<view class="video-title" :style="{ '--title-line-clamp': titleLineClamp }">{{ value.videoTitle }}
</view>
<!-- 视频统计信息,非列表模式下显示 -->
<view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ value.viewCount }}次观看</view>

View File

@@ -8,8 +8,7 @@
<image class="goods-img"
:src="$util.img(item.goods_image, { size: goodsValue.template == 'large-mode' ? 'big' : 'mid' })"
mode="widthFix" @error="imgError(index)"
:style="{ borderRadius: goodsValue.imgAroundRadius * 2 + 'rpx' }"
:show-menu-by-longpress="true" />
:style="{ borderRadius: goodsValue.imgAroundRadius * 2 + 'rpx' }" />
<view class="sell-out" v-if="item.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
@@ -118,7 +117,7 @@
<view class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)"
:lazy-load="true" :show-menu-by-longpress="true" />
:lazy-load="true" />
<view class="sell-out" v-if="item.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
@@ -185,7 +184,7 @@
<view class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image, { size: 'mid' })" mode="widthFix"
@error="imgError(dataIndex)" :lazy-load="true" :show-menu-by-longpress="true" />
@error="imgError(dataIndex)" :lazy-load="true" />
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>

View File

@@ -17,7 +17,7 @@
<div class="goods-img-wrap">
<image class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image, { size: 'mid' })" mode="widthFix"
@error="imgError(index, dataIndex)" :lazy-load="true" :show-menu-by-longpress="true" />
@error="imgError(index, dataIndex)" :lazy-load="true" />
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>

View File

@@ -317,12 +317,7 @@ export default {
this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = '';
// 给每个组件增加位置属性,用于定位,搜索、分类导航等定位
// 如果是Tab组件内部的子组件,添加tabIndex前缀避免moduleIndex冲突
if (this.diyData.tabIndex !== undefined) {
item.moduleIndex = `${this.diyData.tabIndex}-${index + 1}`;
} else {
item.moduleIndex = index + 1;
}
// 特殊处理搜索框 当显示位置为滚动至顶部固定时,只设置背景颜色
if (item.componentName == 'Search' && item.positionWay == 'fixed') {

File diff suppressed because it is too large Load Diff

View File

@@ -53,19 +53,9 @@
<!-- 视频播放弹窗 -->
<uni-popup ref="videoPopup" type="center" style="background:transparent;width:100%;height:100%;">
<view class="video-container" :style="popupVideoStyle">
<video
ref="popupVideoRef"
class="adaptive-video"
:autoPauseIfNavigate="true"
:autoPauseIfOpenNative="true"
:autoplay="false"
:enableAutoRotation="true"
id="myVideo"
:src="video_url"
:controls="true"
@loadedmetadata="onPopupVideoLoaded"
></video>
<view class="video-container" style="position:fixed;top:30%;width:100%;left:0;">
<video class="adaptive-video" :autoPauseIfNavigate="true" :autoPauseIfOpenNative="true"
:autoplay="false" :enableAutoRotation="true" id="myVideo" :src="video_url" :controls="true"></video>
</view>
</uni-popup>
</view>
@@ -136,38 +126,12 @@ export default {
isIndicatorDots() {
return this.value.carousel.type != 'hide' &&
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
},
// 弹窗视频容器样式
popupVideoStyle() {
let style = 'position:fixed;width:100%;left:0;';
if (this.popupVideoRatio) {
// 根据视频比例设置位置
if (this.popupVideoRatio === 'portrait') {
// 竖屏视频:居中显示,使用9/16比例
style += 'top:50%;transform:translateY(-50%);';
style += 'aspect-ratio: 9 / 16;';
} else {
// 横屏视频:靠近顶部显示,使用16/9比例
style += 'top:20%;';
style += 'aspect-ratio: 16 / 9;';
}
} else {
// 默认:居中显示,使用竖屏比例
style += 'top:50%;transform:translateY(-50%);';
style += 'aspect-ratio: 9 / 16;';
}
return style;
}
},
methods: {
// 显示视频播放弹窗
showVideo(item) {
this.video_url = item.videoUrl;
// 重置视频比例为null,等待新的检测结果
this.popupVideoRatio = null;
this.$refs.videoPopup.open();
},
@@ -175,39 +139,6 @@ export default {
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
},
// 弹窗视频加载完成事件
onPopupVideoLoaded(event) {
this.$nextTick(() => {
let width = 0;
let height = 0;
// H5 端:获取真实视频宽高
// #ifdef H5
const video = this.$refs.popupVideoRef;
if (video) {
width = video.videoWidth || 0;
height = video.videoHeight || 0;
}
// #endif
// 微信小程序端:通过事件对象获取视频宽高
// #ifdef MP-WEIXIN
if (event && event.detail) {
width = event.detail.width || 0;
height = event.detail.height || 0;
}
// #endif
if (width && height) {
this.popupVideoRatio = width >= height ? 'landscape' : 'portrait';
console.log(`弹窗视频宽高比检测: ${width}x${height}, 比例: ${this.popupVideoRatio}`);
} else {
// 获取失败,默认竖屏
this.popupVideoRatio = 'portrait';
}
});
}
}
}

View File

@@ -1,36 +1,7 @@
<template>
<view class="video-container" :style="containerStyle">
<!-- H5端使用原生video标签 -->
<!-- #ifdef H5 -->
<video
ref="videoRef"
data-component-name="diy-video"
class="diy-video h5-video"
:src="videoUrl"
:poster="posterUrl"
:style="videoWarpCss"
controls
preload="metadata"
@loadedmetadata="onVideoLoaded"
@click="handlerClick(videoUrl)"
></video>
<!-- #endif -->
<!-- 微信小程序端使用uni-app的video组件 -->
<!-- #ifdef MP-WEIXIN -->
<video
ref="videoRef"
data-component-name="diy-video"
class="diy-video mp-video"
:src="videoUrl"
:poster="posterUrl"
:style="videoWarpCss"
controls
@loadedmetadata="onVideoLoaded"
@error="onVideoError"
></video>
<!-- #endif -->
</view>
<video data-component-name="diy-video" class="diy-video" :src="$util.img(value.videoUrl)"
:poster="$util.img(value.imageUrl)" :style="videoWarpCss" objectFit="cover"
@tap="handlerClick(value.videoUrl)"></video>
</template>
<script>
@@ -44,160 +15,27 @@ export default {
}
},
data() {
return {
videoRatio: null, // 视频比例:'landscape'(横屏)或 'portrait'(竖屏)
videoUrl: '',
posterUrl: ''
};
},
created() {
this.initVideoData();
return {};
},
created() { },
mixins: [DiyMinx],
watch: {
value: {
immediate: true,
handler(newVal) {
if (newVal) {
this.initVideoData();
}
}
}
// 组件刷新监听
componentRefresh: function (nval) { }
},
computed: {
videoWarpCss: function () {
var obj = '';
if (this.value && this.value.componentAngle == 'round') {
if (this.value.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
return obj;
},
// 动态计算容器比例
containerStyle() {
let style = '';
if (this.videoRatio === 'landscape') {
// 横屏视频:16/9比例
style += 'aspect-ratio: 16 / 9;';
} else if (this.videoRatio === 'portrait') {
// 竖屏视频:9/16比例
style += 'aspect-ratio: 9 / 16;';
} else {
// 未加载完成 → 默认使用竖屏比例
style += 'aspect-ratio: 9 / 16;';
}
return style;
}
},
methods: {
// 初始化视频数据
initVideoData() {
this.videoUrl = this.getVideoUrl();
this.posterUrl = this.getPosterUrl();
// H5端:预加载视频获取宽高
// #ifdef H5
this.preloadVideoForRatio();
// #endif
},
// 获取视频URL
getVideoUrl() {
if (!this.value || !this.value.videoUrl) return '';
const videoUrl = this.value.videoUrl;
// 如果是完整的http/https链接,直接使用
if (videoUrl.indexOf('http://') === 0 || videoUrl.indexOf('https://') === 0) {
return videoUrl;
}
// 否则使用$util.img()处理
return this.$util.img(videoUrl);
},
// 获取封面图URL
getPosterUrl() {
if (!this.value || !this.value.imageUrl) return '';
return this.$util.img(this.value.imageUrl);
},
// H5端:预加载视频获取宽高比例
preloadVideoForRatio() {
const videoUrl = this.videoUrl;
if (!videoUrl) return;
console.log('H5端预加载视频获取比例:', videoUrl);
const video = document.createElement('video');
video.preload = 'metadata';
video.src = videoUrl;
video.muted = true;
video.playsInline = true;
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 ? '横屏' : '竖屏');
}
});
video.addEventListener('error', (e) => {
console.error('H5端预加载视频失败:', e);
});
// 触发加载
video.load();
},
// 视频加载完成事件(组件内video标签)
onVideoLoaded(event) {
console.log('视频组件 loadedmetadata 事件触发');
let width = 0;
let height = 0;
// #ifdef H5
if (event && event.target) {
width = event.target.videoWidth || 0;
height = event.target.videoHeight || 0;
console.log('H5端组件视频宽高:', width, height);
}
// #endif
// #ifdef MP-WEIXIN
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('视频暂停');
},
// 视频错误事件
onVideoError(event) {
console.error('视频加载失败:', event);
},
async handlerClick(videoUrl) {
await this.__$emitEvent({
eventName: 'video-tap', data: videoUrl, promiseCallback: (event, handler, awaitedResult) => {
@@ -210,32 +48,11 @@ export default {
</script>
<style scoped>
.video-container {
video {
width: 100%;
position: relative;
overflow: hidden;
background-color: #000;
}
/* 基础样式 */
.diy-video {
width: 100%;
height: 100%;
display: block;
.diy-video>>>.uni-video-container {
background-color: transparent;
}
/* H5端样式 */
/* #ifdef H5 */
.h5-video {
object-fit: cover;
}
/* #endif */
/* 微信小程序端样式 */
/* #ifdef MP-WEIXIN */
.mp-video {
width: 100%;
height: 100%;
}
/* #endif */
</style>

View File

@@ -179,7 +179,6 @@
.chat-message {
width: 100%;
height: 100%;
background: white; /* 白色 */
.message {
padding: 13rpx 20rpx;
@@ -271,7 +270,7 @@
flex-direction: row-reverse;
.content {
background-color: #c4e0ff; /* 浅蓝色 */
background-color: #4cd964;
margin-right: 28rpx;
word-break: break-all;
line-height: 36rpx;

View File

@@ -1,79 +1,43 @@
<template>
<view v-if="pageCount === 1 || need" class="fixed-box"
:style="[customContainerStyle, {
height: containerHeight,
<!-- 悬浮按钮 -->
<view v-if="pageCount == 1 || need" class="fixed-box" :style="[customContainerStyle, {
height: fixBtnShow ? '400rpx' : '320rpx',
backgroundImage: bgUrl ? `url(${bgUrl})` : '',
backgroundSize: 'cover'
}]">
<!-- 中英文切换按钮 -->
<view
v-if="isLanguageSwitchEnabled && fixBtnShow"
class="btn-item common-bg"
@click="toggleLanguage"
>
<view v-if="isLanguageSwitchEnabled && fixBtnShow" class="btn-item common-bg" @click="toggleLanguage">
<text>{{ currentLangDisplayName }}</text>
</view>
<!-- 微信官方客服按钮-->
<button
v-if="fixBtnShow && hasValidKefuConfig && isWeappOfficialKefu"
class="btn-item common-bg"
open-type="contact"
>
<text class="icox icox-kefu" v-if="!kefuimg"></text>
</button>
<!-- 其他类型客服按钮(智能客服、企业微信等)-->
<view
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && !showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
>
<image
v-if="currentKefuImg"
:src="currentKefuImg"
mode="aspectFill"
class="kefu-img"
></image>
<text class="ai-icon" v-if="isAIKefu && !currentKefuImg">🤖</text>
<text class="ai-icon" v-if="isWxworkKefu && !currentKefuImg">💬</text>
<text class="ai-icon" v-if="!isAIKefu && !isWxworkKefu && !currentKefuImg">👤</text>
<!-- AI 智能助手 -->
<view v-if="fixBtnShow && enableAIChat" class="btn-item common-bg" @click="openAIChat"
:style="{ backgroundImage: aiAgentimg ? `url(${aiAgentimg})` : '', backgroundSize: '100% 100%' }">
<text class="ai-icon" v-if="!aiAgentimg">🤖</text>
</view>
<!-- 智能客服按钮(当同时显示小程序系统客服时)-->
<view
v-if="fixBtnShow && hasValidKefuConfig && !isWeappOfficialKefu && showWeappSystemKefu"
class="btn-item common-bg"
@click="handleUnifiedKefuClick"
>
<image
v-if="currentKefuImg"
:src="currentKefuImg"
mode="aspectFill"
class="kefu-img"
></image>
<text class="ai-icon" v-if="isAIKefu && !currentKefuImg">🤖</text>
<text class="ai-icon" v-if="isWxworkKefu && !currentKefuImg">💬</text>
<text class="ai-icon" v-if="!isAIKefu && !isWxworkKefu && !currentKefuImg">👤</text>
</view>
<!-- 小程序系统客服按钮 -->
<button
v-if="fixBtnShow && hasValidKefuConfig && showWeappSystemKefu"
class="btn-item common-bg"
open-type="contact"
>
<!-- 微信小程序客服按钮 -->
<!-- #ifdef MP-WEIXIN -->
<button class="btn-item common-bg" hoverClass="none" openType="contact" sessionFrom="weapp" showMessageCard="true"
:style="[{ backgroundImage: kefuimg ? `url(${kefuimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]">
<text class="icox icox-kefu" v-if="!kefuimg"></text>
</button>
<!-- #endif -->
<!-- 普通客服(仅当未启用 AI 时显示) -->
<!-- #ifdef H5 -->
<template v-if="fixBtnShow">
<button class="btn-item common-bg" hoverClass="none" @click="openCustomerSelectPopup"
:style="[{ backgroundImage: kefuimg ? `url(${kefuimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]">
<text class="icox icox-kefu" v-if="!kefuimg"></text>
</button>
</template>
<!-- #endif -->
<!-- 电话按钮 -->
<view
v-if="fixBtnShow"
class="btn-item common-bg"
@click="call"
:style="[{ backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
>
<!-- 电话按钮(始终显示) -->
<view v-if="fixBtnShow" class="btn-item common-bg" @click="call()"
:style="[{ backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]">
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
</view>
@@ -92,13 +56,16 @@ export default {
return {
pageCount: 0,
fixBtnShow: true,
shopInfo: null,
currentLangIndex: 0,
langIndexMap: {},
customerService: null,
};
},
computed: {
// 安全读取 shopInfo 中的字段,避免 undefined 报错
bgUrl() {
return this.shopInfo?.bgUrl || '';
},
@@ -117,6 +84,9 @@ export default {
isLanguageSwitchEnabled() {
return !!this.shopInfo?.ischina;
},
enableAIChat() {
return !!this.shopInfo?.enableAIChat;
},
currentLangDisplayName() {
const lang = this.langIndexMap[this.currentLangIndex];
return lang === 'zh-cn' ? 'EN' : 'CN';
@@ -126,77 +96,23 @@ export default {
},
customButtonStyle() {
return this.shopInfo?.floatingButton?.button || {};
},
unreadCount() {
const customerService = createCustomerService(this);
return customerService.getUnreadCount();
},
currentKefuImg() {
const customerService = createCustomerService(this);
return customerService.getKefuButtonIcon();
},
isWeappOfficialKefu() {
const customerService = createCustomerService(this);
return customerService.isWeappOfficialKefu();
},
isAIKefu() {
const customerService = createCustomerService(this);
return customerService.isAIKefu();
},
isWxworkKefu() {
const customerService = createCustomerService(this);
return customerService.isWxworkKefu();
},
isThirdKefu() {
const customerService = createCustomerService(this);
return customerService.isThirdKefu();
},
showWeappSystemKefu() {
const customerService = createCustomerService(this);
return customerService.shouldShowExtraWeappSystemKefu();
},
hasValidKefuConfig() {
const customerService = createCustomerService(this);
return customerService.hasValidKefuConfig();
},
//根据显示的按钮数量动态计算容器高度
containerHeight() {
if (!this.fixBtnShow) return '320rpx';
let buttonCount = 1;
if (this.isLanguageSwitchEnabled) buttonCount++;
if (this.showWeappSystemKefu) buttonCount++;
buttonCount++;
const totalRpx = 94 * buttonCount - 14;
const pxValue = Math.round(totalRpx * 0.5);
return ` $ {pxValue}px`;
}
},
watch: {
shopInfo: {
handler(newVal) {
},
immediate: true
}
},
created() {
this.customerService = createCustomerService(this);
this.initLanguage();
this.pageCount = getCurrentPages().length;
uni.getStorage({
key: 'shopInfo',
success: (e) => {
console.log('【调试】当前 shopInfo:', e.data);
this.shopInfo = e.data;
},
fail: () => {
console.warn('未获取到 shopInfo,使用默认设置');
}
});
// 初始化 customerService 实例
this.customerService = createCustomerService(this);
},
methods: {
/**
* 初始化多语言配置
*/
initLanguage() {
this.langList = this.$langConfig.list();
this.langIndexMap = {};
@@ -216,30 +132,36 @@ export default {
}
},
/**
* 电话联系客服
*/
call() {
this.customerService.makePhoneCallByNumber(this.tel);
this.customerService.makePhoneCall(this.tel);
},
/**
* 切换中英文语言,并刷新当前页面(保留所有参数)
*/
toggleLanguage() {
this.currentLangIndex = this.currentLangIndex === 0 ? 1 : 0;
const targetLang = this.langIndexMap[this.currentLangIndex];
uni.setStorageSync("lang", targetLang);
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentRoute = currentPage.route;
uni.reLaunch({
url: '/' + currentRoute
});
// 调用语言切换逻辑(设置 storage + 清空缓存)
this.$langConfig.change(targetLang);
},
handleUnifiedKefuClick() {
this.customerService.handleUnifiedKefuClick({
sendMessageTitle: '来自悬浮按钮的咨询',
sendMessagePath: '/pages/index/index'
});
/**
* 打开 AI 智能助手
*/
openAIChat() {
this.$util.redirectTo(this.$util.AI_CHAT_PAGE_URL);
},
/**
* 打开客服选择对话框
*/
openCustomerSelectPopup() {
this.customerService.openCustomerSelectPopupDialog();
}
}
}
@@ -274,15 +196,17 @@ export default {
margin: 14rpx 0;
transition: 0.1s;
color: var(--hover-nav-text-color);
border-radius: 50%;
border-radius: 40rpx;
width: 80rpx;
height: 80rpx;
padding: 0;
overflow: hidden;
}
/* 定义共同的背景颜色 */
.common-bg {
background-color: var(--hover-nav-bg-color);
/* 使用变量以保持一致性 */
}
.btn-item text {
@@ -312,12 +236,7 @@ export default {
font-weight: bold;
}
.kefu-img {
width: 100%;
height: 100%;
display: block;
border-radius: 50%;
}
.ai-icon {
font-size: 40rpx;

View File

@@ -53,15 +53,8 @@ export default {
}
},
mounted() {
// 检查配置,决定是否显示隐私协议
const shopInfo = uni.getStorageSync('shopInfo');
if (shopInfo?.ischina == 1 && shopInfo?.index_login_auth == 1) {
this.listenPrivacySettingChange()
this.curPageShow()
} else {
// 如果不需要显示隐私协议,直接触发同意事件
this.$emit('agree');
}
},
created() {
try {
@@ -159,62 +152,28 @@ export default {
},
// 主动查询用户隐私政策授权状态
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) => {
// 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 {
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
this.$emit('agree')
}
// #endif
// #ifdef QUICKAPP-WEBVIEW
this.getPrivacySettingByQuickApp();
// #endif
// #ifdef H5 || WEB
// 检查本地存储中是否已经同意过隐私政策
const privacyAgreed = uni.getStorageSync('privacyAgreed');
if (privacyAgreed) {
this.$emit('agree');
} else {
this.popUp()
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
this.closeOtherPagePopUp(this.disPopUp)
}
// #endif
},
//初始化监听程序
curPageShow() {
@@ -302,8 +261,6 @@ export default {
this.privacyResolves.clear()
//关闭弹窗
this.disPopUp()
// 保存用户授权状态到本地存储,无论同意还是不同意,都不再重复弹出
uni.setStorageSync('privacyAgreed', true);
//退出小程序
uni.showModal({
content: '未同意隐私协议,无法使用相关功能',
@@ -324,8 +281,10 @@ export default {
//关闭弹窗
this.disPopUp()
// 保存用户授权状态到本地存储,用于所有环境
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
// 保存用户授权状态到本地存储,用于快应用Webview组件中的H5方式处理
uni.setStorageSync('privacyAgreed', true);
// #endif
this.$emit('agree')
},
//打开弹窗

View File

@@ -3,5 +3,5 @@ export const lang = {
title: '商品分类',
search:'商品搜索',
seeMore : '查看更多',
Make: "立即咨询"
Make: "询底价"
}

View File

@@ -2,7 +2,7 @@ export const lang = {
//title为每个页面的标题
title: '',
select: '选择',
details:'商品详情',
details:'资源详情',
params: '参数',
service: '商品服务',
allGoods: '全部商品',
@@ -11,7 +11,7 @@ export const lang = {
home:'首页',
cart:'购物车',
leave:'立即留言',
make:'立即咨询',
make:'联系我们',
send:'配送',
express:'快递发货',
@@ -22,7 +22,7 @@ export const lang = {
status:'该商品已下架',
sellout:'库存不足',
max:'已达最大限购数量',
makebtn:'立即咨询',
makebtn:'价格电议',
sales:'销量',
stock:'库存',
kefu:'客服',

View File

@@ -6,6 +6,6 @@ export const lang = {
Price:'价格',
Filter:'筛选',
Search:'请输入您要搜索的商品',
Make:'立即咨询',
Make:'立即询底价',
}

View File

@@ -71,14 +71,11 @@
"scope.userLocation" : {
"desc" : "为了更好地为您提供服务"
},
"scope.writePhotosAlbum" : {
"desc" : "为了更好地为您提供服务"
},
"scope.userClipboard" : {
"desc" : "为了方便您复制信息"
},
"scope.camera" : {
"desc" : "用于拍照上传头像"
},
"scope.writePhotosAlbum" : {
"desc" : "用于从相册选择和保存头像"
}
},
"requiredPrivateInfos" : [ "chooseLocation", "getLocation", "chooseAddress" ],
@@ -107,7 +104,7 @@
},
"router" : {
"mode" : "history",
"base" : "/hwappx/test/"
"base" : "/hwappx/2811/"
},
"title" : "",
"devServer" : {

View File

@@ -43,7 +43,7 @@
{
"path": "detail",
"style": {
"navigationBarTitleText": "商品详情"
"navigationBarTitleText": "资源详情"
}
},
{

View File

@@ -3,8 +3,7 @@ export default {
return {
minScrollTop: 100, // 设置回到顶端按钮显示要求,最小页面滚动距离
wechatQrcode: '', // 公众号二维码
diyRoute: '/pages/index/index',
shopInfo: null // 店铺信息
diyRoute: '/pages/index/index'
};
},
onLoad(option) {
@@ -16,15 +15,10 @@ export default {
// #endif
this.name = option.name || 'DIY_VIEW_INDEX'; // 根据店铺运营模式,打开平台(shop)或者连锁门店(stroe)页面,接口会自行判断
// 获取店铺信息
this.shopInfo = uni.getStorageSync('shopInfo');
},
onShow() {
// 获取关注公众号二维码
this.getFollowQrcode();
// 重新获取店铺信息
this.shopInfo = uni.getStorageSync('shopInfo');
},
mounted() {
// 监听图片、文字、商品列表等组件的点击事件
@@ -65,11 +59,6 @@ export default {
officialAccountsClose() {
this.$refs.officialAccountsPopup.close();
},
// 判断是否需要登录授权
_needLoginAuth() {
// 检查 index_login_auth 都开启时才需要登录授权
return this.shopInfo?.index_login_auth == 1;
},
// 统一处理点击及触摸事件
_handleDiyGroupInteractionEvent(payload) {
// #ifdef H5
@@ -77,11 +66,6 @@ export default {
return true;
// #endif
// 如果不需要登录授权,直接返回 true 继续传播
if (!this._needLoginAuth()) {
return true;
}
// 如果已登录,直接返回 true 继续传播
if (this.storeToken) {
return true;

View File

@@ -2,11 +2,6 @@
<view>
<view scroll-y="true" class="goods-detail" :class="isIphoneX ? 'active' : ''">
<view class="goods-container">
<!-- AI客服悬浮按钮 -->
<view class="ai-chat-float-btn" @tap.stop="$emit('aiChatClick')" v-if="showAIChatBtn">
<image :src="aiAvatarUrl" mode="aspectFill" class="ai-avatar-icon" />
</view>
<!-- 弹幕 -->
<pengpai-fadein-out v-if="goodsSkuDetail.barrage_show && goodsSkuDetail.barrageData" ref="pengpai"
:duration="1600" :wait="1900" :top="200" :left="0" :radius="60" :loop="true"
@@ -22,7 +17,7 @@
:item-id="'goods_id_' + index">
<view class="item" @click="previewMedia(index)">
<image :src="$util.img(item, { size: 'big' })" @error="swiperImageError(index)"
mode="aspectFit" :show-menu-by-longpress="true" />
mode="aspectFit" />
</view>
</swiper-item>
</swiper>
@@ -463,8 +458,6 @@
// 商品详情视图
import scroll from '@/common/js/scroll-view.js';
import detail from './detail.js';
import IMAGE_CONFIG from '@/common/js/image-config.js';
import { createCustomerService } from '@/common/js/customer-service.js';
import pengpaiFadeinOut from '../pengpai-fadein-out/pengpai-fadein-out.vue';
@@ -481,15 +474,6 @@ export default {
components: {
pengpaiFadeinOut
},
computed: {
aiAvatarUrl() {
return this.$util.img(IMAGE_CONFIG.aiAvatar);
},
showAIChatBtn() {
const customerService = createCustomerService(this);
return customerService.hasValidKefuConfig() && customerService.isAIKefu();
}
},
mixins: [scroll, detail]
};
</script>
@@ -497,36 +481,4 @@ export default {
<style lang="scss">
@import '@/common/css/goods_detail.scss';
</style>
<style scoped>
.ai-chat-float-btn {
position: fixed;
right: 20rpx;
top: 100rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
z-index: 9999;
transition: all 0.3s ease;
overflow: hidden;
}
.ai-chat-float-btn:active {
transform: scale(0.9);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.ai-chat-float-btn .iconfont {
font-size: 40rpx;
}
.ai-chat-float-btn .ai-avatar-icon {
width: 100%;
height: 100%;
border-radius: 50%;
}
</style>
<style scoped></style>

View File

@@ -26,8 +26,7 @@
export default {
data() {
return {
diyData: null,
mpShareData: null //小程序分享数据
diyData: null
};
},
onLoad() {
@@ -39,25 +38,6 @@ export default {
onShow() {
this.$util.hideHomeButton();
if (this.$refs.category) this.$refs.category[0].pageShow();
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_goods/category').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
},
timeLine: {
title: '发现超多好物,快来看看',
query: '/pages_goods/category'
}
};
});
// #endif
},
onUnload() {
if (!this.storeToken && this.$refs.login) this.$refs.login.cancelCompleteInfo();
@@ -89,24 +69,6 @@ export default {
onPullDownRefresh() {
uni.hideTabBar();
this.getDiyInfo();
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '发现超多好物,快来看看',
query: '/pages_goods/category'
};
},
onAddToFavorites() {
return {
title: '发现超多好物,快来看看',
path: '/pages_goods/category'
};
}
};
</script>

View File

@@ -6,7 +6,7 @@
</view> -->
<!-- #endif -->
<goods-detail-view :goodsSkuDetail="goodsSkuDetail" ref="goodsDetailView" @aiChatClick="sendToAIChat(goodsSkuDetail)">
<goods-detail-view :goodsSkuDetail="goodsSkuDetail" ref="goodsDetailView">
<!-- 价格区域 -->
<template v-slot:price>
<!-- 限时折扣 -->
@@ -77,6 +77,7 @@
</block>
<block v-else>
<template>
<text class="price price-font" style="font-weight: 600;">{{$lang('makebtn')}}</text>
</template>
</block>
</view>
@@ -463,7 +464,6 @@
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 1" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="phoneClick(goodsSkuDetail.goods_mobile)" :text="$lang('make')" :disabled="false" />
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="showinformation" :text="$lang('leave')" :disabled="false" />
<ns-goods-action-button v-if="goodsSkuDetail.isinformation == 3" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="goToAIChat" text="智能客服" :disabled="false" />
<!-- <ns-goods-action-button v-if="goodsSkuDetail.isinformation == 2" class="goods-action-button active3" disabled-text="" backgroundColor="#54c952" @click="buyNow" text="商品留言" :disabled="false" /> -->
</block>
@@ -515,19 +515,6 @@
<!-- 隐私弹窗 -->
<privacy-popup ref="privacyPopup"></privacy-popup>
<!-- 联系电话弹窗 -->
<uni-popup ref="contactPopup" type="center">
<view class="contact-popup">
<view class="contact-content">
<text class="contact-text">可在手机端拨打 021-51815181</text>
</view>
<view class="contact-buttons">
<button class="contact-btn cancel-btn" @click="closeContactPopup">取消</button>
<button class="contact-btn copy-btn" @click="copyPhoneNumber">复制到剪切板</button>
</view>
</view>
</uni-popup>
</view>
</template>
@@ -544,61 +531,9 @@ export default {
goodsDetailView,
nsGoodsPromotion
},
mixins: [base, detail, scroll],
methods: {
/**
* 发送商品信息到智能客服
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
this.$refs.contactPopup.open();
},
/**
* 跳转到智能客服页面
*/
goToAIChat() {
const productInfo = {
goods_id: this.goodsSkuDetail.goods_id || '',
goods_name: this.goodsSkuDetail.goods_name || '',
price: this.goodsSkuDetail.discount_price || this.goodsSkuDetail.member_price || this.goodsSkuDetail.price || '',
image: this.goodsSkuDetail.sku_image || '',
category_id: this.goodsSkuDetail.category_id || ''
};
uni.setStorageSync('selectedProduct', productInfo);
uni.navigateTo({
url: '/pages_tool/ai-chat/index'
});
},
/**
* 关闭联系电话弹窗
*/
closeContactPopup() {
this.$refs.contactPopup.close();
},
/**
* 复制电话号码到剪切板
*/
copyPhoneNumber() {
uni.setClipboardData({
data: '021-51815181',
success: () => {
uni.showToast({
title: '复制成功',
icon: 'success'
});
this.closeContactPopup();
}
});
}
}
mixins: [base, detail, scroll]
};
</script>
<style lang="scss">
@import '@/common/css/goods_detail.scss';
@import './public/css/detail.scss';
@@ -610,57 +545,6 @@ export default {
z-index: 999;
}
.contact-popup {
width: 600rpx;
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
}
.contact-content {
padding: 60rpx 40rpx;
text-align: center;
}
.contact-text {
font-size: 32rpx;
color: #333333;
line-height: 1.5;
}
.contact-buttons {
display: flex;
border-top: 1px solid #e5e5e5;
}
.contact-btn {
flex: 1;
height: 90rpx;
line-height: 90rpx;
text-align: center;
font-size: 32rpx;
border: none;
background: transparent;
margin: 0;
padding: 0;
border-radius: 0;
}
.contact-btn::after {
border: none;
}
.cancel-btn {
color: #333333;
background: #ffffff;
border-right: 1px solid #e5e5e5;
}
.copy-btn {
color: #ffffff;
background: #54c952;
}
</style>
<style scoped>
::v-deep .action-icon-wrap .iconfont.icon-shouye1 {
@@ -756,5 +640,4 @@ export default {
height: 72rpx;
line-height: 72rpx;
}
</style>

View File

@@ -51,7 +51,7 @@
<view class="goods-item margin-bottom" v-for="(item, index) in goodsList" :key="index"
@click="toDetail(item)">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}
</view>
<view class="sell-out" v-if="item.goods_stock <= 0">
@@ -75,23 +75,68 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ langstatus ? $lang('Make') : '询底价' }}</text>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix"></image>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"></image>
</view>
</view>
<view class="pro-info" v-if="item.isinformation == 0">
<view class="delete-price color-tip price-font" v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
<view class="block-wrap">
<view class="sale" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
<view class="sale color-tip" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
? item.unit : '件' }}</view>
</view>
<view class="cart-action-wrap" v-if="config.control && item.is_virtual == 0">
<view class="cart buy-btn click-wrap" :id="'goods-' + item.id"
<!-- 购物车图标 -->
<view v-if="config.style == 'icon-cart'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart shopping-cart-btn iconfont icon-gouwuche click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
购买
<view class="click-event"></view>
</view>
<!--加号图标 -->
<view v-else-if="config.style == 'icon-add'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart plus-sign-btn iconfont icon-add1 click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
</view>
<!-- 按钮 -->
<view v-else-if="config.style == 'button'" :style="{
backgroundColor: config.theme == 'diy' ? config.bgColor : '',
color: config.theme == 'diy' ? config.textColor : '',
fontWeight: config.theme == 'diy' ? (config.fontWeight ? 'bold' : 'normal') : '',
padding: config.theme == 'diy' ? '12rpx ' + config.padding * 2 + 'rpx' : ''
}" class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
{{ config.text }}
<view class="click-event"></view>
</view>
<!--自定义图标 -->
<view v-else-if="config.style == 'icon-diy'" :style="{
color: config.theme == 'diy' ? config.textColor : ''
}" class="icon-diy click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
<diy-icon :icon="config.iconDiy.icon"
:value="config.iconDiy.style ? config.iconDiy.style : null"></diy-icon>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @click.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
</view>
@@ -102,7 +147,7 @@
@click="toDetail(item)"
:style="{ left: listPosition[index] ? listPosition[index].left : '', top: listPosition[index] ? listPosition[index].top : '' }">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}
</view>
<view class="sell-out" v-if="item.stock <= 0">
@@ -123,23 +168,66 @@
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ langstatus ? $lang('Make') : '询底价' }}</text>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix"></image>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"></image>
</view>
<view class="delete-price color-tip price-font" v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="pro-info" v-if="item.isinformation == 0">
<view class="block-wrap">
<view class="sale" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
<view class="sale color-tip" v-if="item.sale_show">已售{{ item.sale_num }}{{ item.unit
? item.unit : '件' }}</view>
</view>
<view class="cart-action-wrap" v-if="config.control && item.is_virtual == 0">
<view class="cart buy-btn click-wrap" :id="'goods-' + item.id"
<!-- 购物车图标 -->
<view v-if="config.style == 'icon-cart'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart shopping-cart-btn iconfont icon-gouwuche click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
购买
<view class="click-event"></view>
</view>
<!--加号图标 -->
<view v-else-if="config.style == 'icon-add'" :style="{
color: config.theme == 'diy' ? config.textColor : '',
borderColor: config.theme == 'diy' ? config.textColor : ''
}" class="cart plus-sign-btn iconfont icon-add1 click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
</view>
<!-- 按钮 -->
<view v-else-if="config.style == 'button'" :style="{
backgroundColor: config.theme == 'diy' ? config.bgColor : '',
color: config.theme == 'diy' ? config.textColor : '',
fontWeight: config.theme == 'diy' ? (config.fontWeight ? 'bold' : 'normal') : '',
padding: config.theme == 'diy' ? '12rpx ' + config.padding * 2 + 'rpx' : ''
}" class="cart buy-btn click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
{{ config.text }}
<view class="click-event"></view>
</view>
<!--自定义图标 -->
<view v-else-if="config.style == 'icon-diy'" :style="{
color: config.theme == 'diy' ? config.textColor : ''
}" class="icon-diy click-wrap" :id="'goods-' + item.id"
@click.stop="$refs.goodsSkuIndex.addCart(config.cartEvent, item, $event)">
<view class="click-event"></view>
<diy-icon :icon="config.iconDiy.icon"
:value="config.iconDiy.style ? config.iconDiy.style : null"></diy-icon>
</view>
<view class="ai-chat-btn-wrap" v-if="item.isinformation == 1">
<view class="ai-chat-btn" @click.stop="sendToAIChat(item)">
立即咨询
</view>
</view>
</view>
@@ -223,14 +311,7 @@ export default {
data() {
return {};
},
mixins: [list],
methods: {
sendToAIChat(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
}
}
mixins: [list]
};
</script>
@@ -246,24 +327,4 @@ export default {
::v-deep .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
max-height: unset !important;
}
.ai-chat-btn-wrap {
display: flex;
justify-content: flex-end;
margin-top: auto;
padding-top: 10rpx;
}
.ai-chat-btn {
background-color: #ff0000;
color: #fff;
padding: 12rpx 30rpx;
border-radius: 40rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1.2;
text-align: center;
white-space: nowrap;
display: inline-block;
}
</style>

View File

@@ -295,8 +295,7 @@
.lineheight-clear {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: flex-end;
}
// 商品列表单列样式
@@ -387,30 +386,46 @@
display: inline-block;
font-weight: bold;
line-height: 1;
margin-top: 0;
margin-top: 16rpx;
background-color: var(--main-color);
border-radius: 35rpx;
padding: 12rpx 24rpx;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
.unit {
margin-right: 6rpx;
color: #ff4444;
color: var(--price-color);
}
.price {
color: #ff4444;
font-size: 32rpx !important;
color: var(--price-color);
color: #fff !important;
font-size: 27rpx !important;
font-weight: bold !important;
}
}
.pro-info {
margin-top: 10rpx;
display: flex;
justify-content: space-between;
margin-top: auto;
align-items: center;
position: relative;
.delete-price {
text-decoration: line-through;
font-size: $font-size-tag !important;
flex: 1;
.unit {
margin-right: 0;
}
}
.block-wrap {
flex: 1;
line-height: 1;
margin-right: 20rpx;
.sale {
font-size: $font-size-tag !important;
color: #999;
}
}
}
@@ -528,9 +543,6 @@
.lineheight-clear {
margin-top: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.discount-price {
@@ -540,26 +552,24 @@
.unit {
margin-right: 6rpx;
color: #ff4444;
color: var(--price-color);
}
.price {
color: #ff4444;
font-size: 32rpx !important;
font-weight: bold !important;
color: var(--price-color);
}
}
.pro-info {
margin-top: 10rpx;
display: flex;
justify-content: space-between;
margin-top: auto;
align-items: center;
.block-wrap {
flex: 1;
line-height: 1;
margin-right: 20rpx;
.sale {
font-size: $font-size-tag !important;
color: #999;
}
}
}
@@ -634,9 +644,9 @@
}
.buy-btn {
background-color: #ff4444;
color: #fff;
border-radius: 30rpx;
background-color: $base-color;
color: var(--btn-text-color);
border-radius: 50rpx;
font-size: $font-size-tag;
padding: 12rpx 30rpx;
line-height: 1;

View File

@@ -88,7 +88,7 @@
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '立即咨询'
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '询底价'
}}</text>
</view>
@@ -182,7 +182,7 @@
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<view class="discount-price" v-else>
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '立即咨询'
<text class="price price-style large">{{ $lang('Make') ? $lang('Make') : '询底价'
}}</text>
</view>
<view class="member-price-tag"

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,25 @@
<template>
<view class="container" :style="wrapperPageStyle">
<!--自定义导航头部 - 只在微信小程序显示,H5端隐藏 -->
<!-- #ifdef MP-WEIXIN -->
<view class="ai-chat-page" :style="wrapperPageStyle">
<!--自定义导航头部 -->
<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="!showBackButton" class="placeholder"></view>
</view>
<view class="header-center">
<text class="header-subtitle">在线为您服务</text>
</view>
<view class="header-right">
<button class="menu-btn" v-show="showMenuButton" @click="showMenu">
<text class="iconfont icon-ellipsis"></text>
</button>
<!-- 占位元素,确保布局平衡 -->
<view v-show="!showMenuButton" class="placeholder"></view>
</view>
</view>
<!-- #endif -->
<!-- 聊天内容区域 -->
<view class="chat-content"
@@ -36,41 +44,6 @@
@product-view="onProductView"
@input-change="onInputChange" />
</view>
<!-- 设置面板 -->
<view v-if="showSettingsPanel" class="settings-popup">
<view class="settings-mask" @click="hideSettings"></view>
<view class="settings-panel">
<view class="settings-header">
<text class="settings-title">设置</text>
<view class="close-btn" @click="hideSettings">
<text>✕</text>
</view>
</view>
<view class="settings-content">
<view class="setting-item" @click="openAvatarSelector">
<view class="setting-icon">
<text class="iconfont icon-user">📷</text>
</view>
<view class="setting-info">
<text class="setting-label">更换头像</text>
<text class="setting-desc">自定义您的聊天头像</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
<view class="setting-item" @click="openNicknameEditor">
<view class="setting-icon">
<text class="iconfont icon-edit">✏️</text>
</view>
<view class="setting-info">
<text class="setting-label">更换昵称</text>
<text class="setting-desc">自定义您的昵称</text>
</view>
<text class="iconfont icon-right">›</text>
</view>
</view>
</view>
</view>
</view>
</template>
@@ -78,12 +51,12 @@
import { mapGetters, mapMutations } from 'vuex'
import navigationHelper from '@/common/js/navigation';
import { EventSafety } from '@/common/js/event-safety';
import aiChatMessage from './ai-chat-message.vue';
import IMAGE_CONFIG from '@/common/js/image-config.js';
export default {
components: {
aiChatMessage
aiChatMessage,
},
data() {
return {
@@ -94,6 +67,10 @@ export default {
type: 'text',
content: '您好!我是AI智能客服,很高兴为您服务!\n\n我可以帮您:\n• 解答产品相关问题\n• 处理订单问题\n• 提供技术支持\n• 推荐相关商品\n\n请告诉我您需要什么帮助?',
timestamp: Date.now() - 60000,
actions: [
{ type: 'like', icon: 'icon-dianzan1', text: '喜欢', count: 0 },
{ type: 'dislike', icon: 'icon-dianzan1', text: '不喜欢', count: 0 }
]
}
],
@@ -105,19 +82,15 @@ export default {
showCustomNavbar: true, // 是否显示自定义导航栏
showBackButton: false, // 是否显示返回按钮
showMenuButton: true, // 是否显示设置菜单按钮
showSettingsPanel: false, // 是否显示设置面板
// --- 聊天内容区域 ---
scrollViewHeight: '0px',
// --- 键盘高度 ---
keyboardHeight: 0,
// 事件处理器引用(用于清理)
safeEventHandlers: new Map()
}
},
computed: {
...mapGetters([
'globalAIKefuConfig'
@@ -125,10 +98,10 @@ export default {
/// ---- 关于AI客服的配置,支持远程配置 ----
userAvatar() {
return this.globalAIKefuConfig?.userAvatar || this.$util.img(IMAGE_CONFIG.userAvatar)
return this.globalAIKefuConfig?.userAvatar || '/static/images/user-avatar.png'
},
aiAvatar() {
return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar)
return this.globalAIKefuConfig?.aiAvatar || '/static/images/ai-avatar.png'
},
/// ---- others ----
containerHeight() {
@@ -146,67 +119,37 @@ export default {
return ``
},
wrapperChatContentStyle() {
// #ifdef H5
// H5端没有自定义导航栏,不需要paddingTop
return {
height: this.containerHeight,
paddingTop: '0'
paddingTop: this.showCustomNavbar ? '0' : (this.navHeight + 'px')
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序端根据导航栏显示情况决定
return {
height: this.containerHeight,
paddingTop: this.showCustomNavbar ? '0' : (this.navBarHeight + 'px')
}
// #endif
}
},
async onLoad(options) {
// ✅ 修改:取消权限校验,允许所有客服类型访问此页面
// const hasAccess = this.checkAccessPermission();
// if (!hasAccess) {
// // 如果校验失败,不继续初始化
// return;
// }
this. $langConfig.title('AI智能客服');
this.initChat();
await this.initPage(options)
},
async onReady() {
await this.initNavigation();
await this.initNavigation()
},
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);
}
});
},
onHide() {
// 取消监听键盘高度变化
uni.offKeyboardHeightChange();
this.keyboardHeight = 0;
if (this.$refs.chat && this.$refs.chat.updateKeyboardHeight) {
this.$refs.chat.updateKeyboardHeight(0);
}
},
onUnload() {
this.cleanup();
this.cleanup()
},
methods: {
// ========== 安全事件处理 ==========
setupSafeEventListeners() {
// 使用 EventSafety 包装事件处理器
const safeHandlers = {
serviceRequest: EventSafety.wrapEventHandler(
this.handleServiceRequest.bind(this),
@@ -222,41 +165,49 @@ export default {
)
}
this. $on('service.requestComponentInfo', safeHandlers.serviceRequest);
this. $on('navigation.requestInfo', safeHandlers.navigationRequest);
this. $on('component.interaction', safeHandlers.componentInteraction);
// 注册事件监听
this.$on('service.requestComponentInfo', safeHandlers.serviceRequest)
this.$on('navigation.requestInfo', safeHandlers.navigationRequest)
this.$on('component.interaction', safeHandlers.componentInteraction)
this.safeEventHandlers.set('serviceRequest', safeHandlers.serviceRequest);
this.safeEventHandlers.set('navigationRequest', safeHandlers.navigationRequest);
this.safeEventHandlers.set('componentInteraction', safeHandlers.componentInteraction);
// 保存处理器引用用于清理
this.safeEventHandlers.set('serviceRequest', safeHandlers.serviceRequest)
this.safeEventHandlers.set('navigationRequest', safeHandlers.navigationRequest)
this.safeEventHandlers.set('componentInteraction', safeHandlers.componentInteraction)
},
setupNavigationEvents() {
// 监听窗口大小变化
uni.onWindowResize((res) => {
console.log('窗口大小变化:', res.size);
this.calculateScrollViewHeight();
});
console.log('窗口大小变化:', res.size)
this.calculateScrollViewHeight()
})
},
// ========== 事件处理方法 ==========
async handleServiceRequest(event) {
console.log('处理服务请求:', EventSafety.extractEventData(event));
console.log('处理服务请求:', EventSafety.extractEventData(event))
// 安全地检查事件目标
if (event.matches('.service-component') ||
event.detail?.componentType === 'service') {
await this.processServiceRequest(event);
await this.processServiceRequest(event)
}
},
async handleNavigationRequest(event) {
console.log('处理导航请求:', event.type);
this.emitNavigationInfo(event);
console.log('处理导航请求:', event.type)
// 提供导航信息
this.emitNavigationInfo(event)
},
handleComponentInteraction(event) {
console.log('处理组件交互:', event.detail);
this.processComponentInteraction(event);
console.log('处理组件交互:', event.detail)
// 安全地处理组件交互
this.processComponentInteraction(event)
},
handleEventError(error, event) {
@@ -264,72 +215,74 @@ export default {
error: error.message,
eventType: event?.type,
component: this.$options.name
});
})
this.showError('操作失败,请重试');
this.showError('操作失败,请重试')
},
// ========== 初始化页面 ==========
initChat() {
console.log('AI聊天页面初始化');
// 检查是否有从商品分类传递过来的商品信息
const selectedProduct = uni.getStorageSync('selectedProduct')
if (selectedProduct) {
console.log('检测到选择的商品:', selectedProduct)
// 清除存储的商品信息,避免重复发送
uni.removeStorageSync('selectedProduct')
// 延迟发送商品信息,确保聊天组件已经初始化
setTimeout(() => {
this.sendProductToAI(selectedProduct)
}, 500)
}
async initPage(options = {}) {
this.$langConfig.title('AI智能客服');
this.initChat()
},
// 初始化导航栏相关配置
async initNavigation() {
try {
this.navBarHeight = await navigationHelper.getNavigationHeight(this, {forceRefresh: false});
this.statusBarHeight = navigationHelper.getStatusBarHeight();
this.calculateScrollViewHeight();
this.setupNavigationEvents();
// 获取导航栏高度
this.navBarHeight = await navigationHelper.getNavigationHeight(this, {forceRefresh: false})
// 获取状态栏高度
this.statusBarHeight = navigationHelper.getStatusBarHeight()
// 计算滚动视图高度
this.calculateScrollViewHeight()
// 注册导航相关事件
this.setupNavigationEvents()
} catch (error) {
console.error('初始化导航栏失败:', error);
this.setFallbackNavigationValues();
console.error('初始化导航栏失败:', error)
this.setFallbackNavigationValues()
}
},
// 计算滚动视图高度
calculateScrollViewHeight() {
const safeArea = navigationHelper.getSafeAreaInsets();
const bottomInset = safeArea.bottom || 0;
const inputHeight = 120;
this.scrollViewHeight = `calc(100vh - $ {this.navBarHeight + this.statusBarHeight + inputHeight + bottomInset}px)`;
const safeArea = navigationHelper.getSafeAreaInsets()
const bottomInset = safeArea.bottom || 0
const inputHeight = 120 // 输入区域高度
this.scrollViewHeight = `calc(100vh - ${this.navBarHeight + this.statusBarHeight + inputHeight + bottomInset}px)`
},
// 备用高度设置
setFallbackNavigationValues() {
// #ifdef MP-WEIXIN
this.navBarHeight = 44;
this.statusBarHeight = 20;
this.navBarHeight = 44
this.statusBarHeight = 20
// #endif
// #ifdef H5
this.navBarHeight = 44;
this.statusBarHeight = 0;
this.navBarHeight = 44
this.statusBarHeight = 0
// #endif
// #ifdef APP-PLUS
this.navBarHeight = 88;
this.statusBarHeight = 44;
this.navBarHeight = 88
this.statusBarHeight = 44
// #endif
},
cleanup() {
// 清理事件监听器
this.safeEventHandlers.forEach((handler, eventType) => {
this. $off(eventType, handler);
});
this.safeEventHandlers.clear();
console.log('组件清理完成');
this.$off(eventType, handler)
})
this.safeEventHandlers.clear()
console.log('组件清理完成')
},
showError(message) {
@@ -337,12 +290,19 @@ export default {
title: message,
icon: 'none',
duration: 2000
});
})
},
// 初始化聊天
initChat() {
// 可以在这里加载历史消息
console.log('AI聊天页面初始化')
},
// 返回上一页
goBack() {
uni.navigateBack();
uni.navigateBack()
},
// 显示菜单
@@ -352,66 +312,59 @@ export default {
success: (res) => {
switch (res.tapIndex) {
case 0:
this.clearChat();
break;
this.clearChat()
break
case 1:
this.exportChat();
break;
this.exportChat()
break
case 2:
this.showSettings();
break;
this.showSettings()
break
case 3:
this.showHelp();
break;
this.showHelp()
break
}
}
});
})
},
// 清空聊天
clearChat() {
uni.showModal({
title: '提示',
content: '确定要清空聊天记录吗?',
success: (res) => {
if (res.confirm) {
this. $refs.chat.clearMessages();
this.$refs.chat.clearMessages()
// 重新添加欢迎消息
this.$refs.chat.addMessage({
id: Date.now(),
role: 'ai',
type: 'text',
content: '聊天记录已清空,有什么可以帮助您的吗?',
timestamp: Date.now()
});
})
}
}
});
})
},
// 导出聊天记录
exportChat() {
uni.showToast({
title: '功能开发中',
icon: 'none'
});
})
},
// 显示设置
showSettings() {
this.showSettingsPanel = true;
},
hideSettings() {
this.showSettingsPanel = false;
},
openAvatarSelector() {
this.hideSettings();
this.$refs.chat.openAvatarSelector();
},
openNicknameEditor() {
this.hideSettings();
this.$refs.chat.openNicknameEditor();
uni.navigateTo({
url: '/pages/settings/index'
})
},
// 显示帮助
showHelp() {
const helpMessage = {
id: Date.now(),
@@ -438,18 +391,27 @@ export default {
- **复制**: 长按消息可复制内容
- **转发**: 支持消息转发功能`,
timestamp: Date.now()
};
this. $refs.chat.addMessage(helpMessage);
}
this.$refs.chat.addMessage(helpMessage)
},
// 用户发送消息
onMessageSent(message) {
console.log('用户发送消息:', message);
console.log('用户发送消息:', message)
// 使用AI服务获取回复
// AI聊天组件内部已经集成了AI服务,这里只需要监听事件
},
// AI回复消息
onAIResponse(message) {
console.log('AI回复消息:', message);
console.log('AI回复消息:', message)
// 可以在这里处理AI回复后的逻辑
// 比如记录对话、更新状态等
},
// 生成AI回复
generateAIResponse(userMessage) {
const responses = [
'我理解您的需求,让我为您详细解答。',
@@ -457,9 +419,9 @@ export default {
'根据您的问题,我建议您可以考虑以下几个方面:',
'这个问题很常见,让我为您提供一些解决方案。',
'我明白您的困惑,让我帮您分析一下。'
];
]
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
const randomResponse = responses[Math.floor(Math.random() * responses.length)]
const aiMessage = {
id: Date.now(),
@@ -471,138 +433,115 @@ export default {
{ type: 'like', count: 0 },
{ type: 'dislike', count: 0 }
]
};
}
this. $refs.chat.addMessage(aiMessage);
this. $emit('ai-response', aiMessage);
this.$refs.chat.addMessage(aiMessage)
this.$emit('ai-response', aiMessage)
},
// 操作按钮点击
onActionClick({ action, message }) {
console.log('操作点击:', action, message)
switch (action.type) {
case 'like':
uni.showToast({ title: '感谢您的反馈!', icon: 'success' });
break;
uni.showToast({
title: '感谢您的反馈!',
icon: 'success'
})
break
case 'dislike':
uni.showToast({ title: '我们会改进服务', icon: 'none' });
break;
uni.showToast({
title: '我们会改进服务',
icon: 'none'
})
break
}
},
// 历史消息加载完成
onHistoryLoaded(messages) {
console.log('历史消息加载完成:', messages.length);
console.log('历史消息加载完成:', messages.length)
},
// 文件预览
onFilePreview(message) {
uni.showToast({ title: '打开文件: ' + message.fileName, icon: 'none' });
console.log('文件预览:', message)
uni.showToast({
title: '打开文件: ' + message.fileName,
icon: 'none'
})
},
// 音频播放
onAudioPlay(message) {
console.log('音频播放:', message);
console.log('音频播放:', message)
},
// 音频暂停
onAudioPause(message) {
console.log('音频暂停:', message);
console.log('音频暂停:', message)
},
// 视频播放
onVideoPlay(message) {
console.log('视频播放:', message);
console.log('视频播放:', message)
},
// 视频暂停
onVideoPause(message) {
console.log('视频暂停:', message);
},
sendProductToAI(product) {
if (!this.$refs.chat) {
console.error('聊天组件未初始化')
return
}
const productContent = `我想了解以下商品的详细信息:\n商品名称:${product.goods_name}\n价格:¥${product.price}\n商品ID:${product.goods_id}`
console.log('商品信息已发送到AI客服:', productContent)
// 使用sendMessageWithContent方法发送商品信息
this.$refs.chat.sendMessageWithContent(productContent)
console.log('视频暂停:', message)
},
// 链接打开
onLinkOpen(message) {
console.log('链接打开:', message)
uni.showModal({
title: '打开链接',
content: `确定要打开链接:${message.url} 吗?`,
success: (res) => {
if (res.confirm) {
// #ifdef H5
window.open(message.url, '_blank');
window.open(message.url, '_blank')
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(message.url);
plus.runtime.openURL(message.url)
// #endif
}
}
});
})
},
// 商品查看
onProductView(message) {
uni.showToast({ title: '查看商品: ' + message.title, icon: 'none' });
console.log('商品查看:', message)
uni.showToast({
title: '查看商品: ' + message.title,
icon: 'none'
})
},
// 输入内容变化
onInputChange(value) {
console.log('输入内容:', value);
},
// ✅ 新增:权限校验(核心修复)
checkAccessPermission() {
const servicerConfig = this. $store.state.servicerConfig;
// 如果配置未加载,延迟重试(最多3次,防白屏)
if (!servicerConfig) {
console.warn('【AI客服】servicerConfig 未加载,200ms后重试...');
setTimeout(() => {
this.checkAccessPermission();
}, 200);
return false;
}
let currentType = 'none';
// #ifdef MP-WEIXIN
currentType = servicerConfig?.weapp?.type || 'none';
// #endif
// #ifdef H5
currentType = servicerConfig?.h5?.type || 'none';
// #endif
if (currentType !== 'aikefu') {
uni.showToast({ title: '当前客服类型不支持此页面', icon: 'none' });
setTimeout(() => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
// 回到首页(假设首页是 tab 页)
uni.switchTab({ url: '/pages/index/index' });
}
}, 1500);
return false;
}
return true;
console.log('输入内容:', value)
}
}
}
</script>
<style lang="scss">
/* 引入图标字体 */
@import url('/common/css/iconfont.css');
@import url('@/common/css/iconfont.css');
/* 页面样式 */
.container {
.ai-chat-page {
display: flex;
flex-direction: column;
background-color: white; /* 白色 */
background-color: #f8f8f8;
overflow: hidden;
position: fixed;
top: 48px;
top: 0;
left: 0;
right: 0;
bottom: 0;
@@ -614,8 +553,8 @@ export default {
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background-color: white; /* 白色 */
border-bottom: 1rpx solid #e5e5e5;
background-color: white;
border-bottom: 2rpx solid #eeeeee;
.header-left {
flex: 0 0 auto;
@@ -626,7 +565,7 @@ export default {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #ffffff; /* 白色按钮 */
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
@@ -671,7 +610,7 @@ export default {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background-color: #ffffff; /* 白色按钮 */
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
@@ -695,135 +634,18 @@ export default {
display: flex;
flex-direction: column;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* 底部tabBar占位样式 */
.page-bottom {
width: 100%;
background-color: transparent;
}
/* 设置面板 */
.settings-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
.settings-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(6rpx);
}
.settings-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
border-radius: 24rpx;
padding: 32rpx;
width: calc(100% - 80rpx);
max-width: 520rpx;
box-shadow: 0 8rpx 36rpx rgba(0, 0, 0, 0.15);
box-sizing: border-box;
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
border-bottom: 2rpx solid #f0f4f8;
padding-bottom: 16rpx;
.settings-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.close-btn {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background-color: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #999;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
}
}
}
.settings-content {
display: flex;
flex-direction: column;
gap: 16rpx;
.setting-item {
display: flex;
align-items: center;
padding: 24rpx;
border-radius: 16rpx;
background-color: #f8fafc;
border: 2rpx solid #f0f4f8;
transition: all 0.2s ease;
&:hover {
background-color: #e9edf2;
border-color: #d9e3ed;
}
.setting-icon {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #c4e0ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
flex-shrink: 0;
}
.setting-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.setting-label {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.setting-desc {
font-size: 22rpx;
color: #999;
}
}
.iconfont.icon-right {
font-size: 36rpx;
color: #ccc;
flex-shrink: 0;
}
}
}
}
}
</style>

View File

@@ -248,8 +248,7 @@ export default {
latitude: 0,
longitude: 0,
},
personnel_bg: '',
mpShareData: null //小程序分享数据
personnel_bg: ''
};
},
onLoad(option) {
@@ -304,24 +303,6 @@ export default {
});
},
onShow() {
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_tool/contact/contact').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '联系我们',
path: '/pages_tool/contact/contact'
},
timeLine: {
title: '联系我们',
query: ''
}
};
});
// #endif
this.$util.hideHomeButton();
},
methods: {
@@ -468,28 +449,9 @@ export default {
longitude: parseFloat(this.shop.longitude),
name: this.$lang('oneClickNavigation'),
});
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '联系我们',
path: '/pages_tool/contact/contact'
}
}
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '联系我们',
query: ''
};
},
onAddToFavorites() {
return {
title: '联系我们',
path: '/pages_tool/contact/contact'
};
},
},
};
</script>
<style lang="scss">

View File

@@ -134,8 +134,7 @@ export default {
levelId: 0,
levelInfo: {
bg_color: '#333'
},
mpShareData: null //小程序分享数据
}
};
},
onLoad() {
@@ -162,26 +161,7 @@ export default {
});
this.levelInfo = levelInfo;
},
onShow() {
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare('/pages_tool/member/card').then(res => {
this.mpShareData = res;
}).catch(err => {
// 如果后台没配置,用默认值
this.mpShareData = {
appMessage: {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
},
timeLine: {
title: '专属会员权益,快来体验',
query: '/pages_tool/member/card'
}
};
});
// #endif
},
onShow() { },
methods: {
headimgError() {
this.memberInfo.headimg = this.$util.getDefaultImage().head;
@@ -205,24 +185,6 @@ export default {
}
this.$util.redirectTo(this.$util.MEMBER_PAGE_URL);
return true;
},
onShareAppMessage() {
return this.mpShareData?.appMessage || {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
};
},
onShareTimeline() {
return this.mpShareData?.timeLine || {
title: '专属会员权益,快来体验',
query: '/pages_tool/member/card'
};
},
onAddToFavorites() {
return {
title: '专属会员权益,快来体验',
path: '/pages_tool/member/card'
};
}
};
</script>

View File

@@ -7,7 +7,7 @@
@click.stop="toDetail(item)">
<view class="pic">
<image :src="$util.img(item.goods_image.split(',')[0], { size: 'mid' })" mode="aspectFill"
@error="goodsImageError(index)" :show-menu-by-longpress="true"></image>
@error="goodsImageError(index)"></image>
</view>
<view class="goods_info">
<view class="goods_name font-size-base">{{ item.sku_name }}</view>

View File

@@ -13,7 +13,7 @@
<text class="iconfont" :class="$util.inArray(item.id, idArr) != -1 ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></text>
</view>
<view class="goods-img" @click="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" :show-menu-by-longpress="true"></image>
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"></image>
<view class="color-base-bg goods-tag" v-if="goodsTag(item) != ''">{{ goodsTag(item) }}</view>
</view>
<view class="info-wrap" @click="toDetail(item)">

View File

@@ -37,7 +37,7 @@
*已有依赖可跳过此步骤*
##### 4.1.1.2 【发布构建】
1. 使用 HBuilderX 打开项目(不用改manifest.json的web,只有H5要改 )
1. 使用 HBuilderX 打开项目
2. 选择菜单栏「发行」 → 「小程序-微信」→ 「发行」,等待构建完成
⚠️ **注意**:底部控制台弹出"请在微信小程序开发者工具中点击上传"后再执行下一步
3. 打开资源管理器→项目根目录,右键选择「在终端中打开」,执行命令:
@@ -67,9 +67,7 @@
- 确认 `site.js` 中的 `uniacid` 为客户编号
- 用微信开发者工具导入解压后的代码目录
- 编译验证无误后,上传发布
##### 4.1.1.5 【本地设置】
![](readme_files/1.jpg)
#####
---
### 4.2 快应用发布
@@ -80,22 +78,20 @@
1. 使用 HBuilderX 打开项目
2. 打开项目根目录的 `manifest.json` 文件,切换至可视化配置界面:
1. 依次点击「Web 配置」→「运行的基础配置」
2. 根据情况查看是否修改路径中/hwappx/????/的客户编号
📌 **通用版**保留原有 `/hwappx/common/`
📌 **测试版**保留原有 `/hwappx/test/`
💡 **个性化定制示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
2. 修改路径中的客户编号
3. 📌 **通用版**保留原有 `/hwappx/common/`
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. 修改以下配置:
💡 **示例**:`/hwappx/2811/`,其中 2811 为定制化版客户编号
3. 选择菜单栏「发行」 → 「自定义发行」 →「H5-xcx.aigc-quickapp.com」,修改以下配置:
1. **网站标题**:快应用
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com或者5g.aigc-quickapp.com)
2. **网站域名**:当前客户域名(示例:xcx.aigc-quickapp.com)
3. 确认后点击「发行」等待构建完成
⚠️ **注意**:底部控制台弹出“项目 lucky_shop 导出Web成功,路径为:D:\项目文件\项目根目录\unpackage\dist\build\web”后再执行下一步
💡 **示例**:`项目 lucky_shop 导出Web成功,路径为:D:\0.项目源码\lucky_shop\unpackage\dist\build\web`
5. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
4. 按控制台提示的路径找到 `web` 目录,将该目录下所有文件手动打包成一个 `.zip` 压缩包
*仅打包文件,不包含外层 web 目录*
##### 4.2.1.2 【重命名】
@@ -109,15 +105,11 @@
🚫 **禁止**:压缩包命名禁止包含 `/ \ : * ? " < > |` 等特殊字符
##### 4.2.1.3 【交付与最终发布】🔍
1. 将重命名后的压缩包发送给运维人员
2. **运维人员操作流程**:
1. 解压压缩包
2. 打开快应用开发者工具,导入解压后的代码目录
3. 验证代码无报错后,执行上传发布操作
##### 4.2.1.3 【测试流程】
H5:打开自己的浏览器输入网址(https://5g.aigc-quickapp.com/hwappx/common/?uniacid=75)改站点
如果是test就把hwappx/common/改为hwappx/test/----------改站点
---

View File

@@ -134,9 +134,8 @@ async function main() {
// 2) 创建 ZIP 文件(如果未指定 --no-zip)
if (!options.noZip) {
const cwd = path.join(__dirname, '..');
const buildDir = options.mode === 'production' ? 'build' : 'dev';
const sourceDir = path.join(cwd, 'unpackage', 'dist', buildDir, 'mp-weixin');
const destDir = path.join(cwd, 'unpackage', 'dist', buildDir);
const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
console.log(`ZIP 文件路径: ${zipFilePath}`);

View File

@@ -1,4 +1,4 @@
module.exports = {
baseUrl: "https://xcx6.aigc-quickapp.com/",//修改域名
uniacid: 2847,//后台对应uniacid
uniacid: 2811,//后台对应uniacid
};

View File

@@ -65,7 +65,6 @@ const store = new Vuex.Store({
bottomNavHidden: false, // 底部导航是否隐藏,true:隐藏,false:显示
aiUnreadCount: 10, // AI未读消息数量
globalAIKefuConfig: null, // AI客服配置
customerServiceType: 'ai',
globalStoreConfig: null, // 门店配置
globalStoreInfo: null, // 门店信息
defaultStoreInfo: null, // 默认门店
@@ -160,9 +159,6 @@ const store = new Vuex.Store({
state.globalAIKefuConfig = value;
uni.setStorageSync('globalAIKefuConfig', value); // 初始化数据调用
},
setCustomerServiceType(state, type) {
state.customerServiceType = type;
},
setGlobalStoreConfig(state, value) {
state.globalStoreConfig = value;
uni.setStorageSync('globalStoreConfig', value); // 初始化数据调用