Files
lucky_shop/App.vue
2025-12-05 16:47:53 +08:00

463 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import auth from 'common/js/auth.js';
import colorList from 'common/js/style_color.js'
import { Weixin } from 'common/js/wx-jssdk.js';
export default {
mixins: [auth],
onLaunch: function(options) {
// 处理uniacid存储
if(options.query.uniacid){
uni.setStorageSync('uniacid', options.query.uniacid);
}
uni.hideTabBar();
// #ifdef MP
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(() => {});
updateManager.onUpdateReady((res) => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) updateManager.applyUpdate();
}
});
});
updateManager.onUpdateFailed(() => {});
// #endif
// #ifdef H5
if (uni.getSystemInfoSync().platform == 'ios') {
uni.setStorageSync('initUrl', location.href);
}
this.$nextTick(() => {
this.createIndependentChatbot();
});
// #endif
// 网络状态监听
uni.onNetworkStatusChange((res) => {
if (!res.isConnected) {
uni.showModal({
title: '网络失去链接',
content: '请检查网络链接',
showCancel: false
});
}
});
// 初始化store
this.$store.dispatch('init');
// 批量同步store数据
const storeKeys = [
{key: 'themeStyle', mutation: 'setThemeStyle', handler: (v) => colorList[v]},
{key: 'addonIsExist', mutation: 'setAddonIsExist'},
{key: 'defaultImg', mutation: 'setDefaultImg'},
{key: 'siteInfo', mutation: 'setSiteInfo'},
{key: 'globalStoreConfig', mutation: 'setGlobalStoreConfig'},
{key: 'globalStoreInfo', mutation: 'setGlobalStoreInfo'},
{key: 'defaultStoreInfo', mutation: 'setDefaultStoreInfo'},
{key: 'servicerConfig', mutation: 'setServicerConfig'},
{key: 'copyright', mutation: 'setCopyright'},
{key: 'mapConfig', mutation: 'setMapConfig'},
{key: 'token', mutation: 'setToken'},
{key: 'memberInfo', mutation: 'setMemberInfo'}
];
storeKeys.forEach(item => {
const value = uni.getStorageSync(item.key);
if (value) {
const data = item.handler ? item.handler(value) : value;
this.$store.commit(item.mutation, data);
}
});
// 会员信息存在时同步购物车
if (uni.getStorageSync('memberInfo')) {
this.$store.dispatch('getCartNumber');
}
// #ifdef H5
if (!uni.getStorageSync('memberInfo')) {
this.getAuthInfo();
}
if (this.$store.state.token) {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 0) {
this.$store.commit('setMemberInfo', res.data);
}
}
});
}
// #endif
// #ifdef MP-ALIPAY
if (options.query?.m) uni.setStorageSync('source_member', options.query.m);
// #endif
},
onShow: function(options) {
// #ifdef MP
this.getAuthInfo();
if (this.$store.state.token) {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 10) {
this.$store.commit('setMemberInfo', res.data);
}
}
});
}
// #endif
// #ifdef MP-ALIPAY
if (options.query?.m) uni.setStorageSync('source_member', options.query.m);
// #endif
},
onHide: function() {},
methods: {
// 安全的 HTML 转义(防止 XSS
htmlEncode(str) {
if (typeof str !== 'string') return '';
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
},
createIndependentChatbot() {
console.log('创建响应式智能客服窗口...');
// 清除已存在的元素
document.querySelectorAll('[id^="independent-chat-"]').forEach(el => el.remove());
// ========== 按钮 ==========
const chatBtn = document.createElement('div');
chatBtn.id = 'independent-chat-btn';
chatBtn.innerHTML = '💬';
chatBtn.style.cssText = `
width: 56px;
height: 56px;
border-radius: 50%;
position: fixed;
bottom: calc(180px + env(safe-area-inset-bottom, 0px));
right: 0px;
z-index: 999999;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
background: linear-gradient(135deg, #ff9a9e, #fad0c4, #a1c4fd, #c2e9fb, #d4fc79, #96e6a1);
background-size: 300% 300%;
animation: rainbowPulse 4s ease infinite;
user-select: none;
-webkit-user-drag: none;
`;
document.body.appendChild(chatBtn);
// ========== 聊天窗口 ==========
const chatWindow = document.createElement('div');
chatWindow.id = 'independent-chat-window';
chatWindow.style.cssText = `
position: fixed;
z-index: 999998;
border-radius: 12px;
background: white;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
display: none;
flex-direction: column;
overflow: hidden;
`;
document.body.appendChild(chatWindow);
// 头部
const chatHeader = document.createElement('div');
chatHeader.style.cssText = `
height: 50px;
background: #1C64F2;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
font-size: 18px;
font-weight: 600;
`;
chatHeader.innerHTML = `<span>智能客服</span><span id="close-chat-window" style="cursor:pointer;font-size:24px;">×</span>`;
chatWindow.appendChild(chatHeader);
// 内容区
const chatContent = document.createElement('div');
chatContent.style.cssText = `
flex: 1;
padding: 16px;
overflow-y: auto;
background: #f9fafb;
`;
chatContent.innerHTML = `
<div style="display:flex;margin-bottom:16px;">
<div style="width:36px;height:36px;border-radius:50%;background:#1C64F2;color:white;display:flex;align-items:center;justify-content:center;margin-right:8px;">🤖</div>
<div style="background:white;padding:8px 12px;border-radius:8px;max-width:70%;">您好!有什么可以帮到您的吗?</div>
</div>
`;
chatWindow.appendChild(chatContent);
// 输入区
const chatInputArea = document.createElement('div');
chatInputArea.style.cssText = `
height: 60px;
display: flex;
align-items: center;
padding: 0 16px;
border-top: 1px solid #eee;
`;
const chatInput = document.createElement('input');
chatInput.type = 'text';
chatInput.placeholder = '请输入您的问题...';
chatInput.style.cssText = `
flex: 1;
height: 36px;
padding: 0 12px;
border: 1px solid #ddd;
border-radius: 18px;
outline: none;
font-size: 14px;
`;
const sendBtn = document.createElement('button');
sendBtn.innerText = '发送'; // 横排文字
sendBtn.style.cssText = `
margin-left: 12px;
padding: 6px 16px;
background: #1C64F2;
color: white;
border: none;
border-radius: 18px;
cursor: pointer;
font-size: 14px;
white-space: nowrap; /* 防止换行 */
text-align: center; /* 居中 */
line-height: 1.5; /* 调整高度 */
`;
chatInputArea.appendChild(chatInput);
chatInputArea.appendChild(sendBtn);
chatWindow.appendChild(chatInputArea);
// ========== 响应式尺寸控制 ==========
const updateWindowSize = () => {
const isMobile = window.innerWidth <= 768;
if (isMobile) {
chatWindow.style.width = 'calc(90vw - 40px)';
chatWindow.style.maxWidth = '400px';
chatWindow.style.height = '70vh';
chatWindow.style.maxHeight = '600px';
chatWindow.style.right = '5vw';
chatWindow.style.bottom = 'calc(86px + env(safe-area-inset-bottom, 0px))';
} else {
chatWindow.style.width = '360px';
chatWindow.style.height = '520px';
chatWindow.style.maxWidth = 'none';
chatWindow.style.maxHeight = 'none';
chatWindow.style.right = '20px';
chatWindow.style.bottom = '86px';
}
chatContent.style.maxHeight = 'calc(100% - 110px)';
};
// 初始化并监听窗口变化
updateWindowSize();
window.addEventListener('resize', updateWindowSize);
// ========== 交互逻辑 ==========
let isShow = false;
const toggleChat = () => {
isShow = !isShow;
chatWindow.style.display = isShow ? 'flex' : 'none';
if (isShow) {
updateWindowSize(); // 确保旋转/切换后尺寸正确
chatInput.focus();
}
};
chatBtn.onclick = toggleChat;
document.getElementById('close-chat-window').onclick = () => {
isShow = false;
chatWindow.style.display = 'none';
};
document.addEventListener('click', (e) => {
if (!chatBtn.contains(e.target) && !chatWindow.contains(e.target)) {
isShow = false;
chatWindow.style.display = 'none';
}
});
// 发送消息
const sendMessage = () => {
const msg = chatInput.value.trim();
if (!msg) return;
const safeMsg = this.htmlEncode(msg);
chatContent.insertAdjacentHTML('beforeend', `
<div style="display:flex;margin-bottom:16px;justify-content:flex-end;">
<div style="background:#1C64F2;color:white;padding:8px 12px;border-radius:8px;max-width:70%;">
${safeMsg}
</div>
<div style="width:36px;height:36px;border-radius:50%;background:#eee;display:flex;align-items:center;justify-content:center;margin-left:8px;">👤</div>
</div>
`);
chatInput.value = '';
chatContent.scrollTop = chatContent.scrollHeight;
setTimeout(() => {
chatContent.insertAdjacentHTML('beforeend', `
<div style="display:flex;margin-bottom:16px;">
<div style="width:36px;height:36px;border-radius:50%;background:#1C64F2;color:white;display:flex;align-items:center;justify-content:center;margin-right:8px;">🤖</div>
<div style="background:white;padding:8px 12px;border-radius:8px;max-width:70%;">
已收到您的问题:"${safeMsg}",我们会尽快回复!
</div>
</div>
`);
chatContent.scrollTop = chatContent.scrollHeight;
}, 800);
};
sendBtn.onclick = sendMessage;
chatInput.onkeydown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
sendMessage();
}
};
console.log('响应式智能客服窗口创建完成');
},
getAuthInfo() {
// #ifdef H5
if (this.$util.isWeiXin()) {
this.$util.getUrlCode(urlParams => {
if (urlParams.source_member) uni.setStorageSync('source_member', urlParams.source_member);
if (urlParams.code === undefined) {
this.$api.sendRequest({
url: '/wechat/api/wechat/authcode',
data: { redirect_url: location.href, scopes: 'snsapi_userinfo' },
success: (res) => res.code >= 0 && (location.href = res.data)
});
} else {
this.$api.sendRequest({
url: '/wechat/api/wechat/authcodetoopenid',
data: { code: urlParams.code },
success: (res) => {
if (res.code >= 0) {
const data = {};
res.data.openid && (data.wx_openid = res.data.openid);
res.data.unionid && (data.wx_unionid = res.data.unionid);
res.data.userinfo && Object.assign(data, res.data.userinfo);
this.authLogin(data);
}
}
});
}
});
}
// #endif
// #ifdef MP
this.getCode(data => this.authLogin(data, 'authOnlyLogin'));
// #endif
},
authLogin(data, type = 'authLogin') {
uni.getStorageSync('source_member') && (data.source_member = uni.getStorageSync('source_member'));
uni.setStorageSync('authInfo', data);
this.$api.sendRequest({
url: type === 'authLogin' ? '/api/login/auth' : '/api/login/authonlylogin',
data,
success: (res) => {
if (res.code >= 0) {
this.$store.commit('setToken', res.data.token);
this.getMemberInfo();
this.$store.dispatch('getCartNumber');
}
}
});
},
shareConfig() {
this.$api.sendRequest({
url: '/wechat/api/wechat/share',
data: { url: window.location.href },
success: (res) => {
if (res.code === 0) {
const wxJS = new Weixin();
wxJS.init(res.data.jssdk_config);
const share_data = JSON.parse(JSON.stringify(res.data.share_config.data));
share_data && wxJS.setShareData({
title: share_data.title,
desc: share_data.desc,
link: share_data.link,
imgUrl: this.$util.img(share_data.imgUrl)
}, res => console.log(res));
res.data.share_config.permission.hideOptionMenu
? wxJS.weixin.hideOptionMenu()
: wxJS.weixin.showOptionMenu();
}
}
});
},
getMemberInfo() {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => res.code >= 0 && this.$store.commit('setMemberInfo', res.data)
});
}
},
watch: {
$route: {
handler(newName, oldName) {
this.$util.isWeiXin() && this.shareConfig();
},
immediate: true
}
}
};
</script>
<style lang="scss">
@import './common/css/main.scss';
@import './common/css/iconfont.css';
@import './common/css/icondiy.css';
@import './common/css/icon/extend.css';
page {
background: #f4f6fa;
}
body {
padding-bottom: 80px !important;
}
/* 聊天窗口滚动条美化 */
#independent-chat-window div::-webkit-scrollbar {
width: 4px;
}
#independent-chat-window div::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 2px;
}
/* 彩虹动画 */
@keyframes rainbowPulse {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* 强制固定定位,防止被其他样式干扰 */
#independent-chat-btn,
#independent-chat-window {
position: fixed !important;
}
</style>