Compare commits
205 Commits
xindeznkf
...
test/hw-se
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cfd2db49e | |||
| 2342a14f2e | |||
| 36b0c22e47 | |||
| 4a52b9c229 | |||
| 9ec90a906f | |||
| e125a5f39c | |||
| 8c0ec46dd8 | |||
| b65ec35d3c | |||
| d113d6125b | |||
| 2f24661cd9 | |||
| 50908a274b | |||
| 9e68562de5 | |||
| 8e5561c2a7 | |||
| 8d5c543a4f | |||
| cf411407dd | |||
| 86e4be302b | |||
| 2fd06796e1 | |||
| 60c51e551a | |||
| d0d3ac47c6 | |||
| af677f6565 | |||
| f889efeb16 | |||
| d026bb6180 | |||
| 82b856f5d9 | |||
| 7f33b6948a | |||
| 3489b89a0b | |||
| dc4f2769f9 | |||
| 5cf87e4d56 | |||
| c9ca63cff5 | |||
| a6072f4895 | |||
| 38947ddc62 | |||
| cfe83ada38 | |||
| 1e9e0ffea8 | |||
| 28acdfb621 | |||
| ad9f1c495b | |||
| 7c2a7468e7 | |||
| b2051dcf92 | |||
| e3c86de84a | |||
| 6e0eb5d4f3 | |||
| 9d186ff094 | |||
| 8611ec4dc3 | |||
| 89bfd94a07 | |||
| 5cf8b7b467 | |||
| 2cc0162435 | |||
| d8e75a0c9e | |||
| 8af3c0ccb9 | |||
| d8b78168a8 | |||
| 7675e8e04d | |||
| 88d05048a6 | |||
| 28a92c0635 | |||
| b5a47b8b4e | |||
| 4f99e8b5f2 | |||
| 87fc49146c | |||
| 826fcfb024 | |||
| 7174d87b79 | |||
| 4bc747cd9c | |||
| e046868665 | |||
| 19c3ec4f9f | |||
| ffbc15929d | |||
| 524280d816 | |||
| 6e736e0733 | |||
| 4891566793 | |||
| b02f5fbc21 | |||
| 4f5a300afa | |||
| aae6e76797 | |||
| 3162d6a9f2 | |||
| 8010ac45a6 | |||
| baebba4cef | |||
| 1be7155fe8 | |||
| 611c0b930b | |||
| ec1903519d | |||
| b1953a020d | |||
| 3ce26322e0 | |||
| 588ec60948 | |||
| 962ea124fa | |||
| 0ca0fe3b0d | |||
| cef2b6e02e | |||
| e018f9a8b9 | |||
| def8a6cf6f | |||
| 857f295923 | |||
| 50ee885b74 | |||
| 410f248510 | |||
| ba5fcfaab1 | |||
| 5a46bb8d69 | |||
| e33ad16d10 | |||
| d8ba7ace0f | |||
| 875072bb68 | |||
| 0912b9b031 | |||
| 7ffe4acc0f | |||
| 4bcf0cbd2d | |||
| fdf85dfec7 | |||
| 1d62b37060 | |||
| 4ad93ceace | |||
| 294856d9ef | |||
| ba2c7d6d64 | |||
| ec9e76fda7 | |||
| 5dba8071e4 | |||
| 19a53ef414 | |||
| e943b11c8f | |||
| e9db54980e | |||
| 3ddcc665b8 | |||
| a981c02baf | |||
| 1c3f759b35 | |||
| bb58866192 | |||
| 069b3c2c07 | |||
| c6fd13956a | |||
| a489e24988 | |||
| 6897fc631b | |||
| 6d23a64aef | |||
| f34533b0c7 | |||
| 72c74f44f0 | |||
| 7f3d9c0f10 | |||
| 37f95e7267 | |||
| d3c630e860 | |||
| c5b57cedeb | |||
| bf24746a7c | |||
| 5ff2925ba1 | |||
| 3dc81e7f53 | |||
| 0417c5c5ed | |||
| db26a85c8b | |||
| 54c39259a7 | |||
| 15361211b1 | |||
| e301ddc6ec | |||
| a560ad4a5d | |||
| 7f7a18803f | |||
| 42563d7184 | |||
| 14a903f42a | |||
| c9f8614927 | |||
| fe9303bf4c | |||
| e4dfd0ae11 | |||
| 7037d0b083 | |||
| e8a79bd245 | |||
| 047cf765da | |||
| 3a30b0817d | |||
| a464a8731f | |||
| 7599a80943 | |||
| 992a01cc76 | |||
| dd09c66215 | |||
| e2b89c348f | |||
| 475edc93a6 | |||
| cb86cba389 | |||
| 0dc4dec616 | |||
| 2a5214df11 | |||
| 09e410df00 | |||
|
|
75dfe80bde | ||
|
|
2a1f33323c | ||
|
|
b441c46993 | ||
| 38ade75046 | |||
| 1e51abd7cd | |||
| 0939449aa7 | |||
| 29b5cfda6f | |||
| ceca4e5956 | |||
| aa9d2e64d2 | |||
| e8ccb87266 | |||
| 153c84266a | |||
| 7ae7a1d3bd | |||
| 1ebb94e9e2 | |||
| 9b53540f91 | |||
| 2a36fa2b60 | |||
| 2223636184 | |||
| 6144dc72b8 | |||
| 47e1c2372d | |||
| 5b9bef9214 | |||
| 08880a15df | |||
| e40e6e73b2 | |||
| dd4176998b | |||
| 2896817435 | |||
| dc956761a0 | |||
| 36fd0621fd | |||
| 28359f2f16 | |||
| b3cc86d088 | |||
| 0ed0789528 | |||
| 16d1a3a945 | |||
| 392a8e2952 | |||
| db7a8d7202 | |||
| bb87f4b1d2 | |||
| fe039ed126 | |||
| 03c1a8d71a | |||
| 9e0cab7ec4 | |||
| a31c004508 | |||
| ee8c24cdf8 | |||
| b043601a77 | |||
| e20968aee2 | |||
| 8ceeca33b4 | |||
| 02e9cfa313 | |||
| 3f894bcb20 | |||
| 8d5d6e3da8 | |||
| ce1ea6eadf | |||
| 191c803f66 | |||
| 4e1448d2c4 | |||
| ce26a9be5f | |||
| 8b01d8f8ec | |||
| 6ab2834fdc | |||
| 85816e416c | |||
| bf8e177c30 | |||
| 57111a00c2 | |||
| aa7c7cbe60 | |||
| d012c7fff7 | |||
| 128d75b921 | |||
| 7e0d61c7e5 | |||
| 26af083839 | |||
| 68bc853dff | |||
| 13166132c7 | |||
| f87c7d963e | |||
| 6f6899deaa | |||
| 7f54666629 |
@@ -38,6 +38,22 @@ const localDevConfig = ({
|
||||
uniacid: 1,
|
||||
domain: 'https://test.aigc-quickapp.com',
|
||||
},
|
||||
})['2811']; // 选择要使用的环境配置
|
||||
'local-2': { // 测试平台
|
||||
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']; // 选择要使用的环境配置
|
||||
|
||||
export default localDevConfig;
|
||||
@@ -18,6 +18,10 @@ const localDevConfig = ({
|
||||
uniacid: 2811,
|
||||
domain: 'https://xcx6.aigc-quickapp.com/',
|
||||
},
|
||||
'2812': { // IVD数商模式
|
||||
uniacid: 2812,
|
||||
domain: 'https://xcx6.aigc-quickapp.com/',
|
||||
},
|
||||
'2724': { // 生物菌肥
|
||||
uniacid: 2724,
|
||||
domain: 'https://xcx.aigc-quickapp.com/',
|
||||
@@ -38,6 +42,14 @@ const localDevConfig = ({
|
||||
uniacid: 1,
|
||||
domain: 'https://test.aigc-quickapp.com',
|
||||
},
|
||||
})['2811']; // 选择要使用的环境配置
|
||||
'local-2': { // 测试平台
|
||||
uniacid: 2,
|
||||
domain: 'http://localhost:8050/',
|
||||
},
|
||||
'local-2-dev': { // 本地开发测试平台
|
||||
uniacid: 2,
|
||||
domain: 'http://localhost:8050/',
|
||||
},
|
||||
})['2812']; // 选择要使用的环境配置
|
||||
|
||||
export default localDevConfig;
|
||||
2
App.vue
2
App.vue
@@ -314,7 +314,7 @@
|
||||
@import './common/css/iconfont.css';
|
||||
@import './common/css/icondiy.css'; // 自定义图标库
|
||||
@import './common/css/icon/extend.css'; // 扩展图标库
|
||||
page{
|
||||
page {
|
||||
background: #f4f6fa;
|
||||
}
|
||||
</style>
|
||||
@@ -123,7 +123,7 @@ image {
|
||||
}
|
||||
|
||||
.choose-store {
|
||||
/deep/ .uni-popup__wrapper{
|
||||
::v-deep .uni-popup__wrapper{
|
||||
background: none!important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,13 +870,13 @@
|
||||
// 海报
|
||||
// .uni-popup__wrapper-box
|
||||
.poster-layer {
|
||||
/deep/ .uni-popup__wrapper.center {
|
||||
::v-deep .uni-popup__wrapper.center {
|
||||
width: 100vw!important;
|
||||
height: 100vh!important;
|
||||
background: none!important;
|
||||
}
|
||||
|
||||
/deep/ .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
|
||||
max-width: 100vw!important;
|
||||
max-height: 100vh!important;
|
||||
background: none!important;
|
||||
|
||||
@@ -559,7 +559,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
@@ -609,7 +609,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
font-weight: 500!important;
|
||||
}
|
||||
|
||||
/deep/ .reward-popup .uni-popup__wrapper-box {
|
||||
::v-deep .reward-popup .uni-popup__wrapper-box {
|
||||
background: none !important;
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
@@ -618,7 +618,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
|
||||
// #ifdef H5
|
||||
// 下拉加载动画【页面】
|
||||
/deep/ body uni-page-refresh div{
|
||||
::v-deep body uni-page-refresh div{
|
||||
width: 14rpx !important;
|
||||
height: 14rpx !important;
|
||||
background-color: #ccc;
|
||||
@@ -626,7 +626,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
clip: rect(-152rpx, 90rpx, 90rpx, -30rpx) !important;
|
||||
animation:.6s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body uni-page-refresh div::after{
|
||||
::v-deep body uni-page-refresh div::after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -22rpx;
|
||||
@@ -636,7 +636,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
background-color: #ccc;
|
||||
animation:.5s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body uni-page-refresh div::before{
|
||||
::v-deep body uni-page-refresh div::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -22rpx;
|
||||
@@ -646,15 +646,15 @@ scroll-view ::-webkit-scrollbar {
|
||||
background-color: #ccc;
|
||||
animation:.7s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body uni-page-refresh > div > div{
|
||||
::v-deep body uni-page-refresh > div > div{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// 下拉加载动画【scroll-view】
|
||||
/deep/ body .uni-scroll-view-refresher{
|
||||
::v-deep body .uni-scroll-view-refresher{
|
||||
background-color: transparent !important;
|
||||
}
|
||||
/deep/ body .uni-scroll-view-refresher div{
|
||||
::v-deep body .uni-scroll-view-refresher div{
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@@ -666,7 +666,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
clip: rect(-152rpx, 90rpx, 90rpx, -30rpx) !important;
|
||||
animation:.6s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body .uni-scroll-view-refresher div::after{
|
||||
::v-deep body .uni-scroll-view-refresher div::after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -22rpx;
|
||||
@@ -676,7 +676,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
background-color: #ccc;
|
||||
animation:.5s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body .uni-scroll-view-refresher div::before{
|
||||
::v-deep body .uni-scroll-view-refresher div::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -22rpx;
|
||||
@@ -686,7 +686,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
background-color: #ccc;
|
||||
animation:.7s backgroundChange linear infinite;
|
||||
}
|
||||
/deep/ body .uni-scroll-view-refresher > div > div{
|
||||
::v-deep body .uni-scroll-view-refresher > div > div{
|
||||
display: none !important;
|
||||
}
|
||||
@keyframes backgroundChange {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 修复图片垂直对齐问题,解决两张图片上下有空白缝隙问题
|
||||
/deep/ ._img {
|
||||
::v-deep ._img {
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -377,7 +377,7 @@ view {
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .goods-form {
|
||||
::v-deep .goods-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
@@ -1354,7 +1354,7 @@ view {
|
||||
border-bottom: 2rpx solid #F4F4F6;
|
||||
}
|
||||
|
||||
/deep/ .form-wrap {
|
||||
::v-deep .form-wrap {
|
||||
margin: 0 24rpx;
|
||||
|
||||
.icon-right {
|
||||
|
||||
@@ -5,6 +5,17 @@ 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);
|
||||
@@ -16,12 +27,28 @@ 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: {
|
||||
@@ -31,28 +58,38 @@ export default {
|
||||
query: message,
|
||||
response_mode: 'blocking', // 强制 blocking
|
||||
user: store.state.memberInfo?.id || 'anonymous',
|
||||
conversation_id: this.getConversationId() || ''
|
||||
conversation_id: this.getConversationId() || '',
|
||||
...getDomainParams()
|
||||
},
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Sending blocking request to:', params.url);
|
||||
console.log('Request data:', params.data);
|
||||
console.log('[AI Service] 发送请求到:', params.url);
|
||||
console.log('[AI Service] 请求参数:', JSON.stringify(params.data, null, 2));
|
||||
|
||||
const response = await http.sendRequest({
|
||||
...params,
|
||||
async: false
|
||||
});
|
||||
|
||||
console.log('[AI Service] 收到响应:', response);
|
||||
|
||||
const result = this.handleResponse(response);
|
||||
if (result.conversationId) {
|
||||
this.setConversationId(result.conversationId);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Dify API请求失败:', 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
|
||||
});
|
||||
throw new Error('AI服务暂时不可用,请稍后重试');
|
||||
}
|
||||
},
|
||||
@@ -62,53 +99,110 @@ export default {
|
||||
*/
|
||||
async sendStreamMessage(message, onChunk, onComplete) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序:降级为普通请求 + 前端打字模拟
|
||||
try {
|
||||
const result = await this.sendMessage(message);
|
||||
const content = result.content || '';
|
||||
const conversationId = result.conversationId || '';
|
||||
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: {}
|
||||
});
|
||||
|
||||
// 保存会话ID(确保连续对话)
|
||||
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);
|
||||
|
||||
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 || '';
|
||||
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: content,
|
||||
conversation_id: conversationId
|
||||
});
|
||||
onComplete({ content, conversation_id: conversationId });
|
||||
}
|
||||
resolve({ content, conversation_id: conversationId });
|
||||
socketTask.close();
|
||||
}
|
||||
|
||||
// 可选:处理 done
|
||||
else if (data.type === 'done') {
|
||||
console.log('对话完成:', data);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data);
|
||||
}
|
||||
}, 80); // 打字速度:80ms/次
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('小程序流式消息降级失败:', error);
|
||||
if (onComplete) {
|
||||
onComplete({ error: error.message || '发送失败' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5:使用真实流式(EventSource / Fetch)
|
||||
return this.sendHttpStream(message, onChunk, onComplete);
|
||||
// #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);
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* HTTP 流式请求(仅 H5 使用)
|
||||
*/
|
||||
@@ -123,20 +217,19 @@ export default {
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uniacid: store.state.uniacid || '1',
|
||||
uniacid: this.getUniacid(),
|
||||
token: store.state.token || 'test_token',
|
||||
inputs: {},
|
||||
query: message,
|
||||
response_mode: 'streaming',
|
||||
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||
conversation_id: this.getConversationId() || ''
|
||||
conversation_id: this.getConversationId() || '',
|
||||
...getDomainParams()
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
if (!response.body) {
|
||||
throw new Error('响应体不可用');
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error('无效响应');
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
@@ -145,50 +238,64 @@ export default {
|
||||
let content = '';
|
||||
let conversationId = '';
|
||||
|
||||
function processBuffer(buf, callback) {
|
||||
const lines = buf.split('\n');
|
||||
buf = lines.pop() || '';
|
||||
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() || ''; // 未完成的行留到下次
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith('data:')) {
|
||||
const jsonStr = trimmed.slice(5).trim();
|
||||
if (jsonStr) {
|
||||
try {
|
||||
const jsonStr = trimmed.slice(5).trim();
|
||||
if (jsonStr && jsonStr !== '[DONE]') {
|
||||
const data = JSON.parse(jsonStr);
|
||||
if (data.event === 'message') {
|
||||
const text = data.answer || data.text || '';
|
||||
content += text;
|
||||
callback(text);
|
||||
if (onChunk) onChunk(text);
|
||||
}
|
||||
if (data.conversation_id) {
|
||||
conversationId = data.conversation_id;
|
||||
}
|
||||
if (data.event === 'message_end') {
|
||||
// 可选:提前完成
|
||||
// 可提前结束
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析流数据失败:', e);
|
||||
console.warn('解析失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
// 处理最后残留的 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete({
|
||||
content,
|
||||
conversation_id: conversationId
|
||||
});
|
||||
onComplete({ content, conversation_id: conversationId });
|
||||
}
|
||||
return { content, conversation_id: conversationId };
|
||||
} catch (error) {
|
||||
@@ -196,11 +303,6 @@ export default {
|
||||
throw error;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 理论上不会执行到这里,但防止 fallback
|
||||
return this.sendStreamMessage(message, onChunk, onComplete);
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -259,9 +361,11 @@ export default {
|
||||
const params = {
|
||||
url: '/api/kefu/createConversation',
|
||||
data: {
|
||||
uniacid: store.state.uniacid,
|
||||
uniacid: this.getUniacid(),
|
||||
token: store.state.token || 'test_token',
|
||||
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||
member_id: store.state.memberInfo?.id || '',
|
||||
member_id: store.state.memberInfo?.id || 'anonymous',
|
||||
...getDomainParams()
|
||||
},
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -284,6 +388,7 @@ export default {
|
||||
async: false,
|
||||
data: {}
|
||||
});
|
||||
console.log('/api/kefu/health response', response);
|
||||
const available = [response?.data?.status, response?.data?.components?.ai_service_config?.status]
|
||||
.filter(item => item === 'healthy').length > 0;
|
||||
return {
|
||||
@@ -305,7 +410,10 @@ export default {
|
||||
try {
|
||||
const response = await http.sendRequest({
|
||||
url: '/api/kefu/clear-conversation',
|
||||
data: { conversation_id: conversationId },
|
||||
data: {
|
||||
conversation_id: conversationId,
|
||||
...getDomainParams()
|
||||
},
|
||||
async: false
|
||||
});
|
||||
return response.success;
|
||||
@@ -324,13 +432,14 @@ export default {
|
||||
throw new Error('会话ID(conversation_id)是必填参数');
|
||||
}
|
||||
const requestData = {
|
||||
uniacid: params.uniacid || store.state.uniacid || '1',
|
||||
uniacid: params.uniacid || this.getUniacid(),
|
||||
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 || ''
|
||||
member_id: params.member_id || store.state.memberInfo?.id || 'anonymous',
|
||||
token: params.token || '',
|
||||
...getDomainParams()
|
||||
};
|
||||
const response = await http.sendRequest({
|
||||
url: '/api/kefu/getHistory',
|
||||
@@ -369,13 +478,17 @@ export default {
|
||||
if (window.currentEventSource) {
|
||||
window.currentEventSource.close();
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
uniacid: store.state.uniacid || '1',
|
||||
user_id: store.state.memberInfo?.id || '123456',
|
||||
const paramsObj = {
|
||||
uniacid: this.getUniacid(),
|
||||
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||
member_id: store.state.memberInfo?.id || 'anonymous',
|
||||
token: store.state.token || 'test_token',
|
||||
query: message,
|
||||
conversation_id: conversationId || '',
|
||||
stream: 'true'
|
||||
});
|
||||
stream: 'true',
|
||||
...getDomainParams()
|
||||
};
|
||||
const params = new URLSearchParams(paramsObj);
|
||||
const url = `${Config.baseUrl}/api/kefu/chat?${params.toString()}`;
|
||||
try {
|
||||
const eventSource = new EventSource(url);
|
||||
|
||||
@@ -98,6 +98,54 @@ export default {
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
|
||||
this._huaweiSignIn(callback);
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* 华为快应用帐号登录
|
||||
*/
|
||||
async _huaweiSignIn(callback) {
|
||||
try {
|
||||
const HwService = require('@/common/js/hw-jssdk.js').default;
|
||||
// 确保 HwService 已初始化
|
||||
if (!HwService.isQuickAppEnv()) {
|
||||
console.warn('[Auth] 非快应用环境,跳过华为帐号登录');
|
||||
return;
|
||||
}
|
||||
const userInfo = await HwService.Account.signIn();
|
||||
if (userInfo && userInfo.openId) {
|
||||
// 将华为 openId 传给后端换取系统 token
|
||||
this.$api.sendRequest({
|
||||
url: '/wechat/api/quickapp/huawei_auth',
|
||||
data: {
|
||||
openid: userInfo.openId,
|
||||
unionid: userInfo.unionId,
|
||||
nickname: userInfo.displayName,
|
||||
avatar: userInfo.avatarUrl,
|
||||
gender: userInfo.gender
|
||||
},
|
||||
success: res => {
|
||||
if (res.code >= 0) {
|
||||
if (res.data.openid) this.authInfo.hw_openid = res.data.openid;
|
||||
if (res.data.unionid) this.authInfo.hw_unionid = res.data.unionid;
|
||||
typeof callback == 'function' && callback(this.authInfo);
|
||||
} else {
|
||||
this.$util.showToast({
|
||||
title: res.message ? res.message : '华为帐号登录失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Auth] 华为帐号登录失败:', error);
|
||||
this.$util.showToast({
|
||||
title: '华为帐号登录失败'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ class ConfigExternal {
|
||||
try {
|
||||
// 动态加载主题配置
|
||||
const themeData = require(`@/common/js/style_color.js`)['default'][theme];
|
||||
console.log('async themeData => ', themeData);
|
||||
// console.log('async themeData => ', themeData);
|
||||
this.loadedConfigs[`theme_${theme}`] = themeData;
|
||||
resolve(themeData);
|
||||
} catch (error) {
|
||||
|
||||
@@ -77,18 +77,16 @@ export default {
|
||||
* 然后将 site.js 文件放到 `unpackage\dist\build\mp-weixin\` 目录下面
|
||||
*/
|
||||
// 商户ID
|
||||
uniacid: 1, //825
|
||||
uniacid: uniacid, //825
|
||||
|
||||
//api请求地址
|
||||
baseUrl: 'https://dev.aigc-quickapp.com/',
|
||||
// baseUrl: 'http://localhost:8010/',
|
||||
baseUrl: domain,
|
||||
|
||||
// 图片域名
|
||||
imgDomain: 'https://dev.aigc-quickapp.com/',
|
||||
//imgDomain: 'http://localhost:8010/',
|
||||
imgDomain: domain,
|
||||
|
||||
// H5端域名
|
||||
h5Domain: 'https://dev.aigc-quickapp.com/',
|
||||
h5Domain: domain,
|
||||
|
||||
// // api请求地址
|
||||
// baseUrl: 'https://tsaas.liveplatform.cn/',
|
||||
@@ -122,5 +120,16 @@ export default {
|
||||
pingInterval: 1500,
|
||||
|
||||
// 版本号
|
||||
version: '1.0'
|
||||
version: '1.0',
|
||||
|
||||
// 华为快应用 HwService 配置
|
||||
// 需在 AppGallery Connect 创建应用后获取
|
||||
huawei: {
|
||||
// AGC 应用配置(替换为实际的 AGC 配置值)
|
||||
appId: '', // 应用ID
|
||||
clientId: '', // 客户端ID
|
||||
clientSecret: '', // 客户端密钥(注意安全,建议由后端下发)
|
||||
// 华为帐号登录回调地址
|
||||
redirectUri: window.location.origin + '/hwappx/common/',
|
||||
}
|
||||
};
|
||||
@@ -2,6 +2,8 @@
|
||||
* 客服统一处理服务
|
||||
* 整合各种客服方式,提供统一的调用接口
|
||||
*/
|
||||
import IMAGE_CONFIG from './image-config.js';
|
||||
|
||||
export class CustomerService {
|
||||
constructor(vueInstance, externalConfig = null) {
|
||||
this.vm = vueInstance;
|
||||
@@ -28,28 +30,29 @@ export class CustomerService {
|
||||
return this.latestPlatformConfig;
|
||||
}
|
||||
|
||||
// 优先级:外部传入的最新配置 > vuex配置 > 空对象
|
||||
// 优先级:外部传入 > vuex store > 空对象
|
||||
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) : null;
|
||||
platformConfig = servicerConfig.h5 && typeof servicerConfig.h5 === 'object' ? servicerConfig.h5 : null;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
platformConfig = servicerConfig.weapp ? (typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null) : null;
|
||||
platformConfig = servicerConfig.weapp && typeof servicerConfig.weapp === 'object' ? servicerConfig.weapp : null;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
platformConfig = servicerConfig.aliapp ? (typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null) : null;
|
||||
platformConfig = servicerConfig.aliapp && typeof servicerConfig.aliapp === 'object' ? servicerConfig.aliapp : null;
|
||||
// #endif
|
||||
|
||||
// #ifdef PC
|
||||
platformConfig = servicerConfig.pc ? (typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null) : null;
|
||||
platformConfig = servicerConfig.pc && typeof servicerConfig.pc === 'object' ? servicerConfig.pc : null;
|
||||
// #endif
|
||||
|
||||
// 处理空数组情况(你的配置中pc/aliapp是空数组,转为null)
|
||||
// 防止空数组被当作有效配置
|
||||
if (Array.isArray(platformConfig)) {
|
||||
platformConfig = null;
|
||||
}
|
||||
@@ -75,6 +78,15 @@ 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} 验证结果
|
||||
@@ -89,32 +101,18 @@ export class CustomerService {
|
||||
warnings: []
|
||||
};
|
||||
|
||||
if (!config) {
|
||||
result.isValid = false;
|
||||
result.errors.push('客服配置不存在');
|
||||
return result;
|
||||
}
|
||||
|
||||
if (config.type === 'aikefu') {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!config.type) {
|
||||
if (!config || !config.type) {
|
||||
result.isValid = false;
|
||||
result.errors.push('客服类型未配置');
|
||||
return result;
|
||||
}
|
||||
|
||||
if (config.type === 'wxwork') {
|
||||
if (!wxworkConfig) {
|
||||
result.isValid = false;
|
||||
result.errors.push('企业微信配置不存在');
|
||||
} else {
|
||||
if (!wxworkConfig.enable) {
|
||||
result.warnings.push('企业微信功能未启用');
|
||||
if (!wxworkConfig || !wxworkConfig.enable) {
|
||||
result.warnings.push('企业微信未启用');
|
||||
}
|
||||
if (!wxworkConfig.contact_url) {
|
||||
result.warnings.push('企业微信活码链接未配置,将使用原有客服方式');
|
||||
}
|
||||
result.warnings.push('企业微信活码链接未配置');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,40 +120,39 @@ export class CustomerService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到Dify客服页面
|
||||
* 跳转到 AI 客服页面(Dify)
|
||||
*/
|
||||
openDifyService() {
|
||||
try {
|
||||
if (this.vm.setAiUnreadCount) {
|
||||
// 清除未读数(如果存在)
|
||||
if (typeof this.vm.setAiUnreadCount === 'function') {
|
||||
this.vm.setAiUnreadCount(0);
|
||||
}
|
||||
// 强制跳转,忽略框架层的封装
|
||||
uni.redirectTo({
|
||||
url: '/pages_tool/ai-chat/index',
|
||||
|
||||
// ✅ 修正路径:必须与 pages.json 中注册的路径一致
|
||||
const aiChatUrl = '/pages_tool/ai-chat/index';
|
||||
|
||||
// ✅ 使用 navigateTo 保留返回栈(体验更好)
|
||||
uni.navigateTo({
|
||||
url: aiChatUrl,
|
||||
fail: (err) => {
|
||||
// 兜底:使用window.location跳转(H5)
|
||||
console.error('跳转 AI 客服失败:', err);
|
||||
// H5 兜底
|
||||
// #ifdef H5
|
||||
window.location.href = '/pages_tool/ai-chat/index';
|
||||
window.location.href = aiChatUrl;
|
||||
// #endif
|
||||
console.error('跳转Dify客服失败:', err);
|
||||
uni.showToast({
|
||||
title: '跳转客服失败',
|
||||
icon: 'none'
|
||||
});
|
||||
uni.showToast({ title: '打开客服失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('跳转Dify客服异常:', e);
|
||||
uni.showToast({
|
||||
title: '跳转客服失败',
|
||||
icon: 'none'
|
||||
});
|
||||
console.error('跳转 AI 客服异常:', e);
|
||||
uni.showToast({ title: '打开客服失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理客服点击事件
|
||||
* @param {Object} options 选项参数
|
||||
* 处理客服点击事件(统一入口)
|
||||
* @param {Object} options 选项参数(用于消息卡片等)
|
||||
*/
|
||||
handleCustomerClick(options = {}) {
|
||||
const validation = this.validateConfig();
|
||||
@@ -170,6 +167,9 @@ export class CustomerService {
|
||||
}
|
||||
|
||||
const config = this.getPlatformConfig();
|
||||
console.log('【当前客服配置】', config);
|
||||
console.log('【客服类型】', config.type);
|
||||
|
||||
const { niushop = {}, sendMessageTitle = '', sendMessagePath = '', sendMessageImg = '' } = options;
|
||||
|
||||
if (config.type === 'none') {
|
||||
@@ -177,44 +177,51 @@ export class CustomerService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 核心分支:根据最新的type处理
|
||||
// 核心路由:根据 type 决定行为
|
||||
switch (config.type) {
|
||||
case 'aikefu':
|
||||
console.log('【跳转 AI 客服】目标路径: /pages_tool/ai-chat/index');
|
||||
this.openDifyService();
|
||||
break;
|
||||
case 'wxwork':
|
||||
console.log('【跳转企业微信客服】');
|
||||
this.openWxworkService(false, config, options);
|
||||
break;
|
||||
case 'third':
|
||||
console.log('【跳转第三方客服】');
|
||||
this.openThirdService(config);
|
||||
break;
|
||||
case 'miniprogram':
|
||||
console.log('【跳转第三方小程序客服】');
|
||||
this.openThirdService(config);
|
||||
break;
|
||||
case 'niushop':
|
||||
console.log('【跳转牛商客服】');
|
||||
this.openNiushopService(niushop);
|
||||
break;
|
||||
case 'weapp':
|
||||
console.log('【跳转微信官方客服】');
|
||||
this.openWeappService(config, options);
|
||||
break;
|
||||
case 'aliapp':
|
||||
console.log('【跳转支付宝客服】');
|
||||
this.openAliappService(config);
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (wxworkConfig?.enable && wxworkConfig?.contact_url && !useOriginalService) {
|
||||
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
|
||||
wx.navigateToMiniProgram({
|
||||
appId: 'wxeb490c6f9b154ef9',
|
||||
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig.contact_url)}`,
|
||||
@@ -225,9 +232,16 @@ 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,
|
||||
@@ -237,120 +251,125 @@ export class CustomerService {
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
if (wxworkConfig?.enable && wxworkConfig?.contact_url) {
|
||||
if (!useOriginalService && wxworkConfig?.enable && wxworkConfig?.contact_url) {
|
||||
window.location.href = wxworkConfig.contact_url;
|
||||
} else if (config.wxwork_url) {
|
||||
location.href = config.wxwork_url;
|
||||
window.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) {
|
||||
if (Object.keys(niushop).length > 0 && this.vm.$util?.redirectTo) {
|
||||
this.vm.$util.redirectTo('/pages_tool/chat/room', niushop);
|
||||
} else {
|
||||
this.makePhoneCall();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开微信小程序客服
|
||||
* @param {Object} config 客服配置
|
||||
* @param {Object} options 选项参数
|
||||
*/
|
||||
openWeappService(config, options = {}) {
|
||||
if (!this.shouldUseCustomService(config)) {
|
||||
console.log('使用官方微信小程序客服');
|
||||
// 如果 useOfficial 为 true 或 undefined,则使用原生系统客服(由 button open-type="contact" 触发)
|
||||
// 此方法仅用于自定义跳转(如 useOfficial: false)
|
||||
if (config.useOfficial !== false) {
|
||||
// 不做任何事,应由 <button open-type="contact"> 触发
|
||||
console.log('使用微信官方客服,请确保按钮为 <button open-type="contact">');
|
||||
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: url,
|
||||
fail: (err) => {
|
||||
console.error('跳转自定义客服页面失败:', err);
|
||||
this.tryThirdPartyService(config, options);
|
||||
}
|
||||
});
|
||||
uni.navigateTo({ url });
|
||||
return;
|
||||
}
|
||||
|
||||
this.tryThirdPartyService(config, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试使用第三方客服
|
||||
* @param {Object} config 客服配置
|
||||
* @param {Object} options 选项参数
|
||||
*/
|
||||
tryThirdPartyService(config, options = {}) {
|
||||
if (config.thirdPartyServiceUrl) {
|
||||
// #ifdef H5
|
||||
window.open(config.thirdPartyServiceUrl, '_blank');
|
||||
// #endif
|
||||
|
||||
// 支持第三方微信小程序客服
|
||||
if (config.thirdPartyMiniAppId || config.mini_app_id) {
|
||||
// #ifdef MP-WEIXIN
|
||||
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
|
||||
});
|
||||
}
|
||||
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;
|
||||
// #ifdef H5
|
||||
window.open(serviceUrl, '_blank');
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.setClipboardData({ data: serviceUrl });
|
||||
uni.showToast({ title: '客服链接已复制', icon: 'none' });
|
||||
// #endif
|
||||
return;
|
||||
}
|
||||
@@ -358,103 +377,55 @@ export class CustomerService {
|
||||
this.fallbackToPhoneCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* 降级到电话客服
|
||||
*/
|
||||
fallbackToPhoneCall() {
|
||||
uni.showModal({
|
||||
title: '联系客服',
|
||||
content: '在线客服暂时不可用,是否拨打电话联系客服?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.makePhoneCall();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开支付宝小程序客服
|
||||
* @param {Object} config 客服配置
|
||||
*/
|
||||
openAliappService(config) {
|
||||
console.log('支付宝小程序客服', config);
|
||||
switch (config.type) {
|
||||
case 'aikefu':
|
||||
if (config.type === 'aikefu') {
|
||||
this.openDifyService();
|
||||
break;
|
||||
case 'third':
|
||||
} else if (config.type === 'third') {
|
||||
this.openThirdService(config);
|
||||
break;
|
||||
default:
|
||||
} else {
|
||||
// 支付宝原生客服由 button open-type="contact" 触发,此处不处理
|
||||
console.log('使用支付宝官方客服');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拨打电话
|
||||
*/
|
||||
// ================== 辅助方法 ==================
|
||||
|
||||
makePhoneCall() {
|
||||
this.vm.$api.sendRequest({
|
||||
url: '/api/site/shopcontact',
|
||||
success: res => {
|
||||
if (res.code === 0 && res.data?.mobile) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: res.data.mobile
|
||||
});
|
||||
uni.makePhoneCall({ phoneNumber: res.data.mobile });
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '暂无客服电话',
|
||||
icon: 'none'
|
||||
});
|
||||
uni.showToast({ title: '暂无客服电话', icon: 'none' });
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '获取客服电话失败',
|
||||
icon: 'none'
|
||||
});
|
||||
uni.showToast({ title: '获取客服电话失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示无客服弹窗
|
||||
*/
|
||||
showNoServicePopup() {
|
||||
const siteInfo = this.vm.$store.state.siteInfo || {};
|
||||
const message = siteInfo?.site_tel
|
||||
? `请联系客服,客服电话是${siteInfo.site_tel}`
|
||||
? `请联系客服,客服电话是 ${siteInfo.site_tel}`
|
||||
: '抱歉,商家暂无客服,请线下联系';
|
||||
|
||||
uni.showModal({
|
||||
title: '联系客服',
|
||||
content: message,
|
||||
showCancel: false
|
||||
});
|
||||
uni.showModal({ title: '联系客服', content: message, showCancel: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示配置错误弹窗
|
||||
* @param {Array} errors 错误列表
|
||||
*/
|
||||
showConfigErrorPopup(errors) {
|
||||
const message = errors.join('\n');
|
||||
uni.showModal({
|
||||
title: '配置错误',
|
||||
content: `客服配置有误:\n${message}`,
|
||||
title: '客服配置错误',
|
||||
content: `配置有误:\n${errors.join('\n')}`,
|
||||
showCancel: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 降级处理:使用原有客服方式
|
||||
*/
|
||||
fallbackToOriginalService() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '无法直接添加企业微信客服,是否使用其他方式联系客服?',
|
||||
content: '无法直接添加企业微信,是否使用其他方式联系客服?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.openWxworkService(true);
|
||||
@@ -463,9 +434,19 @@ export class CustomerService {
|
||||
});
|
||||
}
|
||||
|
||||
fallbackToPhoneCall() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '在线客服不可用,是否拨打电话联系客服?',
|
||||
success: (res) => {
|
||||
if (res.confirm) this.makePhoneCall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客服按钮配置
|
||||
* @returns {Object} 按钮配置
|
||||
* 获取按钮配置(用于 template 中 v-if / open-type 判断)
|
||||
* @returns {Object}
|
||||
*/
|
||||
getButtonConfig() {
|
||||
const config = this.getPlatformConfig();
|
||||
@@ -473,11 +454,10 @@ export class CustomerService {
|
||||
|
||||
let openType = '';
|
||||
// #ifdef MP-WEIXIN
|
||||
if (config.type === 'weapp') {
|
||||
openType = config.useOfficial !== false ? 'contact' : '';
|
||||
if (config.type === 'weapp' && config.useOfficial !== false) {
|
||||
openType = 'contact';
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
if (config.type === 'aliapp') openType = 'contact';
|
||||
// #endif
|
||||
@@ -486,25 +466,238 @@ export class CustomerService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该使用自定义客服处理
|
||||
* @param {Object} config 客服配置
|
||||
* @returns {boolean} 是否使用自定义客服
|
||||
* 获取客服按钮图标
|
||||
* @returns {String} 图标URL
|
||||
*/
|
||||
shouldUseCustomService(config) {
|
||||
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);
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (config?.type === 'weapp') {
|
||||
return config.useOfficial === false;
|
||||
if (!wxworkConfig?.enable || !wxworkConfig?.contact_url) {
|
||||
console.warn('企业微信配置不完整,使用原生客服');
|
||||
uni.showToast({ title: '企业微信配置不完整', icon: 'none' });
|
||||
}
|
||||
// #endif
|
||||
return true;
|
||||
|
||||
// #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' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建客服服务实例
|
||||
* @param {Object} vueInstance Vue实例
|
||||
* @param {Object} externalConfig 外部最新配置
|
||||
* @returns {CustomerService} 客服服务实例
|
||||
* @param {Object} vueInstance Vue 实例(通常是 this)
|
||||
* @param {Object} externalConfig 可选:外部传入的最新配置(如从 DIY 数据中提取)
|
||||
* @returns {CustomerService}
|
||||
*/
|
||||
export function createCustomerService(vueInstance, externalConfig = null) {
|
||||
return new CustomerService(vueInstance, externalConfig);
|
||||
|
||||
@@ -609,10 +609,10 @@ export default {
|
||||
},
|
||||
// 分享给好友
|
||||
onShareAppMessage() {
|
||||
return this.mpShareData.appMessage;
|
||||
return this.mpShareData?.appMessage;
|
||||
},
|
||||
// 分享到朋友圈
|
||||
onShareTimeline() {
|
||||
return this.mpShareData.timeLine;
|
||||
return this.mpShareData?.timeLine;
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,9 @@ class EventBus {
|
||||
return 'xhs'
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
|
||||
return 'quickapp'
|
||||
// #endif
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
computed: {
|
||||
// 是否是英文环境
|
||||
isEnEnv() {
|
||||
return uni.getStorageSync('lang') === 'en-us';
|
||||
return this.$langConfig.getCurrentLocale() === 'en-us';
|
||||
},
|
||||
themeStyle() {
|
||||
return this.$store.state.themeStyle;
|
||||
@@ -65,6 +65,10 @@ export default {
|
||||
componentRefresh() {
|
||||
return this.$store.state.componentRefresh;
|
||||
},
|
||||
// AI客服配置
|
||||
globalAIKefuConfig() {
|
||||
return this.$store.state.globalAIKefuConfig;
|
||||
},
|
||||
// 客服配置
|
||||
servicerConfig() {
|
||||
return this.$store.state.servicerConfig;
|
||||
|
||||
@@ -37,6 +37,11 @@ const app_type = 'app';
|
||||
const app_type_name = 'APP';
|
||||
// #endif
|
||||
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
|
||||
const app_type = 'quickapp';
|
||||
const app_type_name = '快应用';
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
sendRequest(params) {
|
||||
if (!Config.baseUrl) {
|
||||
|
||||
361
common/js/hw-jssdk.js
Normal file
361
common/js/hw-jssdk.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* 华为快应用 HwService JS SDK 统一封装
|
||||
* 用于 H5 壳模式下的快应用环境,通过 hms-js-base 桥接层调用 HMS Core 服务
|
||||
*
|
||||
* 架构说明:
|
||||
* - 所有 HMS 服务通过 @hw-hmscore/hms-js-base 的 hmsjsb.invoke() 统一桥接
|
||||
* - Analytics Kit 有独立封装 @hw-hmscore/analytics-sdk-quickapp(可选安装)
|
||||
* - Account Kit 无独立 quickapp npm 包,直接通过 hmsjsb.invoke 调用
|
||||
* - Push Kit 无独立 quickapp npm 包,直接通过 hmsjsb.invoke 调用
|
||||
*
|
||||
* 使用方式:
|
||||
* import HwService from '@/common/js/hw-jssdk.js'
|
||||
*
|
||||
* // 初始化
|
||||
* await HwService.init()
|
||||
*
|
||||
* // 华为帐号登录
|
||||
* const userInfo = await HwService.Account.signIn()
|
||||
*
|
||||
* // 分析服务埋点
|
||||
* HwService.Analytics.onEvent('event_name', { key: 'value' })
|
||||
*
|
||||
* @version 1.1.0
|
||||
*/
|
||||
|
||||
// hms-js-base 提供底层 JS Bridge 桥接能力
|
||||
let hmsjsb = null
|
||||
let hmsbase = null
|
||||
|
||||
// Analytics SDK 独立包(可选安装)
|
||||
let HiAnalytics = null
|
||||
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
|
||||
try {
|
||||
// 加载基础桥接库(必装)
|
||||
const hmsBase = require('@hw-hmscore/hms-js-base')
|
||||
hmsjsb = hmsBase.hmsjsb
|
||||
hmsbase = hmsBase.hmsbase
|
||||
|
||||
// 尝试加载 Analytics SDK(可选)
|
||||
try {
|
||||
HiAnalytics = require('@hw-hmscore/analytics-sdk-quickapp')
|
||||
} catch (e) {
|
||||
console.log('[HwService] Analytics SDK 未安装,将使用 invoke 桥接方式')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[HwService] hms-js-base 未安装或不在快应用环境')
|
||||
}
|
||||
// #endif
|
||||
|
||||
class HwService {
|
||||
constructor() {
|
||||
this._initialized = false
|
||||
this._config = null
|
||||
|
||||
// 子服务实例
|
||||
this.Account = null
|
||||
this.Analytics = null
|
||||
this.Push = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前是否运行在快应用引擎环境
|
||||
*/
|
||||
isQuickAppEnv() {
|
||||
// #ifdef H5_XCX_5G_QUICKAPP_COM || H5_XCX_AIGC_QUICKAPP_COM || H5_5G_AIGC_QUICKAPP_COM || H5_TEST_AIGC_QUICKAPP_COM
|
||||
if (typeof window !== 'undefined') {
|
||||
return !!(
|
||||
window.qa ||
|
||||
window.__HwService__ ||
|
||||
window.hms ||
|
||||
hmsjsb || // hms-js-base 加载成功
|
||||
navigator.userAgent.indexOf('QuickApp') > -1
|
||||
)
|
||||
}
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 HwService
|
||||
* @param {Object} config { appId, clientId, clientSecret }
|
||||
*/
|
||||
async init(config = {}) {
|
||||
if (this._initialized) {
|
||||
console.log('[HwService] 已初始化,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
this._config = config
|
||||
console.log('[HwService] 开始初始化...')
|
||||
|
||||
try {
|
||||
// 子服务实例化
|
||||
this.Account = new AccountService(hmsjsb)
|
||||
this.Analytics = new AnalyticsService(hmsjsb, HiAnalytics)
|
||||
this.Push = new PushService(hmsjsb)
|
||||
|
||||
this._initialized = true
|
||||
console.log('[HwService] 初始化完成')
|
||||
} catch (error) {
|
||||
console.error('[HwService] 初始化失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 底层 invoke 封装
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 通过 hmsjsb.invoke 调用 HMS Core API 的 Promise 封装
|
||||
* @param {Object} hmsjsbRef hmsjsb 引用
|
||||
* @param {string} apiName API 名称
|
||||
* @param {Object} args 参数
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
function invokeHmsApi(hmsjsbRef, apiName, args = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!hmsjsbRef || typeof hmsjsbRef.invoke !== 'function') {
|
||||
reject(new Error('[HwService] hmsjsb.invoke 不可用'))
|
||||
return
|
||||
}
|
||||
hmsjsbRef.invoke({
|
||||
apiName,
|
||||
args,
|
||||
success: (res) => resolve(res),
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 华为帐号服务 (Account Kit)
|
||||
// 通过 hmsjsb.invoke 桥接(无独立 npm 包)
|
||||
// ============================================================
|
||||
class AccountService {
|
||||
constructor(hmsjsbRef) {
|
||||
this._hmsjsb = hmsjsbRef
|
||||
}
|
||||
|
||||
/**
|
||||
* 华为帐号登录
|
||||
* @param {Array} scopes 授权范围
|
||||
* @returns {Promise<Object>} 用户信息
|
||||
*/
|
||||
async signIn(scopes = ['openid', 'profile']) {
|
||||
console.log('[HwService.Account] signIn...')
|
||||
// apiName 需根据实际华为文档确认,常见值有:
|
||||
// 'accountSignIn' | 'com.huawei.hms.api.Account.signIn'
|
||||
const result = await invokeHmsApi(this._hmsjsb, 'accountSignIn', { scopes })
|
||||
return this._formatUserInfo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默获取华为帐号用户信息
|
||||
*/
|
||||
async getUserInfo() {
|
||||
const result = await invokeHmsApi(this._hmsjsb, 'accountGetUserInfo', {})
|
||||
return this._formatUserInfo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
async signOut() {
|
||||
try {
|
||||
await invokeHmsApi(this._hmsjsb, 'accountSignOut', {})
|
||||
} catch (e) {
|
||||
console.warn('[HwService.Account] signOut 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一格式化用户信息
|
||||
*/
|
||||
_formatUserInfo(rawInfo) {
|
||||
if (!rawInfo) return null
|
||||
return {
|
||||
openId: rawInfo.openId || rawInfo.openid || '',
|
||||
unionId: rawInfo.unionId || rawInfo.unionid || '',
|
||||
displayName: rawInfo.displayName || rawInfo.nickName || rawInfo.nickname || '',
|
||||
avatarUrl: rawInfo.avatarUrl || rawInfo.avatar || rawInfo.headPictureUrl || '',
|
||||
gender: rawInfo.gender || 0,
|
||||
country: rawInfo.country || '',
|
||||
province: rawInfo.province || '',
|
||||
city: rawInfo.city || '',
|
||||
raw: rawInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 华为分析服务 (Analytics Kit)
|
||||
// 支持 @hw-hmscore/analytics-sdk-quickapp(优先)或 invoke 桥接
|
||||
// ============================================================
|
||||
class AnalyticsService {
|
||||
constructor(hmsjsbRef, hiAnalyticsSdk) {
|
||||
this._hmsjsb = hmsjsbRef
|
||||
this._sdk = hiAnalyticsSdk
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报事件
|
||||
* @param {string} eventName 事件名称
|
||||
* @param {Object} params 事件参数
|
||||
*/
|
||||
onEvent(eventName, params = {}) {
|
||||
// 方案一:Analytics SDK(独立封装包)
|
||||
if (this._sdk && typeof this._sdk.onEvent === 'function') {
|
||||
this._sdk.onEvent(eventName, params)
|
||||
return
|
||||
}
|
||||
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
|
||||
try {
|
||||
const instance = this._sdk.HiAnalytics.getInstance()
|
||||
instance.onEvent(eventName, params)
|
||||
return
|
||||
} catch (e) {
|
||||
// fallthrough
|
||||
}
|
||||
}
|
||||
// 方案二:invoke 桥接
|
||||
if (this._hmsjsb) {
|
||||
this._hmsjsb.invoke({
|
||||
apiName: 'analyticsOnEvent',
|
||||
args: { eventName, params },
|
||||
success: () => {},
|
||||
fail: (err) => console.warn('[HwService.Analytics] onEvent 失败:', err)
|
||||
})
|
||||
return
|
||||
}
|
||||
// fallback
|
||||
console.log('[HwService.Analytics] onEvent (fallback):', eventName, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户属性
|
||||
* @param {Object} profile
|
||||
*/
|
||||
setUserProfile(profile = {}) {
|
||||
if (this._sdk && typeof this._sdk.setUserProfile === 'function') {
|
||||
this._sdk.setUserProfile(profile)
|
||||
return
|
||||
}
|
||||
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
|
||||
try {
|
||||
const instance = this._sdk.HiAnalytics.getInstance()
|
||||
instance.setUserProfile(profile)
|
||||
return
|
||||
} catch (e) {}
|
||||
}
|
||||
if (this._hmsjsb) {
|
||||
this._hmsjsb.invoke({
|
||||
apiName: 'analyticsSetUserProfile',
|
||||
args: { profile },
|
||||
success: () => {},
|
||||
fail: (err) => console.warn('[HwService.Analytics] setUserProfile 失败:', err)
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log('[HwService.Analytics] setUserProfile (fallback):', profile)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户 ID
|
||||
* @param {string} userId
|
||||
*/
|
||||
setUserId(userId) {
|
||||
if (this._sdk && typeof this._sdk.setUserId === 'function') {
|
||||
this._sdk.setUserId(userId)
|
||||
return
|
||||
}
|
||||
if (this._sdk && typeof this._sdk.HiAnalytics?.getInstance === 'function') {
|
||||
try {
|
||||
const instance = this._sdk.HiAnalytics.getInstance()
|
||||
instance.setUserId(userId)
|
||||
return
|
||||
} catch (e) {}
|
||||
}
|
||||
if (this._hmsjsb) {
|
||||
this._hmsjsb.invoke({
|
||||
apiName: 'analyticsSetUserId',
|
||||
args: { userId },
|
||||
success: () => {},
|
||||
fail: (err) => console.warn('[HwService.Analytics] setUserId 失败:', err)
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log('[HwService.Analytics] setUserId (fallback):', userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面浏览事件
|
||||
*/
|
||||
onPageView(pageName, pageClassOverride = '') {
|
||||
this.onEvent('$PageView', {
|
||||
pageName,
|
||||
pageClassOverride: pageClassOverride || pageName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 华为推送服务 (Push Kit)
|
||||
// 通过 hmsjsb.invoke 桥接(无独立 npm 包)
|
||||
// ============================================================
|
||||
class PushService {
|
||||
constructor(hmsjsbRef) {
|
||||
this._hmsjsb = hmsjsbRef
|
||||
this._token = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推送令牌
|
||||
*/
|
||||
async getToken() {
|
||||
if (this._token) return this._token
|
||||
try {
|
||||
const result = await invokeHmsApi(this._hmsjsb, 'pushGetToken', {})
|
||||
this._token = result.token || result
|
||||
return this._token
|
||||
} catch (e) {
|
||||
console.warn('[HwService.Push] getToken 不可用:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听推送令牌变更
|
||||
*/
|
||||
onTokenReceived(callback) {
|
||||
if (!this._hmsjsb) return
|
||||
this._hmsjsb.on('pushOnToken', callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听消息到达
|
||||
*/
|
||||
onMessageReceived(callback) {
|
||||
if (!this._hmsjsb) return
|
||||
this._hmsjsb.on('pushOnMessageReceived', callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听通知点击
|
||||
*/
|
||||
onNotificationClicked(callback) {
|
||||
if (!this._hmsjsb) return
|
||||
this._hmsjsb.on('pushOnNotificationClicked', callback)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 导出单例
|
||||
// ============================================================
|
||||
const hwService = new HwService()
|
||||
export default hwService
|
||||
|
||||
export { AccountService, AnalyticsService, PushService }
|
||||
15
common/js/image-config.js
Normal file
15
common/js/image-config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 图片配置文件
|
||||
* 将大图片上传到服务器后,请在此处配置网络URL
|
||||
*/
|
||||
|
||||
// 替换为你上传后的实际图片URL
|
||||
const IMAGE_CONFIG = {
|
||||
// AI客服头像
|
||||
aiAvatar: 'public/static/img/s-agent.jpg',
|
||||
|
||||
// 用户默认头像
|
||||
userAvatar: 'public/static/img/s-user.jpg'
|
||||
};
|
||||
|
||||
export default IMAGE_CONFIG;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { langConfig } from './config-external.js';
|
||||
|
||||
|
||||
// 缓存已加载的语言包
|
||||
const loadedLangPacks = {};
|
||||
|
||||
@@ -8,15 +9,21 @@ function processRoutePath(route) {
|
||||
let routeParts = route.split("/");
|
||||
|
||||
// ---- 处理页面目录映射 <begin> 分包造成的,需要根据实际目录结构进行映射----
|
||||
// 先处理特殊的分包路径
|
||||
if (routeParts[0] === 'pages_tool') {
|
||||
// pages_tool 分包下的页面,直接使用子目录作为语言包路径
|
||||
routeParts = [routeParts[1], ...routeParts.slice(2)];
|
||||
} else if (routeParts[0] === 'pages_goods') {
|
||||
// pages_goods 分包映射到 goods 目录
|
||||
routeParts[0] = 'goods';
|
||||
} else if (routeParts[0] === 'pages_member') {
|
||||
// pages_member 分包映射到 member 目录
|
||||
routeParts[0] = 'member';
|
||||
} else if (routeParts[0] === 'pages_order') {
|
||||
// pages_order 分包映射到 order 目录
|
||||
routeParts[0] = 'order';
|
||||
} else if (routeParts[0] === 'pages_promotion') {
|
||||
// pages_promotion 分包特殊处理
|
||||
const promotionModules = ['point', 'fenxiao', 'merch'];
|
||||
if (routeParts[1] && promotionModules.includes(routeParts[1])) {
|
||||
routeParts = [routeParts[1], ...routeParts.slice(2)];
|
||||
@@ -24,6 +31,7 @@ function processRoutePath(route) {
|
||||
}
|
||||
// ---- 处理页面目录映射 <end>----
|
||||
|
||||
// 去掉pages目录,只保留子目录
|
||||
if (routeParts[0] === 'pages') {
|
||||
routeParts = routeParts.slice(1);
|
||||
}
|
||||
@@ -46,32 +54,51 @@ function loadLangPackSync(lang, path) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentLocale() {
|
||||
return uni.getStorageSync('lang') || "zh-cn";
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
langList: langConfig.langList,
|
||||
|
||||
/**
|
||||
* 解析多语言
|
||||
* 获得当前本地语言
|
||||
* @returns
|
||||
*/
|
||||
getCurrentLocale,
|
||||
|
||||
/**
|
||||
* * 解析多语言
|
||||
* @param {Object} field
|
||||
*/
|
||||
lang(field) {
|
||||
let _this = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_this) return;
|
||||
let _page = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_page) return;
|
||||
|
||||
const locale = uni.getStorageSync('lang') || "zh-cn";
|
||||
const locale = getCurrentLocale(); // 获得当前本地语言
|
||||
|
||||
let value = '';
|
||||
let langPath = '';
|
||||
let value = ''; // 存放解析后的语言值
|
||||
let langPath = ''; // 存放当前页面语言包路径
|
||||
|
||||
try {
|
||||
//公共语言包(同步加载)
|
||||
var lang = loadLangPackSync(locale, 'common');
|
||||
|
||||
let route = _this.route;
|
||||
//当前页面语言包(同步加载)
|
||||
let route = _page.route;
|
||||
langPath = processRoutePath(route);
|
||||
|
||||
// 加载当前页面语言包
|
||||
let currentPageLang = loadLangPackSync(locale, langPath);
|
||||
|
||||
// 合并语言包
|
||||
let mergedLang = { ...lang, ...currentPageLang };
|
||||
|
||||
// 解析字段
|
||||
var arr = field.split(".");
|
||||
if (arr.length > 1) {
|
||||
// 处理嵌套属性,如 common.currencySymbol
|
||||
let temp = mergedLang;
|
||||
let found = true;
|
||||
for (let key of arr) {
|
||||
@@ -93,12 +120,14 @@ export default {
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
//有参数,需要替换
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
value = value.replace("{" + (i - 1) + "}", arguments[i]);
|
||||
}
|
||||
}
|
||||
if (value == undefined || (value == 'title' && field == 'title')) value = '';
|
||||
if (value == undefined || (value == 'title' && field == 'title')) value = ''; // field
|
||||
|
||||
// 多语言调试,注释后可以关闭控制台输出
|
||||
if (field == value) {
|
||||
console.warn(`警告: 字段 ${field} 在语言包 ${langPath} 中未找到对应值,使用默认值 ${field} 当前语言: ${locale}`);
|
||||
}
|
||||
@@ -106,19 +135,23 @@ export default {
|
||||
return value;
|
||||
},
|
||||
/**
|
||||
* 切换语言
|
||||
* * 切换语言
|
||||
* @param {String} value 语言值
|
||||
* @param {String} url 切换后跳转的页面url
|
||||
*/
|
||||
change(value, url = '/pages_tool/member/index') {
|
||||
let _this = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_this) return;
|
||||
let _page = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_page) return;
|
||||
|
||||
uni.setStorageSync("lang", value);
|
||||
const locale = uni.getStorageSync('lang') || "zh-cn";
|
||||
const locale = getCurrentLocale();
|
||||
|
||||
// ✅ 关键修复:清空所有语言包缓存(不再保留任何旧缓存)
|
||||
// 清空已加载的语言包缓存
|
||||
for (let key in loadedLangPacks) {
|
||||
if (!key.startsWith(locale)) {
|
||||
delete loadedLangPacks[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
|
||||
@@ -128,16 +161,39 @@ export default {
|
||||
},
|
||||
//刷新标题、tabbar
|
||||
refresh() {
|
||||
let _this = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_this) return;
|
||||
const locale = uni.getStorageSync('lang') || "zh-cn";
|
||||
let _page = getCurrentPages()[getCurrentPages().length - 1];
|
||||
if (!_page) return;
|
||||
|
||||
const locale = getCurrentLocale();
|
||||
|
||||
this.title(this.lang("title"));
|
||||
|
||||
//设置tabbar的文字语言
|
||||
// uni.setTabBarItem({
|
||||
// index: 0,
|
||||
// text: this.lang("tabBar.home")
|
||||
// });
|
||||
// uni.setTabBarItem({
|
||||
// index: 1,
|
||||
// text: this.lang("tabBar.category")
|
||||
// });
|
||||
// uni.setTabBarItem({
|
||||
// index: 2,
|
||||
// text: this.lang("tabBar.cart")
|
||||
// });
|
||||
// uni.setTabBarItem({
|
||||
// index: 3,
|
||||
// text: this.lang("tabBar.member")
|
||||
// });
|
||||
},
|
||||
title(str) {
|
||||
if (str) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: str
|
||||
title: str,
|
||||
success: function (res) {
|
||||
},
|
||||
fail: function (err) {
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -145,6 +201,7 @@ export default {
|
||||
list() {
|
||||
var list = [];
|
||||
try {
|
||||
//公共语言包
|
||||
for (var i = 0; i < langConfig.langList.length; i++) {
|
||||
let langType = langConfig.langList[i];
|
||||
let item = loadLangPackSync(langType, 'common');
|
||||
|
||||
100
common/js/share.js
Normal file
100
common/js/share.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// 文件分享工具函数
|
||||
export default {
|
||||
/**
|
||||
* 生成文件预览链接
|
||||
* @param {Object} file - 文件对象,包含 name 和 url 属性
|
||||
* @returns {string} - 生成的文件预览链接
|
||||
*/
|
||||
generateFilePreviewUrl(file) {
|
||||
// 推断文件类型
|
||||
let fileType = '';
|
||||
if (file.name) {
|
||||
const ext = file.name.split('.').pop().toLowerCase();
|
||||
if (['pdf'].includes(ext)) {
|
||||
fileType = 'pdf';
|
||||
} else if (['doc', 'docx'].includes(ext)) {
|
||||
fileType = 'word';
|
||||
} else if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'].includes(ext)) {
|
||||
fileType = 'video';
|
||||
}
|
||||
}
|
||||
|
||||
// 构建文件预览链接
|
||||
const previewUrl = `/pages_tool/file-preview/file-preview?fileName=${encodeURIComponent(file.name)}&fileUrl=${encodeURIComponent(file.url || '')}&fileType=${encodeURIComponent(fileType)}`;
|
||||
return previewUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享文件
|
||||
* @param {Object} file - 文件对象,包含 name 和 url 属性
|
||||
* @param {string} title - 分享标题,默认使用文件名
|
||||
* @param {string} desc - 分享描述,默认使用固定文案
|
||||
*/
|
||||
shareFile(file, title = file.name, desc = '查看企业文件:' + file.name) {
|
||||
// 生成文件预览链接
|
||||
const filePreviewUrl = this.generateFilePreviewUrl(file);
|
||||
|
||||
// #ifdef H5
|
||||
// H5 平台分享
|
||||
if (navigator.share) {
|
||||
// 使用 Web Share API
|
||||
navigator.share({
|
||||
title: title,
|
||||
text: desc,
|
||||
url: filePreviewUrl
|
||||
}).catch(err => {
|
||||
console.error('分享失败:', err);
|
||||
uni.showToast({ title: '分享失败', icon: 'none' });
|
||||
});
|
||||
} else {
|
||||
// 不支持 Web Share API 的浏览器
|
||||
uni.setClipboardData({
|
||||
data: filePreviewUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '链接已复制,请粘贴分享', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '复制失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序分享
|
||||
uni.showActionSheet({
|
||||
itemList: ['发送给朋友', '分享到朋友圈'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
// 发送给朋友
|
||||
uni.shareAppMessage({
|
||||
title: title,
|
||||
path: filePreviewUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '分享成功', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '分享失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
} else if (res.tapIndex === 1) {
|
||||
// 分享到朋友圈
|
||||
uni.shareTimeline({
|
||||
title: desc,
|
||||
path: filePreviewUrl,
|
||||
success: () => {
|
||||
uni.showToast({ title: '分享成功', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '分享失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 用户取消分享
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
148
common/js/uniapp.utils.js
Normal file
148
common/js/uniapp.utils.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 将常用的Uniapp提供的函数,存放到这里,按需引用
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 显示错误信息
|
||||
* @param {Exception} err
|
||||
* @param {Boolean} useModal
|
||||
*/
|
||||
const showError = (err, useModal = false) => {
|
||||
const content = err?.message || err?.errMsg || err?.toString();
|
||||
if (!useModal) {
|
||||
uni.showToast({
|
||||
title: content,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '错误提示',
|
||||
content,
|
||||
showCancel: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打电话
|
||||
* @param {string} mobile 电话号码
|
||||
*/
|
||||
export const makePhoneCall = (mobile) => {
|
||||
try {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: `${mobile}`,
|
||||
success(e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文本(返回 Promise)
|
||||
* @param {*} text
|
||||
* @param {*} options
|
||||
* @returns {Promise} 返回 Promise,成功时 resolve,失败时 reject
|
||||
*/
|
||||
export const copyTextAsync = (text, { copySuccess = '', copyFailed = '' } = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 输入验证
|
||||
if (!text && text !== '') {
|
||||
const error = new Error('复制文本不能为空');
|
||||
showError(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 超时监测
|
||||
const timeoutId = setTimeout(() => {
|
||||
let error = new Error('复制操作长时间无响应,请检查相关权限及配置是否正确');
|
||||
// #ifdef MP-WEIXIN
|
||||
error = new Error([
|
||||
'复制操作长时间无响应!',
|
||||
'原因:',
|
||||
'1.微信平台->用户隐私保护指引->"剪贴板"功能未添加或审核未通过;',
|
||||
'2.微信平台对剪贴板API调用频率有限制'
|
||||
].join('\r\n'));
|
||||
// #endif
|
||||
showError(error, true);
|
||||
reject(error);
|
||||
}, 5000);
|
||||
|
||||
try {
|
||||
uni.setClipboardData({
|
||||
data: `${text}`,
|
||||
success: (res) => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
try {
|
||||
if (copySuccess) {
|
||||
uni.showToast({
|
||||
title: copySuccess,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
}
|
||||
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
clearTimeout(timeoutId);
|
||||
try {
|
||||
uni.showToast({
|
||||
title: err.message || err.errMsg || copyFailed || '复制失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} catch (e) {
|
||||
showError(e);
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
showError(err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文本(回调形式,兼容旧代码)
|
||||
* @param {*} text
|
||||
* @param {*} options
|
||||
* @param {Function} callback 回调函数,接收 (success, error) 参数
|
||||
*/
|
||||
export const copyText = (text, options = {}, callback) => {
|
||||
copyTextAsync(text, options)
|
||||
.then(res => {
|
||||
if (callback) callback(true, null);
|
||||
})
|
||||
.catch(err => {
|
||||
if (callback) callback(false, err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开定位
|
||||
* @param {Object} options
|
||||
*/
|
||||
export const openLocation = ({ latitude, longitude, name } = {}) => {
|
||||
try {
|
||||
uni.openLocation({
|
||||
latitude,
|
||||
longitude,
|
||||
name,
|
||||
});
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export const CATEGORY_PAGE_URL = '/pages_goods/category';
|
||||
export const CONTACT_PAGE_URL = '/pages_tool/contact/contact';
|
||||
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
|
||||
export const LOGIN_PAGE_URL = '/pages_tool/login/login';
|
||||
export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index';
|
||||
|
||||
// 当前最新的tabBar.list (参见pages.json 中的tabBar.list 配置)
|
||||
export const systemTabBarList = [
|
||||
@@ -119,6 +120,7 @@ export default {
|
||||
CONTACT_PAGE_URL,
|
||||
INDEX_PAGE_URL,
|
||||
LOGIN_PAGE_URL,
|
||||
AI_CHAT_PAGE_URL,
|
||||
|
||||
/**
|
||||
* 页面跳转
|
||||
@@ -211,6 +213,8 @@ export default {
|
||||
// console.log(img_path)
|
||||
path = img_path;
|
||||
}
|
||||
// 修复:去除 URL 中的双斜杠,避免微信 downloadFile 域名匹配失败
|
||||
path = path.replace(/([^:]\/)\/+/g, "$1");
|
||||
}
|
||||
|
||||
// path += '?t=' + parseInt(new Date().getTime() / 1000);
|
||||
@@ -655,7 +659,6 @@ export default {
|
||||
|
||||
} else if (link.appid) {
|
||||
// 跳转其他小程序
|
||||
|
||||
uni.navigateToMiniProgram({
|
||||
appId: link.appid,
|
||||
path: link.page
|
||||
@@ -663,7 +666,6 @@ export default {
|
||||
|
||||
} else if (link.name == 'MOBILE' && !link.wap_url) {
|
||||
// 拨打电话
|
||||
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: link.mobile,
|
||||
success: (res) => {
|
||||
@@ -843,6 +845,15 @@ export default {
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 隐藏“返回首页/小房子”按钮
|
||||
* 这个函数,用到页面show, onshow 的生命周期时
|
||||
*/
|
||||
hideHomeButton() {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.hideHomeButton();
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param val 转化时间字符串 (转化时分秒)
|
||||
@@ -1202,7 +1213,7 @@ export default {
|
||||
let _isQuickApp = false;
|
||||
try {
|
||||
const ua = navigator?.userAgent?.toLowerCase();
|
||||
console.log('ua = ', ua);
|
||||
// console.log('ua = ', ua);
|
||||
_isQuickApp = ua.indexOf('quickapp') !== -1;
|
||||
|
||||
if (!_isQuickApp) {
|
||||
@@ -1210,7 +1221,7 @@ export default {
|
||||
// 但是会包含uniacid参数,所以这里通过uniacid参数来判断是否为快应用环境
|
||||
_isQuickApp = ua.indexOf('uniacid=') !== -1;
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
|
||||
if (!_isQuickApp) {
|
||||
const systemInfo = this.getDeviceInfo();
|
||||
@@ -1234,5 +1245,32 @@ export default {
|
||||
(typeof qh !== 'undefined' && qh.platform === 'huawei');
|
||||
// #endif
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 蛇形命名转驼峰命名
|
||||
* @param {string} str 蛇形命名字符串
|
||||
* @returns {string} 驼峰命名字符串
|
||||
*/
|
||||
snakeToCamel(str) {
|
||||
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
},
|
||||
|
||||
/**
|
||||
* 蛇形命名转驼峰命名(递归处理对象中的所有属性名)
|
||||
* @param {Object} obj 包含蛇形命名字符串属性的对象
|
||||
* @returns {Object} 驼峰命名字符串属性的对象
|
||||
*/
|
||||
snakeToCamelForObj(obj) {
|
||||
if (typeof obj !== 'object' || obj === null) return obj;
|
||||
if (Array.isArray(obj)) return obj.map(this.snakeToCamelForObj.bind(this));
|
||||
const newObj = {};
|
||||
for (const key in obj) {
|
||||
if (Object.hasOwnProperty.call(obj, key)) {
|
||||
const newKey = this.snakeToCamel(key);
|
||||
newObj[newKey] = this.snakeToCamelForObj(obj[key]);
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
}
|
||||
38
common/mixin/image-longpress.js
Normal file
38
common/mixin/image-longpress.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 微信小程序图片长按识别二维码 mixin
|
||||
* 自动为页面中的所有 image 组件添加 show-menu-by-longpress 属性
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 需要排除的图片类名(不需要长按识别的)
|
||||
excludeImageClass: ['no-longpress', 'swiper-image', 'banner-image']
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
this.addLongpressToImages()
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 为页面中的所有 image 添加长按识别支持
|
||||
*/
|
||||
addLongpressToImages() {
|
||||
// 获取当前页面所有 image 组件
|
||||
const images = document.querySelectorAll && document.querySelectorAll('image')
|
||||
if (images) {
|
||||
images.forEach(img => {
|
||||
// 检查是否需要排除
|
||||
const className = img.className || ''
|
||||
const shouldExclude = this.excludeImageClass.some(cls => className.includes(cls))
|
||||
if (!shouldExclude) {
|
||||
img.setAttribute('show-menu-by-longpress', 'true')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
components-diy/css/common-channel.scss
Normal file
143
components-diy/css/common-channel.scss
Normal file
@@ -0,0 +1,143 @@
|
||||
// 公共微信视频号样式
|
||||
|
||||
// CSS 变量
|
||||
:root {
|
||||
// 尺寸变量
|
||||
--channel-play-btn-size: 80rpx;
|
||||
--channel-play-btn-small-size: 60rpx;
|
||||
--channel-play-btn-icon-size: 40rpx;
|
||||
--channel-play-btn-icon-small-size: 30rpx;
|
||||
--channel-border-radius: 12rpx;
|
||||
--channel-stats-padding: 10rpx;
|
||||
--channel-info-wrap-padding: 10rpx 0;
|
||||
|
||||
// 字体变量
|
||||
--channel-name-font-size: 28rpx;
|
||||
--video-title-font-size: 28rpx;
|
||||
--video-title-small-font-size: 24rpx;
|
||||
--video-stats-font-size: 24rpx;
|
||||
--channel-stats-font-size: 20rpx;
|
||||
|
||||
// 颜色变量
|
||||
--channel-name-color: #333;
|
||||
--video-title-color: #333;
|
||||
--video-title-small-color: #666;
|
||||
--video-stats-color: #999;
|
||||
--channel-stats-color: #fff;
|
||||
--channel-border-color: #f0f0f0;
|
||||
--channel-play-btn-bg: rgba(0, 0, 0, 0.4);
|
||||
--channel-stats-bg: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
|
||||
|
||||
// 间距变量
|
||||
--channel-name-margin-bottom: 4rpx;
|
||||
--video-title-margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 375px) {
|
||||
:root {
|
||||
--channel-play-btn-size: 70rpx;
|
||||
--channel-play-btn-small-size: 50rpx;
|
||||
--channel-play-btn-icon-size: 35rpx;
|
||||
--channel-play-btn-icon-small-size: 25rpx;
|
||||
--channel-name-font-size: 24rpx;
|
||||
--video-title-font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 750px) {
|
||||
:root {
|
||||
--channel-play-btn-size: 90rpx;
|
||||
--channel-play-btn-small-size: 70rpx;
|
||||
--channel-play-btn-icon-size: 45rpx;
|
||||
--channel-play-btn-icon-small-size: 35rpx;
|
||||
--channel-name-font-size: 32rpx;
|
||||
--video-title-font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 播放按钮样式
|
||||
.channel-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--channel-play-btn-size);
|
||||
height: var(--channel-play-btn-size);
|
||||
background-color: var(--channel-play-btn-bg);
|
||||
border-radius: 50%;
|
||||
|
||||
.play-icon {
|
||||
width: var(--channel-play-btn-icon-size);
|
||||
height: var(--channel-play-btn-icon-size);
|
||||
}
|
||||
}
|
||||
|
||||
// 小尺寸播放按钮(用于列表)
|
||||
.channel-play-btn.small {
|
||||
width: var(--channel-play-btn-small-size);
|
||||
height: var(--channel-play-btn-small-size);
|
||||
|
||||
.play-icon {
|
||||
width: var(--channel-play-btn-icon-small-size);
|
||||
height: var(--channel-play-btn-icon-small-size);
|
||||
}
|
||||
}
|
||||
|
||||
// 视频统计信息
|
||||
.channel-stats {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--channel-stats-bg);
|
||||
padding: var(--channel-stats-padding);
|
||||
color: var(--channel-stats-color);
|
||||
font-size: var(--channel-stats-font-size);
|
||||
}
|
||||
|
||||
// 视频信息容器
|
||||
.channel-info-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: var(--channel-info-wrap-padding);
|
||||
|
||||
.channel-name {
|
||||
margin-bottom: var(--channel-name-margin-bottom);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: var(--channel-name-font-size);
|
||||
font-weight: 500;
|
||||
color: var(--channel-name-color);
|
||||
}
|
||||
|
||||
.video-title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: var(--video-title-small-font-size);
|
||||
color: var(--video-title-small-color);
|
||||
}
|
||||
}
|
||||
|
||||
// 视频标题
|
||||
.video-title {
|
||||
font-size: var(--video-title-font-size);
|
||||
font-weight: 500;
|
||||
color: var(--video-title-color);
|
||||
margin-bottom: var(--video-title-margin-bottom);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 视频统计信息(非绝对定位版本)
|
||||
.video-stats {
|
||||
font-size: var(--video-stats-font-size);
|
||||
color: var(--video-stats-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<view class="article-wrap" :style="warpCss">
|
||||
<view :class="['list-wrap', value.style]" :style="warpCss">
|
||||
<!-- 自动垂直滚动 -->
|
||||
<swiper class="auto-scroll-swiper" :style="swiperStyle" :vertical="swiperConfig.vertical !== false"
|
||||
<swiper class="auto-scroll-swiper" :style="[swiperStyle]" :vertical="swiperConfig.vertical !== false"
|
||||
:autoplay="swiperConfig.autoplay !== false" :circular="swiperConfig.circular !== false"
|
||||
:interval="swiperConfig.interval || 3000" :duration="swiperConfig.duration || 500"
|
||||
:display-multiple-items="safeDisplayMultipleItems">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" @click="toDetail(item)">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)">
|
||||
<view class="swiper-item-content">
|
||||
<view :class="['item', value.ornament.type]" :style="itemCss">
|
||||
<view class="article-img">
|
||||
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix"
|
||||
@error="imgError(index)" />
|
||||
@error="imgError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
<text class="title">{{ item.article_title }}</text>
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
<view class="time" :style="{ color: value.timecolor }">
|
||||
{{ audiotime }}
|
||||
</view>
|
||||
<view @click="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"
|
||||
<view @tap.stop="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"
|
||||
style="padding-top: 18rpx"></view>
|
||||
</view>
|
||||
<view class="fui-audio style3" :style="{ background: value.background }" v-else>
|
||||
<!-- <audio src="/static/audio/bgm.mp3" controls loop></audio> -->
|
||||
<view class="img">
|
||||
<image :src="$util.img(value.imageUrl)"></image>
|
||||
<image :src="$util.img(value.imageUrl)" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="name" :style="{ color: value.textcolor }">{{ value.text }}</view>
|
||||
@@ -30,7 +30,7 @@
|
||||
<!-- {{audios[value.id].audiotime}} -->
|
||||
{{ audiotime }}
|
||||
</view>
|
||||
<view @click="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"></view>
|
||||
<view @tap.stop="play()" class="start" :class="status ? 'iconj icon-07zanting' : 'iconj icon-bofang'"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
:style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
|
||||
{{ value.titleStyle.leftText }}
|
||||
</view>
|
||||
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix"></image>
|
||||
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" show-menu-by-longpress></image>
|
||||
<view class="head-content" v-if="value.titleStyle.style == 'style-1'"
|
||||
:style="{ color: value.titleStyle.textColor }">低至0元免费拿</view>
|
||||
<view class="head-right"
|
||||
:style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }"
|
||||
@click="$util.redirectTo('/pages_promotion/bargain/list')">
|
||||
@tap.stop="$util.redirectTo('/pages_promotion/bargain/list')">
|
||||
<text>{{ value.titleStyle.more }}</text>
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
|
||||
@@ -41,7 +41,7 @@
|
||||
<view class="bg">
|
||||
<view class="curr" :style="{ width: progress(item) * 2 + 'rpx' }">
|
||||
<image class="progress-bar" mode="widthFix"
|
||||
:src="$util.img('public/uniapp/bargain/progress_bar_01.png')" />
|
||||
:src="$util.img('public/uniapp/bargain/progress_bar_01.png')" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="num" v-if="item.is_bargaining">
|
||||
@@ -84,12 +84,12 @@
|
||||
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" :lazy-load="true" />
|
||||
@error="imageError(index)" :lazy-load="true" show-menu-by-longpress/>
|
||||
<image class="bg"
|
||||
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
|
||||
:src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix" />
|
||||
@@ -130,11 +130,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex)" :lazy-load="true" />
|
||||
@error="imageError(dataIndex)" :lazy-load="true" show-menu-by-longpress/>
|
||||
<image class="bg"
|
||||
v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'"
|
||||
:src="$util.img('public/uniapp/bargain/bg.png')" mode="widthFix" />
|
||||
@@ -365,7 +365,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -374,7 +374,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view data-component-name="diy-bottom-nav" v-if="tabBarList && tabBarList.list">
|
||||
<view class="tab-bar" :style="{ backgroundColor: tabBarList.backgroundColor }">
|
||||
<view class="tabbar-border"></view>
|
||||
<view class="item" v-for="(item, index) in tabBarList.list" :key="item.id" @click="redirectTo(item.link)">
|
||||
<view class="item" v-for="(item, index) in tabBarList.list" :key="item.id" @tap.stop="redirectTo(item.link)">
|
||||
<view class="bd">
|
||||
<block v-if="item.link.wap_url == '/pages_goods/cart'">
|
||||
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<view data-component-name="diy-category-item" class="item-wrap" :class="type">
|
||||
<block v-if="type == 'category' && category.child_list && category.child_list.length">
|
||||
<view class="category-adv" v-if="category.image_adv" @click="diyRedirectTo(category.link_url)">
|
||||
<image :src="$util.img(category.image_adv)" mode="widthFix" />
|
||||
<view class="category-adv" v-if="category.image_adv" @tap.stop="diyRedirectTo(category.link_url)">
|
||||
<image :src="$util.img(category.image_adv)" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
|
||||
<block v-if="value.level == 2">
|
||||
<view class="category-title">{{ category.category_name }}</view>
|
||||
<view class="category-list">
|
||||
<view class="category-item" v-for="(one, oneIndex) in category.child_list" :key="oneIndex"
|
||||
@click="$util.redirectTo('/pages_goods/list', { category_id: one.category_id })">
|
||||
@tap.stop="$util.redirectTo('/pages_goods/list', { category_id: one.category_id })">
|
||||
<view class="img-box">
|
||||
<image :src="$util.img(one.image)" mode="widthFix" />
|
||||
<image :src="$util.img(one.image)" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
<view class="name">{{ one.category_name }}</view>
|
||||
</view>
|
||||
@@ -23,9 +23,9 @@
|
||||
<view class="category-title">{{ one.category_name }}</view>
|
||||
<view class="category-list">
|
||||
<view class="category-item" v-for="(two, twoIndex) in one.child_list" :key="twoIndex"
|
||||
@click="$util.redirectTo('/pages_goods/list', { category_id: two.category_id })">
|
||||
@tap.stop="$util.redirectTo('/pages_goods/list', { category_id: two.category_id })">
|
||||
<view class="img-box">
|
||||
<image :src="$util.img(two.image)" mode="widthFix" :lazy-load="true" />
|
||||
<image :src="$util.img(two.image)" mode="widthFix" :lazy-load="true" show-menu-by-longpress />
|
||||
</view>
|
||||
<view class="name">{{ two.category_name }}</view>
|
||||
</view>
|
||||
@@ -44,22 +44,22 @@
|
||||
:class="{ 'screen-category-4': value.template == 4 }" :scroll-with-animation="true"
|
||||
:scroll-into-view="scrollIntoView">
|
||||
<view class="item" id="category-2--1" :class="{ selected: categoryId == -1 }"
|
||||
@click="selectCategory(-1)">全部</view>
|
||||
@tap.stop="selectCategory(-1)">全部</view>
|
||||
<view class="item" :id="'category-2-' + oneIndex"
|
||||
:class="{ selected: categoryId == oneIndex }" @click="selectCategory(oneIndex)"
|
||||
:class="{ selected: categoryId == oneIndex }" @tap.stop="selectCategory(oneIndex)"
|
||||
v-for="(one, oneIndex) in category.child_list" :key="oneIndex">
|
||||
{{ one.category_name }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="iconfont icon-unfold" @click="$refs.screenCategoryPopup.open()"></view>
|
||||
<view class="iconfont icon-unfold" @tap.stop="$refs.screenCategoryPopup.open()"></view>
|
||||
</view>
|
||||
<uni-popup type="top" ref="screenCategoryPopup">
|
||||
<view class="screen-category-popup" @click="$refs.screenCategoryPopup.close()">
|
||||
<view class="screen-category-popup" @tap.stop="$refs.screenCategoryPopup.close()">
|
||||
<scroll-view scroll-y="true" class="screen-category"
|
||||
:class="{ 'screen-category-4': value.template == 4 }">
|
||||
<view class="title">全部</view>
|
||||
<view class="item" :class="{ selected: categoryId == oneIndex }"
|
||||
@click="selectCategory(oneIndex)" v-for="(one, oneIndex) in category.child_list"
|
||||
@tap.stop="selectCategory(oneIndex)" v-for="(one, oneIndex) in category.child_list"
|
||||
:key="oneIndex">
|
||||
{{ one.category_name }}
|
||||
</view>
|
||||
@@ -81,13 +81,13 @@
|
||||
:data-template="value.template">
|
||||
<block v-if="goodsList.length">
|
||||
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
|
||||
<view class="goods-img" @click="toDetail(item)">
|
||||
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" />
|
||||
<view class="goods-img" @tap.stop="toDetail(item)">
|
||||
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" show-menu-by-longpress />
|
||||
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
<view class="name-wrap" @click="toDetail(item)">
|
||||
<view class="name-wrap" @tap.stop="toDetail(item)">
|
||||
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
|
||||
</view>
|
||||
<view class="price-wrap">
|
||||
@@ -99,17 +99,14 @@
|
||||
<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)">
|
||||
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
|
||||
<image :src="$util.img('public/uniapp/index/discount.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
</view>
|
||||
<view class="footer-wrap">
|
||||
@@ -120,27 +117,33 @@
|
||||
<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" @click="toDetail(item)">立即购买</view>
|
||||
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view v-if="item.goods_spec_format" class="color-base-bg select-sku" @click="selectSku(item)">
|
||||
<view v-if="item.goods_spec_format" class="color-base-bg select-sku" @tap.stop="selectSku(item)">
|
||||
<text>选规格</text>
|
||||
<text class="num-tag" v-if="item.num">{{ item.num }}</text>
|
||||
</view>
|
||||
<block v-else>
|
||||
<block v-if="cartList['goods_' + item.goods_id]&&cartList['goods_' + item.goods_id]['sku_' + item.sku_id]">
|
||||
<view class="num-action reduce" @click="reduce(item)">
|
||||
<view class="num-action reduce" @tap.stop="reduce(item)">
|
||||
<text class="iconfont icon-jian"></text>
|
||||
</view>
|
||||
<view class="num">{{ cartList['goods_' + item.goods_id]['sku_' + item.sku_id].num }}</view>
|
||||
<view class="num-action" :id="'cart-num-' + index" @click="increase($event, item)">
|
||||
<view class="num-action" :id="'cart-num-' + index" @tap.stop="increase($event, item)">
|
||||
<text class="iconfont icon-jia"></text>
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="num-action" v-else :id="'cart-num-' + index" @click="increase($event, item, 0)">
|
||||
<view class="num-action" v-else :id="'cart-num-' + index" @tap.stop="increase($event, item, 0)">
|
||||
<text class="iconfont icon-jia"></text>
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
@@ -148,17 +151,17 @@
|
||||
</block>
|
||||
</view> -->
|
||||
<!-- <view class="right-wrap" v-if="value.template == 3">
|
||||
<view class="color-base-bg select-sku" @click="toDetail(item)">立即购买</view>
|
||||
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="category-empty" v-else>
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
<!-- <view class="end-tips" ref="endTips" v-if="last && (categoryId == -1 || !category.child_list || (category.child_list && categoryId == category.child_list.length - 1))">已经到底了~</view>
|
||||
<view class="end-tips" ref="endTips" v-else @click="switchCategory('next')">
|
||||
<view class="end-tips" ref="endTips" v-else @tap.stop="switchCategory('next')">
|
||||
<text class="iconfont icon-xiangshangzhanhang"></text>
|
||||
上滑查看下一分类
|
||||
</view> -->
|
||||
@@ -173,13 +176,13 @@
|
||||
<view class="goods-list" :class="{ 'double-column': !isList, 'single-column': isList }"
|
||||
:data-template="value.template">
|
||||
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
|
||||
<view class="goods-img" @click="toDetail(item)">
|
||||
<view class="goods-img" @tap.stop="toDetail(item)">
|
||||
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"
|
||||
:lazy-load="true" />
|
||||
:lazy-load="true" show-menu-by-longpress />
|
||||
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
<view class="name-wrap" @click="toDetail(item)">
|
||||
<view class="name-wrap" @tap.stop="toDetail(item)">
|
||||
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
|
||||
</view>
|
||||
<view class="price-wrap">
|
||||
@@ -191,17 +194,13 @@
|
||||
<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)">
|
||||
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" show-menu-by-longpress />
|
||||
</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 :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
</view>
|
||||
<view class="footer-wrap">
|
||||
@@ -212,32 +211,38 @@
|
||||
<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" @click="toDetail(item)">立即购买</view>
|
||||
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view v-if="item.goods_spec_format" class="color-base-bg select-sku"
|
||||
@click="selectSku(item)">
|
||||
@tap.stop="selectSku(item)">
|
||||
<text>选规格</text>
|
||||
<text class="num-tag" v-if="item.num">{{ item.num }}</text>
|
||||
</view>
|
||||
<block v-else>
|
||||
<block
|
||||
v-if="cartList['goods_' + item.goods_id] && cartList['goods_' + item.goods_id]['sku_' + item.sku_id]">
|
||||
<view class="num-action reduce" @click="reduce(item)">
|
||||
<view class="num-action reduce" @tap.stop="reduce(item)">
|
||||
<text class="iconfont icon-jian"></text>
|
||||
</view>
|
||||
<view class="num">{{ cartList['goods_' + item.goods_id]['sku_' +
|
||||
item.sku_id].num }}</view>
|
||||
<view class="num-action" :id="'cart-num-' + index"
|
||||
@click="increase($event, item)">
|
||||
@tap.stop="increase($event, item)">
|
||||
<text class="iconfont icon-jia"></text>
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="num-action" v-else :id="'cart-num-' + index"
|
||||
@click="increase($event, item, 0)">
|
||||
@tap.stop="increase($event, item, 0)">
|
||||
<text class="iconfont icon-jia"></text>
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
@@ -245,7 +250,7 @@
|
||||
</block>
|
||||
</view>
|
||||
<view class="right-wrap" v-if="value.template == 3">
|
||||
<view class="color-base-bg select-sku" @click="toDetail(item)">立即购买</view>
|
||||
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -718,6 +723,15 @@ 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));
|
||||
@@ -1160,7 +1174,7 @@ export default {
|
||||
.right-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
justify-content: flex-end;
|
||||
|
||||
.num {
|
||||
width: auto;
|
||||
@@ -1314,7 +1328,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper-box {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -1348,7 +1362,7 @@ export default {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/deep/ .loading-layer {
|
||||
::v-deep .loading-layer {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
@@ -1384,4 +1398,25 @@ 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>
|
||||
@@ -3,9 +3,9 @@
|
||||
:style="{ height: 'calc(100vh - ' + tabBarHeight + ')' }">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <block v-if="value.template == 4">
|
||||
<view class="search-box" v-if="value.search" @click="$util.redirectTo('/pages_tool/goods/search')" :style="navbarInnerStyle">
|
||||
<view class="search-box" v-if="value.search" @tap.stop="$util.redirectTo('/pages_tool/goods/search')" :style="navbarInnerStyle">
|
||||
<view class="search-content">
|
||||
<input type="text" class="uni-input font-size-tag" maxlength="50" :placeholder="$lang('search')" confirm-type="search" @click.stop="onClickSearch()" @tap.stop="onClickSearch()" disabled="true" />
|
||||
<input type="text" class="uni-input font-size-tag" maxlength="50" :placeholder="$lang('search')" confirm-type="search" @tap.stop="onClickSearch()" disabled="true" />
|
||||
<text class="iconfont icon-sousuo3"></text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -13,61 +13,61 @@
|
||||
</block> -->
|
||||
<block v-if="value.template != 4">
|
||||
<!-- <view :style="navbarInnerStyle">商品分类</view> -->
|
||||
<view class="search-box" v-if="value.search" @click="onClickSearch()" @tap.stop="onClickSearch()"
|
||||
<view class="search-box" v-if="value.search" @tap.stop="onClickSearch()"
|
||||
:style="wxSearchHeight">
|
||||
<view class="search-content">
|
||||
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('search')"
|
||||
confirm-type="search" @click.stop="onClickSearch()" @tap.stop="onClickSearch()"
|
||||
confirm-type="search" @tap.stop="onClickSearch()"
|
||||
disabled="true" />
|
||||
<text class="iconfont icon-sousuo3"></text>
|
||||
</view>
|
||||
<view class="iconfont" :class="{ 'icon-apps': !isList, 'icon-list': isList }"
|
||||
@click.stop.prevent="changeListStyle()"></view>
|
||||
@tap.stop.prevent="changeListStyle()"></view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="search-box" v-if="value.search" @click="onClickSearch()" @tap.stop="onClickSearch()">
|
||||
<view class="search-box" v-if="value.search" @tap.stop="onClickSearch()">
|
||||
<view class="search-content">
|
||||
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('search')" confirm-type="search"
|
||||
@click.stop="onClickSearch()" @tap.stop="onClickSearch()" disabled="true" />
|
||||
@tap.stop="onClickSearch()" disabled="true" />
|
||||
<text class="iconfont icon-sousuo3"></text>
|
||||
</view>
|
||||
<view class="iconfont" :class="{ 'icon-apps': !isList, 'icon-list': isList }"
|
||||
@click.stop.prevent="changeListStyle()"></view>
|
||||
@tap.stop.prevent="changeListStyle()"></view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="template-four wx" v-if="value.template == 4">
|
||||
<scroll-view scroll-x="true" class="template-four-wrap" :scroll-with-animation="true"
|
||||
:scroll-into-view="'category-one-' + oneCategorySelect" enable-flex="true">
|
||||
<view class="category-item" :id="'category-one-' + index" v-for="(item, index) in templateFourData"
|
||||
:key="index" :class="{ select: oneCategorySelect == index }" @click="templateFourOneFn(index)">
|
||||
:key="index" :class="{ select: oneCategorySelect == index }" @tap.stop="templateFourOneFn(index)">
|
||||
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]">
|
||||
<image :src="$util.img(item.image)" mode="aspectFill" />
|
||||
<image :src="$util.img(item.image)" mode="aspectFill" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{ item.category_name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="category-item-all" @click="$refs.templateFourPopup.open()">
|
||||
<view class="category-item-all" @tap.stop="$refs.templateFourPopup.open()">
|
||||
<view class="category-item-all-wrap">
|
||||
<text class="text">展开</text>
|
||||
<image class="img" :src="$util.img('/public/uniapp/category/unfold.png')" mode="aspectFill"></image>
|
||||
<image class="img" :src="$util.img('/public/uniapp/category/unfold.png')" mode="aspectFill" show-menu-by-longpress></image>
|
||||
</view>
|
||||
</view>
|
||||
<uni-popup type="top" ref="templateFourPopup" :top="uniPopupTop">
|
||||
<view class="template-four-popup">
|
||||
<scroll-view scroll-y="true" class="template-four-scroll" enable-flex="true">
|
||||
<view class="item" :class="{ selected: oneCategorySelect == index }"
|
||||
@click="templateFourOneFn(index)" v-for="(item, index) in templateFourData" :key="index">
|
||||
@tap.stop="templateFourOneFn(index)" v-for="(item, index) in templateFourData" :key="index">
|
||||
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]">
|
||||
<image :src="$util.img(item.image)" mode="aspectFill"></image>
|
||||
<image :src="$util.img(item.image)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{
|
||||
item.category_name }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="pack-up" @click="$refs.templateFourPopup.close()">
|
||||
<view class="pack-up" @tap.stop="$refs.templateFourPopup.close()">
|
||||
<text>点击收起</text>
|
||||
<text class="iconfont icon-iconangledown-copy"></text>
|
||||
</view>
|
||||
@@ -83,7 +83,7 @@
|
||||
{ select: select == index },
|
||||
{ 'border-bottom': value.template == 4 && select + 1 === index },
|
||||
{ 'border-top': value.template == 4 && select - 1 === index }
|
||||
]" @click="switchOneCategory(index)">
|
||||
]" @tap.stop="switchOneCategory(index)">
|
||||
<view class="">{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -119,18 +119,18 @@
|
||||
</view>
|
||||
</block>
|
||||
<view class="category-empty" v-else>
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix"></image>
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
<view class="tips">暂时没有分类哦!</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="category-empty" v-else>
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix"></image>
|
||||
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
<view class="tips">暂时没有分类哦!</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="cart-box" v-if="(value.template == 2 || value.template == 4) && value.quickBuy && storeToken && categoryTree && categoryTree.length">
|
||||
<view class="left-wrap">
|
||||
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation" @click="$util.redirectTo('/pages_goods/cart')">
|
||||
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation" @tap.stop="$util.redirectTo('/pages_goods/cart')">
|
||||
<text class="iconfont icon-ziyuan1"></text>
|
||||
<view class="num" v-if="cartNumber">{{ cartNumber < 99 ? cartNumber : '99+' }}</view>
|
||||
</view>
|
||||
@@ -141,7 +141,7 @@
|
||||
<text class="unit font-size-tag price-font">.{{ cartTotalMoney[1] ? cartTotalMoney[1] : '00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right-wrap"><button type="primary" class="settlement-btn" @click="settlement">去结算</button>
|
||||
<view class="right-wrap"><button type="primary" class="settlement-btn" @tap.stop="settlement">去结算</button>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
@@ -548,7 +548,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.category-page-wrap {
|
||||
width: 100vw;
|
||||
// height: calc(100vh - var(--tab-bar-height, 0));
|
||||
@@ -818,7 +818,7 @@ export default {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/deep/ .template-four {
|
||||
::v-deep .template-four {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
|
||||
645
components-diy/diy-channel-list.vue
Normal file
645
components-diy/diy-channel-list.vue
Normal file
@@ -0,0 +1,645 @@
|
||||
<template>
|
||||
<view class="channel-list-container" :style="[componentStyle, { '--row-count': value.rowCount }]">
|
||||
<!-- 轮播模式 -->
|
||||
<swiper v-if="value.showStyle == 'carousel' && carouselConfig.type != 'hide'" :indicator-dots="isIndicatorDots"
|
||||
:autoplay="carouselConfig.autoplay || false" :interval="carouselConfig.interval || 3000"
|
||||
:duration="carouselConfig.duration || 500" :circular="carouselConfig.circular || false"
|
||||
:style="swiperHeight" class="channel-swiper">
|
||||
<swiper-item v-for="(slide, slideIndex) in swiperSlides" :key="slideIndex">
|
||||
<view :class="['swiper-slide-content', 'row1-of' + value.rowCount]">
|
||||
<view v-for="(item, index) in slide" :key="index" :class="['channel-item', value.mode]">
|
||||
<!-- 视频号视频卡片,轮播模式 -->
|
||||
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
|
||||
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
|
||||
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
|
||||
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 固定布局模式 -->
|
||||
<view v-else-if="value.showStyle == 'fixed'" :class="['channel-list', 'row1-of' + value.rowCount]">
|
||||
<view v-for="(item, index) in value.list" :key="index" class="channel-item">
|
||||
<!-- 视频号视频卡片,列表模式 -->
|
||||
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
|
||||
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
|
||||
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
|
||||
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他布局模式(如滚动布局) -->
|
||||
<scroll-view v-else
|
||||
:class="['channel-nav', value.showStyle == 'fixed' ? 'fixed-layout' : 'singleSlide', 'row1-of' + value.rowCount]"
|
||||
:scroll-x="true"
|
||||
:scroll-y="false"
|
||||
:enhanced="true"
|
||||
:bounces="false">
|
||||
<view class="uni-scroll-view-content">
|
||||
<view v-for="(item, index) in value.list" :key="index"
|
||||
:class="['channel-nav-item', value.mode, 'row1-of' + value.rowCount]">
|
||||
<!-- 视频号视频卡片,滚动模式 -->
|
||||
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
|
||||
:title-line-clamp="value.titleLineClamp" :show-play-btn="value.showPlayBtn"
|
||||
:show-view-count="value.showViewCount" :cover-style="computedCoverStyle"
|
||||
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 微信视频号列表组件
|
||||
* 支持多种布局模式,包括固定布局和滚动布局
|
||||
* 可配置列数、视频高度、标题行数等属性
|
||||
* 用于在页面中展示多个微信视频号视频
|
||||
*/
|
||||
import DiyMinx from './minx.js'
|
||||
import { wechatChannelUtil, wechatChannelConfig } from './js/wechat-channel.js'
|
||||
|
||||
export default {
|
||||
name: 'diy-channel-list',
|
||||
props: {
|
||||
/**
|
||||
* 组件配置数据
|
||||
* @type {Object}
|
||||
* @default () => ({})
|
||||
* @property {string} showStyle - 显示样式,可选值:carousel, fixed, singleSlide
|
||||
* @property {number} rowCount - 每行显示的视频数量
|
||||
* @property {Array} list - 视频列表数据
|
||||
* @property {string} componentBgColor - 组件背景色
|
||||
* @property {string} componentAngle - 组件圆角类型,可选值:round
|
||||
* @property {number} topAroundRadius - 顶部圆角半径
|
||||
* @property {number} bottomAroundRadius - 底部圆角半径
|
||||
* @property {Object} ornament - 装饰效果配置
|
||||
* @property {boolean} showViewCount - 是否显示播放量,可选值:true, false
|
||||
* @property {number} titleLineClamp - 标题显示行数
|
||||
* @property {string} aspectRatio - 视频比例,可选值:16:9, 3:4
|
||||
* @property {boolean} showPlayBtn - 是否显示播放按钮
|
||||
* @property {Object} coverStyle - 视频封面图样式
|
||||
* @property {Object} playBtnStyle - 播放按钮样式
|
||||
* @property {string} mode - 显示模式, 可选值:video, text, image, graphic
|
||||
* @property {number} imageSize - 图片尺寸(仅在特定模式下使用)
|
||||
* @property {number} pageCount - 每页显示的视频数量
|
||||
* @property {Object} carousel - 轮播配置
|
||||
* @property {string} carousel.type - 轮播类型,可选值:default, hide
|
||||
* @property {boolean} carousel.autoplay - 是否自动播放,默认false
|
||||
* @property {number} carousel.interval - 自动播放间隔,默认3000ms
|
||||
* @property {number} carousel.duration - 切换动画时长,默认500ms
|
||||
* @property {boolean} carousel.circular - 是否循环播放,默认false
|
||||
*/
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
mixins: [DiyMinx],
|
||||
data() {
|
||||
return {
|
||||
pageWidth: '', // 页面宽度
|
||||
indicatorDots: false, // 是否显示轮播指示器
|
||||
swiperCurrent: 0 // 当前轮播索引
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 组件创建时的逻辑
|
||||
// 可以在这里进行初始化操作,如获取页面宽度等
|
||||
},
|
||||
mounted() {
|
||||
// 组件挂载后添加鼠标拖拽滚动功能
|
||||
if (!['fixed', 'carousel'].includes(this.value?.showStyle)) {
|
||||
this.$nextTick(() => {
|
||||
this.addMouseDragScroll();
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 组件刷新监听
|
||||
* 当组件需要刷新时触发
|
||||
* @param {*} newValue - 新值
|
||||
*/
|
||||
componentRefresh(newValue) {
|
||||
// 监听组件刷新
|
||||
// 可以在这里处理组件刷新时的逻辑
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
carouselConfig() {
|
||||
return this.value?.carousel || {
|
||||
type: 'default',
|
||||
autoplay: false,
|
||||
interval: 3000,
|
||||
duration: 500,
|
||||
circular: true
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 组件样式
|
||||
* 根据配置动态生成样式字符串,包括背景色、圆角、阴影和边框
|
||||
* @returns {string} 样式字符串
|
||||
*/
|
||||
componentStyle() {
|
||||
const style = {};
|
||||
// 背景色
|
||||
if (this.value?.componentBgColor) {
|
||||
style.backgroundColor = this.value?.componentBgColor;
|
||||
}
|
||||
|
||||
// 圆角样式
|
||||
if (this.value?.componentAngle == 'round') {
|
||||
style.borderTopLeftRadius = (2 * this.value?.topAroundRadius) + 'rpx';
|
||||
style.borderTopRightRadius = (2 * this.value?.topAroundRadius) + 'rpx';
|
||||
style.borderBottomLeftRadius = (2 * this.value?.bottomAroundRadius) + 'rpx';
|
||||
style.borderBottomRightRadius = (2 * this.value?.bottomAroundRadius) + 'rpx';
|
||||
}
|
||||
|
||||
// 装饰效果:阴影
|
||||
style.boxShadow = this.value?.ornament?.type == 'shadow' ? '0 0 10rpx ' + this.value?.ornament?.color : '';
|
||||
// 装饰效果:边框
|
||||
style.border = this.value?.ornament?.type == 'stroke' ? '2rpx solid ' + this.value?.ornament?.color : '';
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
/**
|
||||
* 轮播高度
|
||||
* 根据模式和配置计算轮播高度
|
||||
* @returns {string} 轮播高度样式
|
||||
*/
|
||||
swiperHeight() {
|
||||
let height = 0;
|
||||
|
||||
const pageCount = this.value?.pageCount || 1;
|
||||
|
||||
// 根据不同模式计算高度
|
||||
if (this.value?.mode == 'graphic') {
|
||||
height = (49 + this.value?.imageSize) * pageCount;
|
||||
} else if (this.value?.mode == 'img') {
|
||||
height = (22 + this.value?.imageSize) * pageCount;
|
||||
} else if (this.value?.mode == 'text') {
|
||||
height = 43 * pageCount;
|
||||
} else { // 默认视频模式
|
||||
if (this.value?.aspectRatio == '16:9') {
|
||||
height = [250, 160, 140, 120][this.value.rowCount - 1];
|
||||
} else {
|
||||
height = [510, 280, 220, 180][this.value.rowCount - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return 'height:' + (2 * height) + 'rpx';
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否显示指示器
|
||||
* 根据轮播配置和列表长度判断是否显示指示器
|
||||
* @returns {boolean} 是否显示指示器
|
||||
*/
|
||||
isIndicatorDots() {
|
||||
// 当轮播类型不是隐藏,且视频数量超过一页时显示指示器
|
||||
const pageCount = this.value?.pageCount || 1;
|
||||
return this.value?.carousel?.type != 'hide' && Math.ceil(this.value?.list?.length / (pageCount * this.value?.rowCount)) > 1
|
||||
},
|
||||
|
||||
/**
|
||||
* 轮播幻灯片数据
|
||||
* 将视频列表分割成轮播所需的幻灯片数据
|
||||
* @returns {Array} 轮播幻灯片数据
|
||||
*/
|
||||
swiperSlides() {
|
||||
const slides = [];
|
||||
const list = this.value?.list || [];
|
||||
const pageSize = (this.value?.pageCount || 1) * (this.value?.rowCount || 1);
|
||||
|
||||
// 将列表数据分割成每页显示的数量
|
||||
for (let i = 0; i < list.length; i += pageSize) {
|
||||
slides.push(list.slice(i, i + pageSize));
|
||||
}
|
||||
|
||||
return slides;
|
||||
},
|
||||
/**
|
||||
* 视频封面样式
|
||||
* 根据aspectRatio属性动态计算封面样式
|
||||
* @returns {Object} 封面样式对象
|
||||
*/
|
||||
computedCoverStyle() {
|
||||
const aspectRatio = this.value?.aspectRatio || '16:9';
|
||||
let paddingTop = '56.25%'; // 默认 16:9 比例
|
||||
|
||||
if (aspectRatio === '3:4') {
|
||||
paddingTop = '133.33%'; // 3:4 比例
|
||||
}
|
||||
|
||||
return {
|
||||
width: '100%',
|
||||
height: '0',
|
||||
paddingTop: paddingTop
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 播放视频
|
||||
* 触发 video-play 事件,并在微信小程序中调用视频播放接口
|
||||
* @param {Object} item - 视频数据对象
|
||||
*/
|
||||
async playVideo(item) {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'video-play',
|
||||
data: item,
|
||||
promiseCallback: async (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
try {
|
||||
// 发送视频被点击播放事件
|
||||
this.$emit('channel-video-click-play', item);
|
||||
console.log('播放视频:', item);
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 在微信小程序环境中调用视频播放接口
|
||||
await wechatChannelUtil.playVideo(item);
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
if (item?.channelType === 'wechat') {
|
||||
uni.showToast({
|
||||
title: '视频号仅支持在微信小程序环境中播放',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
} else {
|
||||
// 在非微信小程序环境中,直接触发事件
|
||||
this.$emit('video-play', item);
|
||||
}
|
||||
// #endif
|
||||
} catch (err) {
|
||||
console.error('打开视频号失败', err);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 图片加载错误处理
|
||||
* 当图片加载失败时,设置默认图片
|
||||
* @param {number} index - 图片索引
|
||||
*/
|
||||
imgError(index) {
|
||||
// 图片加载失败的处理逻辑
|
||||
console.log('图片加载失败:', index);
|
||||
// 为失败的图片设置默认图片
|
||||
const item = this.value.list[index];
|
||||
if (item) {
|
||||
// 使用默认图片替代加载失败的图片
|
||||
// #ifdef MP-WEIXIN
|
||||
item.coverUrl = wechatChannelConfig.video.defaultCoverUrl;
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 添加鼠标拖拽滚动功能
|
||||
* 在Web环境中实现与微信小程序相同的拖拽滚动效果
|
||||
*/
|
||||
addMouseDragScroll() {
|
||||
// 只在Web环境中添加
|
||||
// #ifndef MP
|
||||
console.log('addMouseDragScroll called');
|
||||
|
||||
let isDragging = false;
|
||||
let startX = 0;
|
||||
let startScrollLeft = 0;
|
||||
let currentScrollElement = null;
|
||||
|
||||
// 查找最近的可滚动祖先元素
|
||||
const findScrollableParent = (element) => {
|
||||
while (element && element !== document) {
|
||||
const style = window.getComputedStyle(element);
|
||||
if (style.overflowX === 'auto' || style.overflowX === 'scroll') {
|
||||
return element;
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 鼠标按下事件
|
||||
const handleMouseDown = (e) => {
|
||||
// 检查是否点击在组件内
|
||||
if (this.$el.contains(e.target)) {
|
||||
console.log('mousedown event in component:', e);
|
||||
// 查找可滚动元素
|
||||
currentScrollElement = findScrollableParent(e.target);
|
||||
if (currentScrollElement) {
|
||||
console.log('Found scrollable element:', currentScrollElement);
|
||||
isDragging = true;
|
||||
startX = e.pageX;
|
||||
startScrollLeft = currentScrollElement.scrollLeft;
|
||||
currentScrollElement.style.cursor = 'grabbing';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标移动事件
|
||||
const handleMouseMove = (e) => {
|
||||
if (!isDragging || !currentScrollElement) return;
|
||||
console.log('mousemove event:', e);
|
||||
e.preventDefault();
|
||||
const dx = e.pageX - startX;
|
||||
currentScrollElement.scrollLeft = startScrollLeft - dx;
|
||||
console.log('scrollLeft:', currentScrollElement.scrollLeft);
|
||||
};
|
||||
|
||||
// 鼠标释放事件
|
||||
const handleMouseUp = () => {
|
||||
if (isDragging && currentScrollElement) {
|
||||
console.log('mouseup event');
|
||||
currentScrollElement.style.cursor = 'grab';
|
||||
}
|
||||
isDragging = false;
|
||||
currentScrollElement = null;
|
||||
};
|
||||
|
||||
// 添加全局事件监听器
|
||||
document.addEventListener('mousedown', handleMouseDown);
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.addEventListener('mouseleave', handleMouseUp);
|
||||
|
||||
console.log('Global mouse event listeners added');
|
||||
|
||||
// 组件销毁时移除事件监听器
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('mousedown', handleMouseDown);
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.removeEventListener('mouseleave', handleMouseUp);
|
||||
console.log('Global mouse event listeners removed');
|
||||
});
|
||||
|
||||
// 为所有.channel-nav元素添加必要的样式
|
||||
setTimeout(() => {
|
||||
const channelNavs = document.querySelectorAll('.channel-nav');
|
||||
console.log('Found channel-nav elements:', channelNavs.length);
|
||||
channelNavs.forEach(element => {
|
||||
element.style.overflowX = 'auto';
|
||||
element.style.overflowY = 'hidden';
|
||||
element.style.whiteSpace = 'nowrap';
|
||||
element.style.width = '100%';
|
||||
element.style.maxWidth = '100%';
|
||||
element.style.cursor = 'grab';
|
||||
element.style.userSelect = 'none'; // 防止文本选择
|
||||
console.log('Added styles to channel-nav:', element);
|
||||
});
|
||||
}, 100); // 延迟执行,确保DOM已完全渲染
|
||||
|
||||
console.log('Mouse drag scroll setup completed');
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './css/common-channel.scss';
|
||||
|
||||
.channel-list-container {
|
||||
padding: 16px 16px 0px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表布局样式
|
||||
*/
|
||||
.channel-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--row-count, 2), 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航布局样式
|
||||
* 支持固定布局和滚动布局
|
||||
*/
|
||||
.channel-nav {
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx;
|
||||
|
||||
// 确保在H5环境中可以水平滚动
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
// 隐藏滚动条但保留滚动功能
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
// 启用触摸滚动和鼠标拖拽滚动
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
.uni-scroll-view-content {
|
||||
/* #ifdef MP-WEIXIN */
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 16rpx;
|
||||
/* #endif */
|
||||
/* #ifndef MP-WEIXIN */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--row-count, 2), 1fr);
|
||||
gap: 16rpx;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// 单滑动模式
|
||||
&.singleSlide {
|
||||
.uni-scroll-view-content {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 16rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.channel-nav-item {
|
||||
flex-shrink: 0;
|
||||
width: 280rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
&.row1-of1 {
|
||||
width: 100%;
|
||||
}
|
||||
&.row1-of2 {
|
||||
width: calc(50% - 8rpx);
|
||||
}
|
||||
&.row1-of3 {
|
||||
width: calc(33.333% - 10.67rpx);
|
||||
}
|
||||
&.row1-of4 {
|
||||
width: calc(25% - 12rpx);
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// 1 列布局
|
||||
&.row1-of1 {
|
||||
.uni-scroll-view-content {
|
||||
// #ifndef MP-WEIXIN
|
||||
grid-template-columns: 1fr;
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
// 2 列布局
|
||||
&.row1-of2 {
|
||||
.uni-scroll-view-content {
|
||||
// #ifndef MP-WEIXIN
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
// #endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 3 列布局
|
||||
&.row1-of3 {
|
||||
.uni-scroll-view-content {
|
||||
// #ifndef MP-WEIXIN
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
// #endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 4 列布局
|
||||
&.row1-of4 {
|
||||
.uni-scroll-view-content {
|
||||
// #ifndef MP-WEIXIN
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
// #endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保所有视频卡片高度一致
|
||||
*/
|
||||
.channel-item,
|
||||
.channel-nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮播样式
|
||||
* 支持轮播模式的布局和样式
|
||||
*/
|
||||
.channel-swiper {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.swiper-slide-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--row-count, 2), 1fr);
|
||||
gap: 16rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.channel-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 轮播模式下的 1 列布局
|
||||
&.row1-of1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
// 轮播模式下的 2 列布局
|
||||
&.row1-of2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
// 轮播模式下的 3 列布局
|
||||
&.row1-of3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
// 轮播模式下的 4 列布局
|
||||
&.row1-of4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式调整
|
||||
* 在小屏幕设备上调整布局和间距
|
||||
*/
|
||||
@media (max-width: 375px) {
|
||||
|
||||
.channel-list,
|
||||
.channel-nav,
|
||||
.channel-swiper .swiper-slide-content {
|
||||
gap: 12rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
// 小屏幕上的单滑动模式
|
||||
.channel-nav {
|
||||
&.singleSlide {
|
||||
.uni-scroll-view-content {
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.channel-nav-item {
|
||||
width: 240rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频比例样式调整
|
||||
* 根据不同的视频比例调整布局
|
||||
*/
|
||||
|
||||
// // 3:4 比例的视频卡片样式
|
||||
// .channel-video.ratio-3-4 {
|
||||
// // 调整视频卡片的整体高度
|
||||
// ::v-deep .video-cover-wrap {
|
||||
// padding-top: 133.33%; // 3:4 比例
|
||||
// }
|
||||
|
||||
// // 列表模式下的3:4比例调整
|
||||
// &.list-mode {
|
||||
// ::v-deep .video-cover-wrap {
|
||||
// padding-top: 133.33%; // 3:4 比例
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 16:9 比例的视频卡片样式(默认)
|
||||
// .channel-video.ratio-16-9 {
|
||||
// // 保持默认的16:9比例
|
||||
// ::v-deep .video-cover-wrap {
|
||||
// padding-top: 56.25%; // 16:9 比例
|
||||
// }
|
||||
|
||||
// // 列表模式下的16:9比例保持默认
|
||||
// &.list-mode {
|
||||
// ::v-deep .video-cover-wrap {
|
||||
// padding-top: 56.25%; // 16:9 比例
|
||||
// }
|
||||
// }
|
||||
// }</style>
|
||||
489
components-diy/diy-channel-video.vue
Normal file
489
components-diy/diy-channel-video.vue
Normal file
@@ -0,0 +1,489 @@
|
||||
<template>
|
||||
<view class="channel-video" :class="{ 'list-mode': listMode }">
|
||||
<!-- 嵌入式视频播放 -->
|
||||
<view v-if="canUseEmbedMode" class="embed-video-container">
|
||||
<view class="video-cover-wrap" :style="[coverStyle]">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<native-component>
|
||||
<!-- 嵌入式视频播放组件 -->
|
||||
<channel-video :feed-id="value.feedId" :finder-user-name="value.finderUserName"
|
||||
:feed-token="value.feedToken" :auto-play="autoPlay">
|
||||
</channel-video>
|
||||
</native-component>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="video-info">
|
||||
<!-- 视频标题,支持多行显示控制 -->
|
||||
<view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
|
||||
</view>
|
||||
<!-- 视频统计信息 -->
|
||||
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 跳转式视频播放 -->
|
||||
<view v-else @tap.stop="playVideo" class="video-container">
|
||||
<view class="video-cover-wrap" :style="[coverStyle]">
|
||||
<image class="video-cover" :src="$util.img(value.coverUrl)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="channel-play-btn" v-if="showPlayBtn" :style="[playBtnStyle]">
|
||||
<view class="play-icon-css"></view>
|
||||
</view>
|
||||
<view class="video-stats-overlay" v-if="showViewCount && value.showViewCount">
|
||||
{{ value.viewCount }}次观看
|
||||
</view>
|
||||
</view>
|
||||
<view class="video-info">
|
||||
<!-- 视频标题,支持多行显示控制 -->
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { wechatChannelUtil, wechatChannelConfig } from './js/wechat-channel.js'
|
||||
|
||||
/**
|
||||
* 微信视频号视频卡片组件
|
||||
* 支持嵌入式播放和跳转式播放两种模式
|
||||
* 可配置列表模式、视频高度、标题行数等属性
|
||||
*/
|
||||
export default {
|
||||
name: 'diy-channel-video',
|
||||
props: {
|
||||
/**
|
||||
* 视频数据对象
|
||||
* @type {Object}
|
||||
* @required
|
||||
* @property {string} feedId - 视频 feedId
|
||||
* @property {string} feedToken - 视频 token
|
||||
* @property {string} coverUrl - 视频封面图
|
||||
* @property {string} videoTitle - 视频标题
|
||||
* @property {number} viewCount - 观看次数
|
||||
* @property {boolean} showViewCount - 是否显示观看次数
|
||||
* @property {boolean} embedMode - 是否启用嵌入式播放
|
||||
*/
|
||||
value: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 是否为列表模式
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
*/
|
||||
listMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 是否显示观看次数,由父组件可以整体配置 */
|
||||
showViewCount: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 标题显示行数
|
||||
* @type {number}
|
||||
* @default 1
|
||||
*/
|
||||
titleLineClamp: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/** 是否显示播放按钮 */
|
||||
showPlayBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 视频封面图样式
|
||||
* 采用 16:9 比例的响应式高度
|
||||
*/
|
||||
coverStyle: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
width: '100%',
|
||||
height: '0',
|
||||
paddingTop: '56.25%' // 16:9 比例
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 播放按钮样式
|
||||
*/
|
||||
playBtnStyle: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
width: '60rpx',
|
||||
height: '60rpx',
|
||||
borderRadius: '30rpx',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)'
|
||||
})
|
||||
},
|
||||
/** 是否自动播放 */
|
||||
autoPlay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 是否支持嵌入式播放
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canUseEmbedMode() {
|
||||
// #ifdef MP-WEIXIN
|
||||
const enableEmbedMode = Boolean(this.value?.embedMode)
|
||||
&& Boolean(this.value?.feedToken)
|
||||
&& wechatChannelUtil.isEmbedModeSupported();
|
||||
console.log('enableEmbedMode', enableEmbedMode);
|
||||
return enableEmbedMode;
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 播放视频
|
||||
* 触发 video-play 事件,由父组件处理具体播放逻辑
|
||||
*/
|
||||
async playVideo() {
|
||||
this.$emit('video-play', this.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './css/common-channel.scss';
|
||||
|
||||
/**
|
||||
* 视频卡片容器样式
|
||||
* 包含卡片基础样式、悬停效果和列表模式样式
|
||||
*/
|
||||
.channel-video {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
// 悬停效果
|
||||
&:hover {
|
||||
transform: translateY(-4rpx);
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
// 列表模式样式调整
|
||||
&.list-mode {
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.video-info {
|
||||
padding: 14rpx;
|
||||
|
||||
.video-title {
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频容器样式
|
||||
* 用于跳转式播放模式
|
||||
*/
|
||||
.video-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* 嵌入式视频容器样式
|
||||
* 与视频容器样式保持一致
|
||||
*/
|
||||
.embed-video-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频封面容器
|
||||
*/
|
||||
.video-cover-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-top: 56.25%;
|
||||
/* 16:9 比例 */
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12rpx 12rpx 0 0;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
// 悬停时缩放效果
|
||||
.channel-video:hover & {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
// 列表模式下的边框圆角调整
|
||||
.channel-video.list-mode & {
|
||||
border-radius: 10rpx 10rpx 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频封面图片
|
||||
* 绝对定位填充整个容器
|
||||
*/
|
||||
.video-cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频统计信息遮罩
|
||||
* 显示在视频封面底部
|
||||
*/
|
||||
.video-stats-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||
padding: 12rpx 16rpx;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
// 悬停时的透明度变化
|
||||
.channel-video:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.channel-video:not(:hover) & {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频信息区域
|
||||
* 包含标题和统计信息
|
||||
*/
|
||||
.video-info {
|
||||
padding: 16rpx;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
// 悬停时的背景色变化
|
||||
.channel-video:hover & {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频标题
|
||||
* 支持多行显示控制和渐变遮罩效果
|
||||
*/
|
||||
.video-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 10rpx;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--title-line-clamp, 2);
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.3s ease;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
|
||||
// 悬停时的颜色变化
|
||||
.channel-video:hover & {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
// 列表模式下的样式调整
|
||||
.channel-video.list-mode & {
|
||||
-webkit-line-clamp: var(--title-line-clamp, 1);
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 6rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
// 添加渐变遮罩效果,让过长的文字有柔和的结束
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 1.4em;
|
||||
background: linear-gradient(to right, transparent, #fff 90%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// 列表模式下的遮罩高度调整
|
||||
.channel-video.list-mode &::after {
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
// 根据行数调整遮罩高度
|
||||
&[style*="--title-line-clamp: 1"]::after {
|
||||
height: 1.4em;
|
||||
}
|
||||
|
||||
&[style*="--title-line-clamp: 3"]::after {
|
||||
height: 4.2em;
|
||||
}
|
||||
|
||||
&[style*="--title-line-clamp: 4"]::after {
|
||||
height: 5.6em;
|
||||
}
|
||||
|
||||
// 列表模式下的多行遮罩高度调整
|
||||
.channel-video.list-mode &[style*="--title-line-clamp: 1"]::after {
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
.channel-video.list-mode &[style*="--title-line-clamp: 3"]::after {
|
||||
height: 3.9em;
|
||||
}
|
||||
|
||||
.channel-video.list-mode &[style*="--title-line-clamp: 4"]::after {
|
||||
height: 5.2em;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频统计信息
|
||||
* 显示观看次数等数据
|
||||
*/
|
||||
.video-stats {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
// 悬停时的颜色变化
|
||||
.channel-video:hover & {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表模式下的播放按钮样式
|
||||
* 更小的尺寸和悬停效果
|
||||
*/
|
||||
.channel-video.list-mode .channel-play-btn {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// 悬停效果
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
// 播放图标尺寸
|
||||
.play-icon {
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用播放按钮样式优化
|
||||
* 添加悬停效果
|
||||
*/
|
||||
.channel-play-btn {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// 悬停效果
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 纯 CSS 播放按钮图标
|
||||
*/
|
||||
.play-icon-css {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 12rpx solid transparent;
|
||||
border-bottom: 12rpx solid transparent;
|
||||
border-left: 18rpx solid #fff;
|
||||
margin-left: 4rpx;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
// 悬停时的缩放效果
|
||||
.channel-play-btn:hover & {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
// 列表模式下的尺寸调整
|
||||
.channel-video.list-mode & {
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-left: 15rpx solid #fff;
|
||||
margin-left: 3rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 原生组件样式控制
|
||||
* 确保 native-component 和 channel-video 正确填充容器
|
||||
*/
|
||||
native-component {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* channel-video 组件样式控制
|
||||
* 避免受到 wx-channel-video 全局样式的影响
|
||||
*/
|
||||
channel-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,7 @@
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/style1-bg.png') + ')',
|
||||
marginRight: couponItemHeight + 'px',
|
||||
marginLeft: couponItemHeight + 'px'
|
||||
}" @click="couponAction(item, index)">
|
||||
}" @tap.stop="couponAction(item, index)">
|
||||
|
||||
<view class="coupon-info">
|
||||
<view class="coupon-num" :style="{ color: value.moneyColor }"
|
||||
@@ -52,7 +52,7 @@
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/coupon_bg1.png') + ')',
|
||||
marginRight: couponItemHeight + 'px',
|
||||
marginLeft: couponItemHeight + 'px'
|
||||
}" @click="couponAction(item, index)">
|
||||
}" @tap.stop="couponAction(item, index)">
|
||||
<view class="coupon-info">
|
||||
<view class="coupon-num" :style="{ color: value.moneyColor }"
|
||||
v-if="!parseInt(item.discount)">
|
||||
@@ -87,7 +87,7 @@
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/coupon_shu.png') + ')',
|
||||
marginRight: couponItemHeight + 'px',
|
||||
marginLeft: couponItemHeight + 'px'
|
||||
}" @click="couponAction(item, index)">
|
||||
}" @tap.stop="couponAction(item, index)">
|
||||
<view class="coupon-num" :style="{ color: value.moneyColor }"
|
||||
v-if="!parseInt(item.discount)">
|
||||
<text class="font-size-tag coupon-sign">¥</text>
|
||||
@@ -124,7 +124,7 @@
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/style4_bg.png') + ')',
|
||||
marginRight: couponItemHeight + 'px',
|
||||
marginLeft: couponItemHeight + 'px'
|
||||
}" @click="couponAction(item, index)">
|
||||
}" @tap.stop="couponAction(item, index)">
|
||||
<view class="coupon-info">
|
||||
<view class="coupon-num" :style="{ color: value.moneyColor }"
|
||||
v-if="!parseInt(item.discount)">
|
||||
@@ -153,7 +153,7 @@
|
||||
<view class="coupon-all">
|
||||
<view class="coupon-box">
|
||||
<view class="coupon-list" v-for="(item, index) in computedCouponList" :key="index"
|
||||
@click="couponAction(item, index)">
|
||||
@tap.stop="couponAction(item, index)">
|
||||
<image :src="$util.img('public/uniapp/coupon/style5_bg.png')"></image>
|
||||
<view class="coupon">
|
||||
<view class="coupon-info">
|
||||
@@ -199,7 +199,7 @@
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/style6-bg-1.png') + ')',
|
||||
marginRight: couponItemHeight + 'px',
|
||||
marginLeft: couponItemHeight + 'px'
|
||||
}" @click="couponAction(item, index)">
|
||||
}" @tap.stop="couponAction(item, index)">
|
||||
<view class="coupon-content">
|
||||
<view class="price-wrap">
|
||||
<text class="price" :style="{ color: value.moneyColor }">{{ (item.discount == '0.00'
|
||||
@@ -229,7 +229,7 @@
|
||||
<text class="limit" :style="{ color: value.limitColor }" v-else>无门槛使用</text>
|
||||
</view>
|
||||
|
||||
<div v-if="computedCouponList.length <= 2" @click="$util.redirectTo('/pages_goods/category')"
|
||||
<div v-if="computedCouponList.length <= 2" @tap.stop="$util.redirectTo('/pages_goods/category')"
|
||||
class="coupon coupon-null" :style="{
|
||||
color: value.moneyColor,
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/coupon/style6-bg-2.png') + ')',
|
||||
@@ -250,7 +250,7 @@
|
||||
<scroll-view class="coupon-style-seven" scroll-x="true">
|
||||
<view class="wrap">
|
||||
<view class="coupon-list" v-for="(item, index) in computedCouponList" :key="index"
|
||||
@click="couponAction(item, index)">
|
||||
@tap.stop="couponAction(item, index)">
|
||||
<image :src="$util.img('public/uniapp/coupon/style7_bg.png')"></image>
|
||||
<view class="coupon">
|
||||
<view class="coupon-info">
|
||||
@@ -488,7 +488,7 @@ export default {
|
||||
}
|
||||
|
||||
// 风格一
|
||||
/deep/.coupon-style-one {
|
||||
::v-deep .coupon-style-one {
|
||||
height: 110rpx;
|
||||
|
||||
.coupon-item-box {
|
||||
@@ -534,7 +534,7 @@ export default {
|
||||
}
|
||||
|
||||
// 风格二
|
||||
/deep/.coupon-style-two {
|
||||
::v-deep .coupon-style-two {
|
||||
height: 96rpx;
|
||||
|
||||
.coupon-item-box {
|
||||
@@ -591,7 +591,7 @@ export default {
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
/deep/.coupon-style-three {
|
||||
::v-deep .coupon-style-three {
|
||||
height: 284rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
@@ -638,7 +638,7 @@ export default {
|
||||
}
|
||||
|
||||
// 风格四
|
||||
/deep/.coupon-style-four {
|
||||
::v-deep .coupon-style-four {
|
||||
height: 108rpx;
|
||||
|
||||
.coupon-item-box {
|
||||
@@ -997,7 +997,7 @@ export default {
|
||||
}
|
||||
|
||||
//风格七
|
||||
/deep/ .coupon-style-seven {
|
||||
::v-deep .coupon-style-seven {
|
||||
.wrap {
|
||||
display: flex;
|
||||
}
|
||||
@@ -1075,7 +1075,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -1084,7 +1084,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<view data-component-name="diy-fenxiao-goods-list" class="diy-fenxiao" v-if="list.length"
|
||||
:class="['goods-list', value.template, value.style]" :style="goodsListWarpCss">
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="goods-img" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)" />
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix" @error="imgError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="info-wrap"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
|
||||
@@ -32,14 +32,14 @@
|
||||
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
|
||||
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
|
||||
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
|
||||
}" @click.stop="followGoods(item, index)">
|
||||
}" @tap.stop="followGoods(item, index)">
|
||||
关注
|
||||
</view>
|
||||
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 1" :style="{
|
||||
background: value.btnStyle.theme == 'diy' ? 'linear-gradient(to right,' + value.btnStyle.bgColorStart + ',' + value.btnStyle.bgColorEnd + ')' : '',
|
||||
color: value.btnStyle.theme == 'diy' ? value.btnStyle.textColor : '',
|
||||
borderRadius: value.btnStyle.aroundRadius * 2 + 'rpx'
|
||||
}" @click.stop="delFollowTip(item, index)">
|
||||
}" @tap.stop="delFollowTip(item, index)">
|
||||
取消关注
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
:class="{ left_top: value.bottomPosition == 1, right_top: value.bottomPosition == 2, left_bottom: value.bottomPosition == 3, right_bottom: value.bottomPosition == 4 }"
|
||||
:style="style">
|
||||
<block v-for="(item, index) in value.list" :key="index">
|
||||
<view class="button-box" @click="$util.diyRedirectTo(item.link)"
|
||||
<view class="button-box" @tap.stop="$util.diyRedirectTo(item.link)"
|
||||
:style="{ width: value.imageSize + 'px', height: value.imageSize + 'px', fontSize: value.imageSize + 'px' }">
|
||||
<image v-if="!item.iconType || item.iconType == 'img'" :src="$util.img(item.imageUrl)" mode="aspectFit"
|
||||
:show-menu-by-longpress="true" />
|
||||
show-menu-by-longpress />
|
||||
<diy-icon v-else-if="item.iconType && item.iconType == 'icon'" :icon="item.icon"
|
||||
:value="item.style ? item.style : null"></diy-icon>
|
||||
</view>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view @click="submitform" class="fui-btn btn-danger block mtop">提交信息</view>
|
||||
<view @tap.stop="submitform" class="fui-btn btn-danger block mtop">提交信息</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<view class="ul-wrap">
|
||||
<view class="li-item" v-for="(item, index) in list" :key="index">
|
||||
<image class="brand-pic" :src="$util.img(item.image_url)" mode="aspectFit"
|
||||
@click="handlerClick(item)" @tap="handlerClick(item)" @error="imgError(index)"
|
||||
:style="itemCss" />
|
||||
@tap.stop="handlerClick(item)" @error="imgError(index)"
|
||||
:style="itemCss" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
<x-skeleton data-component-name="diy-goods-list" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
|
||||
<view :class="['goods-list', goodsValue.template, goodsValue.style]" :style="goodsListWarpCss">
|
||||
<template v-if="goodsValue.template != 'horizontal-slide'">
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @click="handlerClick(item)"
|
||||
@tap="handlerClick(item)" :class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
|
||||
:class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<view class="goods-img-wrap">
|
||||
<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' }" />
|
||||
:style="{ borderRadius: goodsValue.imgAroundRadius * 2 + 'rpx' }"
|
||||
show-menu-by-longpress />
|
||||
<view class="sell-out" v-if="item.stock <= 0">
|
||||
<text class="iconfont icon-shuqing"></text>
|
||||
</view>
|
||||
@@ -48,9 +49,9 @@
|
||||
<view class="member-price"
|
||||
v-if="(item.member_price && item.member_price == showPrice(item)) || item.promotion_type == 1">
|
||||
<image v-if="item.member_price && item.member_price == showPrice(item)"
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" />
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" show-menu-by-longpress />
|
||||
<image v-else-if="item.promotion_type == 1"
|
||||
:src="$util.img('public/uniapp/index/discount.png')" />
|
||||
:src="$util.img('public/uniapp/index/discount.png')" show-menu-by-longpress />
|
||||
</view>
|
||||
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(item)"
|
||||
class="delete-price price-font"
|
||||
@@ -70,7 +71,7 @@
|
||||
color: goodsValue.btnStyle.theme == 'diy' ? goodsValue.btnStyle.textColor : '',
|
||||
borderColor: goodsValue.btnStyle.theme == 'diy' ? goodsValue.btnStyle.textColor : ''
|
||||
}" class="cart shopping-cart-btn iconfont icon-gouwuche click-wrap" :id="'goods-' + item.id"
|
||||
@click.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
@tap.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
|
||||
@@ -79,7 +80,7 @@
|
||||
color: goodsValue.btnStyle.theme == 'diy' ? goodsValue.btnStyle.textColor : '',
|
||||
borderColor: goodsValue.btnStyle.theme == 'diy' ? goodsValue.btnStyle.textColor : ''
|
||||
}" class="cart plus-sign-btn iconfont icon-add1 click-wrap" :id="'goods-' + item.id"
|
||||
@click.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
@tap.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
<view class="click-event"></view>
|
||||
</view>
|
||||
|
||||
@@ -90,7 +91,7 @@
|
||||
fontWeight: goodsValue.btnStyle.theme == 'diy' ? (goodsValue.btnStyle.fontWeight ? 'bold' : 'normal') : '',
|
||||
padding: goodsValue.btnStyle.theme == 'diy' ? '0 ' + goodsValue.btnStyle.padding * 2 + 'rpx' : ''
|
||||
}" class="cart buy-btn click-wrap" :id="'goods-' + item.id"
|
||||
@click.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
@tap.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
{{ goodsValue.btnStyle.text }}
|
||||
<view class="click-event"></view>
|
||||
<!-- <text class="cart-num" v-if="cartList['goods_' + item.goods_id]">{{ cartList['goods_' + item.goods_id].num }}</text> -->
|
||||
@@ -100,7 +101,7 @@
|
||||
<view v-else-if="goodsValue.btnStyle.style == 'icon-diy'" :style="{
|
||||
color: goodsValue.btnStyle.theme == 'diy' ? goodsValue.btnStyle.textColor : ''
|
||||
}" class="icon-diy click-wrap" :id="'goods-' + item.id"
|
||||
@click.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
@tap.stop="$refs.goodsSkuIndex.addCart(goodsValue.btnStyle.cartEvent, item, $event)">
|
||||
<view class="click-event"></view>
|
||||
<diy-icon :icon="goodsValue.btnStyle.iconDiy.icon"
|
||||
:value="goodsValue.btnStyle.iconDiy.style ? goodsValue.btnStyle.iconDiy.style : null"></diy-icon>
|
||||
@@ -112,12 +113,12 @@
|
||||
</template>
|
||||
<scroll-view v-if="goodsValue.template == 'horizontal-slide' && goodsValue.slideMode == 'scroll'"
|
||||
class="scroll" :scroll-x="true">
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @click="handlerClick(item)"
|
||||
@tap="handlerClick(item)" :class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<view class="goods-item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
|
||||
:class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<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" />
|
||||
:lazy-load="true" show-menu-by-longpress />
|
||||
<view class="sell-out" v-if="item.stock <= 0">
|
||||
<text class="iconfont icon-shuqing"></text>
|
||||
</view>
|
||||
@@ -156,9 +157,9 @@
|
||||
<view class="member-price"
|
||||
v-if="(item.member_price && item.member_price == showPrice(item)) || item.promotion_type == 1">
|
||||
<image v-if="item.member_price && item.member_price == showPrice(item)"
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" />
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" show-menu-by-longpress />
|
||||
<image v-else-if="item.promotion_type == 1"
|
||||
:src="$util.img('public/uniapp/index/discount.png')" />
|
||||
:src="$util.img('public/uniapp/index/discount.png')" show-menu-by-longpress />
|
||||
</view>
|
||||
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(item)"
|
||||
class="delete-price price-font"
|
||||
@@ -179,12 +180,12 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="goods-item" v-for="(dataItem, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="handlerClick(dataItem)" @tap="handlerClick(dataItem)"
|
||||
@tap.stop="handlerClick(dataItem)"
|
||||
:class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<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" />
|
||||
@error="imgError(dataIndex)" :lazy-load="true" show-menu-by-longpress />
|
||||
<view class="sell-out" v-if="dataItem.stock <= 0">
|
||||
<text class="iconfont icon-shuqing"></text>
|
||||
</view>
|
||||
@@ -226,9 +227,9 @@
|
||||
v-if="(dataItem.member_price && dataItem.member_price == showPrice(dataItem)) || dataItem.promotion_type == 1">
|
||||
<image
|
||||
v-if="dataItem.member_price && dataItem.member_price == showPrice(dataItem)"
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" />
|
||||
:src="$util.img('public/uniapp/index/VIP.png')" show-menu-by-longpress />
|
||||
<image v-else-if="dataItem.promotion_type == 1"
|
||||
:src="$util.img('public/uniapp/index/discount.png')" />
|
||||
:src="$util.img('public/uniapp/index/discount.png')" show-menu-by-longpress />
|
||||
</view>
|
||||
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(dataItem)"
|
||||
class="delete-price"
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
<swiper-item v-for="(item, index) in page" :key="index"
|
||||
:class="['swiper-item', [list[index].length / 3] >= 1 && 'flex-between']">
|
||||
<view class="goods-item" v-for="(dataItem, dataIndex) in list[index]" :key="dataIndex"
|
||||
@click="toDetail(dataItem)" :class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(dataItem)" :class="[goodsValue.ornament.type]" :style="goodsItemCss">
|
||||
<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" />
|
||||
@error="imgError(index, dataIndex)" :lazy-load="true" show-menu-by-longpress />
|
||||
<view class="sell-out" v-if="dataItem.stock <= 0">
|
||||
<text class="iconfont icon-shuqing"></text>
|
||||
</view>
|
||||
@@ -33,7 +33,7 @@
|
||||
<view class="label-wrap" v-if="goodsValue.labelStyle.support"
|
||||
:style="{ background: goodsValue.labelStyle.bgColor, color: goodsValue.labelStyle.color }">
|
||||
<image :src="$util.img('app/component/view/goods_recommend/img/label.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress />
|
||||
<text>{{ goodsValue.labelStyle.title }}</text>
|
||||
</view>
|
||||
<view class="discount-price">
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list"
|
||||
:key="index"
|
||||
v-if="index >= [(numItem) * (value.pageCount * value.rowCount)] && index < [(numItem + 1) * (value.pageCount * value.rowCount)]"
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @click="redirectTo(item.link)">
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list"
|
||||
:key="index"
|
||||
v-if="index >= [(numItem - 1) * (value.pageCount * value.rowCount)] && index < [numItem * (value.pageCount * value.rowCount)]"
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @click="redirectTo(item.link)">
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
|
||||
<!-- #endif -->
|
||||
<view class="graphic-img" v-if="value.mode != 'text'"
|
||||
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
|
||||
@@ -23,7 +23,7 @@
|
||||
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
mode="aspectFill"
|
||||
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', borderRadius: value.aroundRadius * 2 + 'rpx' }"
|
||||
:show-menu-by-longpress="true" />
|
||||
show-menu-by-longpress />
|
||||
|
||||
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
|
||||
:value="item.style ? item.style : null"
|
||||
@@ -62,14 +62,14 @@
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="graphic-nav-item" :class="[value.mode]" v-for="(item, index) in value.list" :key="index"
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @click="redirectTo(item.link)">
|
||||
:style="{ width: 100 / value.rowCount + '%' }" @tap.stop="redirectTo(item.link)">
|
||||
<view class="graphic-img" v-if="value.mode != 'text'"
|
||||
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
|
||||
<image v-if="item.iconType == 'img'"
|
||||
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
mode="aspectFill"
|
||||
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', borderRadius: value.aroundRadius * 2 + 'rpx' }"
|
||||
:show-menu-by-longpress="true" />
|
||||
show-menu-by-longpress />
|
||||
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
|
||||
:value="item.style ? item.style : null"
|
||||
:style="{ maxWidth: value.imageSize * 2 + 'rpx', maxHeight: value.imageSize * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
|
||||
|
||||
@@ -251,14 +251,14 @@
|
||||
<diy-icon :value="item"></diy-icon>
|
||||
</template>
|
||||
|
||||
<template v-if="item.componentName == 'WechatChannel'">
|
||||
<!-- 微信视频号 -->
|
||||
<diy-wechat-channel :value="item"></diy-wechat-channel>
|
||||
<template v-if="item.componentName == 'Tab'">
|
||||
<!-- Tab 组件 -->
|
||||
<diy-tab :value="item" :diyGlobal="diyGlobalData"></diy-tab>
|
||||
</template>
|
||||
|
||||
<template v-if="item.componentName == 'WechatChannelList'">
|
||||
<!-- 微信视频号列表 -->
|
||||
<diy-wechat-channel-list :value="item"></diy-wechat-channel-list>
|
||||
<template v-if="['ChannelList', 'WechatChannel'].includes(item.componentName)">
|
||||
<!-- 视频号列表 -->
|
||||
<diy-channel-list :value="item"></diy-channel-list>
|
||||
</template>
|
||||
|
||||
<!-- 自定义扩展组件 -->
|
||||
@@ -272,6 +272,7 @@
|
||||
import DiyMinx from './minx.js'
|
||||
|
||||
export default {
|
||||
name: 'diy-group',
|
||||
props: {
|
||||
diyData: {
|
||||
type: Object
|
||||
@@ -316,7 +317,12 @@ 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') {
|
||||
@@ -351,6 +357,12 @@ export default {
|
||||
}
|
||||
});
|
||||
} else data = this.setPagestyle;
|
||||
|
||||
console.log(`diy-group ['diyDataArray'] = `, {
|
||||
data: data,
|
||||
diyData: this.diyData,
|
||||
diyGlobalData: this.diyGlobalData,
|
||||
})
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<x-skeleton data-component-name="diy-groupbuy" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
|
||||
<view class="diy-groupbuy" :class="[value.template, value.style]" :style="warpCss">
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)">
|
||||
@error="imageError(index)" show-menu-by-longpress>
|
||||
</image>
|
||||
</view>
|
||||
<view class="content"
|
||||
@@ -39,12 +39,12 @@
|
||||
</template>
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
<image class="bg" v-if="value.saleStyle.control"
|
||||
:src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix" />
|
||||
<view class="num" v-if="value.saleStyle.control"
|
||||
@@ -81,11 +81,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex)" />
|
||||
@error="imageError(dataIndex)" show-menu-by-longpress/>
|
||||
<image class="bg" v-if="value.saleStyle.control"
|
||||
:src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix" />
|
||||
<view class="num" v-if="value.saleStyle.control"
|
||||
@@ -274,7 +274,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -283,7 +283,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<view data-component-name="diy-hot-area" :style="hotAreaWarp" class="hot-area-box">
|
||||
<view class="simple-graph-wrap">
|
||||
<image :style="{ height: value.imgHeight }" :src="$util.img(value.imageUrl)" mode="widthFix"
|
||||
:show-menu-by-longpress="true" />
|
||||
show-menu-by-longpress />
|
||||
<!-- 热区功能 -->
|
||||
<view class="heat-map" v-for="(mapItem, mapIndex) in value.heatMapData" :key="mapIndex" :style="{
|
||||
width: mapItem.width + '%',
|
||||
height: mapItem.height + '%',
|
||||
left: mapItem.left + '%',
|
||||
top: mapItem.top + '%'
|
||||
}" @click.stop="$util.diyRedirectTo(mapItem.link)"></view>
|
||||
}" @tap.stop="$util.diyRedirectTo(mapItem.link)"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
'width': (item.imgWidth / 2 + 'rpx') + ';',
|
||||
'height': (item.imgHeight / 2 + 'rpx') + ';'
|
||||
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
:show-menu-by-longpress="true" @tap="redirectTo(item.link)"></image>
|
||||
show-menu-by-longpress @tap.stop="redirectTo(item.link)"></image>
|
||||
<image v-else :style="{
|
||||
'width': (item.imgWidth / 2 + 'rpx') + ';',
|
||||
'height': (item.imgHeight / 2 + 'rpx') + ';'
|
||||
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
:show-menu-by-longpress="true" @tap="previewImg(item.imageUrl)"></image>
|
||||
show-menu-by-longpress @tap.stop="previewImg(item.imageUrl)"></image>
|
||||
</view>
|
||||
|
||||
<!-- 文字部分 -->
|
||||
@@ -103,17 +103,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 预览图片
|
||||
previewImg(imageUrl) {
|
||||
uni.previewImage({
|
||||
current: 0,
|
||||
urls: [this.$util.img(imageUrl)],
|
||||
success: (res) => { },
|
||||
fail: (res) => { },
|
||||
complete: (res) => { }
|
||||
});
|
||||
},
|
||||
|
||||
// 页面跳转
|
||||
redirectTo(link) {
|
||||
if (!link.wap_url || this.$util.getCurrRoute() != this.$util.MEMBER_PAGE_URL || this.storeToken) {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<view data-component-name="diy-img-ads" class="single-graph">
|
||||
<view :style="imgAdsMarginWarp" class="swiper-box">
|
||||
<block v-if="imgAdsValue.list.length == 1">
|
||||
<view class="simple-graph-wrap" :style="imgAdsSwiper" @click="handlerClick(imgAdsValue.list[0].link)"
|
||||
@tap="handlerClick(imgAdsValue.list[0].link)">
|
||||
<view class="simple-graph-wrap" :style="imgAdsSwiper" @tap.stop="handlerClick(imgAdsValue.list[0].link)">
|
||||
<image :style="{ height: imgAdsValue.list[0].imgHeight }"
|
||||
:src="$util.img(imgAdsValue.list[0].imageUrl)" mode="widthFix" :show-menu-by-longpress="true" />
|
||||
:src="$util.img(imgAdsValue.list[0].imageUrl)" mode="widthFix" show-menu-by-longpress />
|
||||
</view>
|
||||
</block>
|
||||
<swiper v-else class="swiper" :style="{ height: swiperHeight }" :class="{
|
||||
@@ -16,10 +15,10 @@
|
||||
indicator-color="rgba(130, 130, 130, .5)" :indicator-active-color="imgAdsValue.indicatorColor"
|
||||
@change="swiperChange">
|
||||
<swiper-item class="swiper-item" :style="imgAdsSwiper" v-for="(item, index) in imgAdsValue.list"
|
||||
:key="index" v-if="item.imageUrl" @click="handlerClick(item.link)" @tap="handlerClick(item.link)">
|
||||
:key="index" v-if="item.imageUrl" @tap.stop="handlerClick(item.link)">
|
||||
<view class="item" :style="imgAdsSwiper + 'height: ' + item.imgHeight">
|
||||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||||
:show-menu-by-longpress="true" />
|
||||
show-menu-by-longpress />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
@@ -280,39 +279,39 @@ export default {
|
||||
}
|
||||
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
/deep/.uni-scroll-view::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.swiper /deep/ .uni-swiper-dots-horizontal {
|
||||
.swiper ::v-deep .uni-swiper-dots-horizontal {
|
||||
bottom: 25rpx;
|
||||
}
|
||||
|
||||
.swiper-left /deep/ .uni-swiper-dots-horizontal {
|
||||
.swiper-left ::v-deep .uni-swiper-dots-horizontal {
|
||||
left: 40rpx;
|
||||
transform: translate(0);
|
||||
}
|
||||
|
||||
.swiper-right /deep/ .uni-swiper-dots-horizontal {
|
||||
.swiper-right ::v-deep .uni-swiper-dots-horizontal {
|
||||
right: 40rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
transform: translate(0);
|
||||
}
|
||||
|
||||
.carousel-angle /deep/ .uni-swiper-dots-horizontal .uni-swiper-dot {
|
||||
.carousel-angle ::v-deep .uni-swiper-dots-horizontal .uni-swiper-dot {
|
||||
width: 24rpx;
|
||||
border-radius: 0;
|
||||
height: 8rpx;
|
||||
}
|
||||
|
||||
.swiper.ns-indicator-dots /deep/ .uni-swiper-dot {
|
||||
.swiper.ns-indicator-dots ::v-deep .uni-swiper-dot {
|
||||
width: 18rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.swiper.ns-indicator-dots /deep/ .uni-swiper-dot-active {
|
||||
.swiper.ns-indicator-dots ::v-deep .uni-swiper-dot-active {
|
||||
width: 36rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@
|
||||
:style="{ background: value.backgroundColor ? value.backgroundColor : '', width: 'calc(100% - 48rpx)' }"
|
||||
@touchmove.stop>
|
||||
<view class="item" :id="'a' + index" v-for="(item, index) in cateList" :key="index"
|
||||
@click="changePageIndex(index)" :class="{ fill: value.styleType == 'fill' }"
|
||||
@tap.stop="changePageIndex(index)" :class="{ fill: value.styleType == 'fill' }"
|
||||
:style="{ background: index == pageIndex && value.styleType == 'fill' ? value.selectColor : '' }">
|
||||
<view class="text-con" :class="index == pageIndex ? 'active' : ''" :style="{
|
||||
color: index == pageIndex ? '' : value.noColor
|
||||
@@ -25,13 +25,13 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text class="iconfont icon-unfold unfold-arrows" :style="{ color: value.moreColor }"
|
||||
@click="unfoldMenu"></text>
|
||||
@tap.stop="unfoldMenu"></text>
|
||||
</view>
|
||||
<uni-popup ref="navTopCategoryPop" type="top" :top="uniPopTop">
|
||||
<view class="nav-topcategory-pop">
|
||||
<text v-for="(item, index) in cateList" :key="index"
|
||||
:class="['category-item', { 'color-base-text color-base-border active': pageIndex == index }]"
|
||||
@click="changePageIndex(index)">
|
||||
@tap.stop="changePageIndex(index)">
|
||||
{{ item.short_name ? item.short_name : item.category_name }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -55,12 +55,12 @@
|
||||
<view class="twoCategory min" v-if="twoCategorylist.length <= 5">
|
||||
<view class="twoCategory-page">
|
||||
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
|
||||
:key="index" @click="toCateGoodsList(item.category_id_2, 2)">
|
||||
:key="index" @tap.stop="toCateGoodsList(item.category_id_2, 2)">
|
||||
<view class="item-box">
|
||||
<image :src="$util.img(item.image)" v-if="item.image"
|
||||
mode="aspectFill" />
|
||||
mode="aspectFill" show-menu-by-longpress/>
|
||||
<image :src="$util.getDefaultImage().goods" v-else
|
||||
mode="aspectFill" />
|
||||
mode="aspectFill" show-menu-by-longpress/>
|
||||
<view>{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -70,12 +70,12 @@
|
||||
v-if="twoCategorylist.length > 5 && twoCategorylist.length <= 10">
|
||||
<view class="twoCategory-page">
|
||||
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
|
||||
:key="index" @click="toCateGoodsList(item.category_id_2, 2)">
|
||||
:key="index" @tap.stop="toCateGoodsList(item.category_id_2, 2)">
|
||||
<view class="item-box">
|
||||
<image :src="$util.img(item.image)" v-if="item.image"
|
||||
mode="aspectFill" />
|
||||
mode="aspectFill" show-menu-by-longpress/>
|
||||
<image :src="$util.getDefaultImage().goods" v-else
|
||||
mode="aspectFill" />
|
||||
mode="aspectFill" show-menu-by-longpress/>
|
||||
<view>{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -86,9 +86,9 @@
|
||||
<swiper-item class="twoCategory-page" v-for="page in maxPage" :key="page">
|
||||
<view class="swiper-item" v-for="(item, index) in twoCategorylist"
|
||||
:key="index" v-if="index >= (page - 1) * 10 && index < page * 10"
|
||||
@click="toCateGoodsList(item.category_id_2, 2)">
|
||||
@tap.stop="toCateGoodsList(item.category_id_2, 2)">
|
||||
<view class="item-box">
|
||||
<image :src="item.image" mode="aspectFill" />
|
||||
<image :src="item.image" mode="aspectFill" show-menu-by-longpress/>
|
||||
<view>{{ item.category_name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -104,19 +104,19 @@
|
||||
|
||||
<!-- 分类广告 -->
|
||||
<image class="category_adv" v-if="cateList[pageIndex].image_adv"
|
||||
:src="$util.img(cateList[pageIndex].image_adv)" mode="widthFix" />
|
||||
:src="$util.img(cateList[pageIndex].image_adv)" mode="widthFix" show-menu-by-longpress/>
|
||||
|
||||
<view class="goods-list double-column" v-if="goodsList[pageIndex].list.length">
|
||||
<view class="goods-item" v-for="(item, index) in goodsList[pageIndex].list"
|
||||
:key="index" @click="toDetail(item)">
|
||||
:key="index" @tap.stop="toDetail(item)">
|
||||
<view class="goods-img">
|
||||
<image :src="goodsImg(item.goods_image)" mode="widthFix"
|
||||
@error="imgError(index)" />
|
||||
@error="imgError(index)" show-menu-by-longpress/>
|
||||
<view class="color-base-bg goods-tag"
|
||||
v-if="value.goodsTag == 'default' && goodsTag(item) != ''">{{
|
||||
goodsTag(item) }}</view>
|
||||
<view class="goods-tag-img" v-if="value.goodsTag == 'diy'">
|
||||
<image :src="$util.img(value.tagImg.imageUrl)" />
|
||||
<image :src="$util.img(value.tagImg.imageUrl)" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
@@ -133,11 +133,11 @@
|
||||
<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" />
|
||||
mode="widthFix" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
|
||||
<image :src="$util.img('public/uniapp/index/discount.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pro-info">
|
||||
@@ -732,7 +732,7 @@ export default {
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
|
||||
/deep/.uni-scroll-view::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<view data-component-name="diy-kefu" class="diy-kefu" :style="style">
|
||||
<view class="fui-list-group merchgroup" v-for="(item, index) in value.list">
|
||||
<view class="fui-list-group merchgroup" v-for="(item, index) in value.list" :key="index">
|
||||
<view class="fui-list jump" v-if="index == 0">
|
||||
<view class="fui-list-media">
|
||||
<image class="round" :src="$util.img(item.imageUrl)" style="border-radius:6rpx"></image>
|
||||
<image class="round" :src="$util.img(item.imageUrl)" style="border-radius:6rpx" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="fui-list-inner">
|
||||
<view class="title" style="height: 75rpx;">
|
||||
@@ -14,7 +14,7 @@
|
||||
</view>
|
||||
<view class="fui-remark jump" style="padding-right: 20rpx; text-align: center; line-height: 140rpx;">
|
||||
<span style="font-size:24rpx;padding: 14rpx 18rpx;border-radius:8rpx"
|
||||
:style="{ background: item.BtBgColor, color: item.BtColor }" @click="previewSqs()">立即添加</span>
|
||||
:style="{ background: item.BtBgColor, color: item.BtColor }" @tap.stop="previewSqs()">立即添加</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<view data-component-name="diy-listmenu" class="diy-listmenu" :style="style">
|
||||
<view class="fui-cell-group">
|
||||
<!-- <image mode="widthFix" style="width: 100%;" :src="$util.img(item.imageUrl)"></image> -->
|
||||
<!-- <image mode="widthFix" style="width: 100%;" :src="$util.img(item.imageUrl)" show-menu-by-longpress></image> -->
|
||||
|
||||
<view v-for="(item, index) in value.list" @click="redirectTo(item.link)" class="fui-cell"
|
||||
<view v-for="(item, index) in value.list" @tap.stop="redirectTo(item.link)" class="fui-cell"
|
||||
:class="item.iconType == 'img' ? 'img-cell' : ''">
|
||||
<view class="fui-cell-icon" :style="{ 'color': item.style ? item.style.iconColor : '#333' }">
|
||||
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon" :value="item.style ? item.style : null"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<x-skeleton data-component-name="diy-live" type="banner" :loading="loading" :configs="skeletonConfig">
|
||||
<view class="live-wrap" @click="handlerClick(liveInfo.roomid)" @tap="handlerClick(liveInfo.roomid)"
|
||||
<view class="live-wrap" @tap.stop="handlerClick(liveInfo.roomid)"
|
||||
v-if="liveInfo">
|
||||
<view class="banner-wrap">
|
||||
<image
|
||||
@@ -25,7 +25,7 @@
|
||||
<block v-if="value.isShowAnchorInfo">
|
||||
<image
|
||||
:src="liveInfo.anchor_img != '' ? $util.img(liveInfo.anchor_img) : $util.getDefaultImage().head"
|
||||
class="anchor-img" @error="liveInfo.anchor_img = $util.getDefaultImage().head" />
|
||||
class="anchor-img" @error="liveInfo.anchor_img = $util.getDefaultImage().head" show-menu-by-longpress/>
|
||||
<text class="anchor-name">主播:{{ liveInfo.anchor_name }}</text>
|
||||
</block>
|
||||
<text class="separate" v-if="value.isShowAnchorInfo && value.isShowLiveGood">|</text>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<scroll-view scroll-x="true" class="many-goods-list-head" :scroll-into-view="'a' + cateIndex"
|
||||
:style="manyWrapCss">
|
||||
<view v-for="(item, index) in value.list" class="scroll-item" :class="{ active: index == cateIndex }"
|
||||
:id="'a' + index" :key="index" @click="handlerClick({ item, index })" @tap="handlerClick({ item, index })">
|
||||
:id="'a' + index" :key="index" @tap.stop="handlerClick({ item, index })">
|
||||
<view class="split-line" v-if="index > 0"></view>
|
||||
<view class="cate">
|
||||
<view class="name" :style="{ color: value.headStyle.titleColor }">{{ item.title }}</view>
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
<view class="fui-list-group merchgroup" style="margin-top:0" v-for="(item, index) in value.list">
|
||||
<map id="map" style="width: 100%; height:600rpx" scale="12" :markers="markerst" bindupdated="bindupdated"
|
||||
:longitude="item.lng" :latitude="item.lat" show-location>
|
||||
<cover-view
|
||||
<!-- <cover-view
|
||||
style="position:absolute;right:10px;bottom:30rpx;z-index:99999;background:#4390FF;padding:5px 10px;wxcs_style_padding:10rpx 20rpx;border-radius:8rpx;color: #fff;"
|
||||
@click="handlerClick(item)" @tap="handlerClick(item)">
|
||||
@tap.stop="handlerClick(item)" @tap="handlerClick(item)">
|
||||
<cover-view style="font-size:24rpx">一键导航</cover-view>
|
||||
</cover-view>
|
||||
</cover-view> -->
|
||||
|
||||
<!-- 使用非原生cover-view, 解决原生cover-view组件渲染机制z-index失效的问题 -->
|
||||
<div
|
||||
style="position:absolute;right:12rpx;bottom:48rpx;z-index:1;background:#4390FF;padding:0rpx 20rpx;border-radius:8rpx;color: #fff;"
|
||||
@tap.stop="handlerClick(item)">
|
||||
<span style="font-size:24rpx;color: #fff;">一键导航</span>
|
||||
</div>
|
||||
</map>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
<view class="common-wrap info-wrap" :class="[`data-style-${value.style}`]">
|
||||
<view class="member-info" :style="memberInfoStyle">
|
||||
<view class="info-wrap" :style="infoStyle" v-if="memberInfo">
|
||||
<view class="headimg" @click="getWxAuth">
|
||||
<view class="headimg" @tap.stop="getWxAuth">
|
||||
<image :src="memberInfo.headimg ? $util.img(memberInfo.headimg) : $util.getDefaultImage().head"
|
||||
mode="widthFix" @error="memberInfo.headimg = $util.getDefaultImage().head" />
|
||||
mode="widthFix" @error="memberInfo.headimg = $util.getDefaultImage().head" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="info">
|
||||
<!-- #ifdef MP -->
|
||||
<block
|
||||
v-if="(memberInfo.nickname.indexOf('u_') != -1 && memberInfo.nickname == memberInfo.username) || memberInfo.nickname == memberInfo.mobile">
|
||||
<view class="nickname"><text class="name" @click="getWxAuth">点击授权头像昵称</text></view>
|
||||
<view class="nickname"><text class="name" @tap.stop="getWxAuth">点击授权头像昵称</text></view>
|
||||
</block>
|
||||
<view class="nickname" v-else>
|
||||
<text class="name" @click="getWxAuth">{{ memberInfo.nickname }}</text>
|
||||
<text class="name" @tap.stop="getWxAuth">{{ memberInfo.nickname }}</text>
|
||||
<view class="member-level"
|
||||
v-if="(value.style == 1 || value.style == 2) && memberInfo.member_level">
|
||||
<!-- <text class="icondiy icon-system-huangguan"></text> -->
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- #ifdef H5 -->
|
||||
<block
|
||||
v-if="$util.isWeiXin() && ((memberInfo.nickname.indexOf('u_') != -1 && memberInfo.nickname == memberInfo.username) || memberInfo.nickname == memberInfo.mobile)">
|
||||
<view class="nickname"><text class="name" @click="getWxAuth">点击获取微信头像</text></view>
|
||||
<view class="nickname"><text class="name" @tap.stop="getWxAuth">点击获取微信头像</text></view>
|
||||
</block>
|
||||
<view class="nickname" v-else>
|
||||
<text class="name" @click="redirect('/pages_tool/member/info')">{{ memberInfo.nickname
|
||||
<text class="name" @tap.stop="redirect('/pages_tool/member/info')">{{ memberInfo.nickname
|
||||
}}</text>
|
||||
<view class="member-level"
|
||||
v-if="(value.style == 1 || value.style == 2) && memberInfo.member_level">
|
||||
@@ -61,12 +61,12 @@
|
||||
</view>
|
||||
<view v-if="ischina == 1"
|
||||
style="background: #fff;height: 60rpx;width: 60rpx;border-radius: 50rpx;line-height:65rpx;text-align: center;color:#000"
|
||||
@click.stop="modifyInfo()">{{ langIndex == 0 ? 'CN' : 'EN' }}</view>
|
||||
@tap.stop="modifyInfo()">{{ langIndex == 0 ? 'CN' : 'EN' }}</view>
|
||||
</view>
|
||||
|
||||
<view class="info-wrap" v-else :style="infoStyle" @click="redirect($util.MEMBER_PAGE_URL)">
|
||||
<view class="info-wrap" v-else :style="infoStyle" @tap.stop="redirect($util.MEMBER_PAGE_URL)">
|
||||
<view class="headimg">
|
||||
<image :src="$util.getDefaultImage().head" mode="widthFix"></image>
|
||||
<image :src="$util.getDefaultImage().head" mode="widthFix" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="nickname"><text class="name">{{ $lang('login') }}</text></view>
|
||||
@@ -75,12 +75,12 @@
|
||||
|
||||
<view v-if="ischina == 1"
|
||||
style="background: #fff;height: 60rpx;width: 60rpx;border-radius: 50rpx;line-height:65rpx;text-align: center;color:#000"
|
||||
@click.stop="modifyInfo()">{{ langIndex == 0 ? 'CN' : 'EN' }}</view>
|
||||
@tap.stop="modifyInfo()">{{ langIndex == 0 ? 'CN' : 'EN' }}</view>
|
||||
</view>
|
||||
|
||||
<view class="account-info" v-show="value.style == 1"
|
||||
:style="{ 'margin-left': parseInt(value.infoMargin) * 2 + 'rpx', 'margin-right': parseInt(value.infoMargin) * 2 + 'rpx' }">
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/balance')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/balance')">
|
||||
<view class="value price-font">
|
||||
{{ memberInfo ? (parseFloat(memberInfo.balance) +
|
||||
parseFloat(memberInfo.balance_money)).toFixed(2) : '--' }}
|
||||
@@ -88,12 +88,12 @@
|
||||
<view class="title">{{ $lang('balance') }}</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="value price-font">{{ memberInfo ? parseFloat(memberInfo.point) : '--' }}</view>
|
||||
<view class="title">{{ $lang('point') }}</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/coupon')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/coupon')">
|
||||
<view class="value price-font">
|
||||
{{ memberInfo && memberInfo.coupon_num != undefined ? memberInfo.coupon_num : '--' }}
|
||||
</view>
|
||||
@@ -110,19 +110,19 @@
|
||||
<text>超级会员</text>
|
||||
</view>
|
||||
<view class="super-text">
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @click="redirectBeforeAuth('/pages_tool/member/card')">查看特权</text>
|
||||
<text class="see" v-else @click="redirectBeforeAuth('/pages_tool/member/card_buy')">会员可享更多权益</text>
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @tap.stop="redirectBeforeAuth('/pages_tool/member/card')">查看特权</text>
|
||||
<text class="see" v-else @tap.stop="redirectBeforeAuth('/pages_tool/member/card_buy')">会员可享更多权益</text>
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
</block>
|
||||
<block v-if="value.style == 3">
|
||||
<view class="super-info">
|
||||
<image :src="$util.img('public/uniapp/member/open_member.png')" class="title" mode="heightFix" />
|
||||
<image :src="$util.img('public/uniapp/member/open_member.png')" class="title" mode="heightFix" show-menu-by-longpress/>
|
||||
<view class="desc">开通可享更多权益</view>
|
||||
</view>
|
||||
<view class="super-text">
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @click="redirectBeforeAuth('/pages_tool/member/card')">查看特权</text>
|
||||
<text class="see" v-else @click="redirectBeforeAuth('/pages_tool/member/card_buy')">立即开通</text>
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @tap.stop="redirectBeforeAuth('/pages_tool/member/card')">查看特权</text>
|
||||
<text class="see" v-else @tap.stop="redirectBeforeAuth('/pages_tool/member/card_buy')">立即开通</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -130,29 +130,29 @@
|
||||
<view class="member-info-style4" v-show="value.style == 4">
|
||||
<view class="super-member" v-if="superMember" :style="superMemberStyle">
|
||||
<view class="super-info">
|
||||
<image :src="$util.img('app/component/view/member_info/img/style_4_vip_huangguan.png')" class="title" mode="widthFix" />
|
||||
<image :src="$util.img('app/component/view/member_info/img/style_4_vip_huangguan.png')" class="title" mode="widthFix" show-menu-by-longpress/>
|
||||
<view class="desc">开通可享更多权益</view>
|
||||
</view>
|
||||
<view class="super-text" :class="{ 'more' : memberInfo && memberInfo.member_level_type }">
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @click="redirectBeforeAuth('/pages_tool/member/card')">查看更多权益</text>
|
||||
<text class="see" v-else @click="redirectBeforeAuth('/pages_tool/member/card_buy')">立即开通</text>
|
||||
<text class="see" v-if="memberInfo && memberInfo.member_level_type" @tap.stop="redirectBeforeAuth('/pages_tool/member/card')">查看更多权益</text>
|
||||
<text class="see" v-else @tap.stop="redirectBeforeAuth('/pages_tool/member/card_buy')">立即开通</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-info" :style="{ 'margin-left': parseInt(value.infoMargin) * 2 + 'rpx', 'margin-right': parseInt(value.infoMargin) * 2 + 'rpx' }">
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/balance_detail')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/balance_detail')">
|
||||
<view class="value price-font">
|
||||
{{ memberInfo ? (parseFloat(memberInfo.balance) + parseFloat(memberInfo.balance_money)).toFixed(2) : '--' }}
|
||||
</view>
|
||||
<view class="title">余额</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="value price-font">{{ memberInfo ? parseFloat(memberInfo.point) : '--' }}
|
||||
</view>
|
||||
<view class="title">积分</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/coupon')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/coupon')">
|
||||
<view class="value price-font">
|
||||
{{ memberInfo && memberInfo.coupon_num != undefined ? memberInfo.coupon_num : '--' }}
|
||||
</view>
|
||||
@@ -161,7 +161,7 @@
|
||||
</view>
|
||||
<view class="style4-other">
|
||||
<view class="style4-btn-wrap">
|
||||
<view @click="redirect('/pages_tool/recharge/list')" class="recharge-btn">余额充值</view>
|
||||
<view @tap.stop="redirect('/pages_tool/recharge/list')" class="recharge-btn">余额充值</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
@@ -169,18 +169,18 @@
|
||||
|
||||
<view class="account-info" v-show="value.style == 2"
|
||||
:style="{ 'margin-left': parseInt(value.infoMargin) * 2 + 'rpx', 'margin-right': parseInt(value.infoMargin) * 2 + 'rpx' }">
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/balance')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/balance')">
|
||||
<view class="value price-font">{{ memberInfo ? (parseFloat(memberInfo.balance) +
|
||||
parseFloat(memberInfo.balance_money)).toFixed(2) : '--' }}</view>
|
||||
<view class="title">{{ $lang('balance') }}</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/point_detail')">
|
||||
<view class="value price-font">{{ memberInfo ? parseFloat(memberInfo.point) : '--' }}</view>
|
||||
<view class="title">{{ $lang('point') }}</view>
|
||||
</view>
|
||||
<view class="solid"></view>
|
||||
<view class="account-item" @click="redirect('/pages_tool/member/coupon')">
|
||||
<view class="account-item" @tap.stop="redirect('/pages_tool/member/coupon')">
|
||||
<view class="value price-font">
|
||||
{{ memberInfo && memberInfo.coupon_num != undefined ? memberInfo.coupon_num : '--' }}
|
||||
</view>
|
||||
@@ -196,14 +196,14 @@
|
||||
<view class="head">
|
||||
<text class="title">获取您的昵称、头像</text>
|
||||
<text class="color-tip tips">获取用户头像、昵称完善个人资料,主要用于向用户提供具有辨识度的用户中心界面</text>
|
||||
<text class="iconfont icon-close color-tip" @click="cancelCompleteInfo"></text>
|
||||
<text class="iconfont icon-close color-tip" @tap.stop="cancelCompleteInfo"></text>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="item-wrap">
|
||||
<text class="label">头像</text>
|
||||
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" />
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
|
||||
<text class="iconfont icon-right color-tip"></text>
|
||||
</button>
|
||||
</view>
|
||||
@@ -218,7 +218,7 @@
|
||||
<button open-type="getAuthorize" scope="userInfo" @getAuthorize="aliappGetUserinfo"
|
||||
:plain="true" class="border-0">
|
||||
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" />
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
|
||||
<text class="iconfont icon-right color-tip"></text>
|
||||
</button>
|
||||
</view>
|
||||
@@ -227,7 +227,7 @@
|
||||
<input type="nickname" placeholder="请输入昵称" v-model="nickName" @blur="blurNickName" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<button type="default" class="save-btn" @click="saveCompleteInfo" :disabled="isDisabled">保存</button>
|
||||
<button type="default" class="save-btn" @tap.stop="saveCompleteInfo" :disabled="isDisabled">保存</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
@@ -1380,11 +1380,11 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
/deep/ .member-info-style4 .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
::v-deep .member-info-style4 .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
@@ -1484,8 +1484,8 @@ export default {
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.member-complete-info-popup /deep/ .uni-popup__wrapper.bottom,
|
||||
.member-complete-info-popup /deep/ .uni-popup__wrapper.bottom .uni-popup__wrapper-box {
|
||||
.member-complete-info-popup ::v-deep .uni-popup__wrapper.bottom,
|
||||
.member-complete-info-popup ::v-deep .uni-popup__wrapper.bottom .uni-popup__wrapper-box {
|
||||
border-top-left-radius: 30rpx !important;
|
||||
border-top-right-radius: 30rpx !important;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<view data-component-name="diy-member-my-order" class="common-wrap" :style="warpCss">
|
||||
<view class="order-wrap">
|
||||
<view class="status-wrap">
|
||||
<view class="item-wrap" @click="redirect('/pages_order/list?status=waitpay')"
|
||||
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitpay')"
|
||||
style="margin-right: 10rpx;">
|
||||
<view class="icon-block">
|
||||
<template v-if="value.style == 3">
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_pay.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_pay.png')" mode="widthFix" show-menu-by-longpress/>
|
||||
<view class="icon-shade"
|
||||
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_pay_shade.png') + ')'">
|
||||
</view>
|
||||
@@ -21,11 +21,11 @@
|
||||
</view>
|
||||
<view class="title">{{ $lang('waitpay') }}</view>
|
||||
</view>
|
||||
<view class="item-wrap" @click="redirect('/pages_order/list?status=waitsend')"
|
||||
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitsend')"
|
||||
style="margin-right: 10rpx;">
|
||||
<view class="icon-block">
|
||||
<template v-if="value.style == 3">
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_send.png')" mode="widthFix"></image>
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_send.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
<view class="icon-shade"
|
||||
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_send_shade.png') + ')'">
|
||||
</view>
|
||||
@@ -40,11 +40,11 @@
|
||||
</view>
|
||||
<view class="title">{{ $lang('waitsend') }}</view>
|
||||
</view>
|
||||
<view class="item-wrap" @click="redirect('/pages_order/list?status=waitconfirm')"
|
||||
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitconfirm')"
|
||||
style="margin-right: 10rpx;">
|
||||
<view class="icon-block">
|
||||
<template v-if="value.style == 3">
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_confirm.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_confirm.png')" mode="widthFix" show-menu-by-longpress/>
|
||||
<view class="icon-shade"
|
||||
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_confirm_shade.png') + ')'">
|
||||
</view>
|
||||
@@ -59,11 +59,11 @@
|
||||
</view>
|
||||
<view class="title">{{ $lang('waitconfirm') }}</view>
|
||||
</view>
|
||||
<view class="item-wrap" @click="redirect('/pages_order/list?status=waitrate')"
|
||||
<view class="item-wrap" @tap.stop="redirect('/pages_order/list?status=waitrate')"
|
||||
style="margin-right: 10rpx;">
|
||||
<view class="icon-block">
|
||||
<template v-if="value.style == 3">
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_use.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/member/order/wait_use.png')" mode="widthFix" show-menu-by-longpress/>
|
||||
<view class="icon-shade"
|
||||
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_rate_shade.png') + ')'">
|
||||
</view>
|
||||
@@ -76,10 +76,10 @@
|
||||
</view>
|
||||
<view class="title">{{ $lang('completed') }}</view>
|
||||
</view>
|
||||
<view class="item-wrap" @click="redirect('/pages_tool/order/activist')">
|
||||
<view class="item-wrap" @tap.stop="redirect('/pages_tool/order/activist')">
|
||||
<view class="icon-block">
|
||||
<template v-if="value.style == 3">
|
||||
<image :src="$util.img('public/uniapp/member/order/refunding.png')" mode="widthFix" />
|
||||
<image :src="$util.img('public/uniapp/member/order/refunding.png')" mode="widthFix" show-menu-by-longpress/>
|
||||
<view class="icon-shade"
|
||||
:style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/refunding_shade.png') + ')'">
|
||||
</view>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<view class="merch-wrap" :style="warpCss">
|
||||
<view :class="['list-wrap', value.style]" :style="warpCss">
|
||||
<view :class="['item', value.ornament.type]" v-for="(item, index) in list" :key="index"
|
||||
:style="itemCss" @click="handlerClick(item)" @tap="handlerClick(item)">
|
||||
:style="itemCss" @tap.stop="handlerClick(item)">
|
||||
<view class="merch-img">
|
||||
<image class="cover-img" :src="$util.img(item.merch_image)" mode="widthFix"
|
||||
@error="imgError(index)" />
|
||||
@error="imgError(index)" show-menu-by-longpress />
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
<text class="title">{{ item.merch_name }}</text>
|
||||
@@ -25,12 +25,12 @@
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="merch-nav-item graphic" v-for="(item, index) in list" :key="index"
|
||||
:style="{ width: 100 / 4 + '%' }" @click="handlerClick(item)" @tap="handlerClick(item)">
|
||||
:style="{ width: 100 / 4 + '%' }" @tap.stop="handlerClick(item)">
|
||||
<view class="graphic-img" v-if="value.mode != 'text'"
|
||||
:style="{ fontSize: value.imageSize * 2 + 'rpx', width: value.imageSize * 2 + 'rpx', height: value.imageSize * 2 + 'rpx' }">
|
||||
<image
|
||||
:src="$util.img(item.merch_image) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
mode="aspectFill" :show-menu-by-longpress="true"
|
||||
mode="aspectFill" show-menu-by-longpress
|
||||
style="max-width: 80rpx; max-height: 80rpx; border-radius: 50rpx;" />
|
||||
</view>
|
||||
<text class="graphic-text"
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
<view class="diy-notes" :style="{ backgroundColor: value.componentBgColor }">
|
||||
<view class="diy-notes-top">
|
||||
<view class="notes-title" :style="{ color: value.titleTextColor }">{{ value.title }}</view>
|
||||
<view class="notes-more" @click="toMore()" :style="{ color: value.moreTextColor }">{{ value.more }}
|
||||
<view class="notes-more" @tap.stop="toMore()" :style="{ color: value.moreTextColor }">{{ value.more }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="diy-notes-box" scroll-x="true" show-scrollbar="true">
|
||||
<view class="notes-box-item" v-for="(item, i) in dataList" :key="i" @click="handlerClick(item)"
|
||||
@tap="handlerClick(item)" :style="notesItemStyle">
|
||||
<view class="notes-box-item" v-for="(item, i) in dataList" :key="i" @tap.stop="handlerClick(item)" :style="notesItemStyle">
|
||||
<view class="notes-item" v-if="item.status == 1">
|
||||
<view class="notes-item-con">
|
||||
<view class="notes-title">{{ item.note_title }}</view>
|
||||
@@ -26,10 +25,10 @@
|
||||
|
||||
<view class="notes-img-wrap" :class="{ 'notes-img-wrap-list': item.cover_type == 1 }">
|
||||
<image v-if="item.cover_type == 0" :src="$util.img(item.img)" @error="imageError(i)"
|
||||
mode="aspectFill" class="notes-item-image" />
|
||||
mode="aspectFill" class="notes-item-image" show-menu-by-longpress/>
|
||||
<image v-else v-for="(imgItem, imgIndex) in item.img" :key="imgIndex"
|
||||
:src="$util.img(imgItem)" @error="imageError(i)" mode="aspectFit"
|
||||
class="notes-item-image-li" />
|
||||
class="notes-item-image-li" show-menu-by-longpress/>
|
||||
</view>
|
||||
|
||||
<view class="notes-item-con">
|
||||
|
||||
@@ -41,12 +41,12 @@
|
||||
<view @touchmove.prevent.stop>
|
||||
<uni-popup ref="noticePopup" type="center">
|
||||
<view class="notice-popup">
|
||||
<view class="head-wrap" @click="closeNoticePopup">
|
||||
<view class="head-wrap" @tap.stop="closeNoticePopup">
|
||||
<text>公告</text>
|
||||
<text class="iconfont icon-close"></text>
|
||||
</view>
|
||||
<view class="content-wrap">{{ notice }}</view>
|
||||
<button type="primary" @click="closeNoticePopup">我知道了</button>
|
||||
<button type="primary" @tap.stop="closeNoticePopup">我知道了</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
<!-- <text class="iconfont icon-shuaxin"></text> -->
|
||||
</view>
|
||||
<view class="qrocde-action">
|
||||
<button type="primary" @click="toLink">
|
||||
<button type="primary" @tap.stop="toLink">
|
||||
<text class="iconfont icon-fukuanma"></text>
|
||||
<text class="action-name">付款码</text>
|
||||
</button>
|
||||
<button type="primary" @click="openPaymentPopup">
|
||||
<button type="primary" @tap.stop="openPaymentPopup">
|
||||
<text class="iconfont icon-saomafu"></text>
|
||||
<text class="action-name">扫码付</text>
|
||||
</button>
|
||||
@@ -26,12 +26,12 @@
|
||||
<view @touchmove.prevent.stop>
|
||||
<uni-popup ref="paymentPopup" type="center">
|
||||
<view class="payment-popup">
|
||||
<view class="head-wrap" @click="closePaymentPopup">
|
||||
<view class="head-wrap" @tap.stop="closePaymentPopup">
|
||||
<text>提示</text>
|
||||
<text class="iconfont icon-close"></text>
|
||||
</view>
|
||||
<view class="content-wrap">扫码付请退出程序后直接使用微信扫一扫或返回上一页使用付款码进行支付</view>
|
||||
<button type="primary" @click="closePaymentPopup">我知道了</button>
|
||||
<button type="primary" @tap.stop="closePaymentPopup">我知道了</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<view class="fui-picture">
|
||||
<view v-for="(item, index) in value.list" style="line-height: 0;">
|
||||
<image mode="widthFix" style="width: 100%;height:auto" :src="$util.img(item.imageUrl)"
|
||||
v-if="item.link.wap_url" @click="handlerClick(item)" @tap="handlerClick(item)"></image>
|
||||
v-if="item.link.wap_url" @tap.stop="handlerClick(item)" show-menu-by-longpress></image>
|
||||
<image mode="widthFix" style="width: 100%;height:auto" :src="$util.img(item.imageUrl)" v-else
|
||||
@click="handlerClick(item)" @tap="handlerClick(item)"></image>
|
||||
@tap.stop="handlerClick(item)" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<!-- <view wx:if="{{!childitem.linkurl}}" bindtap="previewImg" data-src="{{childitem.imgurl}}" style="padding:{{diyitem.style.paddingtop==0?0:diyitem.style.paddingtop+'rpx'}} {{diyitem.style.paddingleft==0?0:diyitem.style.paddingleft+'rpx'}}" wx:for="{{diyitem.data}}" wx:for-index="childid" wx:for-item="childitem" wx:key="{{childid}}">
|
||||
<image mode="widthFix" src="{{childitem.imgurl}}" style="{{bannerheight?'height:'+bannerheight+'px':'height:auto'}}"></image>
|
||||
@@ -52,17 +52,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
previewImg(img) {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.previewImage({
|
||||
current: 0,
|
||||
urls: [this.$util.img(img)],
|
||||
success: function (res) { },
|
||||
fail: function (res) { },
|
||||
complete: function (res) { },
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
redirectTo(link) {
|
||||
if (link.wap_url) {
|
||||
if (this.$util.getCurrRoute() == this.$util.MEMBER_PAGE_URL && !this.storeToken) {
|
||||
@@ -77,7 +66,9 @@ export default {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'picture-tap', data: item, promiseCallback: (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
if (item.link.wap_url) {
|
||||
|
||||
const link = item.link;
|
||||
if (link?.name || link?.wap_url || link?.appid) {
|
||||
this.redirectTo(item.link);
|
||||
} else {
|
||||
this.previewImg(item.imageUrl);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<x-skeleton data-component-name="diy-pinfan" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
|
||||
<view class="diy-pinfan" :class="[value.template, value.style]" :style="warpCss">
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
|
||||
@@ -61,12 +61,12 @@
|
||||
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
@@ -113,11 +113,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex)" />
|
||||
@error="imageError(dataIndex)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
@@ -319,7 +319,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -328,7 +328,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
:style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
|
||||
{{ value.titleStyle.leftText }}
|
||||
</view>
|
||||
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" />
|
||||
<image v-else class="left-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" show-menu-by-longpress/>
|
||||
<view class="head-content">
|
||||
<view class="img-warp">
|
||||
<block v-if="headData && headData.member_list && headData.member_list.length">
|
||||
<image v-for="(item, index) in headData.member_list" :key="index"
|
||||
:src="$util.img(item.member_img || 'public/static/img/default_img/square.png')"
|
||||
mode="aspectFill" @error="headImageError(index)" />
|
||||
mode="aspectFill" @error="headImageError(index)" show-menu-by-longpress/>
|
||||
</block>
|
||||
<block v-else>
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" />
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" show-menu-by-longpress/>
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" show-menu-by-longpress/>
|
||||
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill" show-menu-by-longpress/>
|
||||
</block>
|
||||
</view>
|
||||
<view :style="{ color: value.titleStyle.textColor }">
|
||||
@@ -31,19 +31,19 @@
|
||||
</view>
|
||||
<view class="head-right"
|
||||
:style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }"
|
||||
@click="$util.redirectTo('/pages_promotion/pintuan/list')">
|
||||
@tap.stop="$util.redirectTo('/pages_promotion/pintuan/list')">
|
||||
<text>{{ value.titleStyle.more }}</text>
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
|
||||
@@ -100,12 +100,12 @@
|
||||
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="toDetail(item)"
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="toDetail(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
<view v-if="value.groupStyle.control && value.style == 'style-1'" class="num" :style="{
|
||||
color: value.theme == 'diy' ? value.groupStyle.color : '',
|
||||
background: value.theme == 'diy' ? 'linear-gradient(to right,' + value.groupStyle.bgColorStart + ',' + value.groupStyle.bgColorEnd + ')' : ''
|
||||
@@ -161,11 +161,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex)" />
|
||||
@error="imageError(dataIndex)" show-menu-by-longpress/>
|
||||
<view v-if="value.groupStyle.control && value.style != 'style-2'" class="num" :style="{
|
||||
color: value.theme == 'diy' ? value.groupStyle.color : '',
|
||||
background: value.theme == 'diy' ? 'linear-gradient(to right,' + value.groupStyle.bgColorStart + ',' + value.groupStyle.bgColorEnd + ')' : ''
|
||||
@@ -391,7 +391,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -400,7 +400,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<x-skeleton data-component-name="diy-presale" :type="skeletonType" :loading="loading" :configs="skeletonConfig">
|
||||
<view class="diy-presale" v-if="list.length" :class="[value.template, value.style]" :style="warpCss">
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="handlerClick(item)"
|
||||
@tap="handlerClick(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
|
||||
@@ -40,12 +40,12 @@
|
||||
</template>
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true" :show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @click="handlerClick(item)"
|
||||
@tap="handlerClick(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="item" v-for="(item, index) in list" :key="index" @tap.stop="handlerClick(item)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
@@ -75,12 +75,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', (list.length && [list[pageIndex].length / 3] >= 1) && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in list[pageIndex]" :key="dataIndex"
|
||||
@click="handlerClick(item)" @tap="handlerClick(item)" :class="[value.ornament.type]"
|
||||
:style="goodsItemCss">
|
||||
@tap.stop="handlerClick(item)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex)" />
|
||||
@error="imageError(dataIndex)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
@@ -276,7 +275,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -285,7 +284,7 @@ export default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<view class="uni-scroll-view-content">
|
||||
<!-- #endif -->
|
||||
<view class="quick-nav-item" v-for="(item, index) in value.list" :key="index"
|
||||
@click="handlerClick(item)" @tap="handlerClick(item)"
|
||||
@tap.stop="handlerClick(item)"
|
||||
:style="{ background: 'linear-gradient(to right,' + item.bgColorStart ? item.bgColorStart : '' + ',' + item.bgColorEnd ? item.bgColorEnd : '' + ')' }">
|
||||
<view class="quick-img" v-if="item.imageUrl || item.icon">
|
||||
<image v-if="item.iconType == 'img'"
|
||||
:src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')"
|
||||
mode="heightFix" :show-menu-by-longpress="true"></image>
|
||||
mode="heightFix" show-menu-by-longpress ></image>
|
||||
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon"
|
||||
:value="item.style ? item.style : null" :style="{ fontSize: '60rpx' }"></diy-icon>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view data-component-name="diy-rich-text" class="rich-text-box" :style="richTextWarpCss">
|
||||
<rich-text :nodes="html" @click="handlerClick" @tap="handlerClick"></rich-text>
|
||||
<rich-text :nodes="html" @tap.stop="handlerClick"></rich-text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,22 +8,20 @@
|
||||
<!-- 1左2右 -->
|
||||
<template v-if="value.mode == 'row1-lt-of2-rt'">
|
||||
<view class="template-left">
|
||||
<view :class="['item', value.mode]" @click="handlerClick(value.list[0].link)"
|
||||
@tap="handlerClick(value.list[0].link)"
|
||||
<view :class="['item', value.mode]" @tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)"
|
||||
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }">
|
||||
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
|
||||
:style="list[0].pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="list[0].pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="template-right">
|
||||
<template v-for="(item, index) in list">
|
||||
<template v-if="index > 0">
|
||||
<view :key="index" :class="['item', value.mode]" @click="handlerClick(item.link)"
|
||||
@tap="handlerClick(item.link)"
|
||||
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)"
|
||||
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: item.imgWidth, height: item.imgHeight + 'px' }">
|
||||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||||
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
@@ -35,30 +33,29 @@
|
||||
<view class="template-left">
|
||||
<view :class="['item', value.mode]"
|
||||
:style="{ marginRight: value.imageGap * 2 + 'rpx', width: list[0].imgWidth, height: list[0].imgHeight + 'px' }"
|
||||
@click="handlerClick(value.list[0].link)" @tap="handlerClick(value.list[0].link)">
|
||||
@tap.stop="handlerClick(value.list[0].link, value.list[0].imageUrl)">
|
||||
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'"
|
||||
:style="list[0].pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="list[0].pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="template-right">
|
||||
<view :class="['item', value.mode]"
|
||||
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: list[1].imgWidth, height: list[1].imgHeight + 'px' }"
|
||||
@click="handlerClick(value.list[1].link)" @tap="handlerClick(value.list[1].link)">
|
||||
@tap.stop="handlerClick(value.list[1].link, value.list[1].imageUrl)">
|
||||
<image :src="$util.img(value.list[1].imageUrl)" :mode="list[1].imageMode || 'scaleToFill'"
|
||||
:style="list[1].pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="list[1].pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
<view class="template-bottom">
|
||||
<template v-for="(item, index) in list">
|
||||
<template v-if="index > 1">
|
||||
<view :key="index" :class="['item', value.mode]" @click="handlerClick(item.link)"
|
||||
@tap="handlerClick(item.link)" :style="{
|
||||
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)" :style="{
|
||||
marginRight: value.imageGap * 2 + 'rpx',
|
||||
width: item.imgWidth,
|
||||
height: item.imgHeight + 'px'
|
||||
}">
|
||||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||||
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
@@ -68,10 +65,10 @@
|
||||
|
||||
<template v-else>
|
||||
<view :class="['item', value.mode]" v-for="(item, index) in list" :key="index"
|
||||
@click="handlerClick(item.link)" @tap="handlerClick(item.link)"
|
||||
@tap.stop="handlerClick(item.link, item.imageUrl)"
|
||||
:style="{ marginRight: value.imageGap * 2 + 'rpx', marginBottom: value.imageGap * 2 + 'rpx', width: item.widthStyle, height: item.imgHeight + 'px' }">
|
||||
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
|
||||
:style="item.pageItemStyle" :show-menu-by-longpress="true" />
|
||||
:style="item.pageItemStyle" show-menu-by-longpress />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
@@ -374,11 +371,16 @@ export default {
|
||||
return obj;
|
||||
},
|
||||
|
||||
async handlerClick(link) {
|
||||
async handlerClick(link, imageUrl) {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'rubik-cube-tap', data: link, promiseCallback: (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
|
||||
if (link?.name || link?.wap_url || link?.appid) {
|
||||
this.$util.diyRedirectTo(link);
|
||||
} else if (imageUrl){
|
||||
this.previewImg(imageUrl);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<view data-component-name="diy-search" class="diy-search">
|
||||
<view class="diy-search-wrap" :class="value.positionWay" :style="fixedCss">
|
||||
<view :class="['search-box', 'search-box-' + value.searchStyle]" :style="searchWrapCss"
|
||||
@click="handlerSearchClick" @tap="handlerSearchClick">
|
||||
@tap.stop="handlerSearchClick">
|
||||
<block v-if="[1, 2].includes(value.searchStyle)">
|
||||
<view class="img" v-if="value.searchStyle == 2 && value.iconType == 'img'">
|
||||
<image :src="$util.img(value.imageUrl)" mode="heightFix" />
|
||||
<image :src="$util.img(value.imageUrl)" mode="heightFix" show-menu-by-longpress/>
|
||||
</view>
|
||||
<diy-icon class="icon" v-if="value.searchStyle == 2 && value.iconType == 'icon'" :icon="value.icon"
|
||||
:value="value.style ? value.style : 'null'"
|
||||
@@ -14,32 +14,30 @@
|
||||
<input type="text" class="uni-input ns-font-size-base" maxlength="50" :placeholder="value.title"
|
||||
v-model="searchText" @confirm="handlerSearchClick" disabled="true"
|
||||
:placeholderStyle="placeholderStyle" />
|
||||
<text class="iconfont icon-sousuo3" @click.stop="handlerSearchClick" @tap="handlerSearchClick"
|
||||
<text class="iconfont icon-sousuo3" @tap.stop="handlerSearchClick"
|
||||
:style="{ color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"></text>
|
||||
</view>
|
||||
</block>
|
||||
<block v-if="value.searchStyle == 3">
|
||||
<view class="search-content" :style="inputStyle" @click.stop="handlerSearchClick"
|
||||
@tap="handlerSearchClick">
|
||||
<view class="search-content" :style="inputStyle"
|
||||
@tap.stop="handlerSearchClick">
|
||||
<text class="iconfont icon-sousuo3"
|
||||
:style="{ color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"></text>
|
||||
<input type="text" class="uni-input ns-font-size-base" maxlength="50" :placeholder="value.title"
|
||||
v-model="searchText" @confirm="handlerSearchClick" disabled="true"
|
||||
@click.stop="handlerSearchClick" @tap="handlerSearchClick"
|
||||
@tap.stop="handlerSearchClick"
|
||||
:placeholderStyle="placeholderStyle" />
|
||||
<text class="search-content-btn" @click.stop="handlerSearchClick" @tap="handlerSearchClick"
|
||||
<text class="search-content-btn" @tap.stop="handlerSearchClick"
|
||||
:style="{ 'backgroundColor': value.pageBgColor ? value.pageBgColor : 'rgba(0,0,0,0)' }">搜索</text>
|
||||
</view>
|
||||
<view class="img" v-if="value.iconType == 'img'"
|
||||
@click.stop="handlerRedirectToClick(value.searchLink)"
|
||||
@tap="handlerRedirectToClick(value.searchLink)">
|
||||
<image :src="$util.img(value.imageUrl)" mode="heightFix" />
|
||||
@tap.stop="handlerRedirectToClick(value.searchLink)">
|
||||
<image :src="$util.img(value.imageUrl)" mode="heightFix" show-menu-by-longpress/>
|
||||
</view>
|
||||
<diy-icon class="icon" v-if="value.iconType == 'icon'" :icon="value.icon"
|
||||
:value="value.style ? value.style : 'null'"
|
||||
:style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx' }"
|
||||
@click.stop="handlerRedirectToClick(value.searchLink)"
|
||||
@tap="handlerRedirectToClick(value.searchLink)"></diy-icon>
|
||||
@tap.stop="handlerRedirectToClick(value.searchLink)"></diy-icon>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
@@ -257,7 +255,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/ .uni-input-placeholder {
|
||||
::v-deep .uni-input-placeholder {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:style="{ fontSize: value.titleStyle.fontSize * 2 + 'rpx', color: value.titleStyle.textColor, fontWeight: value.titleStyle.fontWeight ? 'bold' : '' }">
|
||||
{{ value.titleStyle.leftText }}
|
||||
</view>
|
||||
<image v-else class="title-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" />
|
||||
<image v-else class="title-img" :src="$util.img(value.titleStyle.leftImg)" mode="heightFix" show-menu-by-longpress/>
|
||||
<text v-if="value.titleStyle.style == 'style-2' && isFuture"
|
||||
:style="{ color: value.titleStyle.textColor }" class="seckill-style-name">距离开始</text>
|
||||
<view class="time" :style="{ background: value.titleStyle.timeBgColor }">
|
||||
@@ -27,7 +27,7 @@
|
||||
</view>
|
||||
<view class="marketimg-box-title-right" v-if="value.titleStyle.moreSupport"
|
||||
:style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }"
|
||||
@click="toMore">
|
||||
@tap.stop="toMore">
|
||||
<text>{{ value.titleStyle.more }}</text>
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
@@ -35,12 +35,12 @@
|
||||
|
||||
<view class="content-wrap">
|
||||
<template v-if="value.template == 'row1-of1'">
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @click="toDetail(item.id)"
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @tap.stop="toDetail(item.id)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="goods-name" v-if="value.goodsNameStyle.control"
|
||||
@@ -57,7 +57,7 @@
|
||||
}">
|
||||
<image v-if="value.style == 'style-2'" class="progress-bar"
|
||||
:src="$util.img('public/uniapp/seckill/progress_bar_01.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="num"
|
||||
@@ -105,12 +105,12 @@
|
||||
</template>
|
||||
|
||||
<template v-if="value.template == 'row1-of2'">
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @click="toDetail(item.id)"
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @tap.stop="toDetail(item.id)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
<view class="goods-name" v-if="value.goodsNameStyle.control"
|
||||
@@ -150,12 +150,12 @@
|
||||
<template v-if="value.template == 'horizontal-slide'">
|
||||
<scroll-view v-if="value.slideMode == 'scroll'" class="scroll" :scroll-x="true"
|
||||
:show-scrollbar="false">
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @click="toDetail(item.id)"
|
||||
<view class="item" v-for="(item, index) in dataList" :key="index" @tap.stop="toDetail(item.id)"
|
||||
:class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(index)" />
|
||||
@error="imageError(index)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
<view class="goods-name" v-if="value.goodsNameStyle.control"
|
||||
@@ -166,7 +166,7 @@
|
||||
<view class="discount-price" v-if="value.priceStyle.mainControl">
|
||||
<image v-if="value.style == 'style-2'" class="tag"
|
||||
:src="$util.img('addon/seckill/component/view/seckill/img/style_4_tag.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress/>
|
||||
<text class="unit price-style small"
|
||||
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">¥</text>
|
||||
<text class="price price-style large"
|
||||
@@ -190,11 +190,11 @@
|
||||
<swiper-item v-for="(pageItem, pageIndex) in page" :key="pageIndex"
|
||||
:class="['swiper-item', dataList[pageIndex] && [dataList[pageIndex].length / 3].length >= 1 && 'flex-between']">
|
||||
<view class="item" v-for="(item, dataIndex) in dataList[pageIndex]" :key="dataIndex"
|
||||
@click="toDetail(item.id)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
@tap.stop="toDetail(item.id)" :class="[value.ornament.type]" :style="goodsItemCss">
|
||||
<view class="img-wrap" :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }">
|
||||
<image :style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
|
||||
:src="$util.img(item.goods_image, { size: 'mid' })" mode="widthFix"
|
||||
@error="imageError(dataIndex, pageIndex)" />
|
||||
@error="imageError(dataIndex, pageIndex)" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="content"
|
||||
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
|
||||
@@ -206,7 +206,7 @@
|
||||
<view class="discount-price" v-if="value.priceStyle.mainControl">
|
||||
<image v-if="value.style == 'style-2'" class="tag"
|
||||
:src="$util.img('addon/seckill/component/view/seckill/img/style_4_tag.png')"
|
||||
mode="widthFix" />
|
||||
mode="widthFix" show-menu-by-longpress/>
|
||||
<text class="unit price-style small"
|
||||
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">¥</text>
|
||||
<text class="price price-style large"
|
||||
@@ -536,7 +536,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
/deep/.uni-scroll-view ::-webkit-scrollbar {
|
||||
::v-deep .uni-scroll-view ::-webkit-scrollbar {
|
||||
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
|
||||
display: none;
|
||||
width: 0;
|
||||
@@ -545,7 +545,7 @@ scroll-view ::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/deep/::-webkit-scrollbar {
|
||||
::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
/deep/ .uni-scroll-view-content {
|
||||
::v-deep .uni-scroll-view-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ export default {
|
||||
&.between {
|
||||
justify-content: space-between;
|
||||
|
||||
/deep/.uni-scroll-view-content {
|
||||
::v-deep .uni-scroll-view-content {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<block v-if="value.style == 1">
|
||||
<view class="store-box store-one">
|
||||
<view class="store-info">
|
||||
<view class="info-box" :style="{ color: value.textColor }" @click="toStoreList()">
|
||||
<view class="info-box" :style="{ color: value.textColor }" @tap.stop="toStoreList()">
|
||||
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
|
||||
<text class="title">{{ globalStoreInfo.store_name }}</text>
|
||||
<text>
|
||||
@@ -15,26 +15,26 @@
|
||||
</view>
|
||||
<view class="address-wrap" :style="{ color: value.textColor }">
|
||||
<text class="iconfont icon-dizhi"></text>
|
||||
<text v-if="globalStoreInfo && globalStoreInfo.store_id" @click="mapRoute" class="address">{{
|
||||
<text v-if="globalStoreInfo && globalStoreInfo.store_id" @tap.stop="mapRoute" class="address">{{
|
||||
globalStoreInfo.show_address }}</text>
|
||||
<text v-else>获取当前位置...</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="store-image" @click="selectStore()">
|
||||
<view class="store-image" @tap.stop="selectStore()">
|
||||
<image :src="$util.img(globalStoreInfo.store_image)"
|
||||
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
|
||||
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill"></image>
|
||||
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill" show-menu-by-longpress></image>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block v-if="value.style == 2">
|
||||
<view class="store-box store-three" @click="toStoreList()">
|
||||
<view class="store-box store-three" @tap.stop="toStoreList()">
|
||||
<view class="store-info">
|
||||
<view class="store-image" @click="selectStore()">
|
||||
<view class="store-image" @tap.stop="selectStore()">
|
||||
<image :src="$util.img(globalStoreInfo.store_image)"
|
||||
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill"></image>
|
||||
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill"></image>
|
||||
v-if="globalStoreInfo && globalStoreInfo.store_image" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<image :src="$util.getDefaultImage().store" v-else mode="aspectFill" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="info-box" :style="{ color: value.textColor }">
|
||||
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
|
||||
@@ -47,13 +47,13 @@
|
||||
<text class="title" v-else>定位中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="store-icon" @click.stop="search()"><text class="iconfont icon-sousuo3"
|
||||
<view class="store-icon" @tap.stop="search()"><text class="iconfont icon-sousuo3"
|
||||
:style="{ color: value.textColor }"></text></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block v-if="value.style == 3">
|
||||
<view class="store-box store-four" @click="toStoreList()">
|
||||
<view class="store-box store-four" @tap.stop="toStoreList()">
|
||||
<view class="store-left-wrap">
|
||||
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
|
||||
<text class="iconfont icon-weizhi" :style="{ color: value.textColor }"></text>
|
||||
@@ -64,8 +64,8 @@
|
||||
</view>
|
||||
<view class="store-right-search">
|
||||
<input type="text" class="uni-input font-size-tag" disabled placeholder="商品搜索"
|
||||
@click.stop="search()" />
|
||||
<text class="iconfont icon-sousuo3" @click.stop="search()"></text>
|
||||
@tap.stop="search()" />
|
||||
<text class="iconfont icon-sousuo3" @tap.stop="search()"></text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
1290
components-diy/diy-tab.vue
Normal file
1290
components-diy/diy-tab.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<view data-component-name="diy-text" class="diy-text" @click="handlerClick(value.link)"
|
||||
@tap="handlerClick(value.link)" :style="warpCss">
|
||||
<view data-component-name="diy-text" class="diy-text" @tap="handlerClick(value.link)" :style="warpCss">
|
||||
<view :class="value.style == 'style-8' ? 'title2' : 'title'"
|
||||
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor }">
|
||||
<block v-if="value.style == 'style-0'" style="height: 40rpx; line-height: 40rpx;">
|
||||
@@ -90,17 +89,17 @@
|
||||
<view class="style9-box">
|
||||
<view class="style9-center">
|
||||
<view class="left-img">
|
||||
<image :src="$util.img('public/uniapp/diy/style9-1.png')" />
|
||||
<image :src="$util.img('public/uniapp/diy/style9-1.png')" show-menu-by-longpress/>
|
||||
</view>
|
||||
<text class="style9-title"
|
||||
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
|
||||
{{ value.text }}
|
||||
</text>
|
||||
<view class="right-img">
|
||||
<image :src="$util.img('public/uniapp/diy/style9-2.png')" />
|
||||
<image :src="$util.img('public/uniapp/diy/style9-2.png')" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="style9-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
|
||||
@click.stop="handlerClick(value.more.link)" @tap="handlerClick(value.more.link)">
|
||||
@tap.stop="handlerClick(value.more.link)">
|
||||
{{ value.more.text }}
|
||||
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
|
||||
</view>
|
||||
@@ -115,7 +114,7 @@
|
||||
<view class="style10-box">
|
||||
<view class="style10-center">
|
||||
<view class="left-img">
|
||||
<image :src="$util.img('public/uniapp/diy/style10-1.png')"></image>
|
||||
<image :src="$util.img('public/uniapp/diy/style10-1.png')" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<text class="style10-title" v-if="$util.img('public/uniapp/diy/style10-3.png')" :style="{
|
||||
backgroundImage: 'url(' + $util.img('public/uniapp/diy/style10-3.png') + ')',
|
||||
@@ -129,10 +128,10 @@
|
||||
{{ value.text }}
|
||||
</text>
|
||||
<view class="right-img">
|
||||
<image :src="$util.img('public/uniapp/diy/style10-2.png')"></image>
|
||||
<image :src="$util.img('public/uniapp/diy/style10-2.png')" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="style10-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
|
||||
@click.stop="handlerClick(value.more.link)" @tap="handlerClick(value.more.link)">
|
||||
@tap.stop="handlerClick(value.more.link)">
|
||||
{{ value.more.text }}
|
||||
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
|
||||
</view>
|
||||
@@ -159,16 +158,16 @@
|
||||
value.subTitle.text }}</view>
|
||||
</view>
|
||||
<view class="style11-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
|
||||
@click.stop="$util.diyRedirectTo(value.more.link)">
|
||||
@tap.stop="$util.diyRedirectTo(value.more.link)">
|
||||
{{ value.more.text }}
|
||||
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
|
||||
</view>
|
||||
|
||||
<image class="center-img" :src="$util.img('public/uniapp/diy/style11-1.png')"
|
||||
mode="widthFix"></image>
|
||||
mode="widthFix" show-menu-by-longpress></image>
|
||||
|
||||
<image class="right-img" :src="$util.img('public/uniapp/diy/style11-2.png')"
|
||||
mode="widthFix"></image>
|
||||
mode="widthFix" show-menu-by-longpress></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -185,13 +184,13 @@
|
||||
<text class="style12-sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text
|
||||
}}</text>
|
||||
<view class="style12-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
|
||||
@click.stop="$util.diyRedirectTo(value.more.link)">
|
||||
@tap.stop="$util.diyRedirectTo(value.more.link)">
|
||||
<text>{{ value.more.text }}</text>
|
||||
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="style13" v-else-if="value.style == 'style-13'">
|
||||
<image class="left-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix"></image>
|
||||
<image class="left-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
<view class="style13-title" :style="{
|
||||
fontSize: value.fontSize * 2 + 'rpx',
|
||||
color: value.textColor,
|
||||
@@ -199,7 +198,7 @@
|
||||
}">
|
||||
{{ value.text }}
|
||||
</view>
|
||||
<image class="right-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix"></image>
|
||||
<image class="right-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="style14" v-else-if="value.style == 'style-14'">
|
||||
<view class="title-wrap">
|
||||
@@ -278,7 +277,7 @@
|
||||
<text :style="{ fontWeight: value.subTitle.fontWeight }">{{ value.subTitle.text }}</text>
|
||||
</view>
|
||||
<view class="style16-more" v-if="value.more.isShow" :style="{ color: value.more.color }"
|
||||
@click.stop="$util.diyRedirectTo(value.more.link)">
|
||||
@tap.stop="$util.diyRedirectTo(value.more.link)">
|
||||
<text>{{ value.more.text }}</text>
|
||||
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
|
||||
</view>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<view v-for="(item, index) in value.list" :key="index" class="goods-item" @tap="showVideo(item)">
|
||||
<view class="goods-img-wrap">
|
||||
<image class="goods-img" style="border-radius:10rpx 10rpx 0 0;" :src="$util.img(item.imageUrl)"
|
||||
mode="widthFix" @error="imgError(index)"></image>
|
||||
mode="widthFix" @error="imgError(index)" show-menu-by-longpress ></image>
|
||||
<view style="position:absolute;top:10rpx;right:10rpx;">
|
||||
<image style="width:30rpx;" :src="$util.img('addon/personnel/shop/view/enterprise/play.png')"
|
||||
mode="widthFix"></image>
|
||||
mode="widthFix" show-menu-by-longpress ></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-wrap">
|
||||
@@ -34,10 +34,10 @@
|
||||
'max-height': '200rpx;',
|
||||
'border-radius': '8rpx;'
|
||||
}" :src="$util.img(item.imageUrl) || $util.img('public/uniapp/default_img/goods.png')" mode="widthFix"
|
||||
:show-menu-by-longpress="true"></image>
|
||||
show-menu-by-longpress ></image>
|
||||
<view style="position:absolute;top:10rpx;right:10rpx;">
|
||||
<image style="width:30rpx;"
|
||||
:src="$util.img('addon/personnel/shop/view/enterprise/play.png')" mode="widthFix">
|
||||
:src="$util.img('addon/personnel/shop/view/enterprise/play.png')" mode="widthFix" show-menu-by-longpress >
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
@@ -53,9 +53,19 @@
|
||||
|
||||
<!-- 视频播放弹窗 -->
|
||||
<uni-popup ref="videoPopup" type="center" style="background:transparent;width:100%;height:100%;">
|
||||
<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 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>
|
||||
</uni-popup>
|
||||
</view>
|
||||
@@ -126,12 +136,38 @@ 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();
|
||||
},
|
||||
|
||||
@@ -139,6 +175,39 @@ 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,36 @@
|
||||
<template>
|
||||
<video data-component-name="diy-video" class="diy-video" :src="$util.img(value.videoUrl)"
|
||||
:poster="$util.img(value.imageUrl)" :style="videoWarpCss" objectFit="cover"
|
||||
@click="handlerClick(value.videoUrl)" @tap="handlerClick(value.videoUrl)"></video>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -15,27 +44,160 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
videoRatio: null, // 视频比例:'landscape'(横屏)或 'portrait'(竖屏)
|
||||
videoUrl: '',
|
||||
posterUrl: ''
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initVideoData();
|
||||
},
|
||||
created() { },
|
||||
mixins: [DiyMinx],
|
||||
watch: {
|
||||
// 组件刷新监听
|
||||
componentRefresh: function (nval) { }
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.initVideoData();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
videoWarpCss: function () {
|
||||
var obj = '';
|
||||
if (this.value.componentAngle == 'round') {
|
||||
if (this.value && 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) => {
|
||||
@@ -48,11 +210,32 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
video {
|
||||
.video-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.diy-video>>>.uni-video-container {
|
||||
background-color: transparent;
|
||||
/* 基础样式 */
|
||||
.diy-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* H5端样式 */
|
||||
/* #ifdef H5 */
|
||||
.h5-video {
|
||||
object-fit: cover;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 微信小程序端样式 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.mp-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -1,458 +0,0 @@
|
||||
<template>
|
||||
<view :style="componentStyle">
|
||||
<!-- 固定布局模式 -->
|
||||
<view v-if="value.showStyle == 'fixed'" :class="['channel-list', 'row1-of' + value.rowCount]"
|
||||
style="padding:20rpx;">
|
||||
<view v-for="(item, index) in value.list" :key="index" class="channel-item" @tap="playVideo(item)">
|
||||
<view class="channel-img-wrap">
|
||||
<image class="channel-img" style="border-radius:10rpx;" :src="$util.img(item.coverUrl)"
|
||||
mode="aspectFill" @error="imgError(index)"></image>
|
||||
<view class="channel-play-btn">
|
||||
<image class="play-icon" style="width:30rpx;" :src="$util.img('addon/personnel/shop/view/enterprise/play.png')"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="channel-stats">
|
||||
<text>{{ item.viewCount }}次观看</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="channel-info-wrap">
|
||||
<view class="channel-name" :style="{
|
||||
'font-size': (value.font.size * 2 + 'rpx') + ';',
|
||||
'font-weight': value.font.weight + ';',
|
||||
'color': value.font.color + ';'
|
||||
}">{{ item.channelName }}</view>
|
||||
<view class="video-title" :style="{
|
||||
'font-size': (value.font.size * 1.8 + 'rpx') + ';',
|
||||
'color': '#666;'
|
||||
}">{{ item.videoTitle }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他布局模式 -->
|
||||
<scroll-view v-else :class="['channel-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle]"
|
||||
:scroll-x="value.showStyle == 'singleSlide'">
|
||||
<view class="uni-scroll-view-content">
|
||||
<view v-for="(item, index) in value.list" :key="index" :class="['channel-nav-item', value.mode]"
|
||||
:style="{ width: (100 / value.rowCount + '%') + ';' }" @tap="playVideo(item)">
|
||||
<view class="channel-img">
|
||||
<image :style="{
|
||||
'max-width': '100%;',
|
||||
'border-radius': '8rpx;'
|
||||
}" :src="$util.img(item.coverUrl)" mode="aspectFill"
|
||||
:show-menu-by-longpress="true"></image>
|
||||
<view class="channel-play-btn">
|
||||
<image class="play-icon" style="width:30rpx;" :src="$util.img('addon/personnel/shop/view/enterprise/play.png')"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="channel-stats">
|
||||
<text>{{ item.viewCount }}次观看</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="channel-text" :style="{
|
||||
'margin-left': '16rpx;',
|
||||
'font-size': (value.font.size * 2 + 'rpx') + ';',
|
||||
'font-weight': value.font.weight + ';',
|
||||
'color': value.font.color + ';'
|
||||
}">{{ item.videoTitle }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import DiyMinx from './minx.js'
|
||||
export default {
|
||||
name: 'diy-wechat-channel-list',
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
mixins: [DiyMinx],
|
||||
data() {
|
||||
return {
|
||||
pageWidth: '',
|
||||
indicatorDots: false,
|
||||
swiperCurrent: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 组件创建时的逻辑
|
||||
},
|
||||
watch: {
|
||||
componentRefresh(newValue) {
|
||||
// 监听组件刷新
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
componentStyle() {
|
||||
let style = '';
|
||||
style += 'background-color:' + this.value.componentBgColor + ';';
|
||||
|
||||
if (this.value.componentAngle == 'round') {
|
||||
style += 'border-top-left-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
|
||||
style += 'border-top-right-radius:' + (2 * this.value.topAroundRadius) + 'rpx;';
|
||||
style += 'border-bottom-left-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
|
||||
style += 'border-bottom-right-radius:' + (2 * this.value.bottomAroundRadius) + 'rpx;';
|
||||
}
|
||||
|
||||
style += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
|
||||
style += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
swiperHeight() {
|
||||
let height = 0;
|
||||
|
||||
if (this.value.mode == 'graphic') {
|
||||
height = (49 + this.value.imageSize) * this.value.pageCount;
|
||||
} else if (this.value.mode == 'img') {
|
||||
height = (22 + this.value.imageSize) * this.value.pageCount;
|
||||
} else if (this.value.mode == 'text') {
|
||||
height = 43 * this.value.pageCount;
|
||||
}
|
||||
|
||||
return 'height:' + (2 * height) + 'rpx';
|
||||
},
|
||||
|
||||
isIndicatorDots() {
|
||||
return this.value.carousel.type != 'hide' &&
|
||||
1 != Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 播放视频
|
||||
async playVideo(item) {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'video-play', data: item, promiseCallback: (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
// 检查微信环境
|
||||
if (typeof wx === 'undefined') {
|
||||
console.error('当前环境不是微信小程序');
|
||||
return;
|
||||
}
|
||||
// 检查基础库版本
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const SDKVersion = systemInfo.SDKVersion;
|
||||
const versionCompare = (v1, v2) => {
|
||||
const arr1 = v1.split('.');
|
||||
const arr2 = v2.split('.');
|
||||
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
|
||||
const num1 = parseInt(arr1[i] || 0);
|
||||
const num2 = parseInt(arr2[i] || 0);
|
||||
if (num1 > num2) return 1;
|
||||
if (num1 < num2) return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if (versionCompare(SDKVersion, '2.19.2') < 0) {
|
||||
console.error('当前微信基础库版本过低,需要 2.19.2 或以上版本');
|
||||
return;
|
||||
}
|
||||
// 调用微信视频号播放API
|
||||
if (wx.openChannelsActivity) {
|
||||
wx.openChannelsActivity({
|
||||
feedId: item.feedId,
|
||||
finderUserName: item.finderUserName,
|
||||
success: (res) => {
|
||||
console.log('打开视频号成功', res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开视频号失败', err);
|
||||
// 错误码处理
|
||||
switch (err.errCode) {
|
||||
case 40001:
|
||||
console.error('错误:40001,检查主体要求或嵌入式打开的关联关系');
|
||||
break;
|
||||
case 40002:
|
||||
console.error('错误:40002,参数错误,检查 feedId 和 finderUserName');
|
||||
break;
|
||||
default:
|
||||
console.error('错误:' + err.errCode + ',' + (err.errMsg || '未知错误'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('当前环境不支持微信视频号');
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 图片加载错误处理
|
||||
imgError(index) {
|
||||
// 图片加载失败的处理逻辑
|
||||
console.log('图片加载失败:', index);
|
||||
// 为失败的图片设置默认图片
|
||||
const item = this.value.list[index];
|
||||
if (item) {
|
||||
// 使用默认图片替代加载失败的图片
|
||||
item.coverUrl = 'addon/personnel/shop/view/enterprise/default-video-cover.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.channel-list {
|
||||
&.row1-of3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.channel-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 20rpx;
|
||||
width: calc(33.3333333% - 14rpx);
|
||||
box-sizing: border-box;
|
||||
|
||||
&:nth-child(3n + 3) {
|
||||
width: calc(33.33% - 15rpx);
|
||||
}
|
||||
|
||||
&:nth-of-type(1),
|
||||
&:nth-of-type(2),
|
||||
&:nth-of-type(3) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:nth-child(3n) {
|
||||
width: calc(33.3333333% - 15rpx);
|
||||
}
|
||||
|
||||
&:nth-child(3n-1) {
|
||||
margin-left: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.channel-img-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 220rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.channel-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.channel-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-stats {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
|
||||
padding: 10rpx;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.channel-info-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 10rpx 0;
|
||||
|
||||
.channel-name {
|
||||
margin-bottom: 4rpx;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.row1-of2 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.channel-item {
|
||||
position: relative;
|
||||
margin-top: 20rpx;
|
||||
width: calc(50% - 10rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:nth-child(2n) {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
&:nth-of-type(1),
|
||||
&:nth-of-type(2) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.channel-img-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 340rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.channel-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.channel-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-stats {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
|
||||
padding: 10rpx;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.channel-info-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 10rpx 0;
|
||||
|
||||
.channel-name {
|
||||
margin-bottom: 4rpx;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel-nav {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.fixed-layout {
|
||||
.uni-scroll-view-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&.singleSlide {
|
||||
.uni-scroll-view-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.channel-nav-item {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 14rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.channel-img {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.channel-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-stats {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
|
||||
padding: 10rpx;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-text {
|
||||
padding-top: 12rpx;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,232 +0,0 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="diy-wechat-channel" :style="channelWarpCss">
|
||||
<view class="channel-header" @tap="handlerClick">
|
||||
<image class="channel-avatar" :src="$util.img(value.avatarUrl)" mode="aspectFill"></image>
|
||||
<view class="channel-info">
|
||||
<view class="channel-name">{{ value.channelName }}</view>
|
||||
<view class="channel-follow" v-if="value.showFollow">关注</view>
|
||||
</view>
|
||||
<image class="channel-arrow" :src="$util.img('addon/personnel/shop/view/enterprise/arrow.png')" mode="aspectFill"></image>
|
||||
</view>
|
||||
<!-- 嵌入式视频播放 -->
|
||||
<native-component v-if="value.embedMode && typeof wx !== 'undefined' && wx.canIUse('component.channel-video')">
|
||||
<channel-video
|
||||
:feed-id="value.feedId"
|
||||
:finder-user-name="value.finderUserName"
|
||||
:feed-token="value.feedToken"
|
||||
style="width: 100%; height: 320rpx;">
|
||||
</channel-video>
|
||||
</native-component>
|
||||
<!-- 跳转式视频播放 -->
|
||||
<view v-else class="channel-video" @tap="playVideo">
|
||||
<image class="video-cover" :src="$util.img(value.coverUrl)" mode="aspectFill"></image>
|
||||
<view class="video-play-btn">
|
||||
<image class="play-icon" :src="$util.img('addon/personnel/shop/view/enterprise/play.png')" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="video-info">
|
||||
<view class="video-title">{{ value.videoTitle }}</view>
|
||||
<view class="video-stats">
|
||||
<text>{{ value.viewCount }}次观看</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
// 微信视频号组件
|
||||
import DiyMinx from './minx.js'
|
||||
export default {
|
||||
name: 'diy-wechat-channel',
|
||||
props: {
|
||||
value: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
created() { },
|
||||
mixins: [DiyMinx],
|
||||
watch: {
|
||||
// 组件刷新监听
|
||||
componentRefresh: function (nval) { }
|
||||
},
|
||||
computed: {
|
||||
channelWarpCss: function () {
|
||||
var obj = '';
|
||||
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;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handlerClick() {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'channel-tap', data: this.value, promiseCallback: (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
}
|
||||
})
|
||||
},
|
||||
async playVideo() {
|
||||
await this.__$emitEvent({
|
||||
eventName: 'video-play', data: this.value, promiseCallback: (event, handler, awaitedResult) => {
|
||||
if (!awaitedResult) return;
|
||||
// 检查微信环境
|
||||
if (typeof wx === 'undefined') {
|
||||
console.error('当前环境不是微信小程序');
|
||||
return;
|
||||
}
|
||||
// 检查基础库版本
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const SDKVersion = systemInfo.SDKVersion;
|
||||
const versionCompare = (v1, v2) => {
|
||||
const arr1 = v1.split('.');
|
||||
const arr2 = v2.split('.');
|
||||
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
|
||||
const num1 = parseInt(arr1[i] || 0);
|
||||
const num2 = parseInt(arr2[i] || 0);
|
||||
if (num1 > num2) return 1;
|
||||
if (num1 < num2) return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if (versionCompare(SDKVersion, '2.19.2') < 0) {
|
||||
console.error('当前微信基础库版本过低,需要 2.19.2 或以上版本');
|
||||
return;
|
||||
}
|
||||
// 调用微信视频号播放API
|
||||
if (wx.openChannelsActivity) {
|
||||
wx.openChannelsActivity({
|
||||
feedId: this.value.feedId,
|
||||
finderUserName: this.value.finderUserName,
|
||||
success: (res) => {
|
||||
console.log('打开视频号成功', res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开视频号失败', err);
|
||||
// 错误码处理
|
||||
switch (err.errCode) {
|
||||
case 40001:
|
||||
console.error('错误:40001,检查主体要求或嵌入式打开的关联关系');
|
||||
break;
|
||||
case 40002:
|
||||
console.error('错误:40002,参数错误,检查 feedId 和 finderUserName');
|
||||
break;
|
||||
default:
|
||||
console.error('错误:' + err.errCode + ',' + (err.errMsg || '未知错误'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('当前环境不支持微信视频号');
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.diy-wechat-channel {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.channel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.channel-avatar {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.channel-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.channel-follow {
|
||||
font-size: 24rpx;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.channel-arrow {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.channel-video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-cover {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.video-stats {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
188
components-diy/js/wechat-channel.js
Normal file
188
components-diy/js/wechat-channel.js
Normal file
@@ -0,0 +1,188 @@
|
||||
// 微信视频号组件配置
|
||||
export const wechatChannelConfig = {
|
||||
// 图标相关
|
||||
icon: {
|
||||
defaultSize: 80, // 默认图标尺寸(rpx)
|
||||
smallSize: 60, // 小图标尺寸(rpx)
|
||||
channelArrowSize: 24, // 频道箭头图标尺寸(rpx)
|
||||
channelArrow: 'addon/personnel/shop/view/enterprise/arrow.png', // 频道箭头图标路径
|
||||
},
|
||||
|
||||
|
||||
// 视频相关配置
|
||||
video: {
|
||||
defaultHeight: 320, // 默认视频高度(rpx)
|
||||
minHeight: 200, // 最小视频高度(rpx)
|
||||
maxHeight: 500, // 最大视频高度(rpx)
|
||||
defaultCoverUrl: 'addon/personnel/shop/view/enterprise/default-video-cover.png', // 默认视频封面
|
||||
},
|
||||
|
||||
// 播放按钮配置
|
||||
playButton: {
|
||||
size: 80, // 标准尺寸(rpx)
|
||||
smallSize: 60, // 小尺寸(rpx)
|
||||
iconSize: 40, // 标准图标尺寸(rpx)
|
||||
smallIconSize: 30, // 小图标尺寸(rpx)
|
||||
background: 'rgba(0, 0, 0, 0.4)', // 背景颜色
|
||||
},
|
||||
|
||||
// 布局配置
|
||||
layout: {
|
||||
borderRadius: 12, // 圆角(rpx)
|
||||
padding: 16, // 内边距(rpx)
|
||||
margin: 10, // 外边距(rpx)
|
||||
},
|
||||
|
||||
// 字体配置
|
||||
font: {
|
||||
channelNameSize: 28, // 频道名称字体大小(rpx)
|
||||
videoTitleSize: 28, // 视频标题字体大小(rpx)
|
||||
statsSize: 24, // 统计信息字体大小(rpx)
|
||||
},
|
||||
|
||||
// 颜色配置
|
||||
color: {
|
||||
channelName: '#333', // 频道名称颜色
|
||||
videoTitle: '#333', // 视频标题颜色
|
||||
stats: '#999', // 统计信息颜色
|
||||
border: '#f0f0f0', // 边框颜色
|
||||
},
|
||||
|
||||
// 微信相关配置
|
||||
wechat: {
|
||||
minSdkVersion: '2.19.2', // 最小微信基础库版本
|
||||
embedComponent: 'channel-video', // 嵌入式视频组件名称
|
||||
},
|
||||
|
||||
// 错误提示配置
|
||||
error: {
|
||||
notWechat: '当前环境不是微信小程序',
|
||||
lowVersion: '当前微信基础库版本过低,需要 2.19.2 或以上版本',
|
||||
notSupported: '当前环境不支持微信视频号',
|
||||
missingFields: '缺少必要字段: {field}',
|
||||
},
|
||||
};
|
||||
|
||||
// 获取配置项的辅助函数
|
||||
export const getwechatChannelConfig = (key, defaultValue = null) => {
|
||||
const keys = key.split('.');
|
||||
let config = wechatChannelConfig;
|
||||
|
||||
for (const k of keys) {
|
||||
if (config[k] === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
config = config[k];
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
// 微信视频号工具函数
|
||||
export const wechatChannelUtil = {
|
||||
// 版本比较
|
||||
versionCompare(v1, v2) {
|
||||
const arr1 = v1.split('.');
|
||||
const arr2 = v2.split('.');
|
||||
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
|
||||
const num1 = parseInt(arr1[i] || 0);
|
||||
const num2 = parseInt(arr2[i] || 0);
|
||||
if (num1 > num2) return 1;
|
||||
if (num1 < num2) return -1;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
// 检查是否支持嵌入式播放
|
||||
isEmbedModeSupported() {
|
||||
return typeof wx !== 'undefined' && wx.canIUse(wechatChannelConfig.wechat.embedComponent);
|
||||
},
|
||||
|
||||
// 获取默认视频封面
|
||||
getDefaultCoverUrl() {
|
||||
return wechatChannelConfig.video.defaultCoverUrl;
|
||||
},
|
||||
|
||||
// 数据校验
|
||||
validateVideoData(item) {
|
||||
const requiredFields = ['feedId', 'finderUserName'];
|
||||
for (const field of requiredFields) {
|
||||
if (!item[field]) {
|
||||
throw new Error(`缺少必要字段: ${field}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// 播放视频
|
||||
playVideo(item) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// 数据校验
|
||||
this.validateVideoData(item);
|
||||
} catch (err) {
|
||||
this.handleError(err, item);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查微信环境
|
||||
if (typeof wx === 'undefined') {
|
||||
const err = new Error(wechatChannelConfig.error.notWechat);
|
||||
this.handleError(err, item);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查基础库版本
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
const SDKVersion = systemInfo.SDKVersion;
|
||||
if (this.versionCompare(SDKVersion, wechatChannelConfig.wechat.minSdkVersion) < 0) {
|
||||
const err = new Error(wechatChannelConfig.error.lowVersion);
|
||||
this.handleError(err, item);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用微信视频号播放API
|
||||
if (wx.openChannelsActivity) {
|
||||
wx.openChannelsActivity({
|
||||
feedId: item.feedId,
|
||||
finderUserName: item.finderUserName,
|
||||
success: (res) => {
|
||||
console.log('打开视频号成功', res);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
this.handleError(err, item);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const err = new Error(wechatChannelConfig.error.notSupported);
|
||||
this.handleError(err, item);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 统一错误处理
|
||||
handleError(err, item) {
|
||||
console.error('微信视频号错误:', err);
|
||||
const { message = '', errMsg = '微信视频播放失败!', errCode = -1 } = err;
|
||||
|
||||
// 错误码5: 表示用户点击了取消. openChannelsActivity:fail cancel
|
||||
if ([5].includes(errCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const showErrorToast = (otherMsgs = []) => {
|
||||
uni.showToast({
|
||||
title: [errMsg, message,...otherMsgs].join('\n'),
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
showErrorToast();
|
||||
}
|
||||
};
|
||||
@@ -5,5 +5,18 @@ export default {
|
||||
// console.log('__$emitEvent', payload)
|
||||
await this.$eventBus.emit(payload.eventName, payload.data, payload.promiseCallback)
|
||||
},
|
||||
|
||||
// 预览图片
|
||||
previewImg(img) {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.previewImage({
|
||||
current: 0,
|
||||
urls: [this.$util.img(img)],
|
||||
success: function (res) { },
|
||||
fail: function (res) { },
|
||||
complete: function (res) { },
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
# AI智能客服组件
|
||||
|
||||
一个功能完整的AI智能客服对话组件,支持多种消息类型和交互功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持对话上下文管理
|
||||
- ✅ 支持多种消息类型:文本、Markdown、文件、音频、视频、链接、商品卡片
|
||||
- ✅ 支持语音输入和录音
|
||||
- ✅ 支持图片、文件、位置等附件发送
|
||||
- ✅ 支持消息操作按钮(点赞、踩等)
|
||||
- ✅ 支持历史消息加载
|
||||
- ✅ 响应式设计,适配多端
|
||||
|
||||
## 安装使用
|
||||
|
||||
### 1. 引入组件
|
||||
|
||||
在 `pages.json` 中注册组件:
|
||||
|
||||
```json
|
||||
{
|
||||
"usingComponents": {
|
||||
"ai-chat-message": "/components/ai-chat-message/ai-chat-message"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在页面中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="container">
|
||||
<ai-chat-message
|
||||
ref="chat"
|
||||
:initial-messages="messages"
|
||||
@message-sent="onMessageSent"
|
||||
@ai-response="onAIResponse"
|
||||
@action-click="onActionClick" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
id: 1,
|
||||
role: 'ai',
|
||||
type: 'text',
|
||||
content: '您好!我是AI智能客服,有什么可以帮助您的吗?',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMessageSent(message) {
|
||||
console.log('用户发送消息:', message)
|
||||
},
|
||||
onAIResponse(message) {
|
||||
console.log('AI回复消息:', message)
|
||||
},
|
||||
onActionClick({ action, message }) {
|
||||
console.log('操作点击:', action, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props 配置
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| initialMessages | Array | [] | 初始消息列表 |
|
||||
| userAvatar | String | '/static/images/default-avatar.png' | 用户头像 |
|
||||
| aiAvatar | String | '/static/images/ai-avatar.png' | AI头像 |
|
||||
| showLoadMore | Boolean | true | 是否显示加载更多 |
|
||||
| maxMessages | Number | 100 | 最大消息数量 |
|
||||
|
||||
## Events 事件
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| message-sent | message | 用户发送消息 |
|
||||
| ai-response | message | AI回复消息 |
|
||||
| action-click | {action, message} | 操作按钮点击 |
|
||||
| history-loaded | messages | 历史消息加载完成 |
|
||||
| file-preview | message | 文件预览 |
|
||||
| audio-play | message | 音频播放 |
|
||||
| audio-pause | message | 音频暂停 |
|
||||
| video-play | message | 视频播放 |
|
||||
| video-pause | message | 视频暂停 |
|
||||
| link-open | message | 链接打开 |
|
||||
| product-view | message | 商品查看 |
|
||||
| input-change | value | 输入内容变化 |
|
||||
|
||||
## 消息类型格式
|
||||
|
||||
### 文本消息
|
||||
```javascript
|
||||
{
|
||||
id: 1,
|
||||
role: 'user', // 或 'ai'
|
||||
type: 'text',
|
||||
content: '消息内容',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown消息
|
||||
```javascript
|
||||
{
|
||||
id: 2,
|
||||
role: 'ai',
|
||||
type: 'markdown',
|
||||
content: '# 标题\n**粗体** *斜体* `代码`',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 文件消息
|
||||
```javascript
|
||||
{
|
||||
id: 3,
|
||||
role: 'ai',
|
||||
type: 'file',
|
||||
fileName: '文档.pdf',
|
||||
fileSize: 1024000,
|
||||
url: '文件地址',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 音频消息
|
||||
```javascript
|
||||
{
|
||||
id: 4,
|
||||
role: 'ai',
|
||||
type: 'audio',
|
||||
title: '语音消息',
|
||||
duration: 60, // 秒
|
||||
url: '音频地址',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 视频消息
|
||||
```javascript
|
||||
{
|
||||
id: 5,
|
||||
role: 'ai',
|
||||
type: 'video',
|
||||
title: '产品介绍',
|
||||
duration: 120, // 秒
|
||||
url: '视频地址',
|
||||
cover: '封面图',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 链接消息
|
||||
```javascript
|
||||
{
|
||||
id: 6,
|
||||
role: 'ai',
|
||||
type: 'link',
|
||||
title: '帮助文档',
|
||||
description: '详细的使用说明',
|
||||
url: 'https://example.com',
|
||||
image: '缩略图',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 商品卡片
|
||||
```javascript
|
||||
{
|
||||
id: 7,
|
||||
role: 'ai',
|
||||
type: 'product',
|
||||
title: '商品名称',
|
||||
price: 299,
|
||||
description: '商品描述',
|
||||
image: '商品图片',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
```
|
||||
|
||||
## 方法
|
||||
|
||||
通过 ref 调用组件方法:
|
||||
|
||||
```javascript
|
||||
// 添加消息
|
||||
this.$refs.chat.addMessage(message)
|
||||
|
||||
// 清空消息
|
||||
this.$refs.chat.clearMessages()
|
||||
|
||||
// 滚动到底部
|
||||
this.$refs.chat.scrollToBottom()
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制:
|
||||
|
||||
```css
|
||||
.ai-chat-container {
|
||||
--primary-color: #ff4544;
|
||||
--bg-color: #f8f8f8;
|
||||
--text-color: #333;
|
||||
}
|
||||
```
|
||||
|
||||
## 方法
|
||||
|
||||
通过 ref 调用组件方法:
|
||||
|
||||
```javascript
|
||||
// 添加消息
|
||||
this.$refs.chat.addMessage(message)
|
||||
|
||||
// 清空消息
|
||||
this.$refs.chat.clearMessages()
|
||||
|
||||
// 滚动到底部
|
||||
this.$refs.chat.scrollToBottom()
|
||||
|
||||
// 开始语音输入
|
||||
this.$refs.chat.startVoiceInput()
|
||||
|
||||
// 停止语音输入
|
||||
this.$refs.chat.stopVoiceInput()
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
组件使用 SCSS 编写,可以通过 CSS 变量进行主题定制:
|
||||
|
||||
```css
|
||||
.ai-chat-container {
|
||||
--primary-color: #ff4544;
|
||||
--bg-color: #f8f8f8;
|
||||
--text-color: #333;
|
||||
--border-color: #eeeeee;
|
||||
--user-bg: #e6f7ff;
|
||||
--ai-bg: #f6f6f6;
|
||||
}
|
||||
```
|
||||
|
||||
## 图标字体
|
||||
|
||||
组件使用自定义图标字体,需要在页面中引入:
|
||||
|
||||
```html
|
||||
<style>
|
||||
@import url('/components/ai-chat-message/iconfont.css');
|
||||
</style>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 组件已适配项目中已有的 `ns-loading` 组件
|
||||
2. 需要配置对应的图标字体文件
|
||||
3. 音频播放功能在 H5 和 APP 端支持较好
|
||||
4. 文件预览功能依赖平台能力
|
||||
5. 语音输入功能需要用户授权麦克风权限
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"ns-loading": "../ns-loading/ns-loading"
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
<template>
|
||||
<view class="demo-container">
|
||||
<view class="demo-header">
|
||||
<text class="demo-title">AI智能客服组件演示</text>
|
||||
</view>
|
||||
|
||||
<ai-chat-message
|
||||
ref="chat"
|
||||
:initial-messages="demoMessages"
|
||||
user-avatar="/static/images/demo-user.png"
|
||||
ai-avatar="/static/images/demo-ai.png"
|
||||
@message-sent="onMessageSent"
|
||||
@ai-response="onAIResponse"
|
||||
@action-click="onActionClick"
|
||||
@file-preview="onFilePreview"
|
||||
@audio-play="onAudioPlay"
|
||||
@video-play="onVideoPlay"
|
||||
@link-open="onLinkOpen"
|
||||
@product-view="onProductView" />
|
||||
|
||||
<view class="demo-controls">
|
||||
<button class="control-btn" @click="addTextMessage">添加文本消息</button>
|
||||
<button class="control-btn" @click="addMarkdownMessage">添加Markdown</button>
|
||||
<button class="control-btn" @click="addFileMessage">添加文件</button>
|
||||
<button class="control-btn" @click="addAudioMessage">添加音频</button>
|
||||
<button class="control-btn" @click="addVideoMessage">添加视频</button>
|
||||
<button class="control-btn" @click="addLinkMessage">添加链接</button>
|
||||
<button class="control-btn" @click="addProductMessage">添加商品</button>
|
||||
<button class="control-btn" @click="clearMessages">清空消息</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
demoMessages: [
|
||||
{
|
||||
id: 1,
|
||||
role: 'ai',
|
||||
type: 'text',
|
||||
content: '您好!我是AI智能客服演示程序,我可以展示多种消息类型。请点击下方的按钮体验不同功能!',
|
||||
timestamp: Date.now() - 300000,
|
||||
actions: [
|
||||
{ id: 1, text: '开始体验', type: 'like' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 添加文本消息
|
||||
addTextMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'text',
|
||||
content: '这是一个文本消息示例,支持**粗体**、*斜体*和`代码`格式。也可以包含换行符\n这是第二行内容。',
|
||||
timestamp: Date.now(),
|
||||
actions: [
|
||||
{ id: 1, text: '有帮助', type: 'like' },
|
||||
{ id: 2, text: '没帮助', type: 'dislike' }
|
||||
]
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加Markdown消息
|
||||
addMarkdownMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'markdown',
|
||||
content: `# Markdown文档示例
|
||||
|
||||
## 二级标题
|
||||
|
||||
这是一个支持**粗体**、*斜体*和\`代码\`的Markdown消息。
|
||||
|
||||
### 功能特性
|
||||
- ✅ 支持标题层级
|
||||
- ✅ 支持列表展示
|
||||
- ✅ 支持代码高亮
|
||||
- ✅ 支持链接跳转
|
||||
|
||||
[查看详细文档](https://example.com)\n\n**注意:** 这是一个演示内容,实际使用时可以集成真实的文档系统。`,
|
||||
timestamp: Date.now(),
|
||||
actions: [
|
||||
{ id: 1, text: '查看文档', type: 'like' }
|
||||
]
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加文件消息
|
||||
addFileMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'file',
|
||||
fileName: '产品介绍文档.pdf',
|
||||
fileSize: 2048000,
|
||||
url: 'https://example.com/document.pdf',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加音频消息
|
||||
addAudioMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'audio',
|
||||
title: '产品功能介绍',
|
||||
duration: 89,
|
||||
url: 'https://example.com/audio.mp3',
|
||||
timestamp: Date.now(),
|
||||
playing: false,
|
||||
currentTime: 0
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加视频消息
|
||||
addVideoMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'video',
|
||||
title: '产品演示视频',
|
||||
duration: 180,
|
||||
url: 'https://example.com/video.mp4',
|
||||
cover: '/static/images/video-cover.jpg',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加链接消息
|
||||
addLinkMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'link',
|
||||
title: '帮助中心',
|
||||
description: '详细的产品使用说明和常见问题解答',
|
||||
url: 'https://example.com/help',
|
||||
image: '/static/images/link-thumbnail.jpg',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 添加商品消息
|
||||
addProductMessage() {
|
||||
const message = {
|
||||
id: Date.now(),
|
||||
role: 'ai',
|
||||
type: 'product',
|
||||
title: '智能手表 X1',
|
||||
price: 1299,
|
||||
description: '全新一代智能手表,支持心率监测、运动追踪、消息提醒等功能',
|
||||
image: '/static/images/product-demo.jpg',
|
||||
timestamp: Date.now(),
|
||||
actions: [
|
||||
{ id: 1, text: '立即购买', type: 'like' },
|
||||
{ id: 2, text: '查看详情', type: 'dislike' }
|
||||
]
|
||||
}
|
||||
this.$refs.chat.messages.push(message)
|
||||
},
|
||||
|
||||
// 清空消息
|
||||
clearMessages() {
|
||||
this.$refs.chat.messages = [
|
||||
{
|
||||
id: 1,
|
||||
role: 'ai',
|
||||
type: 'text',
|
||||
content: '消息已清空,可以重新开始体验!',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 事件处理
|
||||
onMessageSent(message) {
|
||||
console.log('用户发送消息:', message)
|
||||
uni.showToast({
|
||||
title: '消息发送成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
|
||||
onAIResponse(message) {
|
||||
console.log('AI回复消息:', message)
|
||||
},
|
||||
|
||||
onActionClick({ action, message }) {
|
||||
console.log('操作点击:', action, message)
|
||||
uni.showToast({
|
||||
title: `点击了: ${action.text}`,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
onFilePreview(message) {
|
||||
console.log('文件预览:', message)
|
||||
uni.showToast({
|
||||
title: '正在打开文件...',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
onAudioPlay(message) {
|
||||
console.log('音频播放:', message)
|
||||
},
|
||||
|
||||
onVideoPlay(message) {
|
||||
console.log('视频播放:', message)
|
||||
},
|
||||
|
||||
onLinkOpen(message) {
|
||||
console.log('链接打开:', message)
|
||||
uni.showToast({
|
||||
title: '正在打开链接...',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
onProductView(message) {
|
||||
console.log('商品查看:', message)
|
||||
uni.showToast({
|
||||
title: '正在查看商品...',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
background-color: #ff4544;
|
||||
color: white;
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
|
||||
.demo-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
background-color: white;
|
||||
padding: 20rpx;
|
||||
border-top: 2rpx solid #eeeeee;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
|
||||
.control-btn {
|
||||
flex: 1;
|
||||
min-width: 150rpx;
|
||||
height: 60rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,14 +13,14 @@
|
||||
<view class="message-item " :class="message.isItMe ? 'right' : 'left'">
|
||||
<block v-if="message.isItMe">
|
||||
<view class="head_img">
|
||||
<image class="img" :src="myHeadImg" v-if="myHeadImg" @error="myHeadImgError" mode="aspectFit"/>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else/>
|
||||
<image class="img" :src="myHeadImg" v-if="myHeadImg" @error="myHeadImgError" mode="aspectFit" show-menu-by-longpress/>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else show-menu-by-longpress/>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view class="head_img">
|
||||
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar"></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
|
||||
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar" show-menu-by-longpress></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else show-menu-by-longpress></image>
|
||||
</view>
|
||||
</block>
|
||||
<view class="chat_text">
|
||||
@@ -34,20 +34,20 @@
|
||||
<view class="message-item " :class="message.isItMe ? 'right' : 'left'">
|
||||
<block v-if="message.isItMe">
|
||||
<view class="head_img">
|
||||
<image class="img" :src="myHeadImg" v-if="myHeadImg" mode="aspectFit"></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
|
||||
<image class="img" :src="myHeadImg" v-if="myHeadImg" mode="aspectFit" show-menu-by-longpress></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else show-menu-by-longpress></image>
|
||||
</view>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view class="head_img">
|
||||
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar"></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
|
||||
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar" show-menu-by-longpress></image>
|
||||
<image class="img" :src="defaultHead" mode="aspectFit" v-else show-menu-by-longpress></image>
|
||||
</view>
|
||||
</block>
|
||||
<view class="chat_img">
|
||||
<text class="iconfont icon-warn margin-right color-base-text" v-if="message.isItMe && !message.sendStatus"></text>
|
||||
<view class="content_img" @click="previewMedia($util.img(message.image))" :style="{ backgroundImage: 'url(' + $util.img(message.image) + ')' }">
|
||||
<!-- <image class="img_img" :src="$util.img(message.image)" mode="aspectFit"></image> -->
|
||||
<!-- <image class="img_img" :src="$util.img(message.image)" mode="aspectFit" show-menu-by-longpress></image> -->
|
||||
</view>
|
||||
<!-- <text class="iconfont icon-warn margin-left" v-if="!message.isItMe && !message.sendStatus"></text> -->
|
||||
</view>
|
||||
@@ -64,7 +64,7 @@
|
||||
</view>
|
||||
<uni-popup ref="imgPopup" type="center">
|
||||
<view class="imagePop">
|
||||
<image :src="$util.img(currImg)" mode="aspectFit"></image>
|
||||
<image :src="$util.img(currImg)" mode="aspectFit" show-menu-by-longpress></image>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
@@ -156,11 +156,11 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/.uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/deep/.uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -179,6 +179,7 @@
|
||||
.chat-message {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white; /* 白色 */
|
||||
|
||||
.message {
|
||||
padding: 13rpx 20rpx;
|
||||
@@ -270,7 +271,7 @@
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.content {
|
||||
background-color: #4cd964;
|
||||
background-color: #c4e0ff; /* 浅蓝色 */
|
||||
margin-right: 28rpx;
|
||||
word-break: break-all;
|
||||
line-height: 36rpx;
|
||||
|
||||
@@ -1,48 +1,10 @@
|
||||
<template>
|
||||
<view v-if="pageCount == 1 || need" class="fixed-box"
|
||||
<view v-if="pageCount === 1 || need" class="fixed-box"
|
||||
:style="[customContainerStyle, {
|
||||
height: fixBtnShow ? '400rpx' : '320rpx',
|
||||
height: containerHeight,
|
||||
backgroundImage: bgUrl ? `url(${bgUrl})` : '',
|
||||
backgroundSize: 'cover'
|
||||
}]">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 普通客服(仅当未启用 AI 时显示) -->
|
||||
<template v-else-if="fixBtnShow">
|
||||
<!-- #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 -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button
|
||||
class="btn-item common-bg"
|
||||
hoverClass="none"
|
||||
@click="openKefuSelectPopup"
|
||||
:style="[{ backgroundImage: kefuimg ? `url(${kefuimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
|
||||
>
|
||||
<text class="icox icox-kefu" v-if="!kefuimg"></text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<!-- 中英文切换按钮 -->
|
||||
<view
|
||||
v-if="isLanguageSwitchEnabled && fixBtnShow"
|
||||
@@ -51,12 +13,65 @@
|
||||
>
|
||||
<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"
|
||||
show-menu-by-longpress></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>
|
||||
|
||||
<!-- 智能客服按钮(当同时显示小程序系统客服时)-->
|
||||
<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"
|
||||
show-menu-by-longpress></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"
|
||||
>
|
||||
<text class="icox icox-kefu" v-if="!kefuimg"></text>
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<!-- 电话按钮 -->
|
||||
<view
|
||||
v-if="fixBtnShow"
|
||||
class="btn-item common-bg"
|
||||
@click="call()"
|
||||
@click="call"
|
||||
:style="[{ backgroundImage: phoneimg ? `url(${phoneimg})` : '', backgroundSize: '100% 100%' }, customButtonStyle]"
|
||||
>
|
||||
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
|
||||
@@ -80,18 +95,10 @@ export default {
|
||||
shopInfo: null,
|
||||
currentLangIndex: 0,
|
||||
langIndexMap: {},
|
||||
kefuList: [
|
||||
{ id: 'weixin-official', name: '微信官方客服', isOfficial: true, type: 'weapp' },
|
||||
{ id: 'custom-kefu', name: '自定义在线客服', isOfficial: false, type: 'custom' },
|
||||
{ id: 'qyweixin-kefu', name: '企业微信客服', isOfficial: false, type: 'qyweixin' }
|
||||
],
|
||||
selectedKefu: null,
|
||||
customerService: null,
|
||||
buttonConfig: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 安全读取 shopInfo 中的字段,避免 undefined 报错
|
||||
bgUrl() {
|
||||
return this.shopInfo?.bgUrl || '';
|
||||
},
|
||||
@@ -110,14 +117,6 @@ export default {
|
||||
isLanguageSwitchEnabled() {
|
||||
return !!this.shopInfo?.ischina;
|
||||
},
|
||||
enableAIChat() {
|
||||
const defaultEnable = true;
|
||||
if (!this.shopInfo) return defaultEnable;
|
||||
return this.shopInfo.hasOwnProperty('enableAIChat')
|
||||
? !!this.shopInfo.enableAIChat
|
||||
: defaultEnable;
|
||||
},
|
||||
|
||||
currentLangDisplayName() {
|
||||
const lang = this.langIndexMap[this.currentLangIndex];
|
||||
return lang === 'zh-cn' ? 'EN' : 'CN';
|
||||
@@ -128,17 +127,55 @@ export default {
|
||||
customButtonStyle() {
|
||||
return this.shopInfo?.floatingButton?.button || {};
|
||||
},
|
||||
customTextStyle() {
|
||||
return this.shopInfo?.floatingButton?.text || {};
|
||||
},
|
||||
unreadCount() {
|
||||
return this.$store.state.aiUnreadCount || 0;
|
||||
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
|
||||
}
|
||||
@@ -146,7 +183,6 @@ export default {
|
||||
created() {
|
||||
this.initLanguage();
|
||||
this.pageCount = getCurrentPages().length;
|
||||
|
||||
uni.getStorage({
|
||||
key: 'shopInfo',
|
||||
success: (e) => {
|
||||
@@ -155,9 +191,10 @@ export default {
|
||||
},
|
||||
fail: () => {
|
||||
console.warn('未获取到 shopInfo,使用默认设置');
|
||||
// shopInfo 为 null,computed 会自动返回默认值,无需手动赋值
|
||||
}
|
||||
});
|
||||
// 初始化 customerService 实例
|
||||
this.customerService = createCustomerService(this);
|
||||
},
|
||||
methods: {
|
||||
initLanguage() {
|
||||
@@ -166,7 +203,7 @@ export default {
|
||||
for (let i = 0; i < this.langList.length; i++) {
|
||||
this.langIndexMap[i] = this.langList[i].value;
|
||||
}
|
||||
const savedLang = uni.getStorageSync('lang');
|
||||
const savedLang = this.$langConfig.getCurrentLocale();
|
||||
if (savedLang) {
|
||||
for (let i = 0; i < this.langList.length; i++) {
|
||||
if (this.langList[i].value === savedLang) {
|
||||
@@ -178,78 +215,37 @@ export default {
|
||||
this.currentLangIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
call() {
|
||||
if (this.tel) {
|
||||
uni.makePhoneCall({ phoneNumber: this.tel + '' });
|
||||
} else {
|
||||
uni.showToast({ title: '暂无联系电话', icon: 'none' });
|
||||
}
|
||||
this.customerService.makePhoneCallByNumber(this.tel);
|
||||
},
|
||||
/**
|
||||
* 切换中英文语言,并刷新当前页面(保留所有参数)
|
||||
*/
|
||||
|
||||
toggleLanguage() {
|
||||
this.currentLangIndex = this.currentLangIndex === 0 ? 1 : 0;
|
||||
const targetLang = this.langIndexMap[this.currentLangIndex];
|
||||
|
||||
// 调用语言切换逻辑(设置 storage + 清空缓存)
|
||||
this.$langConfig.change(targetLang);
|
||||
uni.setStorageSync("lang", targetLang);
|
||||
|
||||
// H5 环境需要强制刷新页面才能生效
|
||||
if (uni.getSystemInfoSync().platform === 'browser') {
|
||||
// 延迟 100ms 确保 change() 执行完成
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
openKefuSelectPopup() {
|
||||
const kefuNames = this.kefuList.map(item => item.name);
|
||||
uni.showActionSheet({
|
||||
itemList: kefuNames,
|
||||
success: (res) => {
|
||||
this.selectedKefu = this.kefuList[res.tapIndex];
|
||||
this.reinitCustomerService(this.selectedKefu);
|
||||
this.handleSelectedKefu();
|
||||
}
|
||||
const pages = getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const currentRoute = currentPage.route;
|
||||
|
||||
uni.reLaunch({
|
||||
url: '/' + currentRoute
|
||||
});
|
||||
},
|
||||
reinitCustomerService(kefu) {
|
||||
this.customerService = createCustomerService(this, kefu);
|
||||
this.buttonConfig = this.customerService.getButtonConfig();
|
||||
},
|
||||
handleSelectedKefu() {
|
||||
const kefu = this.selectedKefu;
|
||||
if (!kefu) return;
|
||||
|
||||
if (kefu.isOfficial) {
|
||||
uni.openCustomerServiceConversation({
|
||||
sessionFrom: 'weapp',
|
||||
showMessageCard: true
|
||||
});
|
||||
} else if (kefu.id === 'custom-kefu') {
|
||||
this.customerService.handleCustomerClick();
|
||||
} else if (kefu.id === 'qyweixin-kefu') {
|
||||
this.customerService.handleQyWeixinKefuClick();
|
||||
}
|
||||
},
|
||||
openAIChat() {
|
||||
uni.navigateTo({
|
||||
url: '/pages_tool/ai-chat/index',
|
||||
success: () => {
|
||||
console.log('✅ AI 客服跳转成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 跳转失败:', err);
|
||||
uni.showToast({ title: '跳转失败,请重试', icon: 'none' });
|
||||
}
|
||||
handleUnifiedKefuClick() {
|
||||
this.customerService.handleUnifiedKefuClick({
|
||||
sendMessageTitle: '来自悬浮按钮的咨询',
|
||||
sendMessagePath: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.fixed-box {
|
||||
position: fixed;
|
||||
right: 0rpx;
|
||||
@@ -278,16 +274,15 @@ export default {
|
||||
margin: 14rpx 0;
|
||||
transition: 0.1s;
|
||||
color: var(--hover-nav-text-color);
|
||||
border-radius: 40rpx;
|
||||
border-radius: 50%;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 定义共同的背景颜色 */
|
||||
.common-bg {
|
||||
background-color: var(--hover-nav-bg-color); /* 使用变量以保持一致性 */
|
||||
background-color: var(--hover-nav-bg-color);
|
||||
}
|
||||
|
||||
.btn-item text {
|
||||
@@ -304,7 +299,6 @@ export default {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 14rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 50rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
@@ -318,20 +312,11 @@ export default {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -5rpx;
|
||||
right: -5rpx;
|
||||
background-color: #ff4544;
|
||||
color: white;
|
||||
border-radius: 20rpx;
|
||||
min-width: 30rpx;
|
||||
height: 30rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 30rpx;
|
||||
text-align: center;
|
||||
padding: 0 8rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(255, 69, 68, 0.3);
|
||||
.kefu-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ai-icon {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:src="mOption.src"
|
||||
mode="widthFix"
|
||||
@click="toTopClick"
|
||||
/>
|
||||
show-menu-by-longpress/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<swiper :indicator-dots="advList.length > 1" indicator-active-color="#ffffff" :autoplay="true" :interval="3000" :duration="1000" v-if="advList.length > 1" @change="changeSwiper" :current="currentIndex" :style="{ height: swiperHeight + 'px' }" class="item-wrap">
|
||||
<swiper-item v-for="(item, index) in advList" :key="index" @click="jumppage(item.adv_url)">
|
||||
<view class="image-box">
|
||||
<image :src="$util.img(item.adv_image)" mode="widthFix" :id="'content-wrap' + index"/>
|
||||
<image :src="$util.img(item.adv_image)" mode="widthFix" :id="'content-wrap' + index" show-menu-by-longpress/>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view v-else class="container-box item-wrap">
|
||||
<image :src="$util.img(advList[0]['adv_image'])" mode="widthFix" lazy-load="true" @load="imageLoad" @click="jumppage(advList[0].adv_url)"/>
|
||||
<image :src="$util.img(advList[0]['adv_image'])" mode="widthFix" lazy-load="true" @load="imageLoad" @click="jumppage(advList[0].adv_url)" show-menu-by-longpress/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="message">
|
||||
<view class="goods-item" v-if="goodsInfo.goods_name || goodsInfo.en_goods_name">
|
||||
<image :src="$util.img(goodsInfo.sku_image)" mode="aspectFill"></image>
|
||||
<image :src="$util.img(goodsInfo.sku_image)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ goodsInfo.sku_name ? goodsInfo.sku_name : (isEnEnv ? goodsInfo.en_goods_name : goodsInfo.goods_name) }}</view>
|
||||
<view class="goods-bottom">
|
||||
@@ -14,7 +14,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-item" v-else-if="goodsDetail">
|
||||
<image :src="$util.img(goodsDetail.sku_image)" mode="aspectFill"></image>
|
||||
<image :src="$util.img(goodsDetail.sku_image)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ goodsDetail.sku_name ? goodsDetail.sku_name : (isEnEnv ? goodsDetail.en_goods_name : goodsDetail.goods_name) }}</view>
|
||||
<view class="goods-bottom">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="message">
|
||||
<view class="goods-item" v-if="orderdetails">
|
||||
<image :src="$util.img(orderdetails.order_goods ? orderdetails.order_goods[0].sku_image : '')" mode="aspectFill"></image>
|
||||
<image :src="$util.img(orderdetails.order_goods ? orderdetails.order_goods[0].sku_image : '')" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ orderdetails.order_goods ? orderdetails.order_goods[0].sku_name : '' }}</view>
|
||||
<view class="font-size-goods-tag">订单状态:{{ orderdetails.order_status_name }}</view>
|
||||
@@ -16,7 +16,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-item" v-else-if="orderInfo">
|
||||
<image :src="$util.img(orderInfo.order_goods ? orderInfo.order_goods[0].sku_image : '')" mode="aspectFill"></image>
|
||||
<image :src="$util.img(orderInfo.order_goods ? orderInfo.order_goods[0].sku_image : '')" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ orderInfo.order_goods ? orderInfo.order_goods[0].sku_name : '' }}</view>
|
||||
<view class="font-size-goods-tag">订单状态:{{ orderInfo.order_status_name }}</view>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view class="goods">
|
||||
<view class="goods-msg">
|
||||
<image :src="$util.img(goodsInfo.sku_image)" mode="aspectFill"></image>
|
||||
<image :src="$util.img(goodsInfo.sku_image)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<view class="goods-item">
|
||||
<view class="title">{{ isEnEnv ? goodsInfo.en_goods_name : goodsInfo.goods_name }}</view>
|
||||
<view class="goods-sku">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</view>
|
||||
|
||||
<view v-if="copyright && copyright.gov_record" class="gov-wrap" @click="toHref(copyright.gov_url)" target="_blank">
|
||||
<image :src="$util.img('public/uniapp/common/gov_record.png')" alt="公安备案" />
|
||||
<image :src="$util.img('public/uniapp/common/gov_record.png')" alt="公安备案" show-menu-by-longpress/>
|
||||
<text>{{ copyright.gov_record }}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="empty" :class="{ fixed: fixed }">
|
||||
<view class="empty_img"><image :src="$util.img('public/uniapp/common/common-empty.png')" mode="aspectFit"></image></view>
|
||||
<view class="empty_img"><image :src="$util.img('public/uniapp/common/common-empty.png')" mode="aspectFit" show-menu-by-longpress></image></view>
|
||||
<view class="color-tip margin-top title" :style="{ color: textColor + ' !important' }">{{ text }}</view>
|
||||
<view class="color-tip margin-bottom font-size-sub">{{ subText }}</view>
|
||||
<button type="primary" size="mini" class="button mini" @click="goIndex()" v-if="isIndex">{{ emptyBtn.text }}</button>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</view>
|
||||
<view class="box img-boxs">
|
||||
<view class="img-box" v-for="(v, k) in item.img_lists" :key="k" @click="uploadImg(index)">
|
||||
<image :src="$util.img(v)" mode="aspectFill"></image>
|
||||
<image :src="$util.img(v)" mode="aspectFill" show-menu-by-longpress></image>
|
||||
<text class="iconfont icon-guanbi" @click.stop="delImg(k, index)"></text>
|
||||
</view>
|
||||
<view class="img-box" @click="addImg(index)"><text class="iconfont icon-add1"></text></view>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<view class="sku-info" :style="{ height: systemInfo.windowHeight * 2 + 'rpx' }">
|
||||
<view class="header">
|
||||
<view class="img-wrap" @click="previewMedia()">
|
||||
<image :src="$util.img(goodsDetail.sku_image, { size: 'mid' })" @error="imageError()" />
|
||||
<image :src="$util.img(goodsDetail.sku_image, { size: 'mid' })" @error="imageError()" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="main">
|
||||
<view class="goodname">{{ goodsDetail.sku_name ? goodsDetail.sku_name : (isEnEnv ?
|
||||
@@ -33,7 +33,7 @@
|
||||
@click="change(item_value.sku_id, item_value.spec_id)">
|
||||
<image v-if="item_value.image"
|
||||
:src="$util.img(item_value.image, { size: 'small' })"
|
||||
@error="valueImageError(index, index_value)" />
|
||||
@error="valueImageError(index, index_value)" show-menu-by-longpress/>
|
||||
<text>{{ item_value.spec_value_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -843,15 +843,15 @@ export default {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/deep/ .uni-popup__wrapper {
|
||||
::v-deep .uni-popup__wrapper {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/deep/ .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
::v-deep .sku-layer .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/deep/ .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
::v-deep .uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
|
||||
background: transparent !important;
|
||||
}
|
||||
</style>
|
||||
@@ -5,14 +5,14 @@
|
||||
<view class="header">
|
||||
<block v-if="type == 'point' && goodsDetail.type && goodsDetail.type != 1">
|
||||
<view class="img-wrap" v-if="goodsDetail.type == 2">
|
||||
<image :src="goodsDetail.image ? $util.img(goodsDetail.image) : $util.img('public/uniapp/point/coupon.png')" mode="aspectFit" />
|
||||
<image :src="goodsDetail.image ? $util.img(goodsDetail.image) : $util.img('public/uniapp/point/coupon.png')" mode="aspectFit" show-menu-by-longpress/>
|
||||
</view>
|
||||
<view class="img-wrap" v-if="goodsDetail.type == 3">
|
||||
<image :src="goodsDetail.image ? $util.img(goodsDetail.image) : $util.img('public/uniapp/point/hongbao.png')" mode="aspectFit" />
|
||||
<image :src="goodsDetail.image ? $util.img(goodsDetail.image) : $util.img('public/uniapp/point/hongbao.png')" mode="aspectFit" show-menu-by-longpress/>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else class="img-wrap" @click="previewMedia()">
|
||||
<image :src="$util.img(goodsDetail.sku_image, { size: 'mid' })" @error="imageError()" mode="aspectFit" />
|
||||
<image :src="$util.img(goodsDetail.sku_image, { size: 'mid' })" @error="imageError()" mode="aspectFit" show-menu-by-longpress/>
|
||||
</view>
|
||||
|
||||
<view class="main">
|
||||
@@ -151,7 +151,7 @@
|
||||
selected: item_value['selected'] || skuId == item_value.sku_id,
|
||||
disabled: item_value['disabled'] || (!item_value['selected'] && disabled)
|
||||
}" class="items" @click="change(item_value.sku_id, item_value.spec_id)">
|
||||
<image v-if="item_value.image" :src="$util.img(item_value.image, { size: 'small' })" @error="valueImageError(index, index_value)" />
|
||||
<image v-if="item_value.image" :src="$util.img(item_value.image, { size: 'small' })" @error="valueImageError(index, index_value)" show-menu-by-longpress/>
|
||||
<text>{{ item_value.spec_value_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
80
components/ns-image/ns-image.vue
Normal file
80
components/ns-image/ns-image.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<image
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
:lazy-load="lazyLoad"
|
||||
:fade-show="fadeShow"
|
||||
:webp="webp"
|
||||
:show-menu-by-longpress="enableLongpress"
|
||||
:class="imageClass"
|
||||
:style="imageStyle"
|
||||
@load="onLoad"
|
||||
@error="onError"
|
||||
@tap="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</image>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NsImage',
|
||||
props: {
|
||||
// 图片路径
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
// 图片裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 是否开启长按识别二维码(默认开启)
|
||||
enableLongpress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否懒加载
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图片显示动画
|
||||
fadeShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 支持webp
|
||||
webp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 自定义类名
|
||||
imageClass: {
|
||||
type: [String, Array, Object],
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式
|
||||
imageStyle: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onLoad(e) {
|
||||
this.$emit('load', e)
|
||||
},
|
||||
onError(e) {
|
||||
this.$emit('error', e)
|
||||
},
|
||||
onClick(e) {
|
||||
this.$emit('tap', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 可添加全局图片样式 */
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@
|
||||
<view class="item-wrap" v-if="isOpenCaptcha">
|
||||
<text class="label">验证码</text>
|
||||
<input type="number" placeholder="请输入验证码" v-model="formData.vercode" maxlength="4" />
|
||||
<image :src="captcha.img" class="captcha" @click="getCaptcha"></image>
|
||||
<image :src="captcha.img" class="captcha" @click="getCaptcha" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="item-wrap">
|
||||
<text class="label">动态码</text>
|
||||
@@ -79,7 +79,7 @@
|
||||
<text class="label">头像</text>
|
||||
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" />
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
|
||||
<text class="iconfont icon-right color-tip"></text>
|
||||
</button>
|
||||
</view>
|
||||
@@ -96,7 +96,7 @@
|
||||
<button open-type="getAuthorize" scope="userInfo" @getAuthorize="aliappGetUserinfo"
|
||||
:plain="true" class="border-0">
|
||||
<image :src="avatarUrl ? avatarUrl : $util.getDefaultImage().head"
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" />
|
||||
@error="avatarUrl = $util.getDefaultImage().head" mode="aspectFill" show-menu-by-longpress/>
|
||||
<text class="iconfont icon-right color-tip"></text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.mp-html{
|
||||
/deep/ img{
|
||||
::v-deep img{
|
||||
width:100% !important;
|
||||
display:block;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<view v-if="data.navStyle == 2" class="content-wrap" @click="toLink(data.moreLink.wap_url)">
|
||||
<view class="title-wrap" :style="{ color: data.textNavColor }">
|
||||
<view>
|
||||
<image :src="$util.img(data.topNavImg)" mode="heightFix"></image>
|
||||
<image :src="$util.img(data.topNavImg)" mode="heightFix" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view :style="{ color: data.textNavColor }">{{ data.title }}</view>
|
||||
</view>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<view v-if="data.navStyle == 3" class="content-wrap">
|
||||
<view class="title-wrap" @click="toLink(data.moreLink.wap_url)">
|
||||
<image :src="$util.img(data.topNavImg)" mode="aspectFit"></image>
|
||||
<image :src="$util.img(data.topNavImg)" mode="aspectFit" show-menu-by-longpress></image>
|
||||
</view>
|
||||
<view class="search" @click="$util.redirectTo('/pages_tool/goods/search')" :style="{ height: menuButtonInfo.height - 2 + 'px', lineHeight: menuButtonInfo.height - 2 + 'px' }">
|
||||
<text class="iconfont icon-sousuo3"></text>
|
||||
|
||||
145
components/ns-video-player-popup/ns-video-player-popup.vue
Normal file
145
components/ns-video-player-popup/ns-video-player-popup.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<view class="video-player-popup">
|
||||
<uni-popup ref="videoPopup" type="center" :mask-click="true" @change="onVideoPopupChange">
|
||||
<view class="video-popup-content">
|
||||
<view class="video-popup-header">
|
||||
<text class="video-popup-title">{{ videoTitle }}</text>
|
||||
<text class="iconfont icon-close" @click="close"></text>
|
||||
</view>
|
||||
<view class="video-popup-body">
|
||||
<video
|
||||
v-if="videoUrl"
|
||||
:src="videoUrl"
|
||||
controls
|
||||
autoplay
|
||||
class="video-player"
|
||||
></video>
|
||||
<view v-else class="video-error">视频地址不存在</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ns-video-player-popup',
|
||||
props: {
|
||||
/**
|
||||
* 当前播放的视频信息
|
||||
* @type {Object}
|
||||
* @property {string} title - 视频标题
|
||||
* @property {string} videoUrl - 视频地址
|
||||
*/
|
||||
currentVideo: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
videoUrl() {
|
||||
return this.currentVideo?.videoUrl || '';
|
||||
},
|
||||
|
||||
videoTitle() {
|
||||
return this.currentVideo?.title || '';
|
||||
},
|
||||
/**
|
||||
* 视频弹窗是否显示
|
||||
* @type {boolean}
|
||||
*/
|
||||
isVisible() {
|
||||
return this.$refs.videoPopup?.visible || false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 打开视频弹窗
|
||||
*/
|
||||
open() {
|
||||
if (this.$refs.videoPopup) {
|
||||
this.$refs.videoPopup.open();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭视频弹窗
|
||||
*/
|
||||
close() {
|
||||
if (this.$refs.videoPopup) {
|
||||
this.$refs.videoPopup.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 视频弹窗状态变化
|
||||
* @param {Object} e - 弹窗状态变化事件
|
||||
*/
|
||||
onVideoPopupChange(e) {
|
||||
this.$emit('popup-change', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 视频播放弹窗样式 */
|
||||
.video-player-popup {
|
||||
.video-popup-content {
|
||||
width: 90%;
|
||||
max-width: 600rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.video-popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
background-color: #fafafa;
|
||||
|
||||
.video-popup-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon-close {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.video-popup-body {
|
||||
padding: 20rpx;
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
background-color: #000;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.video-error {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -53,8 +53,15 @@ 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 {
|
||||
@@ -152,28 +159,62 @@ 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 {
|
||||
this.$emit('agree')
|
||||
// 检查本地存储中是否已经同意过隐私政策
|
||||
const privacyAgreed = uni.getStorageSync('privacyAgreed');
|
||||
if (privacyAgreed) {
|
||||
this.$emit('agree');
|
||||
} else {
|
||||
this.popUp()
|
||||
// 额外逻辑:当前页面的隐私弹窗弹起的时候,关掉其他页面的隐私弹窗
|
||||
this.closeOtherPagePopUp(this.disPopUp)
|
||||
}
|
||||
}
|
||||
// #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() {
|
||||
@@ -261,6 +302,8 @@ export default {
|
||||
this.privacyResolves.clear()
|
||||
//关闭弹窗
|
||||
this.disPopUp()
|
||||
// 保存用户授权状态到本地存储,无论同意还是不同意,都不再重复弹出
|
||||
uni.setStorageSync('privacyAgreed', true);
|
||||
//退出小程序
|
||||
uni.showModal({
|
||||
content: '未同意隐私协议,无法使用相关功能',
|
||||
@@ -281,10 +324,8 @@ export default {
|
||||
//关闭弹窗
|
||||
this.disPopUp()
|
||||
|
||||
// #ifdef QUICKAPP-WEBVIEW || H5 || WEB
|
||||
// 保存用户授权状态到本地存储,用于快应用Webview组件中的H5方式处理
|
||||
// 保存用户授权状态到本地存储,用于所有环境
|
||||
uni.setStorageSync('privacyAgreed', true);
|
||||
// #endif
|
||||
this.$emit('agree')
|
||||
},
|
||||
//打开弹窗
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.register-box /deep/ .uni-scroll-view {
|
||||
.register-box ::v-deep .uni-scroll-view {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
@@ -155,11 +155,11 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.register-box /deep/.uni-popup__wrapper-box {
|
||||
.register-box ::v-deep .uni-popup__wrapper-box {
|
||||
overflow: unset !important;
|
||||
}
|
||||
|
||||
.reward-popup /deep/.uni-popup__wrapper {
|
||||
.reward-popup ::v-deep .uni-popup__wrapper {
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:src="$util.img('public/uniapp/common/mescroll-totop.png')"
|
||||
mode="widthFix"
|
||||
@click="toTopClick"
|
||||
/>
|
||||
show-menu-by-longpress/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
<style>
|
||||
/* 回到顶部的按钮 */
|
||||
.mescroll-totop {
|
||||
z-index: 99;
|
||||
z-index: 2147483647;
|
||||
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
|
||||
right: 46rpx !important;
|
||||
bottom: 272rpx !important;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user