This commit is contained in:
2025-10-27 15:55:29 +08:00
commit 6632080b83
513 changed files with 117442 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/unpackage
/.hbuilderx
/.idea

313
App.vue Normal file
View File

@@ -0,0 +1,313 @@
<script>
import auth from 'common/js/auth.js';
import colorList from 'common/js/style_color.js'
import {
Weixin
} from 'common/js/wx-jssdk.js';
export default {
mixins: [auth],
onLaunch: function(options) {
// console.log(options.query.uniacid)
if(options.query.uniacid){
uni.setStorageSync('uniacid', options.query.uniacid);
console.log(uni.getStorageSync('uniacid'))
}
uni.hideTabBar();
// #ifdef MP
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function(res) {
// 请求完新版本信息的回调
});
updateManager.onUpdateReady(function(res) {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(function(res) {
// 新的版本下载失败
});
// #endif
// #ifdef H5
if (uni.getSystemInfoSync().platform == 'ios') {
uni.setStorageSync('initUrl', location.href);
}
// #endif
uni.onNetworkStatusChange(function(res) {
if (!res.isConnected) {
uni.showModal({
title: '网络失去链接',
content: '请检查网络链接',
showCancel: false
});
}
});
this.$store.dispatch('init');
// 存储到store中
// 主题风格
if (uni.getStorageSync('themeStyle')) {
this.$store.commit('setThemeStyle', colorList[uni.getStorageSync('themeStyle')]);
}
// 插件是否存在
if (uni.getStorageSync('addonIsExist')) {
this.$store.commit('setAddonIsExist', uni.getStorageSync('addonIsExist'));
}
// 默认图
if (uni.getStorageSync('defaultImg')) {
this.$store.commit('setDefaultImg', uni.getStorageSync('defaultImg'));
}
// 站点信息
if (uni.getStorageSync('siteInfo')) {
this.$store.commit('setSiteInfo', uni.getStorageSync('siteInfo'));
}
// 门店配置
if (uni.getStorageSync('globalStoreConfig')) {
this.$store.commit('setGlobalStoreConfig', uni.getStorageSync('globalStoreConfig'));
}
// 门店信息
if (uni.getStorageSync('globalStoreInfo')) {
this.$store.commit('setGlobalStoreInfo', uni.getStorageSync('globalStoreInfo'));
}
// 默认门店信息
if (uni.getStorageSync('defaultStoreInfo')) {
this.$store.commit('setDefaultStoreInfo', uni.getStorageSync('defaultStoreInfo'));
}
// 客服配置
if (uni.getStorageSync('servicerConfig')) {
this.$store.commit('setServicerConfig', uni.getStorageSync('servicerConfig'));
}
// 版权信息
if (uni.getStorageSync('copyright')) {
this.$store.commit('setCopyright', uni.getStorageSync('copyright'));
}
// 地址配置
if (uni.getStorageSync('mapConfig')) {
this.$store.commit('setMapConfig', uni.getStorageSync('mapConfig'));
}
if (uni.getStorageSync('token')) {
this.$store.commit('setToken', uni.getStorageSync('token'));
}
// 会员信息
if (uni.getStorageSync('memberInfo')) {
this.$store.commit('setMemberInfo', uni.getStorageSync('memberInfo'));
// 查询购物车信息
this.$store.dispatch('getCartNumber');
}
// #ifdef H5
// 自动授权登录
// 未登录情况下
if (!uni.getStorageSync('memberInfo')) {
this.getAuthInfo();
}
if (this.$store.state.token) {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 0) {
this.$store.commit('setMemberInfo', res.data);
}
}
});
}
// #endif
// #ifdef MP-ALIPAY
if (options.query && options.query.m) uni.setStorageSync('source_member', options.query.m);
// #endif
},
onShow: function(options) {
// #ifdef MP
// 自动授权登录
this.getAuthInfo();
if (this.$store.state.token) {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 0) {
this.$store.commit('setMemberInfo', res.data);
}
}
});
}
// #endif
// #ifdef MP-ALIPAY
if (options.query && options.query.m) uni.setStorageSync('source_member', options.query.m);
// #endif
},
onHide: function() {},
methods: {
/**
* 获取授权信息
*/
getAuthInfo() {
// #ifdef H5
if (this.$util.isWeiXin()) {
this.$util.getUrlCode(urlParams => {
if (urlParams.source_member) uni.setStorageSync('source_member', urlParams
.source_member);
if (urlParams.code == undefined) {
this.$api.sendRequest({
url: '/wechat/api/wechat/authcode',
data: {
redirect_url: location.href,
scopes: 'snsapi_userinfo'
},
success: res => {
if (res.code >= 0) {
location.href = res.data;
}
}
});
} else {
this.$api.sendRequest({
url: '/wechat/api/wechat/authcodetoopenid',
data: {
code: urlParams.code
},
success: res => {
if (res.code >= 0) {
let data = {};
if (res.data.openid) data.wx_openid = res.data.openid;
if (res.data.unionid) data.wx_unionid = res.data.unionid;
if (res.data.userinfo) Object.assign(data, res.data
.userinfo);
this.authLogin(data);
}
}
});
}
});
}
// #endif
// #ifdef MP
this.getCode(data => {
this.authLogin(data, 'authOnlyLogin');
});
// #endif
},
/**
* 授权登录
*/
authLogin(data, type = 'authLogin') {
if (uni.getStorageSync('source_member')) data.source_member = uni.getStorageSync('source_member');
uni.setStorageSync('authInfo', data);
this.$api.sendRequest({
url: type == 'authLogin' ? '/api/login/auth' : '/api/login/authonlylogin',
data,
success: res => {
if (res.code >= 0) {
this.$store.commit('setToken', res.data.token);
this.getMemberInfo()
this.$store.dispatch('getCartNumber');
}
}
});
},
/**
* 公众号分享设置
*/
shareConfig() {
this.$api.sendRequest({
url: '/wechat/api/wechat/share',
data: {
url: window.location.href
},
success: res => {
if (res.code == 0) {
var wxJS = new Weixin();
wxJS.init(res.data.jssdk_config);
let share_data = JSON.parse(JSON.stringify(res.data.share_config.data));
if (share_data) {
wxJS.setShareData({
title: share_data.title,
desc: share_data.desc,
link: share_data.link,
imgUrl: this.$util.img(share_data.imgUrl)
},
res => {
console.log(res);
}
);
}
let hideOptionMenu = res.data.share_config.permission.hideOptionMenu;
let hideMenuItems = res.data.share_config.permission.hideMenuItems;
if (hideOptionMenu) {
wxJS.weixin.hideOptionMenu(); //屏蔽分享好友等按钮
} else {
wxJS.weixin.showOptionMenu(); //放开分享好友等按钮
}
}
},
fail: err => {}
});
},
getMemberInfo() {
this.$api.sendRequest({
url: '/api/member/info',
success: (res) => {
if (res.code >= 0) {
// 登录成功,存储会员信息
this.$store.commit('setMemberInfo', res.data);
}
}
});
}
},
watch: {
$route: {
handler(newName, oldName) {
if (this.$util.isWeiXin()) {
this.shareConfig();
}
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
immediate: true
}
}
};
</script>
<style lang="scss">
@import './common/css/main.scss';
@import './common/css/iconfont.css';
@import './common/css/icondiy.css'; // 自定义图标库
@import './common/css/icon/extend.css'; // 扩展图标库
page{
background: #f4f6fa;
}
</style>

BIN
common/css/custom.ttf Normal file

Binary file not shown.

234
common/css/diy.scss Normal file
View File

@@ -0,0 +1,234 @@
.collectPopupWindow {
position: relative;
height: 113rpx;
width: 510rpx;
margin-left: calc(100% - 530rpx);
image {
width: 100%;
height: 100%;
}
text {
color: #ff4544 !important;
font-size: 24rpx !important;
position: absolute;
top: 48rpx;
right: 25rpx;
}
}
.zhezhao {
width: 100vw;
height: 100vh;
background-color: transparent;
}
image {
max-width: 100% !important;
max-height: 100% !important;
}
.diy-wrap {
/* #ifdef H5 */
height: calc(100vh - 88rpx);
/* #endif */
/* #ifdef MP-WEIXIN */
height: 100vh;
/* #endif */
}
.page-img {
background-size: contain !important;
background-repeat: no-repeat !important;
}
.page-header {
background-size: 100% !important;
background-repeat: no-repeat !important;
background-position: top center;
background-attachment: fixed;
}
.bg-index {
width: 100%;
height: 100%;
box-sizing: border-box;
background-size: 100% !important;
background-repeat: no-repeat !important;
}
.wap-floating {
text {
display: block;
font-size: 60rpx;
color: #ffffff;
text-align: center;
}
}
.wap-floating-collect .uni-popup__mask {
background: transparent;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.popup-box {
width: 450rpx;
background: #ffffff;
border-radius: $border-radius;
overflow: hidden;
.close_title {
width: 100%;
text-align: center;
height: 70rpx;
line-height: 70rpx;
font-size: $font-size-base;
}
.close_content {
width: 100%;
max-height: 500rpx;
padding: $padding;
box-sizing: border-box;
}
.close_content_box {
width: 100%;
max-height: 460rpx;
line-height: 1.3;
}
}
.noStore-text {
color: #000000 !important;
}
.isStore-top {
margin-bottom: 10rpx;
}
.keep-on-record {
text-align: center;
padding-bottom: 20rpx;
image {
width: 150rpx;
height: 60rpx;
}
}
.padding-bottom {
padding-bottom: 40rpx !important;
}
.choose-store {
/deep/ .uni-popup__wrapper{
background: none!important;
}
}
.choose-store-popup {
padding: 30rpx;
background-color: #fff;
.head-wrap {
font-weight: bold;
font-size: $font-size-toolbar;
text-align: center;
margin-bottom: 20rpx;
color: #202021;
}
.position-wrap {
display: flex;
color: #202021;
align-items: center;
margin-bottom: 20rpx;
.icon-dizhi {
font-weight: bold;
font-size: $font-size-tag;
margin-right: 10rpx;
}
.address {
font-weight: bold;
font-size: $font-size-tag;
margin-right: 10rpx;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
.reposition {
display: flex;
align-items: center;
.iconfont {
font-size: $font-size-base;
margin-right: 6rpx;
}
text {
font-size: $font-size-tag;
color: #fd463e;
}
}
}
.store-wrap {
border: 1px solid $base-color;
border-radius: 16rpx;
padding: 20rpx 30rpx;
margin-bottom: 30rpx;
.tag {
background-color: #fee9ea;
color: #fd463e;
font-size: $font-size-activity-tag;
display: inline-block;
border-radius: 6rpx;
padding: 4rpx 12rpx;
// #ifdef H5
transform: scale(0.8);
margin-left: -10rpx;
// #endif
}
.store-name {
margin: 10rpx 0;
font-weight: bold;
color: #202021;
font-size: $font-size-toolbar;
}
.address {
color: #5f6067;
font-size: $font-size-tag;
margin-bottom: 10rpx;
}
.distance {
display: flex;
align-items: center;
color: #5f6067;
font-size: $font-size-tag;
.iconfont {
font-size: $font-size-base;
margin-right: 10rpx;
}
}
}
button {
border-radius: 40rpx;
}
.other-store {
display: flex;
align-items: center;
color: #5e6066;
font-weight: bold;
justify-content: center;
margin-top: 20rpx;
margin-bottom: 20rpx;
.iconfont {
margin-left: 10rpx;
font-size: $font-size-tag;
}
}
}
.page-bottom {
margin-top: 20rpx;
}

1562
common/css/goods_detail.scss Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
/* 扩展图标库文件 */

5731
common/css/icondiy.css Normal file

File diff suppressed because one or more lines are too long

463
common/css/iconfont.css Normal file

File diff suppressed because one or more lines are too long

876
common/css/main.scss Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

103
common/js/auth.js Normal file
View File

@@ -0,0 +1,103 @@
export default {
data() {
return {
authInfo: {}
}
},
methods: {
/**
* 获取用户登录凭证code
*/
getCode(callback) {
// 微信小程序
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
timeout: 3000,
success: res => {
if (res.code) {
this.$api.sendRequest({
url: '/weapp/api/weapp/authcodetoopenid',
data: {
code: res.code
},
success: res => {
if (res.code >= 0) {
if (res.data.openid) this.authInfo.weapp_openid = res.data
.openid;
if (res.data.unionid) this.authInfo.wx_unionid = res.data
.unionid;
typeof callback == 'function' && callback(this.authInfo);
} else {
this.$util.showToast({
title: res.message ? res.message : '小程序配置错误'
});
}
}
})
}
},
fail: (res) => {
// #ifdef MP-WEIXIN
let scene = wx.getLaunchOptionsSync().scene;
if ([1154, 1155].indexOf(scene) == -1) {
this.$util.showToast({
title: res.errMsg
});
}
// #endif
}
})
// #endif
// #ifdef MP-ALIPAY
uni.login({
timeout: 3000,
success: res => {
if (res.code) {
this.$api.sendRequest({
url: '/aliapp/api/aliapp/authcodetouserid',
data: {
code: res.code
},
success: res => {
if (res.code >= 0) {
if (res.data.user_id) this.authInfo.ali_openid = res.data
.user_id;
typeof callback == 'function' && callback(this.authInfo);
} else {
this.$util.showToast({
title: res.message ? res.message : '小程序配置错误'
});
}
}
})
}
},
fail: (err) => {
this.$util.showToast({
title: res.errMsg
});
}
})
// #endif
// #ifdef H5
if (this.$util.isWeiXin()) {
this.$api.sendRequest({
url: '/wechat/api/wechat/authcode',
data: {
redirect_url: location.href,
scopes: 'snsapi_userinfo'
},
success: res => {
if (res.code >= 0) {
location.href = res.data;
}
}
});
}
// #endif
}
}
}

50
common/js/config.js Normal file
View File

@@ -0,0 +1,50 @@
var config = {
// baseUrl: site.baseUrl,
// imgDomain: site.baseUrl,
// h5Domain: site.baseUrl,
//api请求地址
baseUrl: 'https://xcx30.5g-quickapp.com/',
// 图片域名
imgDomain: 'https://xcx30.5g-quickapp.com/',
// H5端域名
h5Domain: 'https://xcx30.5g-quickapp.com/',
// // api请求地址
// baseUrl: 'https://tsaas.liveplatform.cn/',
// // 图片域名
// imgDomain: 'https://tsaas.liveplatform.cn/',
// // H5端域名
// h5Domain: 'https://tsaas.liveplatform.cn/',
// api请求地址
// baseUrl: 'http://saas.cn/',
// // 图片域名
// imgDomain: 'http://saas.cn/',
// // H5端域名
// h5Domain: 'http://saas.cn/',
// 腾讯地图key
mpKey: 'TUHBZ-CNWKU-UHAVP-GZQ26-HNZFO-3YBF4',
//客服地址
webSocket: '{{$webSocket}}',
//本地端主动给服务器ping的时间, 0 则不开启 , 单位秒
pingInterval: 1500,
uniacid:305,//825
// uniacid:site.uniacid,//825
// 版本号
version: '1.0'
};
export default config;

611
common/js/diy.js Normal file
View File

@@ -0,0 +1,611 @@
import WxMap from 'common/js/map-wx-jssdk.js';
import Config from '@/common/js/config.js';
let systemInfo = uni.getSystemInfoSync();
export default {
data() {
return {
diyData: {
global: {
title: '',
popWindow: {
imageUrl: '',
count: -1,
link: {},
imgWidth: '',
imgHeight: ''
}
}
},
id: 0,
name: '',
topIndexValue: null,
statusBarHeight: systemInfo.statusBarHeight,
collectTop: 44,
showTip: false,
mpCollect: false,
mpShareData: null, //小程序分享数据
scrollTop: 0, // 滚动位置
paddingTop: (44 + systemInfo.statusBarHeight) + 'px',
marginTop: -(44 + systemInfo.statusBarHeight) + 'px',
followOfficialAccount: null, // 关注公众号组件
latitude: null, // 纬度
longitude: null, // 经度
currentPosition: '', // 当前位置
nearestStore: null, // 离自己最近的门店
storeTimeOut: null, // 没有获取到定位,则获取默认门店
locationModule: '', // 模式locationPicker H5选择地图
diyRoute: '', // 页面路由
openBottomNav: false,
isShowCopyRight: false,
//启动广告
adv:{},
};
},
onLoad(option) {
uni.hideTabBar();
if (option.source_member) uni.setStorageSync('source_member', option.source_member);
// 小程序扫码进入
if (option.scene) {
var sceneParams = decodeURIComponent(option.scene);
sceneParams = sceneParams.split('&');
if (sceneParams.length) {
sceneParams.forEach(item => {
if (item.indexOf('m') != -1) uni.setStorageSync('source_member', item.split('-')[1]);
});
}
}
// #ifdef H5
// H5地图选择位置回调数据
if (option.module && option.module == 'locationPicker') {
option.name = ''; // 清空地址
this.locationModule = option.module;
this.latitude = option.latng.split(',')[0];
this.longitude = option.latng.split(',')[1];
}
// #endif
this.id = option.id || 0;
this.name = option.name || '';
uni.removeStorageSync('manual_store_info'); // 清除手动切换门店缓存
uni.removeStorageSync('manual_change_store'); // 清楚手动切换门店标识
// H5才会执行
if (this.locationModule == 'locationPicker') {
// H5地图选址后的回调
this.getNearestStore();
this.getCurrentLocation();
} else if (this.mapConfig.wap_is_open == 1) {
// 每次都要定位,获取当前位置
/*this.$util.getLocation({
fail: (res) => {
// 拒绝定位,进入默认总店
this.enterDefaultStore();
}
});*/
// 如果3秒没有获取到定位则获取默认门店H5使用
// #ifdef H5
this.storeTimeOut = setTimeout(() => {
this.enterDefaultStore();
}, 1000 * 3);
// #endif
} else {
// 关闭定位
this.enterDefaultStore();
}
},
onShow() {
this.init();
},
onHide() {
if (this.storeTimeOut) {
clearTimeout(this.storeTimeOut);
}
// 跳转页面要关闭门店弹出框
this.closeChooseStorePopup();
// 清除限时秒杀定时器
this.$store.commit('setDiySeckillInterval', 0);
},
computed: {
bgColor() {
let str = '';
if (this.diyData && this.diyData.global) {
str = this.diyData.global.pageBgColor;
}
return str;
},
bgImg() {
let str = '';
if (this.diyData && this.diyData.global) {
str = this.diyData.global.topNavBg ? 'url(' + this.$util.img(this.diyData.global.bgUrl) + ')' : this.diyData.global.pageBgColor;
}
return str;
},
bgUrl() {
let str = '';
if (this.diyData && this.diyData.global) {
str = this.diyData.global.topNavBg ? 'transparent' : this.diyData.global.bgUrl;
}
return str;
},
backgroundUrl() {
var str = this.diyData.global.bgUrl && this.diyData.global.bgUrl != 'transparent' ? 'url(' + this.$util.img(this.diyData.global.bgUrl) + ') ' : '';
return str;
},
textNavColor() {
if (this.diyData && this.diyData.global && this.diyData.global.textNavColor) {
return this.diyData.global.textNavColor;
} else {
return '#ffffff';
}
},
//计算首页弹框的显示宽高
popWindowStyle() {
// 做大展示宽高
let max_width = 290;
let max_height = 410;
// 参照宽高
let refer_width = 290;
let refer_height = 290;
let scale = this.diyData.global.popWindow.imgHeight / this.diyData.global.popWindow.imgWidth;
let width, height;
if (scale < refer_height / refer_width) {
width = max_width;
height = width * scale;
} else {
height = max_height;
width = height / scale;
}
let obj = '';
if (this.diyData.global.popWindow && this.diyData.global.popWindow.count != -1 && this.diyData.global.popWindow.imageUrl) {
obj += 'height:' + (height * 2) + 'rpx;';
obj += 'width:' + (width * 2) + 'rpx;';
}
return obj;
}
},
watch: {
location: function (nVal) {
if (nVal) {
this.latitude = nVal.latitude;
this.longitude = nVal.longitude;
this.getNearestStore();
this.getCurrentLocation();
}
}
},
methods: {
play(){
console.log(123)
},
async init() {
// 定位信息过期后,重新获取定位
if(this.mapConfig.wap_is_open == 1 && this.locationStorage && this.locationStorage.is_expired) {
this.$util.getLocation({
fail: (res) => {
// 拒绝定位,进入默认总店
this.enterDefaultStore();
}
});
}
if (this.storeToken) {
//记录分享关系
if (uni.getStorageSync('source_member')) {
this.$util.onSourceMember(uni.getStorageSync('source_member'));
}
}
await this.getDiyInfo();
//获取启动广告
await this.getDiyAdv();
this.$store.commit('setDiySeckillInterval', 1);
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare().then(res => {
this.mpShareData = res;
});
// #endif
let manualChangeStore = uni.getStorageSync('manual_change_store'); // 手动切换门店
if (manualChangeStore) {
uni.removeStorageSync('manual_change_store');
// 滚动至顶部
uni.pageScrollTo({
duration: 200,
scrollTop: 0
});
}
},
callback() {
if (this.$refs.indexPage) {
this.$refs.indexPage.initPageIndex();
}
},
//计算高度
getHeight() {
// #ifdef H5
if (this.diyData && this.diyData.global && this.diyData.global.navBarSwitch) {
// H5端导航栏样式1 2 3不显示要减去高度
if ([1, 2, 3].indexOf(parseInt(this.diyData.global.navStyle)) != -1) {
this.paddingTop = 0;
this.marginTop = 0;
}
}
// #endif
// #ifdef MP || APP-PLUS
let time = setInterval(() => {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.page-header').boundingClientRect(data => {
if (data && data.height) {
// 从状态栏高度开始算
this.paddingTop = data.height + 'px';
this.marginTop = -data.height + 'px';
clearInterval(time);
}
}).exec();
});
}, 50);
// #endif
},
async getDiyAdv(){
//启动广告
let res = await this.$api.sendRequest({
url: '/api/diyview/getstartadv',
data: {},
async: false
});
this.adv = res.value
// 弹框形式,首次弹出 1每次弹出 0
if(this.adv.advshow == 1){
setTimeout(() => {
if (res.value.advtype == 1) {
var popwindow_count = uni.getStorageSync(this.id + this.name + '_popwindow_count');
if ((this.$refs.uniPopupWindow && popwindow_count == '') || (
this.$refs.uniPopupWindow && popwindow_count == 1)) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.id + this.name + '_popwindow_count', 1);
}
} else if (res.value.advtype == 0) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.id + this.name + '_popwindow_count', 0);
}
}, 500);
}
},
async getDiyInfo() {
let res = await this.$api.sendRequest({
url: '/api/diyview/info',
data: {
id: this.id,
name: this.name,
en_type:uni.getStorageSync("lang"),//获取语言底部
},
async: false
});
if (res.code != 0 || !res.data) {
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
if (res.code == -3) {
this.$util.showToast({
title: res.message
});
this.diyData = {};
return;
}
this.$util.showToast({
title: '未配置自定义页面数据'
});
this.diyData = {};
return;
}
let diyDataValue = res.data;
if (diyDataValue.value) {
this.diyData = JSON.parse(diyDataValue.value);
this.$langConfig.title(this.diyData.global.title);
this.mpCollect = this.diyData.global.mpCollect;
this.setPublicShare();
/* if (this.diyData.global.popWindow && this.diyData.global.popWindow.imageUrl) {
// 弹框形式,首次弹出 1每次弹出 0
setTimeout(() => {
if (this.diyData.global.popWindow.count == 1) {
var popwindow_count = uni.getStorageSync(this.id + this.name + '_popwindow_count');
if ((this.$refs.uniPopupWindow && popwindow_count == '') || (
this.$refs.uniPopupWindow && popwindow_count == 1)) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.id + this.name + '_popwindow_count', 1);
}
} else if (this.diyData.global.popWindow.count == 0) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.id + this.name + '_popwindow_count', 0);
}
}, 500);
}*/
// 修改diy数据结构排序
let searchIndex = -1;
let topCategoryIndex = -1;
this.diyData.value.forEach((item, index) => {
if (item.componentName == 'Search') {
if (item.positionWay == 'fixed') {
searchIndex = index;
}
}
if (item.componentName == 'TopCategory') {
topCategoryIndex = index;
}
})
if (searchIndex != -1 && topCategoryIndex != -1) {
let searchData = this.diyData.value.slice(searchIndex, searchIndex + 1);
let topCategoryData = this.diyData.value.slice(topCategoryIndex, topCategoryIndex + 1);
this.diyData.value.splice(searchIndex, 1);
if (searchIndex > topCategoryIndex) {
this.diyData.value.splice(topCategoryIndex, 1);
this.diyData.value.splice(0, 0, ...topCategoryData);
this.diyData.value.splice(1, 0, ...searchData);
} else
this.diyData.value.splice(0, 0, ...searchData);
} else if (searchIndex != -1 && topCategoryIndex == -1) {
let searchData = this.diyData.value.slice(searchIndex, searchIndex + 1);
this.diyData.value.splice(searchIndex, 1);
this.diyData.value.splice(0, 0, ...searchData);
}
for (var i = 0; i < this.diyData.value.length; i++) {
// 分类导航组件
if (this.diyData.value[i].componentName == 'TopCategory') {
this.topIndexValue = this.diyData.value[i];
this.topIndexValue.moduleIndex = i; //设置定位索引,根据此来确定定位顺序
this.diyData.value.splice(i, 1);
continue;
}
// 关注公众号组件
if (this.diyData.value[i].componentName == 'FollowOfficialAccount') {
this.followOfficialAccount = this.diyData.value[i];
// #ifdef H5
this.diyData.value.splice(i, 1);
// #endif
continue;
}
}
// #ifdef MP
//小程序收藏
if (!uni.getStorageSync('isCollect') && this.diyData.global.mpCollect) {
this.$refs.collectPopupWindow.open();
this.showTip = true;
}
// #endif
this.getHeight();
if (this.diyData && this.diyData.global) {
this.openBottomNav = this.diyData.global.openBottomNav;
}
this.isShowCopyRight = true;
}
},
closePopupWindow() {
this.$refs.uniPopupWindow.close();
uni.setStorageSync(this.id + this.name + '_popwindow_count', -1);
},
closeCollectPopupWindow() {
this.$refs.collectPopupWindow.close();
uni.setStorageSync('isCollect', true);
},
uniPopupWindowFn() {
this.$util.diyRedirectTo(this.diyData.global.popWindow.link);
this.closePopupWindow();
},
openChooseStorePopup() {
if (this.globalStoreConfig && this.globalStoreConfig.confirm_popup_control == 1) {
let storeInfo = this.globalStoreInfo;
// 首次进入门店,没有门店信息 || 当前位置的门店和缓存门店不一致要弹框
if (!storeInfo || storeInfo && this.nearestStore && storeInfo.store_id != this.nearestStore.store_id) {
if (this.$refs.chooseStorePopup) this.$refs.chooseStorePopup.open();
}
}
let manualStoreInfo = uni.getStorageSync('manual_store_info'); // 手动选择门店
if (manualStoreInfo) {
this.nearestStore = manualStoreInfo;
}
this.changeStore(this.nearestStore); // 切换门店数据
},
closeChooseStorePopup() {
if (this.$refs.chooseStorePopup) this.$refs.chooseStorePopup.close();
},
// 确认进入门店
enterStore() {
this.closeChooseStorePopup();
},
// 选择其他门店
chooseOtherStore() {
this.$util.redirectTo('/pages_tool/store/list');
this.closeChooseStorePopup();
},
// 打开地图重新选择位置
reposition() {
// #ifdef MP
/*uni.chooseLocation({
success: res => {
this.latitude = res.latitude;
this.longitude = res.longitude;
this.currentPosition = res.name;
this.getNearestStore();
this.getCurrentLocation();
},
fail(res) {
uni.getSetting({
success: function (res) {
var statu = res.authSetting;
if (!statu['scope.userLocation']) {
uni.showModal({
title: '是否授权当前位置',
content: '需要获取您的地理位置,请确认授权,否则地图功能将无法使用',
success(tip) {
if (tip.confirm) {
uni.openSetting({
success: function (data) {
if (data.authSetting['scope.userLocation'] === true) {
this.$util.showToast({
title: '授权成功'
});
//授权成功之后再调用chooseLocation选择地方
setTimeout(function () {
uni.chooseLocation({
success: data => {
this.latitude = res.latitude;
this.longitude = res.longitude;
this.currentPosition = res.name;
this.getNearestStore();
this.getCurrentLocation();
}
});
}, 1000);
}
}
});
} else {
this.$util.showToast({
title: '授权失败'
});
}
}
});
}
}
});
}
});*/
// #endif
// #ifdef H5
let backurl = Config.h5Domain; // 地图选择位置后的回调页面路径
window.location.href = 'https://apis.map.qq.com/tools/locpicker?search=1&type=0&backurl=' +
encodeURIComponent(backurl) + '&key=' + Config.mpKey + '&referer=myapp';
// #endif
},
// 获取离自己最近的一个门店
getNearestStore() {
let data = {};
if (this.latitude && this.longitude) {
data.latitude = this.latitude;
data.longitude = this.longitude;
}
this.$api.sendRequest({
url: '/api/store/nearestStore',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.nearestStore = res.data;
this.nearestStore.show_address = this.nearestStore.full_address.replace(/,/g, ' ') + ' ' + this.nearestStore.address;
this.openChooseStorePopup();
}
}
});
},
// 根据经纬度获取位置
getCurrentLocation() {
var _this = this;
let data = {};
if (this.latitude && this.longitude) {
data.latitude = this.latitude;
data.longitude = this.longitude;
}
this.$api.sendRequest({
url: '/api/store/getLocation',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.currentPosition = res.data.formatted_addresses.recommend; // 结合知名地点形成的描述性地址,更具人性化特点
} else {
this.currentPosition = '未获取到定位';
}
}
});
},
// 定位失败,进入默认门店
enterDefaultStore() {
if (this.defaultStoreInfo) {
if (!this.nearestStore) {
this.nearestStore = this.defaultStoreInfo;
this.nearestStore.show_address = this.nearestStore.full_address.replace(/,/g, ' ') + ' ' + this.nearestStore.address;
}
if (this.currentPosition == '') this.currentPosition = '未获取到定位';
this.openChooseStorePopup();
}
},
// 设置公众号分享
setPublicShare() {
let shareUrl = this.$config.h5Domain + this.diyRoute;
if (this.id) shareUrl += '?id=' + this.id;
else if (this.name) shareUrl += '?name=' + this.name;
this.$util.setPublicShare({
title: this.diyData.global.title,
desc: '',
link: shareUrl,
imgUrl: this.siteInfo ? this.$util.img(this.siteInfo.logo_square) : ''
});
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
if (this.$refs.topNav) {
if (e.scrollTop >= 20) {
this.$refs.topNav.navTopBg();
} else {
this.$refs.topNav.unSetnavTopBg();
}
}
},
// 下拉刷新
onPullDownRefresh() {
this.$store.commit('setComponentRefresh');
setTimeout(() => {
uni.stopPullDownRefresh();
}, 50);
},
// 分享给好友
onShareAppMessage() {
return this.mpShareData.appMessage;
},
// 分享到朋友圈
onShareTimeline() {
return this.mpShareData.timeLine;
}
}

55
common/js/emjoy.js Normal file
View File

@@ -0,0 +1,55 @@
export default {
emjoyList: {
"[emjoy_01]": 'public/static/img/emjoy/emjoy_01.gif',
"[emjoy_02]": 'public/static/img/emjoy/emjoy_02.gif',
"[emjoy_03]": 'public/static/img/emjoy/emjoy_03.gif',
"[emjoy_04]": 'public/static/img/emjoy/emjoy_04.gif',
"[emjoy_05]": 'public/static/img/emjoy/emjoy_05.gif',
"[emjoy_06]": 'public/static/img/emjoy/emjoy_06.gif',
"[emjoy_07]": 'public/static/img/emjoy/emjoy_07.gif',
"[emjoy_08]": 'public/static/img/emjoy/emjoy_08.gif',
"[emjoy_09]": 'public/static/img/emjoy/emjoy_09.gif',
"[emjoy_10]": 'public/static/img/emjoy/emjoy_10.gif',
"[emjoy_11]": 'public/static/img/emjoy/emjoy_11.gif',
"[emjoy_12]": 'public/static/img/emjoy/emjoy_12.gif',
"[emjoy_13]": 'public/static/img/emjoy/emjoy_13.gif',
"[emjoy_14]": 'public/static/img/emjoy/emjoy_14.gif',
"[emjoy_15]": 'public/static/img/emjoy/emjoy_15.gif',
"[emjoy_16]": 'public/static/img/emjoy/emjoy_16.gif',
"[emjoy_17]": 'public/static/img/emjoy/emjoy_17.gif',
"[emjoy_18]": 'public/static/img/emjoy/emjoy_18.gif',
"[emjoy_19]": 'public/static/img/emjoy/emjoy_19.gif',
"[emjoy_20]": 'public/static/img/emjoy/emjoy_20.gif',
"[emjoy_21]": 'public/static/img/emjoy/emjoy_21.gif',
"[emjoy_22]": 'public/static/img/emjoy/emjoy_22.gif',
"[emjoy_23]": 'public/static/img/emjoy/emjoy_23.gif',
"[emjoy_24]": 'public/static/img/emjoy/emjoy_24.gif',
"[emjoy_25]": 'public/static/img/emjoy/emjoy_25.gif',
"[emjoy_26]": 'public/static/img/emjoy/emjoy_26.gif',
"[emjoy_27]": 'public/static/img/emjoy/emjoy_27.gif',
"[emjoy_28]": 'public/static/img/emjoy/emjoy_28.gif',
"[emjoy_29]": 'public/static/img/emjoy/emjoy_29.gif',
"[emjoy_30]": 'public/static/img/emjoy/emjoy_30.gif',
"[emjoy_31]": 'public/static/img/emjoy/emjoy_31.gif',
"[emjoy_32]": 'public/static/img/emjoy/emjoy_32.gif',
"[emjoy_33]": 'public/static/img/emjoy/emjoy_33.gif',
"[emjoy_34]": 'public/static/img/emjoy/emjoy_34.gif',
"[emjoy_35]": 'public/static/img/emjoy/emjoy_35.gif',
"[emjoy_36]": 'public/static/img/emjoy/emjoy_36.gif',
"[emjoy_37]": 'public/static/img/emjoy/emjoy_37.gif',
"[emjoy_38]": 'public/static/img/emjoy/emjoy_38.gif',
"[emjoy_39]": 'public/static/img/emjoy/emjoy_39.gif',
"[emjoy_40]": 'public/static/img/emjoy/emjoy_40.gif',
"[emjoy_41]": 'public/static/img/emjoy/emjoy_41.gif',
"[emjoy_42]": 'public/static/img/emjoy/emjoy_42.gif',
"[emjoy_43]": 'public/static/img/emjoy/emjoy_43.gif',
"[emjoy_44]": 'public/static/img/emjoy/emjoy_44.gif',
"[emjoy_45]": 'public/static/img/emjoy/emjoy_45.gif',
"[emjoy_46]": 'public/static/img/emjoy/emjoy_46.gif',
"[emjoy_47]": 'public/static/img/emjoy/emjoy_47.gif',
}
}

View File

@@ -0,0 +1,24 @@
export default {
data() {
return {
fenxiaoWords: {}
}
},
methods: {
getFenxiaoWrods() {
this.$api.sendRequest({
url: '/fenxiao/api/config/words',
success: res => {
if (res.code >= 0 && res.data) {
this.fenxiaoWords = res.data;
uni.setStorageSync('fenxiaoWords', res.data);
}
}
})
}
},
onShow() {
if (uni.getStorageSync('fenxiaoWords')) this.fenxiaoWords = uni.getStorageSync('fenxiaoWords');
this.getFenxiaoWrods();
}
}

186
common/js/golbalConfig.js Normal file
View File

@@ -0,0 +1,186 @@
export default {
data() {
return {
// 页面样式,动态设置主色调
themeColor: '' //''--base-color:#fa5d14;--base-help-color:#ff7e00;'
}
},
onLoad() {},
onShow() {
// 刷新多语言
this.$langConfig.refresh();
let time = setInterval(() => {
let theme = this.themeStyle;
if (theme && theme.main_color) {
this.themeColorSet();
clearInterval(time);
}
}, 50);
},
computed: {
themeStyle() {
return this.$store.state.themeStyle;
},
// 插件是否存在
addonIsExist() {
return this.$store.state.addonIsExist;
},
tabBarList() {
return this.$store.state.tabBarList;
},
siteInfo() {
return this.$store.state.siteInfo;
},
memberInfo() {
return this.$store.state.memberInfo;
},
storeToken() {
return this.$store.state.token;
},
bottomNavHidden() {
return this.$store.state.bottomNavHidden;
},
globalStoreConfig() {
return this.$store.state.globalStoreConfig;
},
globalStoreInfo() {
return this.$store.state.globalStoreInfo;
},
// 定位信息
location() {
return this.$store.state.location;
},
// 定位信息(缓存)
locationStorage() {
let data = uni.getStorageSync('location');
if (data) {
var date = new Date();
if (this.mapConfig.wap_valid_time > 0) {
data.is_expired = (date.getTime() / 1000) > data.valid_time; // 是否过期
} else {
data.is_expired = false;
}
}
return data;
},
// 默认总店(定位失败后使用)
defaultStoreInfo() {
return this.$store.state.defaultStoreInfo;
},
// 组件刷新计数
componentRefresh() {
return this.$store.state.componentRefresh;
},
// 客服配置
servicerConfig() {
return this.$store.state.servicerConfig;
},
diySeckillInterval() {
return this.$store.state.diySeckillInterval;
},
tabBarHeight() {
return this.$store.state.tabBarHeight;
},
mapConfig() {
return this.$store.state.mapConfig;
},
copyright() {
let copyright = this.$store.state.copyright;
// 判断是否授权
if (copyright && !copyright.auth) {
copyright.logo = '';
copyright.copyright_link = '';
}
return copyright;
},
cartList() {
return this.$store.state.cartList;
},
cartIds() {
return this.$store.state.cartIds;
},
cartNumber() {
return this.$store.state.cartNumber;
},
cartMoney() {
return this.$store.state.cartMoney;
}
},
methods: {
themeColorSet() {
let theme = this.themeStyle;
this.themeColor = `--base-color:${theme.main_color};--base-help-color:${theme.aux_color};`;
if (this.tabBarHeight != '56px') this.themeColor += `--tab-bar-height:${this.tabBarHeight};`
Object.keys(theme).forEach(key => {
let data = theme[key];
if (typeof(data) == "object") {
Object.keys(data).forEach(k => {
this.themeColor += '--' + k.replace(/_/g, "-") + ':' + data[k] + ';';
});
} else if (typeof(key) == "string" && key) {
this.themeColor += '--' + key.replace(/_/g, "-") + ':' + data + ';';
}
});
for (let i = 9; i >= 5; i--) {
let color = this.$util.colourBlend(theme.main_color, '#ffffff', (i / 10));
this.themeColor += `--base-color-light-${i}:${color};`;
}
},
// 颜色变浅(>0、变深函数<0
lightenDarkenColor(color, amount) {
var usePound = false;
if (color[0] == "#") {
color = color.slice(1);
usePound = true;
}
var num = parseInt(color, 16);
var r = (num >> 16) + amount;
if (r > 255) r = 255;
else if (r < 0) r = 0;
var b = ((num >> 8) & 0x00FF) + amount;
if (b > 255) b = 255;
else if (b < 0) b = 0;
var g = (num & 0x0000FF) + amount;
if (g > 255) g = 255;
else if (g < 0) g = 0;
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
},
/**
* 切换门店
* @param {Object} info 门店信息
* @param {Object} isJump 是否跳转到首页
*/
changeStore(info, isJump) {
if (info) {
this.$store.commit('setGlobalStoreInfo', info);
}
let route = this.$util.getCurrRoute();
if (isJump && route != 'pages/index/index') {
uni.setStorageSync('manual_change_store', true); // 手动切换门店
this.$store.dispatch('getCartNumber'); //重新获取购物车数据
this.$util.redirectTo('/pages/index/index');
}
}
},
filters: {
/**
* 金额格式化输出
* @param {Object} money
*/
moneyFormat(money) {
if (isNaN(parseFloat(money))) return money;
return parseFloat(money).toFixed(2);
}
}
}

View File

@@ -0,0 +1,371 @@
// 商品详情业务
import htmlParser from '@/common/js/html-parser';
export default {
data() {
return {
skuId: 0,
goodsId: 0,
// 商品详情
goodsSkuDetail: {
goods_id: 0,
goods_service: []
},
preview: 0, //是否开启预览0不开启1开启
//评价
contactData: {
title: '',
path: '',
img: ''
},
shareQuery: '', // 分享参数
shareUrl: '', // 分享链接
source_member: 0, //分享人的id
chatRoomParams: {}, // 联系客服参数
isIphoneX: false, //判断手机是否是iphoneX以上
whetherCollection: 0,
posterParams: {}, //海报所需参数
shareImg: '',
navbarData: {
title: '',
topNavColor: "#ffffff",
topNavBg: false,
navBarSwitch: true, // 导航栏是否显示
textNavColor: "#333333",
moreLink: {
name: ""
},
navStyle: 1,
bgUrl: '',
textImgPosLink: 'left'
},
}
},
onLoad(data) {
//刷新多语言
this.$langConfig.refresh();
// #ifdef MP-ALIPAY
let options = my.getLaunchOptionsSync();
options.query && Object.assign(data, options.query);
// #endif
this.preview = data.preview || 0;
this.isIphoneX = this.$util.uniappIsIPhoneX();
if (data.source_member) {
uni.setStorageSync('source_member', data.source_member);
this.source_member = data.source_member;
}
//记录分享关系
if (this.storeToken && uni.getStorageSync('source_member')) {
this.$util.onSourceMember(uni.getStorageSync('source_member'));
}
// 小程序扫码进入
if (data.scene) {
var sceneParams = decodeURIComponent(data.scene);
sceneParams = sceneParams.split('&');
if (sceneParams.length) {
sceneParams.forEach(item => {
if (item.indexOf('m') != -1) uni.setStorageSync('source_member', item.split('-')[1]);
if (item.indexOf('is_test') != -1) uni.setStorageSync('is_test', 1);
});
}
}
},
onShow() {
},
methods: {
// 处理商品详情数据
handleGoodsSkuData() {
// this.navbarData.title = this.goodsSkuDetail.goods_name.length > 9 ? this.goodsSkuDetail.goods_name.substr(0, 9) + "..." : this.goodsSkuDetail.goods_name;
//设置标题
// this.$langConfig.title(this.navbarData.title);
if (this.goodsSkuDetail.config) {
this.navbarData.navBarSwitch = this.goodsSkuDetail.config.nav_bar_switch;
}
this.whetherCollection = this.goodsSkuDetail.is_collect; // 用户关注商品状态
this.modifyGoodsInfo();
// 初始化商品详情视图数据
if (this.$refs.goodsDetailView) this.$refs.goodsDetailView.init({
sku_id: this.skuId,
goods_id: this.goodsSkuDetail.goods_id,
preview: this.preview,
source_member: this.source_member,
posterParams: this.posterParams,
posterApi: this.posterApi,
shareUrl: this.shareUrl,
goodsRoute: this.goodsRoute,
isVirtual: this.goodsSkuDetail.is_virtual,
deliveryType: this.goodsSkuDetail.express_type,
whetherCollection: this.goodsSkuDetail.is_collect,
evaluateConfig: this.goodsSkuDetail.evaluate_config,
evaluateList: this.goodsSkuDetail.evaluate_list,
evaluateCount: this.goodsSkuDetail.evaluate_count
});
//媒体
if (this.goodsSkuDetail.video_url) this.switchMedia = "video";
if (!Array.isArray(this.goodsSkuDetail.sku_images)) {
if (this.goodsSkuDetail.sku_images) this.goodsSkuDetail.sku_images = this.goodsSkuDetail.sku_images.split(",");
else this.goodsSkuDetail.sku_images = [];
}
// 多规格时合并主图
if (this.goodsSkuDetail.goods_spec_format && this.goodsSkuDetail.goods_image) {
if (!Array.isArray(this.goodsSkuDetail.goods_image)) this.goodsSkuDetail.goods_image = this.goodsSkuDetail.goods_image.split(",");
this.goodsSkuDetail.sku_images = this.goodsSkuDetail.goods_image.concat(this.goodsSkuDetail.sku_images);
}
let maxHeight = '';
let systemInfo = uni.getSystemInfoSync();
this.goodsSkuDetail.goods_image_list.forEach((item, index) => {
if (typeof item.pic_spec == "string")
item.pic_spec = item.pic_spec.split('*');
let ratio = item.pic_spec[0] / systemInfo.windowWidth;
item.pic_spec[0] = item.pic_spec[0] / ratio;
item.pic_spec[1] = item.pic_spec[1] / ratio;
if (!maxHeight || maxHeight > item.pic_spec[1]) {
maxHeight = item.pic_spec[1];
}
});
this.goodsSkuDetail.swiperHeight = maxHeight + 'px';
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || "件";
// 当前商品SKU规格
if (this.goodsSkuDetail.sku_spec_format) this.goodsSkuDetail.sku_spec_format = JSON.parse(this.goodsSkuDetail.sku_spec_format);
// 商品属性
if (this.goodsSkuDetail.goods_attr_format) {
let goods_attr_format = JSON.parse(this.goodsSkuDetail.goods_attr_format);
this.goodsSkuDetail.goods_attr_format = this.$util.unique(goods_attr_format, "attr_id");
for (var i = 0; i < this.goodsSkuDetail.goods_attr_format.length; i++) {
for (var j = 0; j < goods_attr_format.length; j++) {
if (this.goodsSkuDetail.goods_attr_format[i].attr_id == goods_attr_format[j].attr_id && this.goodsSkuDetail.goods_attr_format[i].attr_value_id != goods_attr_format[j].attr_value_id) {
this.goodsSkuDetail.goods_attr_format[i].attr_value_name += "、" + goods_attr_format[j].attr_value_name;
}
}
}
}
// 商品SKU格式
if (this.goodsSkuDetail.goods_spec_format) this.goodsSkuDetail.goods_spec_format = JSON.parse(this.goodsSkuDetail.goods_spec_format);
// 商品详情
if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = (this.goodsSkuDetail.goods_content);
console.log(this.goodsSkuDetail.goods_content)
// if (this.goodsSkuDetail.goods_content) this.goodsSkuDetail.goods_content = htmlParser(this.goodsSkuDetail.goods_content);
//商品服务
if (this.goodsSkuDetail.goods_service) {
for (let i in this.goodsSkuDetail.goods_service) {
this.goodsSkuDetail.goods_service[i]['icon'] = this.goodsSkuDetail.goods_service[i]['icon'] ? JSON.parse(this.goodsSkuDetail.goods_service[i]['icon']) : '';
}
}
this.contactData = {
title: this.goodsSkuDetail.sku_name,
path: this.shareUrl,
img: this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
})
};
if (this.$refs.goodsPromotion) this.$refs.goodsPromotion.refresh(this.goodsSkuDetail.goods_promotion);
if (this.goodsRoute != '/pages/goods/detail') this.setPublicShare();
// this.getBarrageData();
if (this.addonIsExist.form) {
this.getGoodsForm();
}
},
/**
* 刷新商品详情数据
* @param {Object} data
*/
refreshGoodsSkuDetail(data) {
this.goodsSkuDetail = Object.assign({}, this.goodsSkuDetail, data);
if (this.$refs.goodsPromotion) this.$refs.goodsPromotion.refresh(this.goodsSkuDetail.goods_promotion);
if (this.$refs.goodsDetailView) {
// 初始化商品详情视图数据
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || "件";
// 解决轮播图数量不一致时,切换到第一个
if (this.swiperCurrent > this.goodsSkuDetail.sku_images.length) {
this.swiperAutoplay = true;
this.swiperCurrent = 1;
setTimeout(() => {
this.swiperAutoplay = false;
}, 40);
}
}
this.navbarData.title = this.goodsSkuDetail.goods_name.length > 9 ? this.goodsSkuDetail.goods_name.substr(0, 9) + "..." : this.goodsSkuDetail.goods_name;
this.$langConfig.title(this.navbarData.title);
if (this.goodsSkuDetail.membercard) {
this.membercard = this.goodsSkuDetail.membercard;
}
},
goodsDetailViewInit() {
// 初始化商品详情视图数据
if (this.$refs.goodsDetailView) this.$refs.goodsDetailView.init({
sku_id: this.skuId,
goods_id: this.goodsSkuDetail.goods_id,
preview: this.preview,
source_member: this.source_member,
posterParams: this.posterParams,
posterApi: this.posterApi,
shareUrl: this.shareUrl,
goodsRoute: this.goodsRoute,
isVirtual: this.goodsSkuDetail.is_virtual,
deliveryType: this.goodsSkuDetail.express_type,
whetherCollection: this.goodsSkuDetail.is_collect,
evaluateConfig: this.goodsSkuDetail.evaluate_config,
evaluateList: this.goodsSkuDetail.evaluate_list,
evaluateCount: this.goodsSkuDetail.evaluate_count
});
},
goHome() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/index/index');
},
goCart() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/goods/cart');
},
//-------------------------------------关注-------------------------------------
//更新商品信息
modifyGoodsInfo() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
//更新商品点击量
this.$api.sendRequest({
url: "/api/goods/modifyclicks",
data: {
sku_id: this.skuId
},
success: res => {
}
});
//添加足迹
this.$api.sendRequest({
url: "/api/goodsbrowse/add",
data: {
goods_id: this.goodsSkuDetail.goods_id,
sku_id: this.skuId
},
success: res => {
}
});
},
//-------------------------------------关注-------------------------------------
async editCollection() {
if (this.$refs.goodsDetailView) {
this.whetherCollection = await this.$refs.goodsDetailView.collection();
}
},
openSharePopup() {
if (this.$refs.goodsDetailView) {
this.$refs.goodsDetailView.openSharePopup();
}
},
//弹幕
getBarrageData() {
this.$api.sendRequest({
url: '/api/goods/goodsbarrage',
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
if (res.code == 0 && res.data) {
let barrageData = [];
for (let i in res.data.list) {
if (res.data.list[i]['title']) {
let title = res.data.list[i]['title'].substr(0, 1) + '*' + res.data.list[i]['title'].substr(res.data.list[i]['title'].length - 1, 1)
barrageData.push({
img: res.data.list[i]['img'] ? res.data.list[i]['img'] : this.$util.getDefaultImage().head,
title: title + '已下单'
});
}
}
this.goodsSkuDetail.barrageData = barrageData;
}
}
});
},
/**
* 设置公众号分享
*/
setPublicShare() {
let shareUrl = this.$config.h5Domain + this.shareUrl;
if (this.memberInfo && this.memberInfo.member_id) shareUrl += '&source_member=' + this.memberInfo.member_id;
this.$util.setPublicShare({
title: this.goodsSkuDetail.goods_name,
desc: '',
link: shareUrl,
imgUrl: typeof this.goodsSkuDetail.goods_image == 'object' ? this.goodsSkuDetail.goods_image[0] : this.goodsSkuDetail.goods_image.split(',')[0]
})
},
/**
* 获取商品表单
*/
getGoodsForm() {
this.$api.sendRequest({
url: "/form/api/form/goodsform",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
if (res.code == 0 && res.data) this.$set(this.goodsSkuDetail, 'goods_form', res.data);
}
});
}
},
/**
* 自定义分享内容
* @param {Object} res
*/
onShareAppMessage(res) {
var path = this.shareUrl;
if (this.memberInfo && this.memberInfo.member_id) path += '&source_member=' + this.memberInfo.member_id;
return {
title: this.goodsSkuDetail.sku_name,
imageUrl: this.shareImg ? this.$util.img(this.shareImg) : this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
}),
path: path,
success: res => {
},
fail: res => {
}
};
},
// 分享到微信朋友圈
// #ifdef MP-WEIXIN
onShareTimeline() {
let query = this.shareQuery;
if (this.memberInfo && this.memberInfo.member_id) query += '&source_member=' + this.memberInfo.member_id;
return {
title: this.goodsSkuDetail.sku_name,
query: query,
imageUrl: this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
})
};
}
// #endif
}

440
common/js/html-parser.js Normal file
View File

@@ -0,0 +1,440 @@
import util from './util.js'
/*
* HTML5 Parser By Sam Blowes
*
* Designed for HTML5 documents
*
* Original code by John Resig (ejohn.org)
* http://ejohn.org/blog/pure-javascript-html-parser/
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* ----------------------------------------------------------------------------
* License
* ----------------------------------------------------------------------------
*
* This code is triple licensed using Apache Software License 2.0,
* Mozilla Public License or GNU Public License
*
* ////////////////////////////////////////////////////////////////////////////
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* ////////////////////////////////////////////////////////////////////////////
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Simple HTML Parser.
*
* The Initial Developer of the Original Code is Erik Arvidsson.
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
* Reserved.
*
* ////////////////////////////////////////////////////////////////////////////
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ----------------------------------------------------------------------------
* Usage
* ----------------------------------------------------------------------------
*
* // Use like so:
* HTMLParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* // or to get an XML string:
* HTMLtoXML(htmlString);
*
* // or to get an XML DOM Document
* HTMLtoDOM(htmlString);
*
* // or to inject into an existing document/DOM node
* HTMLtoDOM(htmlString, document);
* HTMLtoDOM(htmlString, document.body);
*
*/
// Regular Expressions for parsing tags and attributes
var startTag =
/^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
var attr =
/([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
var empty = makeMap(
'area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'
); // Block Elements - HTML 5
// fixed by xxx 将 ins 标签从块级名单中移除
var block = makeMap(
'a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'
); // Inline Elements - HTML 5
var inline = makeMap(
'abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'
); // Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap(
'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap(
'checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'
); // Special Elements (can contain anything)
var special = makeMap('script,style');
function HTMLParser(html, handler) {
var index;
var chars;
var match;
var stack = [];
var last = html;
stack.last = function() {
return this[this.length - 1];
};
while (html) {
chars = true; // Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf('<!--') == 0) {
index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
} // end tag
} else if (html.indexOf('</') == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
} // start tag
} else if (html.indexOf('<') == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
var text = index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) {
handler.chars(text);
}
}
} else {
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function(all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (handler.chars) {
handler.chars(text);
}
return '';
});
parseEndTag('', stack.last());
}
if (html == last) {
throw 'Parse Error: ' + html;
}
last = html;
} // Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
if (handler.start) {
var attrs = [];
rest.replace(attr, function(match, name) {
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ?
arguments[4] : fillAttrs[
name] ? name : '';
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName) {
var pos = 0;
} // Find the closest opened tag of the same type
else {
for (var pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break;
}
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
} // Remove the open elements from the stack
stack.length = pos;
}
}
}
function makeMap(str) {
var obj = {};
var items = str.split(',');
for (var i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
function removeDOCTYPE(html) {
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
}
/**
* 忽略注释
* @param {Object} html
*/
function replaceAnnotation(html) {
var html = html.replace(/<!--[\s\S]*-->/gi, '');
return html;
}
/**
* 替换图片
* @param {Object} html
*/
function replaceImage(html) {
// #ifdef MP
let info = uni.getSystemInfoSync();
var screenWidth = info.windowWidth;
screenWidth -= 20;
screenWidth += 'px';
// #endif
// #ifdef H5
var screenWidth = '100%';
// #endif
let rep = `<img style="width:100% !important;display:block;max-width: ${screenWidth} !important;"`;
var html = html.replace(/\\/g, '').replace(/<img/g, rep);
html = html.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, (match, capture) => {
return rep + ' src="' + util.img(capture) + '"/>';
});
return html;
}
function replaceVideo(html){
// #ifdef MP
let info = uni.getSystemInfoSync();
var screenWidth = info.windowWidth;
screenWidth -= 20;
screenWidth += 'px';
// #endif
// #ifdef H5
var screenWidth = '100%';
// #endif
let rep = `<video style="width:100% !important;display:block;max-width: ${screenWidth} !important;"`;
var html = html.replace(/\\/g, '').replace(/<video/g, rep);
console.log(html)
html = html.replace(/<video [^>]*src=['"]([^'"]+)[^>]*>/gi, (match, capture) => {
return rep + ' src="' + util.img(capture) + '"/>';
});
// console.log(html)
return html;
}
/**
* 将style属性中的双引号改为单引号
* @param {Object} html
*/
function replaceStyleQuotes(html) {
var html = html.replace(/style\s*=\s*["][^>]*;[^"]?/gi, (match, capture) => {
match = match.replace(/[:](\s?)[\s\S]*/gi, (a, b) => {
return a.replace(/"/g, "'");
});
return match;
});
return html;
}
function parseAttrs(attrs) {
return attrs.reduce(function(pre, attr) {
var value = attr.value;
var name = attr.name;
if (pre[name]) {
pre[name] = pre[name] + " " + value;
} else {
pre[name] = value;
}
return pre;
}, {});
}
function parseHtml(html) {
html = removeDOCTYPE(html);
html = replaceAnnotation(html); //忽略注释
html = replaceImage(html); //替换图片
html = replaceStyleQuotes(html); //将style属性中的双引号改为单引号
html = replaceVideo(html); //替换视频链接
var stacks = [];
var results = {
node: 'root',
children: []
};
HTMLParser(html, {
start: function start(tag, attrs, unary) {
var node = {
name: tag
};
if (attrs.length !== 0) {
node.attrs = parseAttrs(attrs);
}
if (unary) {
var parent = stacks[0] || results;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
stacks.unshift(node);
}
},
end: function end(tag) {
var node = stacks.shift();
if (node.name !== tag) console.error('invalid state: mismatch end tag');
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
chars: function chars(text) {
var node = {
type: 'text',
text: text
};
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
comment: function comment(text) {
var node = {
node: 'comment',
text: text
};
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
});
return results.children;
}
export default parseHtml;

255
common/js/http.js Normal file
View File

@@ -0,0 +1,255 @@
import Config from './config.js'
import Util from './util.js'
import store from '@/store/index.js'
// #ifdef H5
const app_type = Util.isWeiXin() ? 'wechat' : 'h5';
const app_type_name = Util.isWeiXin() ? '微信公众号' : 'H5';
// #endif
// #ifdef MP-WEIXIN
const app_type = 'weapp';
const app_type_name = '微信小程序';
// #endif
// #ifdef MP-ALIPAY
const app_type = 'aliapp';
const app_type_name = '支付宝小程序';
// #endif
// #ifdef MP-BAIDU
const app_type = 'baiduapp';
const app_type_name = '百度小程序';
// #endif
// #ifdef MP-TOUTIAO
const app_type = 'MP-TOUTIAO';
const app_type_name = '头条小程序';
// #endif
// #ifdef MP-QQ
const app_type = 'MP-QQ';
const app_type_name = 'QQ小程序';
// #endif
// #ifdef APP-PLUS
const app_type = 'app';
const app_type_name = 'APP';
// #endif
export default {
sendRequest(params) {
if (!Config.baseUrl) {
uni.showToast({
title: '未配置请求域名',
'icon': 'none',
duration: 10000
});
return;
}
var method = params.data != undefined ? 'POST' : 'GET', // 请求方式
url = Config.baseUrl + params.url, // 请求路径
data = {
app_type,
app_type_name
};
// token
data.token = store.state.token || '';
data.uniacid = Config.uniacid
// 门店id
var default_store_info = store.state.defaultStoreInfo;
if (default_store_info) {
data.store_id = default_store_info.store_id;
}
var store_info = store.state.globalStoreInfo;
if (store_info) data.store_id = store_info.store_id;
// 参数
if (params.data != undefined) Object.assign(data, params.data);
if (params.async === false) {
//同步
return new Promise((resolve, reject) => {
uni.request({
url: url,
method: method,
data: data,
header: params.header || {
// 'Accept': 'application/json',
'content-type': 'application/x-www-form-urlencoded;application/json'
},
dataType: params.dataType || 'json',
responseType: params.responseType || 'text',
success: (res) => {
// try {
// res.data = JSON.parse(res.data);
// } catch (e) {
// //TODO handle the exception
// console.log('api error', e);
// }
if (res.data.code == -3 && store.state.siteState > 0) {
store.commit('setSiteState', -3);
Util.redirectTo('/pages_tool/storeclose/storeclose', {}, 'reLaunch');
return;
}
if (res.data.refreshtoken) {
store.commit('setToken', res.data.refreshtoken);
}
if (res.data.code == -10009 || res.data.code == -10010) {
store.commit('setToken', '');
store.commit('setMemberInfo', '');
}
resolve(res.data);
},
fail: (res) => {
if (res.errMsg && res.errMsg == 'request:fail url not in domain list') {
uni.showToast({
title: Config.baseUrl + '不在request 合法域名列表中',
'icon': 'none',
duration: 10000
});
return;
}
reject(res);
},
complete: (res) => {
if ((res.errMsg && res.errMsg != "request:ok") || (res.statusCode && [200, 500].indexOf(res.statusCode) == -1)) {
uni.showToast({
title: Config.baseUrl + '请求失败',
'icon': 'none',
duration: 10000
});
return;
}
reject(res.data);
}
});
});
} else {
//异步
uni.request({
url: url,
method: method,
data: data,
header: params.header || {
// 'Accept': 'application/json',
'content-type': 'application/x-www-form-urlencoded;application/json'
},
dataType: params.dataType || 'json',
responseType: params.responseType || 'text',
success: (res) => {
// try {
// res.data = JSON.parse(res.data);
// } catch (e) {
// //TODO handle the exception
// console.log('api error', e);
// }
if (res.data.code == -3 && store.state.siteState > 0) {
store.commit('setSiteState', -3);
Util.redirectTo('/pages_tool/storeclose/storeclose', {}, 'reLaunch');
return;
}
if (res.data.refreshtoken) {
store.commit('setToken', res.data.refreshtoken);
}
if (res.data.code == -10009 || res.data.code == -10010) {
store.commit('setToken', '');
store.commit('setMemberInfo', '');
}
typeof params.success == 'function' && params.success(res.data);
},
fail: (res) => {
if (res.errMsg && res.errMsg == 'request:fail url not in domain list') {
uni.showToast({
title: Config.baseUrl + '不在request 合法域名列表中',
'icon': 'none',
duration: 10000
});
return;
}
typeof params.fail == 'function' && params.fail(res);
},
complete: (res) => {
if ((res.errMsg && res.errMsg != "request:ok") || (res.statusCode && [200, 500].indexOf(res.statusCode) == -1)) {
uni.showToast({
title: Config.baseUrl + '请求失败',
'icon': 'none',
duration: 10000
});
return;
}
typeof params.complete == 'function' && params.complete(res.data);
}
});
}
},
uploadBase64(params) {
uni.request({
url: Config.baseUrl + '/api/upload/headimgBase64',
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded;application/json'
},
data: {
app_type,
app_type_name,
images: params.base64
},
dataType: 'json',
responseType: 'text',
success: res => {
typeof params.success == 'function' && params.success(res.data);
},
fail: () => {
typeof params.fail == 'function' && params.fail(res);
}
});
},
pullImage(params) {
uni.request({
url: Config.baseUrl + '/api/upload/headimgPull',
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded;application/json'
},
data: {
app_type,
app_type_name,
path: params.path
},
dataType: 'json',
responseType: 'text',
success: res => {
typeof params.success == 'function' && params.success(res.data);
},
fail: () => {
typeof params.fail == 'function' && params.fail(res);
}
});
},
upload(params) {
uni.uploadFile({
url: Config.baseUrl + params.url,
filePath: params.filePath,
name: params.name || 'file',
fileType: params.fileType || 'image',
formData: {
app_type,
app_type_name,
},
header: {
'content-type': 'application/x-www-form-urlencoded;application/json'
},
success: (res) => {
typeof params.success == 'function' && params.success(JSON.parse(res.data));
},
fail: (res) => {
typeof params.fail == 'function' && params.fail(res);
}
});
}
}

129
common/js/lang.js Normal file
View File

@@ -0,0 +1,129 @@
const langList = ['zh-cn', 'en-us'];
var locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
export default {
langList: ['zh-cn', 'en-us'],
/**
* * 解析多语言
* @param {Object} field
*/
lang(field) {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
var value = '';
let newRoute;
try {
//公共语言包
var lang = require('../../lang/' + locale + '/common.js').lang;
//当前页面语言包
let route = _this.route.split("/");
newRoute = route.slice(1, route.length);
let currentPageLang = require('../../lang/' + locale + '/' + newRoute.join("/") + '.js').lang;
for (let f in currentPageLang) {
lang[f] = currentPageLang[f];
}
var arr = field.split(".");
if (arr.length > 1) {
for (let i in arr) {
var next = parseInt(i) + 1;
if (next < arr.length) {
value = lang[arr[i]][arr[next]];
}
}
} else {
value = lang[field];
}
} catch (e) {
if (field.indexOf("common.") != -1 || field.indexOf("tabBar.") != -1) {
value = lang[field];
} else {
value = field;
}
}
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 = ''; // field
return value;
},
//切换语言
change(value) {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
uni.setStorageSync("lang", value);
locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
this.refresh();
uni.reLaunch({
url: '/pages/member/index'
});
},
//刷新标题、tabbar
refresh() {
let _this = getCurrentPages()[getCurrentPages().length - 1];
if (!_this) return;
locale = uni.getStorageSync('lang') || "zh-cn"; //设置语言
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,
success: function (res) {
},
fail: function (err) {
}
});
}
},
// 获取语言包列表
list() {
var list = [];
try {
//公共语言包
for (var i = 0; i < langList.length; i++) {
let item = require('../../lang/' + langList[i] + '/common.js').lang;
list.push({
name: item.common.name,
value: langList[i]
});
}
} catch (e) {
// "没有找到语言包:", '../../lang/' + locale + '/common.js'
}
return list;
}
}

1125
common/js/map-wx-jssdk.js Normal file

File diff suppressed because it is too large Load Diff

107
common/js/map/openMap.js Normal file
View File

@@ -0,0 +1,107 @@
import TransformCoordinate from './transformCoordinate.js'
function openMapByDefault(latitude, longitude, name) {
uni.openLocation({
latitude: latitude,
longitude: longitude,
name: name,
fail: (e) => {
uni.showModal({
content: '打开地图失败,请稍后重试'
})
},
})
}
function openMapByAndroid(latitude, longitude, name) {
let url = ''; // 回调地址
let identity = ''; // 程序名称
if (plus.runtime.isApplicationExist({
pname: 'com.baidu.BaiduMap'
})) {
url =
`baidumap://map/marker?location=${latitude},${longitude}&title=${name}&coord_type=gcj02&src=andr.baidu.openAPIdemo`
identity = 'com.baidu.BaiduMap'
openURL(url, identity)
} else if (plus.runtime.isApplicationExist({
pname: 'com.autonavi.minimap'
})) { // 高德
url = `androidamap://viewMap?sourceApplication=appname&poiname=${name}&lat=${latitude}&lon=${longitude}&dev=0`
identity = 'com.autonavi.minimap'
openURL(url, identity)
} else {
openMapByDefault(latitude, longitude, name)
}
}
function openMapByIos(latitude, longitude, name) {
let url = ''; // 回调地址
let errorCB = ''; // url失败的回调地址
let identity = ''; // 程序名称
if (plus.runtime.isApplicationExist({
action: 'baidumap://'
})) {
url =
`baidumap://map/marker?location=${latitude},${longitude}&title=${name}&content=${name}&src=ios.baidu.openAPIdemo&coord_type=gcj02`;
openURL(url, identity)
} else if (plus.runtime.isApplicationExist({
action: 'iosamap://'
})) { // 高德
url = `iosamap://viewMap?sourceApplication=applicationName&poiname=${name}&lat=${latitude}&lon=${longitude}&dev=0`
openURL(url, identity)
} else {
openMapByDefault(latitude, longitude, name)
}
}
function openURL(url, identity) {
let newurl = encodeURI(url);
plus.runtime.openURL(newurl, function(res) {
uni.showModal({
content: res.message
})
}, identity);
}
function getCoordByType(longitude, latitude, coord_type) {
switch (coord_type) {
case 'gcj02':
return [longitude, latitude]
break;
case 'bd09':
return TransformCoordinate.bd09togcj02(longitude, latitude)
break;
case 'wgs84':
return TransformCoordinate.wgs84togcj02(longitude, latitude)
break;
default:
return [longitude, latitude]
break;
}
}
export default {
/* 打开地图 */
openMap(latitude, longitude, name, coord_type = 'gcj02') {
let arr = getCoordByType(longitude, latitude, coord_type)
// #ifdef APP-PLUS
switch (uni.getSystemInfoSync().platform) {
case 'android':
console.log('运行Android上')
openMapByAndroid(arr[1], arr[0], name)
break;
case 'ios':
console.log('运行iOS上')
openMapByIos(arr[1], arr[0], name)
break;
default:
openMapByDefault(arr[1], arr[0], name)
console.log('运行在开发者工具上')
break;
}
// #endif
// #ifndef APP-PLUS
openMapByDefault(arr[1], arr[0], name)
// #endif
}
}

View File

@@ -0,0 +1,124 @@
/**
* Created by Wandergis on 2015/7/8.
* 提供了百度坐标BD09、国测局坐标火星坐标GCJ02、和WGS84坐标系之间的转换
*/
//定义一些常量
var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var PI = 3.1415926535897932384626;
var a = 6378245.0;
var ee = 0.00669342162296594323;
/**
* 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
* 即 百度 转 谷歌、高德
* @param bd_lon
* @param bd_lat
* @returns {*[]}
*/
function bd09togcj02(bd_lon, bd_lat) {
var x_pi = 3.14159265358979324 * 3000.0 / 180.0;
var x = bd_lon - 0.0065;
var y = bd_lat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
var gg_lng = z * Math.cos(theta);
var gg_lat = z * Math.sin(theta);
return [gg_lng, gg_lat]
}
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
* 即谷歌、高德 转 百度
* @param lng
* @param lat
* @returns {*[]}
*/
function gcj02tobd09(lng, lat) {
var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
var bd_lng = z * Math.cos(theta) + 0.0065;
var bd_lat = z * Math.sin(theta) + 0.006;
return [bd_lng, bd_lat]
}
/**
* WGS84转GCj02
* @param lng
* @param lat
* @returns {*[]}
*/
function wgs84togcj02(lng, lat) {
if (out_of_china(lng, lat)) {
return [lng, lat]
} else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
var mglat = lat + dlat;
var mglng = lng + dlng;
return [mglng, mglat]
}
}
/**
* GCJ02 转换为 WGS84
* @param lng
* @param lat
* @returns {*[]}
*/
function gcj02towgs84(lng, lat) {
if (out_of_china(lng, lat)) {
return [lng, lat]
} else {
var dlat = transformlat(lng - 105.0, lat - 35.0);
var dlng = transformlng(lng - 105.0, lat - 35.0);
var radlat = lat / 180.0 * PI;
var magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
var sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);
mglat = lat + dlat;
mglng = lng + dlng;
return [lng * 2 - mglng, lat * 2 - mglat]
}
}
function transformlat(lng, lat) {
var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret
}
function transformlng(lng, lat) {
var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;
return ret
}
/**
* 判断是否在国内,不在国内则不做偏移
* @param lng
* @param lat
* @returns {boolean}
*/
function out_of_china(lng, lat) {
return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false);
}
export default {
bd09togcj02: bd09togcj02, // 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
gcj02tobd09: gcj02tobd09, // 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
wgs84togcj02: wgs84togcj02, //
gcj02towgs84: gcj02towgs84,
}

10
common/js/pc.js Normal file
View File

@@ -0,0 +1,10 @@
(function() {
var u = navigator.userAgent,
w = window.innerWidth;
if (w >= 960) {
window.innerWidth = 960;
window.onload = function() {
window.innerWidth = 960;
}
}
})();

28
common/js/scroll-view.js Normal file
View File

@@ -0,0 +1,28 @@
export default {
data() {
return {
showTop: false,
scrollTop: 0,
oldLocation: 0
}
},
methods: {
scrollToTopNative() {
uni.pageScrollTo({
duration: 200,
scrollTop: 0
});
}
},
onReachBottom() {
if(this.$refs.goodrecommend) this.$refs.goodrecommend.getLikeList(10)
},
onPageScroll(e) {
this.oldLocation = e.scrollTop;
if (e.scrollTop > 400) {
this.showTop = true;
} else {
this.showTop = false;
}
}
}

207
common/js/socketTest.js Normal file
View File

@@ -0,0 +1,207 @@
import config from './config.js'
export default {
data() {
return {
timeoutObj: null, //ping定时器
servicer_id: null, //绑定
pingInterval: config.pingInterval //本地端主动给服务器ping的时间, 0 则不开启
}
},
onLoad() {
let that = this;
// 因为图片上传所以不能onhide关闭长链接但是每次打开客服都会有重复请求所以优先关闭再去打开长链接
uni.closeSocket();
// .判断是否已连接
that.checkOpenSocket();
// uni.onSocketClose(function(res) {
// console.log('WebSocket 已关闭!');
// });
},
methods: {
// 判断是否已连接
checkOpenSocket() {
console.log('判断是否已连接');
// alert('判断是否已连接')
let self = this;
uni.sendSocketMessage({
data: 'ping',
success: (res) => {
console.log('连接成功,检查');
// alert('连接成功,检查')
// self.getChatList();
},
fail: (err) => { // 未连接打开websocket连接
console.log('连接失败');
// alert('连接失败')
self.openConnection();
}
});
},
openConnection() { // 打开连接
console.log('打开连接');
// alert('打开连接')
// uni.closeSocket(); // 确保已经关闭后再重新打开
uni.connectSocket({
url: config.webSocket,
method: 'POST',
success(res) {
console.log('连接成功 connectSocket=', res);
// alert('连接成功 connectSocket=', res);
},
fail(err) {
console.log('连接失败 connectSocket=', err);
}
});
// uni.onSocketOpen((res) => {
// console.log('连接成功', res);
// });
this.onSocketMessage(); // 打开成功监听服务器返回的消息
},
// 打开成功监听服务器返回的消息
onSocketMessage() { // 消息
console.log("开始监听");
let that = this;
this.pingInterval = config.pingInterval;
this.timeoutObj = null;
uni.onSocketMessage((res) => { //type:init,connect,close,string,order,goods
let msg = JSON.parse(res.data);
console.log("监听该服务器消息", res);
if (msg.type == 'close') {
clearInterval(that.timeoutObj);
that.timeoutObj = null;
uni.closeSocket();
return;
}
this.reset();
this.getSocketMsg(res.data); // 监听到有新服务器消息
});
},
// 监听到有新服务器消息
getSocketMsg(reData) { // 监听到服务器消息
let that = this;
// console.log(reData)
let giveMsg = JSON.parse(reData);
let data = {
isItMe: false,
};
data.contentType = giveMsg.type;
// alert(data.contentType)
if (giveMsg.type == 'init') {
// alert(123)
that.$api.sendRequest({
url: '/servicer/api/chat/bind',
data: {
client_id: giveMsg.data.client_id,
site_id: that.siteId
},
success(res) {
if (res.code == 0) {
that.servicer_id = res.data.servicer_id;
} else {
that.servicer_id = 0;
}
that.getChatList();
}
})
} else if (giveMsg.type == 'connect') {
// that.servicer_id = giveMsg.data.servicer_id;
// let NewArr = that.messageList;
// let index = null;
// for (let i = 0; i < NewArr.length; i++) {
// if (NewArr[i].contentType == 'online' || NewArr[i].contentType == 'noline') {
// index = i;
// }
// }
// NewArr.splice(index, 1)
// that.messageList = NewArr;
// let obj = {}
// if (that.servicer_id > 0) {
// obj.contentType = 'online';
// } else if (that.servicer_id == 0) {
// obj.contentType = 'noline';
// }
// that.messageList.push(obj);
return false;
} else if (giveMsg.type == 'string') {
data.content = giveMsg.data.servicer_say;
} else if (giveMsg.type == 'image') {
data.image = giveMsg.data.servicer_say;
} else if (giveMsg.type == 'order') {
data.order_id = giveMsg.data.order_id;
} else if (giveMsg.type == 'goodssku') {
data.sku_id = giveMsg.data.goods_sku_id;
}
if (giveMsg.type == 'init') return;
that.messageList.push(data);
that.$nextTick(() => {
that.setPageScrollTo()
})
},
// 检测心跳reset
reset() {
console.log("检测心跳");
clearInterval(this.timeoutObj);
this.start(); // 启动心跳
},
// 启动心跳 start
start() {
console.log("启动心跳");
let self = this;
this.timeoutObj = setInterval(function () {
uni.sendSocketMessage({
data: 'ping',
success: (res) => {
console.log('连接中....');
},
fail: (err) => {
console.log('连接失败重新连接....');
self.openConnection();
}
});
}, this.pingInterval);
}
},
// onHide() {
// // alert("关闭")
// // 改之前的
// // console.log("我出发了")
// // this.checkOpenSocket();
// clearInterval(this.timeoutObj);
// this.timeoutObj = null;
// this.$api.sendRequest({
// url: '/servicer/api/chat/bye',
// data: {
// servicer_id: this.servicer_id,
// site_id: this.siteId
// },
// success(res) {
// uni.closeSocket();
// },
// fail: (err) => {
// uni.closeSocket();
// }
// });
// },
onUnload() {
// alert("关闭")
clearInterval(this.timeoutObj);
this.timeoutObj = null;
this.$api.sendRequest({
url: '/servicer/api/chat/bye',
data: {
servicer_id: this.servicer_id,
site_id: this.siteId
},
success(res) {
// alert("关闭1")
uni.closeSocket();
},
fail: (err) => {
// alert("关闭2")
uni.closeSocket();
}
});
}
}

51
common/js/storage.js Normal file
View File

@@ -0,0 +1,51 @@
import Config from './config.js'
const prefix = Config.baseUrl.replace(/(http:\/\/)|(https:\/\/)/g, '');
var oldPrefix = uni.getStorageSync('prefix');
// 域名不一致,清空
if (oldPrefix != prefix) {
uni.clearStorageSync();
}
uni.setStorageSync('prefix', prefix);
const handleKey = (key) => {
const storageKey = (prefix ? `${prefix}_` : '') + key;
return storageKey;
};
export function uniStorage() {
const setStorageSync = uni.setStorageSync;
const setStorage = uni.setStorage;
const getStorage = uni.getStorage;
const getStorageSync = uni.getStorageSync;
const removeStorage = uni.removeStorage;
const removeStorageSync = uni.removeStorageSync;
uni.setStorage = (options) => {
options.key = handleKey(options.key);
setStorage(options)
};
uni.setStorageSync = (key, data) => {
setStorageSync(handleKey(key), data)
};
uni.getStorage = (options) => {
options.key = handleKey(options.key);
getStorage(options)
};
uni.getStorageSync = (key) => {
return getStorageSync(handleKey(key))
};
uni.removeStorage = (options) => {
options.key = handleKey(options.key);
removeStorage(options)
};
uni.removeStorageSync = (key) => {
return removeStorageSync(handleKey(key))
}
}

419
common/js/style_color.js Normal file
View File

@@ -0,0 +1,419 @@
export default {
'default': {
//红色
name: 'default',
main_color: '#F4391c',
aux_color: '#F7B500',
bg_color: '#FF4646',//主题背景
bg_color_shallow: '#FF4646',//主题背景渐变浅色
promotion_color: '#FF4646',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF4F4',//淡背景
price_color: 'rgb(252,82,39)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgb(252,82,39,1)',//价格
promotion_tag: '#FF4646',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF4646',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'green': {
name: 'green',
main_color: '#19C650',
aux_color: '#FA6400',
bg_color: '#19C650',
bg_color_shallow: '#19C650',
promotion_color: '#19C650',
promotion_aux_color: '#FA6400',
main_color_shallow: '#F0FFF5',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#19C650',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#19C650',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'blue': {
name: 'blue',
main_color: '#36ABFF',
aux_color: '#FA6400',
bg_color: '#36ABFF',
bg_color_shallow: '#36ABFF',
promotion_color: '#36ABFF ',
promotion_aux_color: '#FA6400',
main_color_shallow: '#E2F3FF',
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#36ABFF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#36ABFF',//按钮颜色
goods_btn_color_shallow: '#FA6400',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'pink': {
name: 'pink',
main_color: '#FF407E',
aux_color: '#F7B500',
bg_color: '#FF407E',//主题背景
bg_color_shallow: '#FF407E',//主题背景渐变浅色
promotion_color: '#FF407E',//活动背景
promotion_aux_color: '#F7B500',//活动背景辅色
main_color_shallow: '#FFF5F8',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FF407E',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FF407E',//按钮颜色
goods_btn_color_shallow: '#F7B500',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'gold': {
name: 'gold',
main_color: '#CFAF70',
aux_color: '#444444',
bg_color: '#CFAF70',//主题背景
bg_color_shallow: '#CFAF70',//主题背景渐变浅色
promotion_color: '#CFAF70',//活动背景
promotion_aux_color: '#444444',//活动背景辅色
main_color_shallow: '#FFFAF1',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#CFAF70',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#CFAF70',//按钮颜色
goods_btn_color_shallow: '#444444',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'purple': {
name: 'purple',
main_color: '#A253FF',
aux_color: '#222222',
bg_color: '#A253FF',//主题背景
bg_color_shallow: '#A253FF',//主题背景渐变浅色
promotion_color: '#A253FF',//活动背景
promotion_aux_color: '#222222',//活动背景辅色
main_color_shallow: '#F8F3FF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#A253FF',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#A253FF',//按钮颜色
goods_btn_color_shallow: '#222222',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'yellow': {
name: 'yellow',
main_color: '#FFD009',
aux_color: '#1D262E',
bg_color: '#FFD009',//主题背景
bg_color_shallow: '#FFD009',//主题背景渐变浅色
promotion_color: '#FFD009',//活动背景
promotion_aux_color: '#1D262E',//活动背景辅色
main_color_shallow: '#FFFBEF',//淡背景
price_color: 'rgba(252,82,39,1)',//价格颜色
btn_text_color: '#303133',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(252,82,39,1)',//价格
promotion_tag: '#FFD009',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#FC5227',
goods_cart_num_corner: '#FC5227',//购物车数量角标
goods_btn_color: '#FFD009',//按钮颜色
goods_btn_color_shallow: '#1D262E',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#7c7878',
super_member_end_bg: '#201a18',
super_member_start_text_color: '#FFDBA6',
super_member_end_text_color: '#FFEBCA',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
},
'black': {
name: 'black',
main_color: '#222222',
aux_color: '#FFFFFF',
bg_color: '#222222',//主题背景
bg_color_shallow: '#333333',//主题背景渐变浅色
promotion_color: '#222222',//活动背景
promotion_aux_color: '#FA8B00',//活动背景辅色
main_color_shallow: '#efefef',//淡背景
price_color: 'rgba(255,0,0,1)',//价格颜色
btn_text_color: '#FFFFFF',//按钮文字颜色
goods_detail: {
goods_price: 'rgba(255,0,0,1)',//价格
promotion_tag: '#222222',
goods_card_bg: '#201A18',//会员卡背景
goods_card_bg_shallow: '#7C7878',//会员卡背景浅色
goods_card_color: '#FFD792',
goods_coupon: '#222222',
goods_cart_num_corner: '#FF0000',//购物车数量角标
goods_btn_color: '#222222',//按钮颜色
goods_btn_color_shallow: '#FA8B00',//副按钮颜色
},
pintuan: {
pintuan_label_bg: '#F7B500',
pintuan_label_color: '#FFFFFF',
pintuan_color: '#FA6400',
pintuan_promotion_color: '#FA3A1D',//活动背景
pintuan_promotion_aux_color: '#FD9A01',//活动背景辅色
},
super_member: {
super_member_start_bg: '#fadcb5',
super_member_end_bg: '#f6bd74',
super_member_start_text_color: '#ab6126',
super_member_end_text_color: '#d19336',
},
bargain: {
bargain_promotion_color: '#F0353E',//活动背景
bargain_promotion_aux_color: '#FD9A01',//活动辅色
},
seckill: {
seckill_promotion_color: '#F83530',//活动背景
seckill_promotion_aux_color: '#FD9A01',//活动辅色
},
giftcard: {
giftcard_promotion_color: '#FF3369',//活动背景
giftcard_promotion_aux_color: '#F7B500',//活动辅色
},
groupby: {
groupby_promotion_color: '#E64136',//活动背景
groupby_promotion_aux_color: '#F7B500',//活动辅色
},
}
}

990
common/js/util.js Normal file
View File

@@ -0,0 +1,990 @@
import Config from './config.js'
import store from '@/store/index.js'
import Http from './http.js'
import {
Weixin
} from 'common/js/wx-jssdk.js';
export default {
/**
* 页面跳转
* @param {string} to 跳转链接 /pages/idnex/index
* @param {Object} param 参数 {key : value, ...}
* @param {string} mode 模式
*/
redirectTo(to, param, mode) {
let url = to;
let tabbarList = ['/pages/index/index', '/pages/goods/category', '/pages/vr/index', '/pages/contact/contact', '/pages/member/index'];
if (param != undefined) {
Object.keys(param).forEach(function (key) {
if (url.indexOf('?') != -1) {
url += "&" + key + "=" + param[key];
} else {
url += "?" + key + "=" + param[key];
}
});
}
for (let i = 0; i < tabbarList.length; i++) {
if (url.indexOf(tabbarList[i]) == 0) {
uni.switchTab({
url
});
return;
}
}
switch (mode) {
case 'tabbar':
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
uni.switchTab({
url
});
break;
case 'redirectTo':
// 关闭当前页面,跳转到应用内的某个页面。
uni.redirectTo({
url
});
break;
case 'reLaunch':
// 关闭所有页面,打开到应用内的某个页面。
uni.reLaunch({
url
});
break;
default:
// 保留当前页面,跳转到应用内的某个页面
uni.navigateTo({
url
});
}
},
/**
* 图片路径转换
* @param {String} img_path 图片地址
* @param {Object} params 参数针对商品、相册里面的图片区分大中小size: big、mid、small
*/
img(img_path, params) {
var path = "";
if (img_path != undefined && img_path != "") {
if (img_path.split(',').length > 1) {
img_path = img_path.split(',')[0];
}
if (params && img_path != this.getDefaultImage().goods) {
// 过滤默认图
let arr = img_path.split(".");
let suffix = arr[arr.length - 1];
arr.pop();
arr[arr.length - 1] = arr[arr.length - 1] + "_" + params.size.toUpperCase();
arr.push(suffix);
// if(img_path.indexOf('attachment') == -1){
// img_path = arr.join(".");
// }
}
if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) {
path = Config.imgDomain + "/" + img_path;
} else {
// console.log(img_path)
path = img_path;
}
}
// path += '?t=' + parseInt(new Date().getTime() / 1000);
return path;
},
/**
* 时间戳转日期格式
* @param {Object} timeStamp
*/
timeStampTurnTime(timeStamp, type = "") {
if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) {
var date = new Date();
date.setTime(timeStamp * 1000);
var y = date.getFullYear();
var m = date.getMonth() + 1;
m = m < 10 ? ('0' + m) : m;
var d = date.getDate();
d = d < 10 ? ('0' + d) : d;
var h = date.getHours();
h = h < 10 ? ('0' + h) : h;
var minute = date.getMinutes();
var second = date.getSeconds();
minute = minute < 10 ? ('0' + minute) : minute;
second = second < 10 ? ('0' + second) : second;
if (type) {
if (type == 'yearMonthDay') {
return y + '年' + m + '月' + d + '日';
}
return y + '-' + m + '-' + d;
} else {
return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second;
}
} else {
return "";
}
},
/**
* 日期格式转时间戳
* @param {Object} string
*/
timeTurnTimeStamp(string) {
var f = string.split(' ', 2);
var d = (f[0] ? f[0] : '').split('-', 3);
var t = (f[1] ? f[1] : '').split(':', 3);
return (new Date(
parseInt(d[0], 10) || null,
(parseInt(d[1], 10) || 1) - 1,
parseInt(d[2], 10) || null,
parseInt(t[0], 10) || null,
parseInt(t[1], 10) || null,
parseInt(t[2], 10) || null
)).getTime() / 1000;
},
/**
* 倒计时
* @param {Object} seconds 秒
*/
countDown(seconds) {
let [day, hour, minute, second] = [0, 0, 0, 0]
if (seconds > 0) {
day = Math.floor(seconds / (60 * 60 * 24))
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
}
if (day < 10) {
day = '0' + day
}
if (hour < 10) {
hour = '0' + hour
}
if (minute < 10) {
minute = '0' + minute
}
if (second < 10) {
second = '0' + second
}
return {
d: day,
h: hour,
i: minute,
s: second
};
},
/**
* 数值去重
* @param {Array} arr 数组
* @param {string} field 字段
*/
unique(arr, field) {
const res = new Map();
return arr.filter((a) => !res.has(a[field]) && res.set(a[field], 1));
},
/**
* 判断值是否在数组中
* @param {Object} elem
* @param {Object} arr
*/
inArray: function (elem, arr) {
return arr == null ? -1 : arr.indexOf(elem);
},
/**
* 获取某天日期
* @param {Object} day
*/
getDay: function (day) {
var today = new Date();
var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day;
today.setTime(targetday_milliseconds);
const doHandleMonth = function (month) {
var m = month;
if (month.toString().length == 1) {
m = "0" + month;
}
return m
}
var tYear = today.getFullYear();
var tMonth = today.getMonth();
var tDate = today.getDate();
var tWeek = today.getDay();
var time = parseInt(today.getTime() / 1000);
tMonth = doHandleMonth(tMonth + 1);
tDate = doHandleMonth(tDate);
const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
return {
't': time,
'y': tYear,
'm': tMonth,
'd': tDate,
'w': week[tWeek]
};
},
/**
* 图片选择加上传
* @param num
* @param params
* @param callback
* @param url
*/
upload: function (num, params, callback, url) {
// #ifdef H5
var app_type = this.isWeiXin() ? 'wechat' : 'h5';
var app_type_name = this.isWeiXin() ? '微信公众号' : 'H5';
// #endif
// #ifdef MP-WEIXIN
var app_type = 'weapp';
var app_type_name = '微信小程序';
// #endif
// #ifdef MP-ALIPAY
var app_type = 'aliapp';
var app_type_name = '支付宝小程序';
// #endif
// #ifdef MP-BAIDU
var app_type = 'baiduapp';
var app_type_name = '百度小程序';
// #endif
// #ifdef MP-TOUTIAO
var app_type = 'MP-TOUTIAO';
var app_type_name = '头条小程序';
// #endif
// #ifdef MP-QQ
var app_type = 'MP-QQ';
var app_type_name = 'QQ小程序';
// #endif
var data = {
token: store.state.token,
app_type: app_type,
app_type_name: app_type_name
}
data = Object.assign(data, params);
var imgs_num = num;
var _self = this;
uni.chooseImage({
count: imgs_num,
sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], //从相册或者拍照
success: async function (res) {
const tempFilePaths = res.tempFilePaths;
var _data = data;
var imgs = [];
uni.showLoading({
title: '图片上传中'
})
for (var i = 0; i < tempFilePaths.length; i++) {
var path = await _self.upload_file_server(tempFilePaths[i], _data, params.path,
url);
imgs.push(path);
if (imgs.length == tempFilePaths.length) {
uni.hideLoading()
uni.showToast({
title: '上传成功',
icon: 'none'
})
typeof callback == 'function' && callback(imgs);
}
}
},
fail: err => {
uni.hideLoading()
uni.showToast({
title: '上传失败',
icon: 'none'
})
}
});
},
//上传
upload_file_server(tempFilePath, data, path, url = "", callback) {
if (url) {
var uploadUrl = Config.baseUrl + url
} else {
var uploadUrl = Config.baseUrl + '/api/upload/' + path
}
return new Promise((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath: tempFilePath,
name: 'file',
fileType: data.fileType || 'image',
formData: data,
success: function (res) {
var path_str = JSON.parse(res.data);
if (path_str.code >= 0) {
resolve(path_str.data.pic_path);
typeof callback == 'function' && callback(path_str.data.pic_path);
} else {
reject("error");
}
}
});
});
},
/**
* 复制
* @param {Object} value
* @param {Object} callback
*/
copy(value, callback) {
// #ifdef H5
var oInput = document.createElement('input'); //创建一个隐藏input重要
oInput.value = value; //赋值
oInput.setAttribute("readonly", "readonly");
document.body.appendChild(oInput);
oInput.select(); // 选择对象
document.execCommand("Copy"); // 执行浏览器复制命令
oInput.className = 'oInput';
oInput.style.display = 'none';
uni.hideKeyboard();
this.showToast({
title: '复制成功'
});
typeof callback == 'function' && callback();
// #endif
// #ifdef MP || APP-PLUS
uni.setClipboardData({
data: value,
success: () => {
typeof callback == 'function' && callback();
}
});
// #endif
},
/**
* 是否是微信浏览器
*/
isWeiXin() {
// #ifndef H5
return false;
// #endif
var ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == "micromessenger") {
return true;
} else {
return false;
}
},
/**
* 显示消息提示框
* @param {Object} params 参数
*/
showToast(params = {}) {
params.title = params.title || "";
params.icon = params.icon || "none";
// params.position = params.position || 'bottom';
params.duration = params.duration || 1500;
uni.showToast(params);
if (params.success) params.success();
},
/**
* 检测苹果X以上的手机
*/
isIPhoneX() {
let res = uni.getSystemInfoSync();
if (res.model.search('iPhone X') != -1) {
return true;
}
return false;
},
//判断安卓还是iOS
isAndroid() {
let platform = uni.getSystemInfoSync().platform
if (platform == 'ios') {
return false;
} else if (platform == 'android') {
return true;
}
},
/**
* 深度拷贝对象
* @param {Object} obj
*/
deepClone(obj) {
const isObject = function (obj) {
return typeof obj == 'object';
}
if (!isObject(obj)) {
throw new Error('obj 不是一个对象!')
}
//判断传进来的是对象还是数组
let isArray = Array.isArray(obj)
let cloneObj = isArray ? [] : {}
//通过for...in来拷贝
for (let key in obj) {
cloneObj[key] = isObject(obj[key]) ? this.deepClone(obj[key]) : obj[key]
}
return cloneObj
},
/**
* 自定义模板的跳转链接
* @param {Object} link
*/
diyRedirectTo(link) {
//if (link == null || Object.keys(link).length == 1) return;
// 外部链接
if (link.wap_url && link.wap_url.indexOf('http') != -1 || link.wap_url && link.wap_url.indexOf('http') != -1) {
// #ifdef H5
window.location.href = link.wap_url;
// #endif
// #ifdef MP
this.redirectTo('/pages_tool/webview/webview', {
src: encodeURIComponent(link.wap_url)
});
// #endif
} else if (link.appid) {
// 跳转其他小程序
uni.navigateToMiniProgram({
appId: link.appid,
path: link.page
})
} else if (link.name == 'MOBILE' && !link.wap_url) {
// 拨打电话
uni.makePhoneCall({
phoneNumber: link.mobile,
success: (res) => {
},
fail: (res) => {
}
});
} else if (link.name == 'MEMBER_CONTACT') {
// 客服
let servicerConfig = store.state.servicerConfig
let config = {
type: 'none'
};
// #ifdef H5
config = servicerConfig.h5;
// #endif
// #ifdef MP-WEIXIN
config = servicerConfig.weapp;
// #endif
switch (config.type) {
case 'wxwork':
// 企业微信客服
// #ifdef H5
window.location.href = config.wxwork_url;
// #endif
// #ifdef MP-WEIXIN
wx.openCustomerServiceChat({
extInfo: {
url: config.wxwork_url
},
corpId: config.corpid,
showMessageCard: true,
sendMessageTitle: 'this.sendMessageTitle',
sendMessagePath: 'this.sendMessagePath',
sendMessageImg: 'this.sendMessageImg'
});
// #endif
break;
case 'third':
// 第三方客服
window.location.href = config.third_url;
break;
case 'niushop':
// Niushop客服
this.redirectTo('/pages_tool/chat/room');
break;
case 'weapp':
// 微信小程序,由于需要手动点击按钮触发,所以要跳转到中间页
this.redirectTo(link.wap_url);
break;
default:
// 拨打客服电话
let siteInfo = store.state.siteInfo;
if (siteInfo && siteInfo.site_tel) {
uni.makePhoneCall({
phoneNumber: siteInfo.site_tel
});
} else {
this.showToast({
title: '抱歉,商家暂无客服,请线下联系',
})
}
}
} else if (link.wap_url) {
this.redirectTo(link.wap_url);
}
},
/**
* 获取默认图
*/
getDefaultImage() {
let defaultImg = store.state.defaultImg;
defaultImg.goods = this.img(defaultImg.goods);
defaultImg.head = this.img(defaultImg.head);
defaultImg.store = this.img(defaultImg.store);
defaultImg.article = this.img(defaultImg.article);
defaultImg.kefu = this.img(defaultImg.kefu);
defaultImg.phone = this.img(defaultImg.phone);
return defaultImg;
},
/**
* 判断手机是否为iphoneX系列
*/
uniappIsIPhoneX() {
let isIphoneX = false;
let systemInfo = uni.getSystemInfoSync();
// #ifdef MP
if (systemInfo.model.search('iPhone X') != -1 || systemInfo.model.search('iPhone 11') != -1 || systemInfo.model.search('iPhone 12') != -1 || systemInfo.model.search('iPhone 13') != -1) {
isIphoneX = true;
}
// #endif
// #ifdef H5
var u = navigator.userAgent;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isIOS) {
if (systemInfo.screenWidth == 375 && systemInfo.screenHeight == 812 && systemInfo.pixelRatio == 3) {
isIphoneX = true;
} else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 3) {
isIphoneX = true;
} else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 2) {
isIphoneX = true;
}
}
// #endif
return isIphoneX;
},
/**
* 判断手机是否为iphone11系列
*/
uniappIsIPhone11() {
let isIphone11 = false;
let systemInfo = uni.getSystemInfoSync();
// #ifdef MP
if (systemInfo.model.search('iPhone 11') != -1) {
isIphone11 = true;
}
// #endif
return isIphone11;
},
// #ifdef H5
//判断该浏览器是否为safaria浏览器
isSafari() {
let res = uni.getSystemInfoSync();
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 &&
ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 &&
ua.indexOf('ios') === -1 && ua.indexOf('browser') === -1) {
return true;
} else {
return false;
}
},
// #endif
numberFixed(e, f) {
if (!f) {
f = 0;
}
return Number(e).toFixed(f);
},
/**
* 获取url参数
*/
getUrlCode(callback) {
var url = location.search;
var theRequest = new Object();
if (url.indexOf('?') != -1) {
var str = url.substr(1);
var strs = str.split('&');
for (var i = 0; i < strs.length; i++) {
theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1];
}
}
typeof callback == 'function' && callback(theRequest);
},
/**
* 获取当前页面路由
*/
getCurrRoute() {
let routes = getCurrentPages(); // 获取当前打开过的页面路由数组
return routes.length ? routes[routes.length - 1].route : '';
},
goBack(backUrl = '/pages/index/index') {
if (getCurrentPages().length == 1) {
this.redirectTo(backUrl);
} else {
uni.navigateBack();
}
},
/**
*
* @param val 转化时间字符串 (转化时分秒)
* @returns {string}
*/
getTimeStr(val) {
var h = parseInt(val / 3600).toString();
var m = parseInt((val % 3600) / 60).toString();
if (m.length == 1) {
m = '0' + m;
}
if (h.length == 1) {
h = '0' + h;
}
return h + ':' + m;
},
/**
* 获取定位信息
*/
getLocation(param = {}) {
/*uni.getLocation({
type: param.type ?? 'gcj02',
success: res => {
store.commit('setLocation', res);
typeof param.success == 'function' && param.success(res);
},
fail: res => {
typeof param.fail == 'function' && param.fail(res);
},
complete: res => {
typeof param.complete == 'function' && param.complete(res);
}
});*/
},
// 计算两个经纬度之间的距离
getDistance(lat1, lng1, lat2, lng2) {
var radLat1 = lat1 * Math.PI / 180.0;
var radLat2 = lat2 * Math.PI / 180.0;
var a = radLat1 - radLat2;
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * 6378.137; // EARTH_RADIUS;
s = Math.round(s * 10000) / 10000;
return s;
},
//记录分享人
onSourceMember(source_member) {
Http.sendRequest({
url: '/api/Member/alterShareRelation',
data: {
share_member: source_member,
},
success: res => {
if (res.code >= 0) {
uni.removeStorage({
key: 'source_member',
success: res => {
console.log('删除成功', res)
}
})
}
}
})
},
/**
* 微信订阅消息
*/
subscribeMessage(keywords) {
// #ifdef MP-WEIXIN
let url = '/weapp/api/weapp/messagetmplids';
// #endif
// #ifdef MP-ALIPAY
let url = '/aliapp/api/aliapp/messagetmplids';
// #endif
Http.sendRequest({
url,
data: {
keywords: keywords
},
success: res => {
if (res.data.length) {
// #ifdef MP-WEIXIN
uni.requestSubscribeMessage({
tmplIds: res.data,
success: (res) => {
console.log("res", res)
},
fail: (res) => {
console.log('fail', res)
}
})
// #endif
// #ifdef MP-ALIPAY
my.requestSubscribeMessage({
entityIds: res.data,
success: res => {
console.log("res", res)
},
fail: res => {
console.log('fail', res)
}
});
// #endif
}
}
})
},
/**
* 获取小程序分享内容数据
*/
getMpShare(path) {
//如果没有特别指定 则获取当前页面的路由
if (!path) {
let route = this.getCurrentRoute();
path = route.path;
if (path == '/pages/member/index') {
return new Promise((resolve, reject) => {
resolve({})
});
}
}
return new Promise((resolve, reject) => {
Http.sendRequest({
url: "/weapp/api/weapp/share",
data: {
path: path
},
success: res => {
if (res.code >= 0) {
let shareConfig = res.data.data;
if (shareConfig) {
//分享给好友
let appMessageData = {
title: shareConfig.title,
path: shareConfig.path,
imageUrl: shareConfig.imageUrl,
success: res => {
},
fail: res => {
}
}
//分享到朋友圈
let query = '';
if (shareConfig.path.indexOf('?') > 0) {
query = shareConfig.path.split('?')[1];
}
let timeLineData = {
title: shareConfig.title,
query: shareConfig.path,
imageUrl: shareConfig.imageUrl,
}
resolve({
appMessage: appMessageData,
timeLine: timeLineData,
})
} else {
reject(res.data);
}
} else {
reject(res.data)
}
}
});
})
},
/**
* 设置公众号分享
* @param shareData
* @param callback
*/
setPublicShare(shareData, callback) {
if (!this.isWeiXin()) return;
Http.sendRequest({
url: '/wechat/api/wechat/jssdkconfig',
data: {
url: uni.getSystemInfoSync().platform == 'ios' ? uni.getStorageSync('initUrl') : window.location
.href
},
success: res => {
if (res.code == 0) {
var wxJS = new Weixin();
wxJS.init(res.data);
wxJS.weixin.showOptionMenu();
wxJS.setShareData({
title: shareData.title ?? '',
desc: shareData.desc ?? '',
link: shareData.link ?? location.href,
imgUrl: shareData.imgUrl ? this.img(shareData.imgUrl) : ''
}, (res) => {
typeof callback == 'function' && callback(res);
})
}
}
})
},
//获取当前路由
getCurrentRoute() {
let currentRoutes = getCurrentPages(); // 获取当前打开过的页面路由数组
let currentRoute = currentRoutes[currentRoutes.length - 1].route //获取当前页面路由
let currentParam = currentRoutes[currentRoutes.length - 1].options; //获取路由参数
// 拼接参数
let param = [];
for (let key in currentParam) {
param.push(key + '=' + currentParam[key])
}
let currentPath = '/' + currentRoute;
let currentQuery = param.join('&');
if (currentQuery) currentPath += '?' + currentQuery;
return {
path: currentPath,
query: currentQuery,
}
},
//获取分享路由
getCurrentShareRoute(member_id) {
let route = this.getCurrentRoute();
//去掉原来的分享人数据
route.path = route.path.replace(/[?|&]source_member=\d+/, '');
if (member_id) {
//路径的处理
if (route.path.indexOf('?') > 0) {
route.path += '&';
} else {
route.path += '?';
}
route.path += 'source_member=' + member_id;
//参数的处理
if (route.query) {
route.query += '&';
}
route.query += 'source_member=' + member_id;
}
return route;
},
/**
* 对象转style字符串
* @param {Object} obj
*/
objToStyle(obj) {
let s = [];
for (let i in obj) {
s.push(i + ':' + obj[i]);
}
return s.join(';')
},
/**
* 颜色减值
* @param {Object} c1
* @param {Object} c2
* @param {Object} ratio
*/
colourBlend(c1, c2, ratio) {
ratio = Math.max(Math.min(Number(ratio), 1), 0)
let r1 = parseInt(c1.substring(1, 3), 16)
let g1 = parseInt(c1.substring(3, 5), 16)
let b1 = parseInt(c1.substring(5, 7), 16)
let r2 = parseInt(c2.substring(1, 3), 16)
let g2 = parseInt(c2.substring(3, 5), 16)
let b2 = parseInt(c2.substring(5, 7), 16)
let r = Math.round(r1 * (1 - ratio) + r2 * ratio)
let g = Math.round(g1 * (1 - ratio) + g2 * ratio)
let b = Math.round(b1 * (1 - ratio) + b2 * ratio)
r = ('0' + (r || 0).toString(16)).slice(-2)
g = ('0' + (g || 0).toString(16)).slice(-2)
b = ('0' + (b || 0).toString(16)).slice(-2)
return '#' + r + g + b
},
/**
* 生成贝塞尔曲线轨迹
* @param {Object} points
* @param {Object} times
*/
bezier(points, times) {
var bezier_points = [];
var points_D = [];
var points_E = [];
const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0][
'y'
], 2));
// 邻控制BC点间距
const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1][
'y'
], 2));
// D每次在AB方向上移动的距离
if (points[0]['x'] > points[2]['x']) {
var EACH_MOVE_AD = -(DIST_AB / times);
// E每次在BC方向上移动的距离
var EACH_MOVE_BE = -(DIST_BC / times);
} else {
var EACH_MOVE_AD = +(DIST_AB / times);
// E每次在BC方向上移动的距离
var EACH_MOVE_BE = +(DIST_BC / times);
}
// 点AB的正切
const TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']);
// 点BC的正切
const TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']);
// 点AB的弧度值
const RADIUS_AB = Math.atan(TAN_AB);
// 点BC的弧度值
const RADIUS_BC = Math.atan(TAN_BC);
// 每次执行
for (var i = 1; i <= times; i++) {
// AD的距离
var dist_AD = EACH_MOVE_AD * i;
// BE的距离
var dist_BE = EACH_MOVE_BE * i;
// D点的坐标
var point_D = {};
point_D['x'] = dist_AD * Math.cos(RADIUS_AB) + points[0]['x'];
point_D['y'] = dist_AD * Math.sin(RADIUS_AB) + points[0]['y'];
points_D.push(point_D);
// E点的坐标
var point_E = {};
point_E['x'] = dist_BE * Math.cos(RADIUS_BC) + points[1]['x'];
point_E['y'] = dist_BE * Math.sin(RADIUS_BC) + points[1]['y'];
points_E.push(point_E);
// 此时线段DE的正切值
var tan_DE = (point_E['y'] - point_D['y']) / (point_E['x'] - point_D['x']);
// tan_DE的弧度值
var radius_DE = Math.atan(tan_DE);
// 地市DE的间距
var dist_DE = Math.sqrt(Math.pow((point_E['x'] - point_D['x']), 2) + Math.pow((point_E['y'] - point_D['y']),
2));
// 此时DF的距离
var dist_DF = (dist_AD / DIST_AB) * dist_DE;
// 此时DF点的坐标
var point_F = {};
point_F['x'] = dist_DF * Math.cos(radius_DE) + point_D['x'];
point_F['y'] = dist_DF * Math.sin(radius_DE) + point_D['y'];
bezier_points.push(point_F);
}
return {
'bezier_points': bezier_points
};
},
/**
* 验证手机号
* @param {string} mobile 被验证的mobile
* @return {object} 验证后的结果
**/
verifyMobile(mobile) {
var parse = /^\d{11}$/.test(mobile);
return parse;
}
}

165
common/js/validate.js Normal file
View File

@@ -0,0 +1,165 @@
/**
* 数据验证(表单验证)
*/
module.exports = {
error: '',
check: function (data, rule) {
for (var i = 0; i < rule.length; i++) {
if (!rule[i].checkType) {
return true;
}
if (!rule[i].name) {
return true;
}
if (!rule[i].errorMsg) {
return true;
}
if (!data[rule[i].name]) {
this.error = rule[i].errorMsg;
return false;
}
switch (rule[i].checkType) {
case 'custom':
if (typeof rule[i].validate == 'function') {
if (!rule[i].validate(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
}
break;
case 'required':
var reg = new RegExp('/[\S]+/');
if (reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'string':
var reg = new RegExp('^.{' + rule[i].checkRule + '}$');
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'int':
var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$');
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'between':
if (!this.isNumber(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenD':
var reg = /^-?[1-9][0-9]?$/;
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenF':
var reg = /^-?[0-9][0-9]?.+[0-9]+$/;
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'same':
if (data[rule[i].name] != rule[i].checkRule) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'notsame':
if (data[rule[i].name] == rule[i].checkRule) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'email':
var reg = /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/;
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'phoneno':
var reg = /^\d{11}$/;
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'zipcode':
var reg = /^[0-9]{6}$/;
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'reg':
var reg = new RegExp(rule[i].checkRule);
if (!reg.test(data[rule[i].name])) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'in':
if (rule[i].checkRule.indexOf(data[rule[i].name]) == -1) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'notnull':
if (data[rule[i].name] == 0 || data[rule[i].name] == undefined || data[rule[i].name] == null || data[rule[i].name]
.length < 1) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'lengthMin':
if (data[rule[i].name].length < rule[i].checkRule) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'lengthMax':
if (data[rule[i].name].length > rule[i].checkRule) {
this.error = rule[i].errorMsg;
return false;
}
break;
}
}
return true;
},
isNumber: function (checkVal) {
var reg = /^-?[1-9][0-9]?.?[0-9]*$/;
return reg.test(checkVal);
}
}

119
common/js/wx-jssdk.js Normal file
View File

@@ -0,0 +1,119 @@
/**
* 微信jssdk调用
*/
let Weixin = function () {
var wx = require('jweixin-module');
this.weixin = wx;
this.init = function (params) {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来若要查看传入的参数可以在pc端打开参数信息会通过log打出仅在pc端时才会打印。
appId: params.appId, // 必填,公众号的唯一标识
timestamp: params.timestamp, // 必填,生成签名的时间戳
nonceStr: params.nonceStr, // 必填,生成签名的随机串
signature: params.signature, // 必填,签名
jsApiList: ['chooseWXPay', 'openAddress', 'updateAppMessageShareData',
'updateTimelineShareData', 'scanQRCode', 'hideMenuItems'
] // 必填需要使用的JS接口列表
});
}
/**
* 发起支付
* @param jsApiParame
* @param callback
* @param cancel
*/
this.pay = function (jsApiParame, callback, cancel) {
wx.ready(function () {
wx.chooseWXPay({
timestamp: jsApiParame
.timestamp, // 支付签名时间戳注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: jsApiParame.nonceStr, // 支付签名随机串,不长于 32 位
package: jsApiParame.package, // 统一支付接口返回的prepay_id参数值提交格式如prepay_id=\*\*\*
signType: jsApiParame.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: jsApiParame.paySign, // 支付签名
success: function (res) {
typeof callback == 'function' && callback(res);
},
cancel: function (res) {
typeof cancel == 'function' && cancel(res);
}
});
})
}
/**
* 获取收货地址
* @param {Object} callback
*/
this.openAddress = function (callback) {
wx.ready(function () {
wx.openAddress({
success: function (res) {
typeof callback == 'function' && callback(res);
},
fail: (res) => {
console.log('获取收货地址 fail',res);
alert(JSON.stringify(res))
}
});
})
}
/**
* 分享给好友
* @param {Object} params
* @param {Object} callback
*/
this.setShareData = function (params, callback) {
wx.ready(function () {
// 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
wx.updateAppMessageShareData({
title: params.title || '', // 分享标题
desc: params.desc || '', // 分享描述
link: params.link || '', // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: params.imgUrl || '', // 分享图标
success: function (res) {
typeof callback == 'function' && callback(res);
},
fail: function (err) {
}
})
// 自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
wx.updateTimelineShareData({
title: params.title || '', // 分享标题
link: params.link || '', // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: params.imgUrl || '', // 分享图标
success: function (res) {
typeof callback == 'function' && callback(res);
}
})
});
}
/**
* 扫一扫
* @param {Object} callback
*/
this.scanQRCode = function (callback) {
wx.ready(function () {
wx.scanQRCode({
needResult: 1,
scanType: ["qrCode"],
success: function (res) {
typeof callback == 'function' && callback(res);
}
});
})
}
}
export {
Weixin
}

View File

@@ -0,0 +1,638 @@
<template>
<view class="order-container" :class="{ 'safe-area': isIphoneX }">
<!-- #ifdef MP-WEIXIN -->
<view class="payment-navbar" :style="{ 'padding-top': menuButtonBounding.top + 'px', height: menuButtonBounding.height + 'px' }">
<view class="nav-wrap">
<text class="iconfont icon-back_light" @click="back"></text>
<view class="navbar-title">确认订单</view>
</view>
</view>
<view class="payment-navbar-block" :style="{ height: menuButtonBounding.bottom + 'px' }"></view>
<!-- #endif -->
<scroll-view scroll-y="true" class="order-scroll-container">
<view class="payment-navbar-block"></view>
<template v-if="paymentData">
<template v-if="paymentData.is_virtual">
<!-- 虚拟商品联系方式 -->
<view class="mobile-wrap">
<view class="tips color-base-text">
<text class="iconfont icongantanhao"></text>
购买虚拟类商品需填写手机号方便商家与您联系
</view>
<view class="form-group">
<text class="icon">
<image :src="$util.img('public/uniapp/order/icon-mobile.png')" mode="widthFix"></image>
</text>
<text class="text">手机号码</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
</template>
<template v-else>
<!-- 配送方式 -->
<view class="delivery-mode" v-if="goodsData.delivery.express_type.length > 1">
<view class="action">
<view :class="{ active: item.name == orderCreateData.delivery.delivery_type }" v-for="(item, index) in goodsData.delivery.express_type" :key="index" @click="selectDeliveryType(item)">
{{ item.title }}
<!-- 外圆角 -->
<view class="out-radio"></view>
</view>
</view>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'express'">
<view class="info-wrap" v-if="memberAddress" @click="selectAddress">
<view class="content">
<text class="name">{{ memberAddress.name ? memberAddress.name : '' }}</text>
<text class="mobile">{{ memberAddress.mobile ? memberAddress.mobile : '' }}</text>
<view class="desc-wrap">
{{ memberAddress.full_address ? memberAddress.full_address : '' }}
{{ memberAddress.address ? memberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'local'">
<view v-if="localMemberAddress">
<block v-if="storeList && Object.keys(storeList).length > 1">
<view class="local-delivery-store" v-if="storeInfo" @click="openPopup('deliveryPopup')">
<view class="info">
<text class="store-name">{{ storeInfo.store_name }}</text>
提供配送
</view>
<view class="cell-more">
<text>点击切换</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<view v-else class="local-delivery-store">
<view class="info">
<text class="store-name">您的附近没有可配送的门店请选择其他配送方式</text>
</view>
</view>
</block>
<view class="info-wrap local" @click="selectAddress">
<view class="content">
<text class="name">{{ localMemberAddress.name ? localMemberAddress.name : '' }}
</text>
<text class="mobile">{{ localMemberAddress.mobile ? localMemberAddress.mobile : '' }}
</text>
<view class="desc-wrap">
{{ localMemberAddress.full_address ? localMemberAddress.full_address : '' }}
{{ localMemberAddress.address ? localMemberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="local-box" v-if="calculateGoodsData.config.local && calculateGoodsData.delivery.local.info.time_is_open == 1">
<view class="pick-block" @click="localtime('')">
<view class="title font-size-base">送达时间</view>
<view class="time-picker">
<text :class="{ 'color-tip': !deliveryTime }">{{ deliveryTime ? deliveryTime : '请选择送达时间' }}</text>
<text class="iconfont icon-right cell-more"></text>
</view>
</view>
</view>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<!-- 门店信息 -->
<view class="store-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'store'">
<block v-if="storeInfo">
<view @click="openPopup('deliveryPopup')" class="store-info">
<view class="store-address-info">
<view class="info-wrap">
<view class="title">
<text>{{ storeInfo.store_name }}</text>
</view>
<view class="store-detail">
<view v-if="storeInfo.open_date">营业时间{{ storeInfo.open_date }}</view>
<view class="address">{{ storeInfo.full_address }} {{ storeInfo.address }}
</view>
</view>
</view>
<view class="cell-more iconfont icon-right" v-if="storeList && Object.keys(storeList).length > 1"></view>
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">姓名</text>
<input type="text" placeholder-class="color-tip placeholder" class="input" disabled v-model="orderCreateData.member_address.name" />
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">预留手机</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
<view class="store-time" @click="storetime('')">
<view class="left">提货时间</view>
<view class="right">
{{ deliveryTime }}
<text class="iconfont icon-right"></text>
</view>
</view>
</block>
<view v-else class="empty">当前无自提门店请选择其它配送方式</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
</template>
<!-- 店铺 -->
<view class="site-wrap order-goods" v-if="calculateGoodsData">
<view class="site-header">
<view class="iconfont icon-dianpu"></view>
<text class="site-name">门店</text>
</view>
<view class="site-body">
<!-- 商品 -->
<view class="goods-item" v-for="(goodsItem, goodsIndex) in calculateGoodsData.goods_list" :key="goodsIndex">
<view class="goods-wrap">
<view class="goods-img" @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })">
<image :src="$util.img(goodsItem.sku_image, { size: 'mid' })" @error="imageError(goodsIndex)" mode="aspectFill"/>
</view>
<view class="goods-info">
<view class="top-wrap">
<view @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })" class="goods-name">{{ goodsItem.sku_name }}</view>
<view class="sku" v-if="goodsItem.sku_spec_format">
<view class="goods-spec">
<block v-for="(x, i) in goodsItem.sku_spec_format" :key="i">
<view>{{ x.spec_value_name }}</view>
</block>
</view>
</view>
<block v-if="goodsItem.is_virtual == 0">
<view class="error-tips" v-if="orderCreateData.delivery &&
orderCreateData.delivery.delivery_type &&
goodsItem.support_trade_type &&
goodsItem.support_trade_type.indexOf(orderCreateData.delivery.delivery_type) == -1
">
<text class="iconfont icon-gantanhao"></text>
<text>该商品不支持{{ orderCreateData.delivery.delivery_type_name }}</text>
</view>
</block>
<view class="error-tips" v-if="goodsItem.error && goodsItem.error.message">
<text class="iconfont icon-gantanhao"></text>
<text>{{ goodsItem.error.message }}</text>
</view>
</view>
<view class="goods-sub-section">
<view class="color-base-text">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="goods-price price-style large">{{ parseFloat(goodsItem.price).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(goodsItem.price).toFixed(2).split('.')[1] }}</text>
</view>
<view>
<text class="font-size-tag">x</text>
<text class="font-size-base">{{ goodsItem.num }}</text>
</view>
</view>
</view>
</view>
<view class="member-goods-card order-cell" v-if="calculateGoodsData.goods_list[goodsIndex].member_card_list" @click="selectMemberGoodsCard(goodsIndex)">
<text class="tit">次卡抵扣</text>
<view class="box text-overflow">
<block v-if="calculateGoodsData.goods_list[goodsIndex].card_promotion_money">
<text class="text">次卡抵扣{{ calculateGoodsData.goods_list[goodsIndex].card_use_num }}张/{{ calculateGoodsData.goods_list[goodsIndex].card_use_num }}次</text>
<text class="price-font">-¥{{ calculateGoodsData.goods_list[goodsIndex].card_promotion_money | moneyFormat }}</text>
</block>
<text class="color-tip" v-else>请选择次卡</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="goods-form" v-if="goodsData.goods_list[goodsIndex].goods_form" @click="editForm(goodsIndex)">
<ns-form :data="goodsData.goods_list[goodsIndex].goods_form.json_data" ref="goodsForm" :custom-attr="{ sku_id: goodsItem.sku_id, form_id: goodsData.goods_list[goodsIndex].goods_form.id }"/>
<text class="cell-more iconfont icon-right"></text>
<view class="shade"></view>
</view>
</view>
</view>
</view>
<view class="site-wrap buyer-message">
<view class="order-cell">
<text class="tit">买家留言</text>
<view class="box text-overflow " @click="openPopup('buyerMessagePopup')">
<text v-if="orderCreateData.buyer_message">{{ orderCreateData.buyer_message }}</text>
<text class="color-sub" v-else>无留言</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<view v-if="paymentData.system_form" class="system-form-wrap">
<view class="order-cell">
<text class="tit">{{ paymentData.system_form.form_name }}</text>
</view>
<ns-form :data="paymentData.system_form.json_data" ref="form"/>
</view>
<view class="site-wrap" v-if="calculateGoodsData || promotionInfo || (calculateGoodsData && calculateGoodsData.max_usable_point > 0) || goodsData.invoice">
<view class="site-footer">
<view class="order-cell coupon" v-if="modules.indexOf('coupon') != -1">
<text class="tit">优惠券</text>
<view class="box text-overflow" @click="openPopup('couponPopup')">
<template v-if="orderCreateData.coupon && orderCreateData.coupon.coupon_id">
<text>已使用优惠券,优惠</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ (calculateData && calculateData.coupon_money ? calculateData.coupon_money : 0) | moneyFormat }}</text>
</template>
<text v-else>不使用优惠券</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell" v-if="promotionInfo">
<text class="tit">活动优惠</text>
<view class="box text-overflow" @click="openPopup('promotionPopup')">
<text>{{ promotionInfo.title }}</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell point" v-if="calculateGoodsData && calculateGoodsData.max_usable_point > 0">
<text class="tit">
<text>使用{{ parseInt(calculateGoodsData.max_usable_point) }}积分可抵扣</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</text>
<view class="box"></view>
<ns-switch class="balance-switch" @change="usePoint" :checked="orderCreateData.is_point == 1"/>
</view>
<view class="order-cell order-invoice-cell" v-if="goodsData.invoice.invoice_status == 1">
<text class="tit">发票</text>
<view class="box text-overflow" @click="openPopup('invoicePopup')">
<text v-if="orderCreateData.is_invoice == 1">{{ orderCreateData.invoice_type == 1 ? '纸质' : '电子' }}发票({{ orderCreateData.invoice_content }})</text>
<text v-else>无需发票</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
</view>
<view class="site-wrap box member-card-wrap" v-if="paymentData.recommend_member_card && Object.keys(paymentData.recommend_member_card).length > 0">
<view class="head" @click="selectMemberCard">
<text class="iconfont icon-huiyuan"></text>
<view class="info">
开通{{ paymentData.recommend_member_card.level_name }}
<text>本单预计可省</text>
<text class="price-color">{{ paymentData.recommend_member_card.discount_money | moneyFormat }}</text>
<text>元</text>
</view>
<text class="iconfont" :class="orderCreateData.is_open_card == 1 ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></text>
</view>
<view class="body" v-if="orderCreateData.is_open_card">
<view class="item" :class="{ 'active color-base-border': item.key == orderCreateData.member_card_unit }" v-for="(item, index) in cardChargeType" :key="index" @click="selectMembercardUnit(item.key)">
<view class="title">{{ item.title }}</view>
<view class="price price-font">{{ $lang('common.currencySymbol') }}{{ parseFloat(item.value) }}/{{ item.unit }}</view>
<text class="iconfont icon-icon color-base-text price-font identify" v-if="item.key == orderCreateData.member_card_unit"></text>
</view>
</view>
</view>
<!-- 订单金额 -->
<template v-if="calculateData">
<view class="order-money">
<view class="order-cell">
<text class="tit">商品金额</text>
<view class="box">
<text class="unit color-title price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money color-title price-font">{{ calculateData.goods_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.is_virtual == 0 && calculateData.delivery_money > 0">
<text class="tit">运费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.delivery_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_money > 0">
<text class="tit">
<text>税费</text>
<text class="color-base-text font-bold price-font">({{ goodsData.invoice.invoice_rate }}%)</text>
</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_delivery_money > 0">
<text class="tit">发票邮寄费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_delivery_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.promotion_money > 0">
<text class="tit">优惠</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.promotion_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.coupon_money">
<text class="tit">优惠券</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.coupon_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.point_money > 0">
<text class="tit">积分抵扣</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.member_card_money > 0">
<text class="tit">会员卡</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.member_card_money | moneyFormat }}
</text>
</view>
</view>
</view>
<view v-if="transactionAgreement.title && transactionAgreement.content" class="agreement">购买前请先阅读<text @click="$refs.agreementPopup.open()">《{{ transactionAgreement.title }}》</text>,下单即代表同意该协议</view>
<view class="order-submit bottom-safe-area">
<view class="order-settlement-info">
<text class="font-size-base color-tip margin-right">共{{ calculateData.goods_num }}件</text>
<text class="font-size-base">合计:</text>
<text class=" unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class=" money price-font">{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[0] }}</text>
<text class=" unit price-font">.{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[1] }}</text>
</view>
<view class="submit-btn">
<button type="primary" class="mini" size="mini" @click="create()" v-if="!surplusStartMoney()">提交订单</button>
<button v-else class="no-submit mini" size="mini">差{{ surplusStartMoney() | moneyFormat }}起送</button>
</view>
</view>
<view class="order-submit-block"></view>
<payment ref="choosePaymentPopup" @close="payClose" v-if="calculateData"></payment>
</template>
<!-- 活动优惠弹窗 -->
<uni-popup ref="promotionPopup" type="bottom" v-if="promotionInfo">
<view class="promotion-popup popup">
<view class="popup-header">
<text class="tit">活动优惠</text>
<text class="iconfont icon-close" @click="closePopup('promotionPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view class="order-cell" style="align-items: baseline;">
<view class="tit">
<text class="promotion-mark ns-gradient-promotionpages-payment">{{ promotionInfo.title }}
</text>
</view>
<view class="promotion-content">
<view class="tit tit-content" style="white-space: pre-line;" v-html="promotionInfo.content"></view>
</view>
</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="closePopup('promotionPopup')">确定</view>
</view>
</view>
</uni-popup>
<!-- 门店列表弹窗 -->
<uni-popup ref="deliveryPopup" type="bottom">
<view class="delivery-popup popup">
<view class="popup-header">
<text class="tit">已为您甄选出附近所有相关门店</text>
<text class="iconfont icon-close" @click="closePopup('deliveryPopup')"></text>
</view>
<view class="popup-body store-popup" :class="{ 'safe-area': isIphoneX }">
<mescroll-uni @getData="getStore" ref="mescroll" top="50px">
<block slot="list">
<view class="delivery-content">
<block v-if="storeData">
<view class="item-wrap" v-for="(item, index) in storeData" :key="index" @click="selectPickupPoint(item)">
<view class="detail">
<view class="name" :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''">
<text>{{ item.store_name }}</text>
<text v-if="item.distance">({{ item.distance }}km)</text>
</view>
<view class="info">
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">营业时间:{{ item.open_date }}</view>
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">地址:{{ item.full_address }}{{ item.address }}</view>
</view>
</view>
<view class="icon" v-if="item.store_id == orderCreateData.delivery.store_id">
<text class="iconfont icon-yuan_checked color-base-text"></text>
</view>
</view>
</block>
<view v-else class="empty-wrap">
<ns-empty text="所选择收货地址附近没有可以自提的门店" :isIndex="false"></ns-empty>
</view>
</view>
</block>
</mescroll-uni>
</view>
</view>
</uni-popup>
<!-- 留言弹窗 -->
<uni-popup ref="buyerMessagePopup" type="bottom">
<view style="height: auto;" class="buyermessag-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家留言</text>
<text class="iconfont icon-close" @click="closePopup('buyerMessagePopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view>
<view class="buyermessag-cell">
<view class="buyermessag-form-group">
<textarea type="text" maxlength="100" placeholder="留言前建议先与商家协调一致" placeholder-class="color-tip" v-model="orderCreateData.buyer_message"/>
</view>
</view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveBuyerMessage" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<!-- 优惠券弹窗 -->
<uni-popup ref="couponPopup" type="bottom" v-if="calculateGoodsData" :mask-click="false">
<view class="coupon-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">优惠券</text>
<text class="iconfont icon-close" @click="closePopup('couponPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-if="coupon_list.length > 0">
<view class="coupon-item" v-for="(couponItem, couponIndex) in coupon_list" :key="couponIndex" @click="selectCoupon(couponItem)">
<view class="coupon-info" :style="{ backgroundColor: 'var(--main-color-shallow)' }">
<view class="info-wrap">
<image class="coupon-line" mode="heightFix" :src="$util.img('public/uniapp/coupon/coupon_line.png')"/>
<view class="coupon-money">
<template v-if="couponItem.type == 'divideticket'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'reward'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'discount'">
<text class="money">{{ parseFloat(couponItem.discount) }}</text>
<text class="unit">折</text>
</template>
<view class="at-least">
<template v-if="couponItem.at_least > 0">
满{{ couponItem.at_least }}可用
</template>
<template v-else>
无门槛
</template>
</view>
</view>
</view>
<view class="desc-wrap">
<view class="coupon-name">{{ couponItem.coupon_name }}</view>
<view v-if="couponItem.type == 'discount' && couponItem.discount_limit > 0" class="limit">最多可抵¥{{ couponItem.discount_limit }}</view>
<view class="time font-size-goods-tag">有效期:{{ couponItem.end_time ? $util.timeStampTurnTime(couponItem.end_time) : '长期有效' }}</view>
</view>
<view class="iconfont" :class="orderCreateData.coupon.coupon_id == couponItem.coupon_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</view>
</view>
<view v-else class="coupon-empty">暂无可用的优惠券</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="useCpopon">确定</view>
</view>
</view>
</uni-popup>
<!-- 交易协议 -->
<view @touchmove.prevent>
<uni-popup ref="agreementPopup" type="center" :maskClick="false">
<view class="agreement-conten-box">
<view class="close">
<text class="iconfont icon-close" @click="$refs.agreementPopup.close()"></text>
</view>
<view class="title">{{ transactionAgreement.title }}</view>
<view class="con">
<scroll-view scroll-y="true" class="con">
<rich-text :nodes="transactionAgreement.content"></rich-text>
</scroll-view>
</view>
</view>
</uni-popup>
</view>
<!-- 表单修改弹窗 -->
<uni-popup ref="editFormPopup" type="bottom">
<view style="height: auto;" class="form-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家信息</text>
<text class="iconfont icon-close" @click="$refs.editFormPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<ns-form v-if="tempFormData" :data="tempFormData.json_data" ref="tempForm"></ns-form>
</scroll-view>
<view class="popup-footer" @click="saveForm" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<uni-popup ref="memberGoodsCardPopup" type="bottom">
<view class="member-card-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">选择次卡</text>
<text class="iconfont icon-close" @click="$refs.memberGoodsCardPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-for="(item, index) in selectGoodsCard.cardList" class="card-item" @click="selectGoodsCard.click(item.item_id)">
<view class="content">
<view class="title">{{ item.goods_name }}</view>
<view class="info">
<text v-if="item.card_type == 'timecard'">不限次数</text>
<text v-if="item.card_type == 'oncecard'">剩余{{ item.num - item.use_num }}次</text>
<text v-if="item.card_type == 'commoncard'">剩余{{ item.total_num - item.total_use_num }}次</text>
<text>|</text>
<text>{{ item.end_time ? $util.timeStampTurnTime(item.end_time) : '长期有效' }}</text>
</view>
</view>
<view class="iconfont" :class="selectGoodsCard.itemId == item.item_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveMemberGoodsCard" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
</template>
</scroll-view>
<!-- 门店自提时间 -->
<ns-select-time @selectTime="selectPickupTime" ref="timePopup"></ns-select-time>
<ns-login ref="login"></ns-login>
<loading-cover ref="loadingCover"></loading-cover>
</view>
</template>
<script>
import payment from './payment.js';
export default {
name: 'common-payment',
data() {
return {};
},
props: {
api: Object,
createDataKey: String
},
mixins: [payment]
};
</script>
<style lang="scss">
@import '@/common/css/order_parment.scss';
.order-cell .promotion-content {
flex: 1;
}
</style>

View File

@@ -0,0 +1,658 @@
<template>
<view class="order-container" :class="{ 'safe-area': isIphoneX }">
<!-- #ifdef MP-WEIXIN -->
<view class="payment-navbar" :style="{ 'padding-top': menuButtonBounding.top + 'px', height: menuButtonBounding.height + 'px' }">
<view class="nav-wrap">
<text class="iconfont icon-back_light" @click="back"></text>
<view class="navbar-title">确认订单</view>
</view>
</view>
<view class="payment-navbar-block" :style="{ height: menuButtonBounding.bottom + 'px' }"></view>
<!-- #endif -->
<scroll-view scroll-y="true" class="order-scroll-container">
<view class="payment-navbar-block"></view>
<template v-if="paymentData">
<template v-if="paymentData.is_virtual">
<!-- 虚拟商品联系方式 -->
<view class="mobile-wrap">
<view class="tips color-base-text">
<text class="iconfont icongantanhao"></text>
购买虚拟类商品需填写手机号方便商家与您联系
</view>
<view class="form-group">
<text class="icon">
<image :src="$util.img('public/uniapp/order/icon-mobile.png')" mode="widthFix"></image>
</text>
<text class="text">手机号码</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
</template>
<template v-else>
<!-- 配送方式 -->
<view class="delivery-mode" v-if="goodsData.delivery.express_type.length > 1">
<view class="action">
<view :class="{ active: item.name == orderCreateData.delivery.delivery_type }" v-for="(item, index) in goodsData.delivery.express_type" :key="index" @click="selectDeliveryType(item)">
{{ item.title }}
<!-- 外圆角 -->
<view class="out-radio"></view>
</view>
</view>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'express'">
<view class="info-wrap" v-if="memberAddress" @click="selectAddress">
<view class="content">
<text class="name">{{ memberAddress.name ? memberAddress.name : '' }}</text>
<text class="mobile">{{ memberAddress.mobile ? memberAddress.mobile : '' }}</text>
<view class="desc-wrap">
{{ memberAddress.full_address ? memberAddress.full_address : '' }}
{{ memberAddress.address ? memberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<view class="address-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'local'">
<view v-if="localMemberAddress">
<block v-if="storeList && Object.keys(storeList).length > 1">
<view class="local-delivery-store" v-if="storeInfo" @click="openPopup('deliveryPopup')">
<view class="info">
<text class="store-name">{{ storeInfo.store_name }}</text>
提供配送
</view>
<view class="cell-more">
<text>点击切换</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<view v-else class="local-delivery-store">
<view class="info">
<text class="store-name">您的附近没有可配送的门店请选择其他配送方式</text>
</view>
</view>
</block>
<view class="info-wrap local" @click="selectAddress">
<view class="content">
<text class="name">{{ localMemberAddress.name ? localMemberAddress.name : '' }}
</text>
<text class="mobile">{{ localMemberAddress.mobile ? localMemberAddress.mobile : '' }}
</text>
<view class="desc-wrap">
{{ localMemberAddress.full_address ? localMemberAddress.full_address : '' }}
{{ localMemberAddress.address ? localMemberAddress.address : '' }}
</view>
</view>
<text class="cell-more iconfont icon-right"></text>
</view>
<view class="local-box" v-if="calculateGoodsData.config.local && calculateGoodsData.delivery.local.info.time_is_open == 1">
<view class="pick-block" @click="localtime('')">
<view class="title font-size-base">送达时间</view>
<view class="time-picker">
<text :class="{ 'color-tip': !deliveryTime }">{{ deliveryTime ? deliveryTime : '请选择送达时间' }}</text>
<text class="iconfont icon-right cell-more"></text>
</view>
</view>
</view>
</view>
<view class="empty-wrap" v-else @click="selectAddress">
<view class="info">请设置收货地址</view>
<view class="cell-more">
<view class="iconfont icon-right"></view>
</view>
</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
<!-- 门店信息 -->
<view class="store-box" :class="{ 'not-delivery-type': goodsData.delivery.express_type.length <= 1 }" v-if="orderCreateData.delivery.delivery_type == 'store'">
<block v-if="storeInfo">
<view @click="openPopup('deliveryPopup')" class="store-info">
<view class="store-address-info">
<view class="info-wrap">
<view class="title">
<text>{{ storeInfo.store_name }}</text>
</view>
<view class="store-detail">
<view v-if="storeInfo.open_date">营业时间{{ storeInfo.open_date }}</view>
<view class="address">{{ storeInfo.full_address }} {{ storeInfo.address }}
</view>
</view>
</view>
<view class="cell-more iconfont icon-right" v-if="storeList && Object.keys(storeList).length > 1"></view>
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">姓名</text>
<input type="text" placeholder-class="color-tip placeholder" class="input" disabled v-model="orderCreateData.member_address.name" />
</view>
</view>
<view class="mobile-wrap store-mobile" v-if="orderCreateData.member_address">
<view class="form-group">
<text class="text">预留手机</text>
<input type="number" maxlength="11" placeholder="请输入您的手机号码" placeholder-class="color-tip placeholder" class="input" v-model="orderCreateData.member_address.mobile" />
</view>
</view>
<view class="store-time" @click="storetime('')">
<view class="left">提货时间</view>
<view class="right">
{{ deliveryTime }}
<text class="iconfont icon-right"></text>
</view>
</view>
</block>
<view v-else class="empty">当前无自提门店请选择其它配送方式</view>
<image class="address-line" :src="$util.img('public/uniapp/order/address-line.png')"></image>
</view>
</template>
<!-- 店铺 -->
<view class="site-wrap order-goods" v-for="(calculateGoodsData, siteIndex) in shop_goods_list" :key="siteIndex">
<view class="site-header">
<view class="iconfont icon-dianpu"></view>
<text class="site-name">{{calculateGoodsData.site_name}}</text>
</view>
<view class="site-body">
<!-- 商品 -->
<view class="goods-item" v-for="(goodsItem, goodsIndex) in goodsSpecFormat(calculateGoodsData.goods_list)" :key="goodsIndex">
<view class="goods-wrap">
<view class="goods-img" @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })">
<image :src="$util.img(goodsItem.sku_image, { size: 'mid' })" @error="imageError(goodsIndex)" mode="aspectFill"/>
</view>
<view class="goods-info">
<view class="top-wrap">
<view @click="$util.redirectTo('/pages/goods/detail', { goods_id: goodsItem.goods_id })" class="goods-name">{{ goodsItem.sku_name }}</view>
<view class="sku" v-if="goodsItem.sku_spec_format">
<view class="goods-spec">
<block v-for="(x, i) in goodsItem.sku_spec_format" :key="i">
<view>{{ x.spec_value_name }}</view>
</block>
</view>
</view>
<block v-if="goodsItem.is_virtual == 0">
<view class="error-tips" v-if="orderCreateData.delivery &&
orderCreateData.delivery.delivery_type &&
goodsItem.support_trade_type &&
goodsItem.support_trade_type.indexOf(orderCreateData.delivery.delivery_type) == -1
">
<text class="iconfont icon-gantanhao"></text>
<text>该商品不支持{{ orderCreateData.delivery.delivery_type_name }}</text>
</view>
</block>
<view class="error-tips" v-if="goodsItem.error && goodsItem.error.message">
<text class="iconfont icon-gantanhao"></text>
<text>{{ goodsItem.error.message }}</text>
</view>
</view>
<view class="goods-sub-section">
<view class="color-base-text">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="goods-price price-style large">{{ parseFloat(goodsItem.price).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(goodsItem.price).toFixed(2).split('.')[1] }}</text>
</view>
<view>
<text class="font-size-tag">x</text>
<text class="font-size-base">{{ goodsItem.num }}</text>
</view>
</view>
</view>
</view>
<view class="goods-form" v-if="goodsData.goods_list[goodsIndex].goods_form" @click="editForm(goodsIndex)">
<ns-form :data="goodsData.goods_list[goodsIndex].goods_form.json_data" ref="goodsForm" :custom-attr="{ sku_id: goodsItem.sku_id, form_id: goodsData.goods_list[goodsIndex].goods_form.id }"/>
<text class="cell-more iconfont icon-right"></text>
<view class="shade"></view>
</view>
</view>
</view>
<view class="site-wrap buyer-message">
</view>
<view class="order-money" style="margin: 0;">
<view class="order-cell">
<text class="tit">买家留言</text>
<view class="box text-overflow " @click="openPopup('buyerMessagePopup')">
<text v-if="orderCreateData.buyer_message">{{ orderCreateData.buyer_message[calculateGoodsData.merch_id] }}</text>
<text class="color-sub" v-else>无留言</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell coupon" v-if="modules.indexOf('coupon') != -1">
<text class="tit">优惠券</text>
<view class="box text-overflow"@click="openSiteCoupon(calculateGoodsData.merch_id)">
<template v-if="orderCreateData.coupon[calculateGoodsData.merch_id].coupon_id">
<text class="money price-font" style="max-width: 100%;">已选择1张优惠券</text>
<!-- <text class="unit price-font">优惠</text>
<text class="money price-font">{{ (calculateData && calculateData.coupon_money ? calculateData.coupon_money : 0) | moneyFormat }}</text> -->
</template>
<text v-else>不使用优惠券</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<view class="site-wrap buyer-message">
<view class="order-cell">
<view class="box shop-item">
<text class="color-tip goods-num">共{{ calculateGoodsData.goods_num }}件</text>
<text class="font-size-base">小计:</text>
<text class="color-base-text unit">{{ $lang('common.currencySymbol') }}</text>
<text class="color-base-text money">{{ calculateGoodsData.goods_money | moneyFormat }}</text>
</view>
</view>
</view>
</view>
<view v-if="paymentData.system_form" class="system-form-wrap">
<view class="order-cell">
<text class="tit">{{ paymentData.system_form.form_name }}</text>
</view>
<ns-form :data="paymentData.system_form.json_data" ref="form"/>
</view>
<!-- <view class="site-wrap" v-if="calculateGoodsData || promotionInfo || (calculateGoodsData && calculateGoodsData.max_usable_point > 0) || goodsData.invoice">
<view class="site-footer">
<view class="order-cell coupon" v-if="modules.indexOf('coupon') != -1">
<text class="tit">优惠券</text>
<view class="box text-overflow" @click="openPopup('couponPopup')">
<template v-if="orderCreateData.coupon && orderCreateData.coupon.coupon_id">
<text>已使用优惠券,优惠</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ (calculateData && calculateData.coupon_money ? calculateData.coupon_money : 0) | moneyFormat }}</text>
</template>
<text v-else>不使用优惠券</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell" v-if="promotionInfo">
<text class="tit">活动优惠</text>
<view class="box text-overflow" @click="openPopup('promotionPopup')">
<text>{{ promotionInfo.title }}</text>
</view>
<text class="iconfont icon-right"></text>
</view>
<view class="order-cell point" v-if="calculateGoodsData && calculateGoodsData.max_usable_point > 0">
<text class="tit">
<text>使用{{ parseInt(calculateGoodsData.max_usable_point) }}积分可抵扣</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</text>
<view class="box"></view>
<ns-switch class="balance-switch" @change="usePoint" :checked="orderCreateData.is_point == 1"/>
</view>
<view class="order-cell order-invoice-cell" v-if="goodsData.invoice.invoice_status == 1">
<text class="tit">发票</text>
<view class="box text-overflow" @click="openPopup('invoicePopup')">
<text v-if="orderCreateData.is_invoice == 1">{{ orderCreateData.invoice_type == 1 ? '纸质' : '电子' }}发票({{ orderCreateData.invoice_content }})</text>
<text v-else>无需发票</text>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
</view> -->
<view class="site-wrap box member-card-wrap" v-if="paymentData.recommend_member_card && Object.keys(paymentData.recommend_member_card).length > 0">
<view class="head" @click="selectMemberCard">
<text class="iconfont icon-huiyuan"></text>
<view class="info">
开通{{ paymentData.recommend_member_card.level_name }}
<text>本单预计可省</text>
<text class="price-color">{{ paymentData.recommend_member_card.discount_money | moneyFormat }}</text>
<text>元</text>
</view>
<text class="iconfont" :class="orderCreateData.is_open_card == 1 ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></text>
</view>
<view class="body" v-if="orderCreateData.is_open_card">
<view class="item" :class="{ 'active color-base-border': item.key == orderCreateData.member_card_unit }" v-for="(item, index) in cardChargeType" :key="index" @click="selectMembercardUnit(item.key)">
<view class="title">{{ item.title }}</view>
<view class="price price-font">{{ $lang('common.currencySymbol') }}{{ parseFloat(item.value) }}/{{ item.unit }}</view>
<text class="iconfont icon-icon color-base-text price-font identify" v-if="item.key == orderCreateData.member_card_unit"></text>
</view>
</view>
</view>
<!-- 订单金额 -->
<template v-if="calculateData">
<view class="order-money">
<view class="order-cell">
<text class="tit">商品金额</text>
<view class="box">
<text class="unit color-title price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money color-title price-font">{{ calculateData.goods_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.is_virtual == 0 && calculateData.delivery_money > 0">
<text class="tit">运费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.delivery_money | moneyFormat }}</text>
</view>
</view>
<!-- <view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_money > 0">
<text class="tit">
<text>税费</text>
<text class="color-base-text font-bold price-font">({{ goodsData.invoice.invoice_rate }}%)</text>
</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_money | moneyFormat }}</text>
</view>
</view> -->
<!-- <view class="order-cell" v-if="orderCreateData.is_invoice && calculateData.invoice_delivery_money > 0">
<text class="tit">发票邮寄费</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.invoice_delivery_money | moneyFormat }}</text>
</view>
</view> -->
<view class="order-cell" v-if="calculateData.promotion_money > 0">
<text class="tit">优惠</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.promotion_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.coupon_money">
<text class="tit">优惠券</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.coupon_money | moneyFormat }}</text>
</view>
</view>
<view class="order-cell" v-if="calculateData.point_money > 0">
<text class="tit">积分抵扣</text>
<view class="box color-base-text">
<text class="operator">-</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.point_money | moneyFormat }}</text>
</view>
</view>
<!-- <view class="order-cell" v-if="calculateData.member_card_money > 0">
<text class="tit">会员卡</text>
<view class="box color-base-text">
<text class="operator">+</text>
<text class="unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class="money price-font">{{ calculateData.member_card_money | moneyFormat }}
</text>
</view>
</view> -->
</view>
<view v-if="transactionAgreement.title && transactionAgreement.content" class="agreement">购买前请先阅读<text @click="$refs.agreementPopup.open()">《{{ transactionAgreement.title }}》</text>,下单即代表同意该协议</view>
<view class="order-submit bottom-safe-area">
<view class="order-settlement-info">
<text class="font-size-base color-tip margin-right">共{{ calculateData.goods_num }}件</text>
<text class="font-size-base">合计:</text>
<text class=" unit price-font">{{ $lang('common.currencySymbol') }}</text>
<text class=" money price-font">{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[0] }}</text>
<text class=" unit price-font">.{{ parseFloat(calculateData.pay_money).toFixed(2).split('.')[1] }}</text>
</view>
<view class="submit-btn">
<button type="primary" class="mini" size="mini" @click="create()" v-if="!surplusStartMoney()">提交订单</button>
<!-- <button v-else class="no-submit mini" size="mini">差{{ surplusStartMoney() | moneyFormat }}起送</button> -->
</view>
</view>
<view class="order-submit-block"></view>
<payment ref="choosePaymentPopup" @close="payClose" v-if="calculateData"></payment>
</template>
<!-- 活动优惠弹窗 -->
<uni-popup ref="promotionPopup" type="bottom" v-if="promotionInfo">
<view class="promotion-popup popup">
<view class="popup-header">
<text class="tit">活动优惠</text>
<text class="iconfont icon-close" @click="closePopup('promotionPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view class="order-cell" style="align-items: baseline;">
<view class="tit">
<text class="promotion-mark ns-gradient-promotionpages-payment">{{ promotionInfo.title }}
</text>
</view>
<view class="promotion-content">
<view class="tit tit-content" style="white-space: pre-line;" v-html="promotionInfo.content"></view>
</view>
</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="closePopup('promotionPopup')">确定</view>
</view>
</view>
</uni-popup>
<!-- 门店列表弹窗 -->
<uni-popup ref="deliveryPopup" type="bottom">
<view class="delivery-popup popup">
<view class="popup-header">
<text class="tit">已为您甄选出附近所有相关门店</text>
<text class="iconfont icon-close" @click="closePopup('deliveryPopup')"></text>
</view>
<view class="popup-body store-popup" :class="{ 'safe-area': isIphoneX }">
<mescroll-uni @getData="getStore" ref="mescroll" top="50px">
<block slot="list">
<view class="delivery-content">
<block v-if="storeData">
<view class="item-wrap" v-for="(item, index) in storeData" :key="index" @click="selectPickupPoint(item)">
<view class="detail">
<view class="name" :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''">
<text>{{ item.store_name }}</text>
<text v-if="item.distance">({{ item.distance }}km)</text>
</view>
<view class="info">
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">营业时间:{{ item.open_date }}</view>
<view :class="item.store_id == orderCreateData.delivery.store_id ? 'color-base-text' : ''" class="font-size-goods-tag">地址:{{ item.full_address }}{{ item.address }}</view>
</view>
</view>
<view class="icon" v-if="item.store_id == orderCreateData.delivery.store_id">
<text class="iconfont icon-yuan_checked color-base-text"></text>
</view>
</view>
</block>
<view v-else class="empty-wrap">
<ns-empty text="所选择收货地址附近没有可以自提的门店" :isIndex="false"></ns-empty>
</view>
</view>
</block>
</mescroll-uni>
</view>
</view>
</uni-popup>
<!-- 留言弹窗 -->
<uni-popup ref="buyerMessagePopup" type="bottom">
<view style="height: auto;" class="buyermessag-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家留言</text>
<text class="iconfont icon-close" @click="closePopup('buyerMessagePopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view>
<view class="buyermessag-cell">
<view class="buyermessag-form-group">
<textarea type="text" maxlength="100" placeholder="留言前建议先与商家协调一致" placeholder-class="color-tip" v-model="orderCreateData.buyer_message"/>
</view>
</view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveBuyerMessage" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<!-- 优惠券弹窗 -->
<uni-popup ref="couponPopup" type="bottom" :mask-click="false">
<view class="coupon-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">优惠券</text>
<text class="iconfont icon-close" @click="closePopup('couponPopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-if="merchCoupon.data.length > 0">
<view class="coupon-item" v-for="(couponItem, couponIndex) in merchCoupon.data" :key="couponIndex" @click="selectCoupon(couponItem,merchCoupon.merch_id)">
<view class="coupon-info" :style="{ backgroundColor: 'var(--main-color-shallow)' }">
<view class="info-wrap">
<image class="coupon-line" mode="heightFix" :src="$util.img('public/uniapp/coupon/coupon_line.png')"/>
<view class="coupon-money">
<template v-if="couponItem.type == 'divideticket'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'reward'">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text class="money">{{ parseFloat(couponItem.money) }}</text>
</template>
<template v-else-if="couponItem.type == 'discount'">
<text class="money">{{ parseFloat(couponItem.discount) }}</text>
<text class="unit">折</text>
</template>
<view class="at-least">
<template v-if="couponItem.at_least > 0">
满{{ couponItem.at_least }}可用
</template>
<template v-else>
无门槛
</template>
</view>
</view>
</view>
<view class="desc-wrap">
<view class="coupon-name">{{ couponItem.coupon_name }}</view>
<view v-if="couponItem.type == 'discount' && couponItem.discount_limit > 0" class="limit">最多可抵¥{{ couponItem.discount_limit }}</view>
<view class="time font-size-goods-tag">有效期:{{ couponItem.end_time ? $util.timeStampTurnTime(couponItem.end_time) : '长期有效' }}</view>
</view>
<view class="iconfont" :class="selectCouponId == couponItem.coupon_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</view>
</view>
<view v-else class="coupon-empty">暂无可用的优惠券</view>
</scroll-view>
<view class="popup-footer" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg" @click="useCpopon">确定</view>
</view>
</view>
</uni-popup>
<!-- 交易协议 -->
<view @touchmove.prevent>
<uni-popup ref="agreementPopup" type="center" :maskClick="false">
<view class="agreement-conten-box">
<view class="close">
<text class="iconfont icon-close" @click="$refs.agreementPopup.close()"></text>
</view>
<view class="title">{{ transactionAgreement.title }}</view>
<view class="con">
<scroll-view scroll-y="true" class="con">
<rich-text :nodes="transactionAgreement.content"></rich-text>
</scroll-view>
</view>
</view>
</uni-popup>
</view>
<!-- 表单修改弹窗 -->
<uni-popup ref="editFormPopup" type="bottom">
<view style="height: auto;" class="form-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">买家信息</text>
<text class="iconfont icon-close" @click="$refs.editFormPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<ns-form v-if="tempFormData" :data="tempFormData.json_data" ref="tempForm"></ns-form>
</scroll-view>
<view class="popup-footer" @click="saveForm" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
<uni-popup ref="memberGoodsCardPopup" type="bottom">
<view class="member-card-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">选择次卡</text>
<text class="iconfont icon-close" @click="$refs.memberGoodsCardPopup.close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view v-for="(item, index) in selectGoodsCard.cardList" class="card-item" @click="selectGoodsCard.click(item.item_id)">
<view class="content">
<view class="title">{{ item.goods_name }}</view>
<view class="info">
<text v-if="item.card_type == 'timecard'">不限次数</text>
<text v-if="item.card_type == 'oncecard'">剩余{{ item.num - item.use_num }}次</text>
<text v-if="item.card_type == 'commoncard'">剩余{{ item.total_num - item.total_use_num }}次</text>
<text>|</text>
<text>{{ item.end_time ? $util.timeStampTurnTime(item.end_time) : '长期有效' }}</text>
</view>
</view>
<view class="iconfont" :class="selectGoodsCard.itemId == item.item_id ? 'icon-yuan_checked color-base-text' : 'icon-yuan_checkbox'"></view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveMemberGoodsCard" :class="{ 'bottom-safe-area': isIphoneX }">
<view class="confirm-btn color-base-bg">确定</view>
</view>
</view>
</uni-popup>
</template>
</scroll-view>
<!-- 门店自提时间 -->
<ns-select-time @selectTime="selectPickupTime" ref="timePopup"></ns-select-time>
<ns-login ref="login"></ns-login>
<loading-cover ref="loadingCover"></loading-cover>
</view>
</template>
<script>
import payment from './payment.js';
export default {
name: 'common-payment',
data() {
return {};
},
props: {
api: Object,
createDataKey: String
},
mixins: [payment]
};
</script>
<style lang="scss">
@import '@/common/css/order_parment.scss';
.order-cell .promotion-content {
flex: 1;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
<template>
<x-skeleton type="list" :loading="loading" :configs="skeletonConfig">
<view class="article-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" @click="toDetail(item)" :style="itemCss">
<view class="article-img">
<image class="cover-img" :src="$util.img(item.cover_img)" mode="widthFix" @error="imgError(index)" />
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</text>
<text class="desc" style="color:#888;font-size: 24rpx; display: -webkit-box;-webkit-box-orient: vertical;-webkit-line-clamp: 2;overflow: hidden;text-overflow: ellipsis;">{{ item.article_abstract }}</text>
<view class="read-wrap">
<block v-if="item.category_name">
<text class="category-icon"></text>
<text>{{ item.category_name }}</text>
</block>
<text class="date">{{ $util.timeStampTurnTime(item.create_time, 'date') }}</text>
</view>
</view>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.article_id_arr = this.value.articleIds.toString();
}
this.$api.sendRequest({
url: '/api/article/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_tool/article/detail', {
article_id: item.article_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].cover_img = this.$util.getDefaultImage().article;
}
}
};
</script>
<style lang="scss">
.article-wrap {
.list-wrap {
&.style-1 {
.item {
display: flex;
padding: 20rpx;
margin-top: 24rpx;
&:first-of-type {
margin-top: 0;
}
.article-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
font-weight: bold;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,444 @@
<template>
<view>
<view class="fui-audio style1" :style="{background:value.background}" v-if="value.type == 'style-2'">
<view class="content" style="padding-top: 20rpx;">
<view class="name" :style="{color:value.textcolor}">{{value.text}}</view>
<view class="author" :style="{color:value.subtitlecolor}">{{value.desc}}----{{value.id}}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{width:audiowidth}"></view>
</view>
<view class="time" :style="{color:value.timecolor}">
{{audiotime}}
</view>
<view @click="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>
</view>
<view class="content">
<view class="name" :style="{color:value.textcolor}">{{value.text}}</view>
<view class="author" :style="{color:value.subtitlecolor}">{{value.desc}}</view>
</view>
<view class="progress">
<view class="progressBar" :style="{width:audiowidth}"></view>
</view>
<view class="time" :style="{color:value.timecolor}">
<!-- {{audios[value.id].audiotime}} -->
{{audiotime}}
</view>
<view @click="play()" class="start" :class="status?'iconj icon-07zanting':'iconj icon-bofang'"></view>
</view>
</view>
</template>
<script>
// 视频
export default {
name: 'diy-audio',
props: {
value: {
type: Object
}
},
data() {
return {
audiosObj:[],
audios: {},
audioContext:null,
audiotime:'00:01',
audiowidth:0,
status:0,//1播放0停止
};
},
created() {
// console.log(this.value)
this.audios[this.value.id] = {
audiotime:'00:01',
audiowidth:0
}
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
videoWarpCss: 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: {
play(){
var t = this.value.id, a = this.audiosObj[t] || !1;
var e = {
audio:this.$util.img(this.value.audioUrl),
}
if (!a) {
a = uni.createInnerAudioContext("audio_" + t);
var i = this.audiosObj;
i[t] = a, this.audiosObj = i
// uni.setStorageSync('audio_list',audio_list)
var audio_list = uni.getStorageSync('audio_list')?uni.getStorageSync('audio_list'):[]
if(audio_list.includes(t) == false){
audio_list.push(t)
uni.setStorageSync('audio_list',audio_list)
}
}
console.log(uni.getStorageSync('audio_list'))
var n = this;
// console.log(a)
a.onPlay(function() {
var e = setInterval(function() {
var i = a.currentTime / a.duration * 100 + "%", s = Math.floor(Math.ceil(a.currentTime) / 60), o = (Math.ceil(a.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(a.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = n.audios;
// console.log(i)
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r, n.audios = c;
}, 1e3);
});
var s = n.$util.img(n.value.audioUrl), o = n.audios[n.value.id].seconds || 0, r = 0, u = 1;
0 == u && a.onEnded(function(e) {
c[t].status = !1,n.status=!1,c[t].seconds = 0,console.log(c),n.audios = c;
});
var c = n.audios;
c[t] || (c[t] = {}), a.paused && 0 == o ? (a.src = s, a.play(), 1 == u && (a.loop = !0),
c[t].status = !0,n.status=!0, n.pauseOther(t)) : a.paused && o > 0 ? (a.play(), 0 == r ? a.seek(o) : a.seek(0),
c[t].status = !0,n.status=!0, n.pauseOther(t)) : (a.pause(), c[t].status = !1,n.status=!1),n.audios = c;
console.log(n.audios)
},
pauseOther: function(e) {
var t = this;
// console.log(this.audiosObj[this.value.id]);
var i = this.audiosObj[this.value.id],a = this.value.id
// console.log(i)
// console.log(a)
// if (a != e) {
// i.pause();
// var n = t.audios;
// n[a] && (n[a].status = !1, this.audios=n);
// }
var audios = document.getElementsByTagName("audio");
// 暂停函数
function pauseAll() {
var self = this;
[].forEach.call(audios, function (i) {
// 将audios中其他的audio全部暂停
i !== self && i.pause();
})
}
// 给play事件绑定暂停函数
[].forEach.call(audios, function (i) {
i.addEventListener("play", pauseAll.bind(i));
})
// var audio_list = uni.getStorageSync('audio_list')
// audio_list.forEach(function(value, index) {
// if (value != e) {
// console.log(e)
// uni.createInnerAudioContext("audio_" + value).pause();
// }
// });
// this.audiosObj.forEach(function(value, index) {
// console.log(value);
// });
// this.each(this.audiosObj, function(a, i) {
// if (a != e) {
// i.pause();
// var n = t.data.audios;
// n[a] && (n[a].status = !1, t.setData({
// audios: n
// }));
// }
// });
},
play_bak(){
var t = this.value.id
this.audioContext = uni.createInnerAudioContext("audio_" + this.value.id);
this.audioContext.src = this.$util.img(this.value.audioUrl);
var that = this
if(this.status == 1){
this.audioContext.pause();
this.status = 0
return false
}
this.audioContext.play();
this.status = 1
this.audioContext.onCanplay(function(s){
var e = setInterval(function() {
var i = parseFloat(that.audioContext.currentTime) / parseFloat(that.audioContext.duration) * 100 + "%", s = Math.floor(Math.ceil(that.audioContext.currentTime) / 60), o = (Math.ceil(that.audioContext.currentTime) % 60 / 100).toFixed(2).slice(-2), r = Math.ceil(that.audioContext.currentTime);
s < 10 && (s = "0" + s);
var u = s + ":" + o, c = that.audios;
c[t].audiowidth = i, c[t].Time = e, c[t].audiotime = u, c[t].seconds = r
that.audios = c
// console.log(c)
console.log(that.audios[that.value.id].audiotime)
that.audiotime = that.audios[that.value.id].audiotime
that.audiowidth = that.audios[that.value.id].audiowidth
console.log(i)
that.lyg = i
}, 1e3);
});
this.audioContext.onEnded(() => {
console.log('播放结束');
this.status = 0
});
}
}
};
</script>
<style scoped>
.fui-audio {
width: 100%;
border: 1rpx solid #eeeeee;
padding: 0 30rpx 0 20rpx;
box-sizing: border-box;
position: relative;
overflow: hidden;
background: #fff;
}
.fui-audio .img {
width: 100rpx;
height: 100rpx;
background: #000;
}
.fui-audio .img image {
width: 100%;
height: 100%;
}
.fui-audio .name {
font-size: 26rpx;
color: #333;
}
.fui-audio .author {
font-size: 26rpx;
color: #666;
}
.fui-audio .time {
font-size: 24rpx;
color: #999;
}
.fui-audio .start {
border: 0;
padding: 0;
margin: 0;
font-size: 28rpx;
}
.progressBar {
height: 2rpx;
width: 0;
background: #333;
}
.fui-audio.style1 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style1 .img,.fui-audio.style2 .img {
display: none;
}
.fui-audio.style1 .name,.fui-audio.style2 .name {
float: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300rpx;
}
.fui-audio.style1 .author,.fui-audio.style2 .author {
float: left;
margin-left: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.fui-audio.style1 .time,.fui-audio.style4 .time {
display: none;
}
.fui-audio.style1 .start {
position: absolute;
top: 0rpx;
right: 40rpx;
width: 40rpx;
height: 40rpx;
color: #000;
}
.fui-audio.style1 .progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.fui-audio.style2 {
height: 86rpx;
line-height: 82rpx;
}
.fui-audio.style2 .img {
display: none;
}
.fui-audio.style2 .time {
position: absolute;
top: 0;
right: 30rpx;
}
.fui-audio.style2 .name {
margin-left: 70rpx;
}
.fui-audio.style2 .start {
position: absolute;
top: 0rpx;
left: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style2 .progress,.fui-audio.style3 .progress {
display: none;
}
.fui-audio.style3 {
padding: 8rpx;
}
.fui-audio.style3 .start {
position: absolute;
top: 30rpx;
left: 28rpx;
width: 56rpx;
height: 56rpx;
color: #fff;
/* border: 2rpx solid #fff; */
border-radius: 50%;
text-indent: 18rpx;
line-height: 56rpx;
}
.fui-audio.style3 .img,.fui-audio.style4 .img {
float: left;
margin-right: 20rpx;
}
.fui-audio.style3 .content {
width: 468rpx;
}
.fui-audio.style3 .content,.fui-audio.style4 .content {
float: left;
height: 100rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.fui-audio.style3 .content .name {
height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .content .author {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style3 .time {
position: absolute;
top: 40rpx;
right: 30rpx;
}
.fui-audio.style4 {
padding: 10rpx;
}
.fui-audio.style4 .content {
padding-bottom: 18rpx;
height: 82rpx;
width: 500rpx;
}
.fui-audio.style4 .start {
position: absolute;
top: 32rpx;
right: 30rpx;
width: 30rpx;
height: 30rpx;
color: #000;
}
.fui-audio.style4 .name {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .author {
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fui-audio.style4 .progress {
background: #f5f5f5;
height: 4rpx;
position: absolute;
bottom: 24rpx;
left: 130rpx;
right: 30rpx;
border-radius: 2rpx;
overflow: hidden;
}
.fui-audio.style4 .progressBar {
height: 4rpx;
}
.diy-audio>>>.uni-video-container {
background-color: transparent;
}
</style>

View File

@@ -0,0 +1,723 @@
<template>
<x-skeleton :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-bargain" :class="[value.template, value.style]" :style="warpCss">
<!-- 商品头部 -->
<view v-if="value.titleStyle.isShow && list && list.length" :class="[value.titleStyle.style, 'bargain-head']" :style="{ backgroundImage: 'url(' + $util.img(value.titleStyle.backgroundImage) + '), linear-gradient(to right,' + value.titleStyle.bgColorStart + ',' + value.titleStyle.bgColorEnd + ')' }">
<view v-if="value.titleStyle.leftStyle == 'text'" class="left-text" :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>
<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')">
<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)" :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)"/>
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="progress" v-if="value.style == 'style-2'">
<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')"/>
</view>
</view>
<view class="num" v-if="item.is_bargaining">
已砍
<text>{{ (item.price - item.curr_price).toFixed(2) }}</text>
仅差
<text>{{ item.curr_price }}</text>
</view>
<view class="num" v-else>
最低可砍至
<text>{{ item.floor_price }}</text>
</view>
</view>
<view class="progress" v-if="value.style == 'style-3'">
最低可砍至
<text class="num">{{ item.floor_price }}</text>
</view>
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">.{{ item.price.split('.')[1] }}</text>
</view>
<button v-if="value.btnStyle.control" :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'
}">
{{ item.is_bargaining ? '继续砍价' : value.btnStyle.text }}
</button>
</view>
</view>
</view>
</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)" :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"/>
<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"/>
<view class="num" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price"
v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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"/>
<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"/>
<view class="num" v-if="value.saleStyle.control && value.template == 'horizontal-slide' && value.style != 'style-2'" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已砍{{ item.sale_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name" :style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }" :class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl && value.template == 'horizontal-slide' && value.style != 'style-2'">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.floor_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.floor_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-bargain',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1,
loading: true,
skeletonType: '',
skeletonConfig: {}
};
},
components: {},
async created() {
this.initSkeleton();
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: async function(nval) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') await this.getDataing();
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
if (this.value.componentBgColor) obj += 'background:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '420rpx';
else return '400rpx';
}
if (this.value.ornament.type == 'shadow') return '386rpx';
else return '378rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
// 查找自己参与的砍价
async getDataing() {
var res = await this.$api.sendRequest({
url: '/bargain/api/goods/bargainingList',
data: {},
async: false
});
res.data &&
res.data.forEach((item, index) => {
item.is_bargaining = 1;
});
this.list = res.data || [];
this.loading = false;
},
// 查找可砍价的商品
getData() {
var data = {
num: this.value.count,
is_exclude_bargaining: 1
};
if (this.value.sources == 'diy') {
data.num = 0;
data.id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/bargain/api/goods/lists',
data: data,
success: res => {
if (res.code == 0) {
if (this.value.template == 'row1-of1' && this.value.style == 'style-2') this.list =
this.list.concat(res.data).splice(0, this.value.count);
else this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
progress(data) {
// 214 表示当前进度条的宽度
let progress = (((parseFloat(data.price) - parseFloat(data.curr_price)) / parseFloat(data.price)) * 214)
.toFixed();
if (progress == 'NaN') {
progress = 0;
}
return progress;
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/bargain/detail', {
b_id: e.bargain_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-bargain {
overflow: hidden;
// 头部
.bargain-head {
&.style-1 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-content {
position: relative;
color: #fff;
font-size: $font-size-tag;
margin-right: auto;
margin-left: 20rpx;
line-height: 1;
&::after {
content: '';
position: absolute;
width: 2rpx;
height: 24rpx;
background-color: #fff;
top: 50%;
transform: translateY(-50%);
left: -12rpx;
}
}
.head-right {
display: flex;
align-items: center;
font-size: $font-size-sub;
color: #fff;
}
}
&.style-2 {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
padding: 0 20rpx;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
height: 40rpx;
width: 156rpx;
}
.head-right {
display: flex;
align-items: center;
justify-content: center;
height: 36rpx;
background: linear-gradient(270deg, #ffbd5b 0%, #fd882e 100%);
border-radius: 24rpx;
padding: 2rpx;
text:nth-child(1) {
position: relative;
left: 6rpx;
transform: scale(0.9);
}
text:nth-child(2) {
padding: 6rpx 4rpx 4rpx;
background-color: #fff;
color: #ffbd5b;
border-radius: 50%;
transform: scale(0.6);
font-weight: bold;
line-height: 1;
}
}
}
}
// 商品列表
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
padding-bottom: 20rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 6rpx 0 6rpx 20rpx;
.goods-name {
&.multi-hidden {
line-height: 1.3;
}
}
.price-wrap {
display: flex;
justify-content: space-between;
align-items: center;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
button {
margin: 0;
padding: 0 20rpx;
color: var(--btn-text-color);
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
font-weight: bold;
}
}
}
&.style-2 {
.discount-price {
position: relative;
}
.progress {
display: flex;
flex-direction: column;
margin-top: 10rpx;
margin-right: 16rpx;
.bg {
margin-left: 6rpx;
width: auto;
height: 20rpx;
border-radius: 20rpx;
background-color: #ffeadb;
position: relative;
&::after {
content: '';
width: 26rpx;
height: 26rpx;
border-radius: 50%;
background-color: #fa1a1a;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: -18rpx;
}
.curr {
width: 0;
height: 20rpx;
border-radius: 20rpx;
background-color: #fa1a1a;
position: relative;
.progress-bar {
position: absolute;
right: -20rpx;
width: 30rpx;
height: 30rpx;
max-width: inherit !important;
max-height: inherit !important;
top: 50%;
transform: translateY(-50%);
z-index: 1;
}
}
}
.num {
font-size: $font-size-tag;
margin-top: 12rpx;
line-height: 1;
text {
color: #fa1a1a;
}
}
}
}
&.style-3 {
.progress {
display: flex;
color: $color-tip;
font-size: $font-size-sub;
.num {
color: var(--price-color);
}
}
.item {
.content {
justify-content: space-around;
}
.img-wrap {
overflow: hidden;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 100%;
}
.bg {
position: absolute;
width: 100%;
height: 60rpx;
bottom: 0;
left: 0;
z-index: 2;
}
.num {
width: 180rpx;
position: absolute;
bottom: 10rpx;
padding-left: 20rpx;
font-size: 20rpx;
line-height: 1;
color: #ffffff;
z-index: 3;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 160rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
.original-price {
margin-top: 4rpx;
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
box-sizing: border-box;
}
}
}
}
</style>

View File

@@ -0,0 +1,293 @@
<template>
<view 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="bd">
<block v-if="item.link.wap_url == '/pages/goods/cart'">
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2" :animation="cartAnimation" id="tabbarCart">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'" :src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath" :value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath" :value="item.style ? item.style : null"></diy-icon>
</block>
<view class="cart-count-mark font-size-activity-tag"
:class="{ max: item.link.wap_url == '/pages/goods/cart' && cartNumber > 99 }"
:style="{ background: 'var(--price-color)' }" v-if="cartNumber > 0">
{{ cartNumber > 99 ? '99+' : cartNumber }}
</view>
</view>
</block>
<block v-else>
<view class="icon" v-if="tabBarList.type == 1 || tabBarList.type == 2">
<block v-if="verify(item.link)">
<image v-if="item.selected_icon_type == 'img'" :src="$util.img(item.selectedIconPath)" />
<diy-icon v-if="item.selected_icon_type == 'icon'" :icon="item.selectedIconPath"
:value="item.selected_style ? item.selected_style : null"></diy-icon>
</block>
<block v-else>
<image v-if="item.icon_type == 'img'" :src="$util.img(item.iconPath)" />
<diy-icon v-if="item.icon_type == 'icon'" :icon="item.iconPath" :value="item.style ? item.style : null"></diy-icon>
</block>
</view>
</block>
<view class="label" v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'diy'" :style="{ color: verify(item.link) ? tabBarList.textHoverColor : tabBarList.textColor }">
{{ lang =='en-us'?item.en_text:item.text }}
</view>
<view class="label" v-if="(tabBarList.type == 1 || tabBarList.type == 3) && tabBarList.theme == 'default'" :style="{ color: verify(item.link) ? 'var(--base-color)' : '#333333' }">
{{ lang =='en-us'?item.en_text:item.text }}
</view>
</view>
</view>
</view>
<!-- 解决fixed定位后底部导航栏塌陷问题 -->
<view class="tab-bar-placeholder"></view>
</view>
</template>
<script>
export default {
name: 'diy-bottom-nav',
props: {
value: {
type: Object
},
name: {
type: String,
default: ''
}
},
data() {
return {
lang:uni.getStorageSync("lang"),
currentRoute: '', //当前页面路径
jumpFlag: true, //是否可以跳转,防止重复点击
cartAnimation: {}
};
},
mounted() {
let currentPage = getCurrentPages()[getCurrentPages().length - 1];
if (currentPage && currentPage.route) {
this.currentRoute = currentPage.route;
}
this.$nextTick(() => {
if (!this.$store.state.cartPosition) {
let query = uni.createSelectorQuery().in(this);
query.select('#tabbarCart')
.boundingClientRect(data => {
if (data) this.$store.commit('setCartPosition', data);
}).exec();
query.select('.tab-bar')
.boundingClientRect(data => {
if (data) this.$store.commit('setTabBarHeight', data.height +
'px');
}).exec();
}
});
},
computed: {
cartChange() {
return this.$store.state.cartChange;
}
},
watch: {
cartChange: function (nval, oval) {
if (nval > oval) {
let animation = uni.createAnimation({
duration: 200,
timingFunction: 'ease'
});
animation.scale(1.2).step();
this.cartAnimation = animation.export();
setTimeout(() => {
animation.scale(1).step();
this.cartAnimation = animation.export();
}, 300);
}
}
},
methods: {
redirectTo(link) {
this.$emit('callback');
this.$util.diyRedirectTo(link);
},
verify(link) {
if (link == null || link == '' || !link.wap_url) return false;
if (this.name) {
var url = this.currentRoute + '?name=' + this.name;
} else {
var url = this.currentRoute;
}
// 首页特殊处理
if (link.wap_url == '/pages/index/index' && this.name == 'DIY_VIEW_INDEX') {
return true;
} else if (url && link.wap_url.indexOf(url) != -1) {
return true;
}
return false;
}
}
};
</script>
<style lang="scss">
.placeholder {
height: 112rpx;
&.bluge {
height: 180rpx;
}
}
.safe-area {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.tab-bar {
background-color: #fff;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 998;
display: flex;
border-top: 2rpx solid #f5f5f5;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.tabbar-border {
background-color: rgba(255, 255, 255, 0.329412);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 2rpx;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
.item {
display: flex;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex: 1;
flex-direction: column;
padding-bottom: 10rpx;
box-sizing: border-box;
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
.icon {
position: relative;
display: inline-block;
margin-top: 10rpx;
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
image {
width: 100%;
height: 100%;
}
>view {
height: inherit;
display: flex;
align-items: center;
}
.bar-icon {
font-size: 42rpx;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
line-height: 1;
margin-top: 12rpx;
}
}
&.bulge {
.bd {
position: relative;
height: 100rpx;
flex-direction: column;
text-align: center;
.icon {
margin-top: -60rpx;
margin-bottom: 4rpx;
border-radius: 50%;
width: 100rpx;
height: 102rpx;
padding: 10rpx;
border-top: 2rpx solid #f5f5f5;
background-color: #fff;
box-sizing: border-box;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.label {
position: relative;
text-align: center;
font-size: 24rpx;
height: 40rpx;
line-height: 40rpx;
}
}
}
.cart-count-mark {
position: absolute;
top: -8rpx;
right: -18rpx;
width: 24rpx;
height: 24rpx !important;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
padding: 6rpx;
border-radius: 50%;
z-index: 99;
&.max {
width: 40rpx;
border-radius: 24rpx;
right: -28rpx;
}
}
}
}
.tab-bar-placeholder {
padding-bottom: calc(constant(safe-area-inset-bottom) + 112rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 112rpx);
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
<template>
<view></view>
</template>
<script>
// 自定义扩展组件
export default {
name: 'diy-comp-extend',
props: {
value: {
type: Object
}
},
data() {
return {};
},
computed: {},
created() {},
methods: {}
};
</script>
<style></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,419 @@
<template>
<view 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)" :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)"/>
</view>
<view class="info-wrap" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view class="name-wrap">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]"
>
{{ item.goods_name }}
</view>
</view>
<view class="pro-info">
<view class="discount-price">
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }"> </text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{ item.commission_money.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor : '' }">{{ '.' + item.commission_money.split('.')[1] }}</text>
</view>
<view class="sale-btn" v-if="value.btnStyle.control && item.is_collect == 0"
: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="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)"
>
取消关注
</view>
</view>
<view class="delete-price" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
{{ item.discount_price }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'diy-fenxiao-goods-list',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
currentRoute: ''
};
},
created() {
let currentPage = getCurrentPages()[getCurrentPages().length - 1];
this.currentRoute = '/' + currentPage.route;
if (!this.storeToken) {
this.$util.redirectTo(
'/pages_tool/login/login',
{
back: this.currentRoute
},
'redirectTo'
);
}
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
}
},
computed: {
goodsListWarpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
//页面跳转
toDetail(e) {
this.$util.redirectTo('/pages/goods/detail', { goods_id: e.goods_id });
},
//关注
followGoods(e, index) {
let goods_id = e.goods_id;
let sku_id = e.sku_id;
this.$api.sendRequest({
url: '/fenxiao/api/goodscollect/add',
data: {
goods_id: goods_id,
sku_id: sku_id
},
success: res => {
if (res.code >= 0) {
this.$util.showToast({ title: '关注成功' });
this.list[index].is_collect = 1;
this.list[index].collect_id = res.data;
}
this.$forceUpdate();
}
});
},
//取消关注
delFollowTip(e, index) {
uni.showModal({
title: '提示',
content: '确认取消关注该商品吗',
success: res => {
if (res.confirm) {
this.delFollow(e.collect_id, index);
}
}
});
},
delFollow(e, f) {
this.$api.sendRequest({
url: '/fenxiao/api/goodscollect/delete',
data: {
collect_id: e
},
success: res => {
let msg = '';
if (res.code == 0) {
msg = '取消成功';
} else {
msg = res.message;
}
this.$util.showToast({
title: msg
});
let arr = this.list;
arr[f].is_collect = 0;
this.list = arr;
this.$forceUpdate();
}
});
},
toMore() {
this.$util.redirectTo('/pages_promotion/fenxiao/goods_list');
},
getData() {
var data = {
page: 1,
page_size: this.value.count
};
if (this.value.sources == 'category') {
data.category_id = this.value.categoryId;
data.category_level = 1;
} else if (this.value.sources == 'diy') {
data.page_size = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/fenxiao/api/goods/page',
data: data,
success: res => {
if (res.code == 0) {
this.list = res.data.list;
}
}
});
},
imgError(index) {
if (this.list[index]) this.list[index].goods_image = this.$util.getDefaultImage().goods;
}
}
};
</script>
<style lang="scss">
.diy-fenxiao {
}
// 商品列表单列样式
.goods-list.row1-of1 {
.goods-item {
background-color: #fff;
display: flex;
margin-bottom: 20rpx;
&:last-of-type {
margin-bottom: 0;
}
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
.goods-img {
width: 180rpx;
overflow: hidden;
margin-right: 20rpx;
image {
width: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
.name-wrap {
flex: 1;
margin-bottom: 10rpx;
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
&.multi-hidden {
height: 72rpx;
}
}
}
.pro-info {
display: flex;
flex-direction: column;
justify-content: space-between;
.sale {
font-size: 20rpx;
line-height: 1;
color: #999;
}
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
.price-wrap {
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
.delete-price {
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
.sale-btn {
position: absolute;
right: 20rpx;
bottom: 26rpx;
height: 50rpx;
line-height: 52rpx;
color: #fff;
width: 120rpx;
text-align: center;
background-color: $base-color;
}
}
}
}
// 商品列表两列样式
.goods-list.row1-of2 {
display: flex;
flex-wrap: wrap;
.goods-item {
position: relative;
background: #fff;
overflow: hidden;
margin-right: 20rpx;
margin-top: 20rpx;
width: calc(50% - 10rpx);
&:nth-child(2n + 2) {
width: calc(50% - 11rpx);
margin-right: 0;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 0;
}
&.shadow {
width: calc(50% - 18rpx);
&:nth-child(2n-1) {
margin-left: 8rpx;
}
&:nth-of-type(1),
&:nth-of-type(2) {
margin-top: 8rpx;
}
}
.goods-img {
position: relative;
overflow: hidden;
height: 350rpx;
image {
width: 100%;
height: 100%;
position: absolute;
top: 49%;
left: 0;
transform: translateY(-50%);
}
}
.info-wrap {
padding: 10rpx 20rpx;
.name-wrap {
margin-bottom: 10rpx;
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
&.multi-hidden {
height: 72rpx;
}
}
}
.pro-info {
margin-top: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.sale {
font-size: 20rpx;
line-height: 1;
color: #999;
}
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
.delete-price {
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
.sale-btn {
position: absolute;
right: 20rpx;
bottom: 26rpx;
height: 50rpx;
line-height: 52rpx;
color: #fff;
width: 120rpx;
text-align: center;
background-color: $base-color;
}
}
}
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<view class="float-btn" :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)" :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"/>
<diy-icon v-else-if="item.iconType && item.iconType == 'icon'" :icon="item.icon"
:value="item.style ? item.style : null"></diy-icon>
</view>
</block>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
export default {
name: 'diy-float-btn',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
navHeight: 0,
statusBarHeight: systemInfo.statusBarHeight
};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
components: {},
methods: {},
computed: {
style() {
let style = {},
height = 54;
// #ifdef MP
height = systemInfo.platform == 'ios' ? 54 : 58;
// #endif
switch (parseInt(this.value.bottomPosition)) {
case 1:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 2:
style.top = (this.navHeight + this.statusBarHeight + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 3:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
case 4:
style.bottom = (100 + parseInt(this.value.btnBottom)) * 2 + 'rpx';
break;
}
return this.$util.objToStyle(style);
}
}
};
</script>
<style lang="scss">
.float-btn {
position: fixed;
bottom: 20%;
right: 40rpx;
z-index: 990;
&.left_top {
top: 100rpx;
left: 30rpx;
}
&.right_top {
top: 100rpx;
right: 30rpx;
}
&.left_bottom {
bottom: 160rpx;
left: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
&.right_bottom {
bottom: 160rpx;
right: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
/*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom);
/*兼容 IOS>11.2*/
}
.button-box {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
image {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<!-- #ifdef MP -->
<view v-if="value.isShow">
<official-account></official-account>
</view>
<!--#endif -->
</template>
<script>
// 关注公众号
export default {
name: 'diy-follow-official-account',
props: {
value: {
type: Object
}
},
data() {
return {};
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
methods: {}
};
</script>
<style></style>

View File

@@ -0,0 +1,110 @@
<template>
<view class="diy-from" :style="style">
<view class="fui-cell-group">
<view class="fui-cell ">
<view class="fui-cell-label ">您的姓名</view>
<view class="fui-cell-info">
<input v-model="Form.realname" class="fui-input" placeholder="请输入您的姓名" value=""></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">手机号码</view>
<view class="fui-cell-info">
<input v-model="Form.mobile" class="fui-input" maxlength="11" placeholder="请输入您的手机号" type="number" ></input>
</view>
</view>
<view class="fui-cell ">
<view class="fui-cell-label">您的邮箱</view>
<view class="fui-cell-info">
<input v-model="Form.mailbox" class="fui-input" placeholder="请输入您的邮箱" type="text" ></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">所在城市</view>
<view class="fui-cell-info">
<input v-model="Form.citys" class="fui-input" placeholder="请输入您的所在地" value=""></input>
</view>
</view>
<view class="fui-cell">
<view class="fui-cell-label">备注</view>
<view class="fui-cell-info">
<input v-model="Form.remark" class="fui-input" placeholder="请输入备注" value=""></input>
</view>
</view>
</view>
<view @click="submitform" class="fui-btn btn-danger block mtop">提交信息</view>
</view>
</template>
<script>
export default {
name: 'diy-from',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
markers:[],
Form:{
realname:'',
mobile:'',
mailbox:'',
citys:'',
remark:'',
}
};
},
created() {
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
markerst(){
return [{
id:1,
latitude:this.value.list[0].lat,
longitude:this.value.list[0].lng
}]
},
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
submitform(){
this.$api.sendRequest({
url: '/api/member/information',
data: this.Form,
success: res => {
this.$util.showToast({
title: res.message
});
}
});
}
}
};
</script>
<style lang="scss">
.diy-from{
background: #fff;
padding-bottom: 20rpx;
border-radius: 10rpx;
}
</style>

View File

@@ -0,0 +1,158 @@
<template>
<x-skeleton type="waterfall" :loading="loading" :configs="skeletonConfig">
<view :class="['brand-wrap', value.ornament.type]" :style="warpCss">
<view :class="[value.style]">
<view class="title-wrap" v-show="value.title" :style="{ color: value.textColor, fontWeight: value.fontWeight ? 'bold' : '' }">{{ value.title }}
</view>
<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="toDetail(item)" @error="imgError(index)" :style="itemCss"/>
</view>
</view>
</view>
</view>
</x-skeleton>
</template>
<script>
// 商品品牌
import uniGrid from '@/components/uni-grid/uni-grid.vue';
import uniGridItem from '@/components/uni-grid-item/uni-grid-item.vue';
export default {
name: 'diy-goods-brand',
props: {
value: {
type: Object
}
},
components: {
uniGrid,
uniGridItem
},
data() {
return {
list: [],
loading: true,
skeletonConfig: {
gridRows: 2,
gridColumns: 4,
gridRowsGap: '20rpx',
headWidth: '120rpx',
headHeight: '120rpx',
textShow: false
}
};
},
created() {
this.getBrandList();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getBrandList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
},
// 子项样式
itemCss() {
var obj = '';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return obj;
}
},
methods: {
getBrandList() {
var data = {
page: 1,
page_size: this.value.count
};
if (this.value.sources == 'diy') {
data.page_size = 0;
data.brand_id_arr = this.value.brandIds.toString();
}
this.$api.sendRequest({
url: '/api/goodsbrand/page',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data.list;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/list', {
brand_id: item.brand_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].image_url = this.$util.getDefaultImage().goods;
}
}
};
</script>
<style lang="scss">
.brand-wrap {
&.shadow {
margin-left: 8rpx;
margin-right: 8rpx;
margin-top: 8rpx;
margin-bottom: 8rpx;
}
.style-1 {
.title-wrap {
text-align: center;
padding: 20rpx 0 10rpx;
}
.ul-wrap {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.li-item {
display: flex;
align-items: center;
justify-content: center;
width: calc(100% / 4 - 20rpx) !important;
height: 124rpx;
margin: 10rpx;
background-color: #fff;
.brand-pic {
width: 100%;
height: 100%;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,599 @@
<template>
<x-skeleton type="waterfall" :loading="loading" :configs="skeletonConfig">
<view v-if="list.length" :class="['goods-list', goodsValue.style]" :style="goodsListWarpCss">
<view class="top-wrap" v-if="goodsValue.topStyle.support">
<text :class="['js-icon', goodsValue.topStyle.icon.value]" :style="{ backgroundColor: goodsValue.topStyle.icon.bgColor, color: goodsValue.topStyle.icon.color }"></text>
<text class="title" :style="{ color: goodsValue.topStyle.color }">{{ goodsValue.topStyle.title }}</text>
<text class="line" :style="{ color: goodsValue.topStyle.subColor }"></text>
<text class="sub" :style="{ color: goodsValue.topStyle.subColor }">{{ goodsValue.topStyle.subTitle }}</text>
</view>
<swiper :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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"/>
<view class="sell-out" v-if="dataItem.stock <= 0">
<text class="iconfont icon-shuqing"></text>
</view>
</div>
<view :class="['info-wrap', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="goodsValue.goodsNameStyle.control || goodsValue.priceStyle.mainControl || goodsValue.priceStyle.lineControl || goodsValue.labelStyle.support">
<view v-if="goodsValue.goodsNameStyle.control" class="goods-name"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.goodsNameStyle.color : '', fontWeight: goodsValue.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': goodsValue.nameLineMode == 'single' }, { 'multi-hidden': goodsValue.nameLineMode == 'multiple' }]">
{{ dataItem.goods_name }}
</view>
<view class="pro-info">
<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"/>
<text>{{ goodsValue.labelStyle.title }}</text>
</view>
<view class="discount-price">
<view class="price-wrap" v-if="goodsValue.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }"></text>
<text class="price price-style large" :style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }">{{ showPrice(dataItem).split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.mainColor + '!important' : '' }">{{ '.' + showPrice(dataItem).split('.')[1] }}</text>
</view>
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(dataItem)" class="delete-price price-font" :style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.lineColor : '' }">{{ showMarketPrice(dataItem) }}</view>
<view class="sale" v-if="goodsValue.saleStyle.control" :style="{ color: goodsValue.theme == 'diy' ? goodsValue.saleStyle.color : '' }">
{{ dataItem.sale_num }}{{ dataItem.unit ? dataItem.unit : '' }}
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-goods-recommend',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
loading: true,
skeletonConfig: {
gridRows: 1,
gridColumns: 3,
headWidth: '200rpx',
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '60%'],
},
list: [],
goodsValue: {},
page: 1
};
},
created() {
this.goodsValue = this.value;
this.getGoodsList();
},
watch: {
'globalStoreInfo.store_id': {
handler(nval, oval) {
if (nval != oval) {
this.getGoodsList();
}
},
deep: true
},
// 组件刷新监听
componentRefresh: function(nval) {
this.getGoodsList();
}
},
computed: {
goodsListWarpCss() {
var obj = '';
obj += 'background-color:' + this.goodsValue.componentBgColor + ';';
if (this.goodsValue.componentAngle == 'round') {
obj += 'border-top-left-radius:' + this.goodsValue.topAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.goodsValue.topAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.goodsValue.bottomAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.goodsValue.bottomAroundRadius * 2 + 'rpx;';
}
if (this.goodsValue.bgUrl) {
obj += `background-image: url('${this.$util.img(this.goodsValue.bgUrl)}');`;
}
return obj;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.goodsValue.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.goodsValue.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.goodsValue.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.goodsValue.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.goodsValue.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.goodsValue.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.goodsValue.ornament.color + ';';
}
if (this.goodsValue.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.goodsValue.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
var width = '';
if (this.goodsValue.style != 'style-2') {
width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value.margin
.both * 2) * 2] / 6;
} else {
width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this
.rpxUpPx(this.value.margin.both * 2) * 2
] / 6;
}
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
return obj;
},
swiperHeight() {
if (this.goodsValue.style == 'style-3') {
return '330rpx';
} else if (this.goodsValue.style != 'style-2') {
if (this.value.nameLineMode == 'multiple') {
return '348rpx';
}
return '312rpx';
} else {
if (this.value.nameLineMode == 'multiple') {
return '360rpx';
}
return '320rpx';
}
}
},
methods: {
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getGoodsList() {
var data = {
num: this.goodsValue.count
};
if (this.goodsValue.sources == 'category') {
data.category_id = this.goodsValue.categoryId;
data.category_level = 1;
} else if (this.goodsValue.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.goodsValue.goodsId.toString();
}
data.order = this.goodsValue.sortWay;
this.$api.sendRequest({
url: '/api/goodssku/components',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
// 切屏滚动,每页显示固定数量
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/detail', {
goods_id: item.goods_id
});
},
imgError(pageIndex, index) {
this.list[pageIndex][index].goods_image = this.$util.getDefaultImage().goods;
},
showPrice(data) {
let price = data.discount_price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
showMarketPrice(item) {
let price = this.showPrice(item);
if (item.market_price > 0) {
return item.market_price;
} else if (item.price > price) {
return item.price;
}
return '';
},
}
};
</script>
<style lang="scss" scoped>
.goods-list {
.goods-item {
line-height: 1;
.sale {
line-height: 1;
color: $color-tip;
font-size: $font-size-activity-tag;
}
.info-wrap {
.goods-name {
margin-bottom: 10rpx;
line-height: 1.3;
}
}
.sell-out{
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
text{
color: #fff;
font-size: 180rpx;
}
}
}
}
// 商品列表横向滚动样式
.goods-list.style-1 {
width: 100%;
white-space: nowrap;
background-repeat: round;
.top-wrap {
display: flex;
align-items: center;
padding: 20rpx 0;
.js-icon {
border-radius: 50%;
font-size: 40rpx;
margin-right: 10rpx;
width: 70rpx;
height: 70rpx;
text-align: center;
line-height: 70rpx;
}
.line {
height: 28rpx;
margin: 0 10rpx;
border: 2rpx solid;
}
.title {
font-weight: bold;
font-size: $font-size-toolbar;
}
.sub {
font-size: $font-size-tag;
}
}
.flex-between {
justify-content: space-between;
}
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
.swiper-item {
display: flex;
align-items: center;
}
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.goods-img, .goods-img-wrap {
position: relative;
width: 100%;
height: 196rpx;
}
.info-wrap {
display: flex;
flex-direction: column;
padding: 10rpx;
&.multi-content {
height: 130rpx;
box-sizing: border-box;
}
.goods-name {
font-size: $font-size-sub;
&.multi-hidden {
white-space: break-spaces;
}
}
.pro-info {
margin-top: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
line-height: 1;
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
.delete-price {
margin-left: 10rpx;
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
}
}
}
// 商品列表横向滚动样式
.goods-list.style-2 {
width: 100%;
white-space: nowrap;
background-repeat: round;
padding-bottom: 20rpx;
.top-wrap {
display: flex;
align-items: center;
padding: 20rpx;
.js-icon {
border-radius: 50%;
font-size: 40rpx;
margin-right: 20rpx;
width: 70rpx;
height: 70rpx;
text-align: center;
line-height: 70rpx;
}
.line {
height: 28rpx;
margin: 0 10rpx;
border: 2rpx solid;
}
.title {
font-weight: bold;
font-size: $font-size-toolbar;
}
.sub {
font-size: $font-size-tag;
}
}
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
padding: 20rpx;
border-radius: 20rpx;
background-color: #fff;
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&.shadow {
margin-top: 8rpx;
width: 200rpx;
}
.goods-img, .goods-img-wrap {
position: relative;
width: 100%;
height: 200rpx;
}
.info-wrap {
padding: 10rpx;
.goods-name {
line-height: 1;
&.multi-hidden {
line-height: 1.3;
height: 68rpx;
white-space: break-spaces;
}
}
.pro-info {
display: flex;
flex-direction: column;
justify-content: space-between;
.discount-price {
display: flex;
justify-content: space-between;
align-items: center;
.price-wrap {
line-height: 1.3;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
text {
font-weight: bold;
color: $base-color;
&:last-of-type {
font-size: 32rpx;
}
}
}
}
.delete-price {
margin-left: 10rpx;
text-decoration: line-through;
flex: 1;
line-height: 28rpx;
color: $color-tip;
font-size: $font-size-activity-tag;
}
}
}
}
}
.goods-list.style-3 {
background-position: bottom;
.swiper {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
padding: 10rpx 0;
.swiper-item {
display: flex;
align-items: center;
}
}
.goods-item {
overflow: hidden;
width: 200rpx;
display: inline-block;
box-sizing: border-box;
&.shadow {
// margin-top: 20rpx;
}
.goods-img, .goods-img-wrap {
position: relative;
width: 100%;
height: 200rpx;
}
.info-wrap {
display: flex;
flex-direction: column;
padding: 10rpx;
.pro-info {
text-align: center;
.label-wrap {
border-radius: 40rpx;
display: inline-block;
margin: 10rpx 0;
position: relative;
padding-left: 52rpx;
padding-right: 16rpx;
line-height: 1.7;
image {
position: absolute;
top: -2rpx;
left: -2rpx;
width: 46rpx;
height: 46rpx;
}
text {
font-size: $font-size-tag;
}
}
.discount-price {
.price-wrap {
line-height: 1;
white-space: nowrap;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
}
text {
font-weight: bold;
color: $base-color;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,304 @@
<template>
<view :style="componentStyle">
<block v-if="value.showStyle == 'pageSlide'">
<swiper :class="['graphic-nav', 'pageSlide', value.carousel.type]" circular :indicator-dots="false" :style="swiperHeight" @change="swiperChange">
<swiper-item class="graphic-nav-wrap"
v-for="(numItem, numIndex) in Math.ceil(value.list.length / (value.pageCount * value.rowCount))">
<!-- #ifdef MP -->
<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)">
<!-- #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)">
<!-- #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' }">
<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"/>
<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>
<text class="tag" v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="value.mode != 'img'" class="graphic-text"
:style="{ fontSize: value.font.size * 2 + 'rpx', fontWeight: value.font.weight, color: value.font.color }">
{{ item.title }}
</text>
</view>
</swiper-item>
</swiper>
<view class="swiper-dot-box" v-if="isIndicatorDots" :class="value.carousel.type">
<view v-for="(numItem, numIndex) in Math.ceil(value.list.length / (value.pageCount * value.rowCount))" :key="numIndex">
<view class="swiper-dot" :class="{'active':numIndex==swiperCurrent}"></view>
</view>
</view>
</block>
<scroll-view v-else :scroll-x="value.showStyle == 'singleSlide'" :class="['graphic-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle ]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #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)">
<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"/>
<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>
<text :class="['tag', { alone: value.mode == 'text' }]" v-if="item.label.control"
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }">
{{ item.label.text }}
</text>
</view>
<text v-if="value.mode != 'img'" class="graphic-text" :style="{ fontSize: value.font.size * 2 + 'rpx', fontWeight: value.font.weight, color: value.font.color }">
{{ item.title }}
</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
export default {
name: 'diy-graphic-nav',
props: {
value: {
type: Object
}
},
data() {
return {
pageWidth: '',
indicatorDots: false,
swiperCurrent: 0
};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
componentStyle() {
var css = '';
css += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
css += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament
.color :
'') + ';';
css += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color :
'') + ';';
return css;
},
// 滑块容器的高度
swiperHeight() {
var css = '';
var height = 0;
if (this.value.mode == 'graphic') {
height = (21 + 6 + 14 + 8 + this.value.imageSize) * this.value
.pageCount; // 21 = 文字高度8 = 文字上边距14 = 上下内边距8 = 外边距
} else if (this.value.mode == 'img') {
height = (14 + 8 + this.value.imageSize) * this.value.pageCount; // 14 = 上下内边距8 = 外边距
} else if (this.value.mode == 'text') {
height = (21 + 14 + 8) * this.value.pageCount; // 21 = 文字高度14 = 上下内边距8 = 外边距
}
css += 'height:' + height * 2 + 'rpx';
return css;
},
// 是否显示轮播点
isIndicatorDots() {
var bool = true;
bool = this.value.carousel.type == 'hide' || Math.ceil(this.value.list.length / (this.value.pageCount * this.value.rowCount)) == 1 ? false : true;
return bool;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == 'pages/member/index' && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
console.log(link)
this.$util.diyRedirectTo(link);
},
swiperChange(e) {
this.swiperCurrent = e.detail.current
}
}
};
</script>
<style>
/* 固定显示 */
.graphic-nav.fixed-layout>>>.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
}
/* 单行滑动 */
.graphic-nav.singleSlide>>>.uni-scroll-view-content {
display: flex;
}
.graphic-nav.pageSlide>>>.uni-swiper-dots-horizontal {
bottom: 0rpx;
}
.graphic-nav.pageSlide.straightLine>>>.uni-swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
.graphic-nav.pageSlide.circle>>>.uni-swiper-dot {
width: 14rpx;
height: 14rpx;
}
</style>
<style lang="scss">
.graphic-nav {
padding: 16rpx;
box-sizing: border-box;
&.singleSlide {
.graphic-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.graphic-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
}
.graphic-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.graphic-text {
padding-top: 12rpx;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.graphic-text {
padding-top: 0;
}
}
.graphic-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
font-size: 90rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: $color-sub;
}
}
}
}
.swiper-dot-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: -20rpx;
padding-bottom: 8rpx;
.swiper-dot {
background-color: rgba(0, 0, 0, .3);
margin: 8rpx;
&.active {
background-color: rgba(0, 0, 0, 1);
}
}
&.straightLine {
.swiper-dot {
width: 30rpx;
border-radius: 0;
height: 8rpx;
}
}
&.circle {
.swiper-dot {
width: 15rpx;
border-radius: 50%;
height: 15rpx;
}
}
}
</style>

View File

@@ -0,0 +1,317 @@
<template>
<view class="diy-group">
<view v-for="(item, index) in diyDataArray" :key="index" :style="item.pageStyle">
<view v-if="addonIsExist.store && item.componentName == 'StoreShow'">
<!-- 门店展示 -->
<diy-store :value="item"></diy-store>
</view>
<template v-if="item.componentName == 'Kefu'">
<!-- 客服按钮 -->
<diy-kefu :value="item"></diy-kefu>
</template>
<template v-if="item.componentName == 'Form'">
<!-- 表单组件 -->
<diy-form :value="item"></diy-form>
</template>
<template v-if="addonIsExist.store && item.componentName == 'StoreLabel'">
<!-- 门店标签 -->
<diy-store-label :value="item"></diy-store-label>
</template>
<template v-if="item.componentName == 'Picture'">
<!-- 单图组组件 -->
<diy-picture :value="item"></diy-picture>
</template>
<template v-if="item.componentName == 'Listmenu'">
<!-- 列表按钮组件 -->
<diy-listmenu :value="item"></diy-listmenu>
</template>
<template v-if="item.componentName == 'Text'">
<!-- 文本 -->
<diy-text :value="item"></diy-text>
</template>
<template v-if="item.componentName == 'Notice'">
<!-- 公告 -->
<diy-notice :value="item"></diy-notice>
</template>
<template v-if="item.componentName == 'GraphicNav'">
<!-- 图文导航 -->
<diy-graphic-nav :value="item"></diy-graphic-nav>
</template>
<template v-if="item.componentName == 'ImageAds'">
<!-- 图片广告 -->
<diy-img-ads :value="item"></diy-img-ads>
</template>
<template v-if="item.componentName == 'Search'">
<!-- 搜索 -->
<diy-search :value="item" :topNavColor="topNavColor" :global="diyGlobalData.global" :haveTopCategory="haveTopCategory" :followOfficialAccount="followOfficialAccount"></diy-search>
</template>
<template v-if="item.componentName == 'RichText'">
<!-- 富文本 -->
<diy-rich-text :value="item"></diy-rich-text>
</template>
<template v-if="item.componentName == 'HorzLine'">
<!-- 辅助线 -->
<diy-horz-line :value="item"></diy-horz-line>
</template>
<template v-if="item.componentName == 'HorzBlank'">
<!-- 辅助空白 -->
<diy-horz-blank :value="item"></diy-horz-blank>
</template>
<template v-if="item.componentName == 'Coupon' && addonIsExist.coupon">
<!-- 优惠券 -->
<diy-coupon :value="item"></diy-coupon>
</template>
<template v-if="item.componentName == 'GoodsList'">
<!-- 商品列表 -->
<diy-goods-list :value="item"></diy-goods-list>
</template>
<template v-if="item.componentName == 'ManyGoodsList'">
<!-- 多商品组 -->
<diy-many-goods-list :value="item" :global="diyGlobalData.global" :scrollTop="scrollTop"></diy-many-goods-list>
</template>
<template v-if="item.componentName == 'RubikCube'">
<!-- 魔方橱窗 -->
<diy-rubik-cube :value="item"></diy-rubik-cube>
</template>
<template v-if="item.componentName == 'Video'">
<!-- 视频 -->
<diy-video :value="item"></diy-video>
</template>
<template v-if="item.componentName == 'Seckill' && addonIsExist.seckill">
<!-- 秒杀 -->
<diy-seckill :value="item"></diy-seckill>
</template>
<template v-if="item.componentName == 'Pintuan' && addonIsExist.pintuan">
<!-- 拼团 -->
<diy-pintuan :value="item"></diy-pintuan>
</template>
<template v-if="item.componentName == 'Groupbuy' && addonIsExist.groupbuy">
<!-- 团购 -->
<diy-groupbuy :value="item"></diy-groupbuy>
</template>
<!-- 拼团返利 -->
<template v-if="item.componentName == 'Pinfan' && addonIsExist.pinfan">
<diy-pinfan :value="item"></diy-pinfan>
</template>
<template v-if="item.componentName == 'Bargain' && addonIsExist.bargain">
<!-- 砍价 -->
<diy-bargain :value="item"></diy-bargain>
</template>
<template v-if="item.componentName == 'Presale' && addonIsExist.bargain">
<!-- 预售 -->
<diy-presale :value="item"></diy-presale>
</template>
<template v-if="item.componentName == 'Notes' && addonIsExist.notes">
<!-- 店铺笔记 -->
<diy-notes :value="item"></diy-notes>
</template>
<template v-if="item.componentName == 'FloatBtn'">
<!-- 浮动按钮 -->
<diy-float-btn :value="item"></diy-float-btn>
</template>
<template v-if="item.componentName == 'LiveInfo'">
<!-- 小程序直播 -->
<!-- #ifdef MP-WEIXIN -->
<diy-live :value="item"></diy-live>
<!-- #endif -->
</template>
<template v-if="item.componentName == 'FenxiaoGoodsList'">
<!-- 分销商品 -->
<diy-fenxiao-goods-list :value="item"></diy-fenxiao-goods-list>
</template>
<template v-if="item.componentName == 'GoodsRecommend'">
<!-- 商品推荐 -->
<diy-goods-recommend :value="item"></diy-goods-recommend>
</template>
<template v-if="item.componentName == 'GoodsBrand'">
<!-- 商品品牌 -->
<diy-goods-brand :value="item"></diy-goods-brand>
</template>
<template v-if="item.componentName == 'Article'">
<!-- 文章 -->
<diy-article :value="item"></diy-article>
</template>
<template v-if="item.componentName == 'MerchList'">
<!-- 商户列表 -->
<diy-merch-list :value="item"></diy-merch-list>
</template>
<template v-if="item.componentName == 'MemberInfo'">
<!-- 自定义会员中心会员信息 -->
<diy-member-info ref="diyMemberIndex" :value="item" :global="diyGlobalData.global"></diy-member-info>
</template>
<template v-if="item.componentName == 'MemberMyOrder'">
<!-- 自定义会员中心我的订单 -->
<diy-member-my-order ref="diyMemberMyOrder" :value="item"></diy-member-my-order>
</template>
<template v-if="item.componentName == 'QuickNav'">
<!-- 快捷导航 -->
<diy-quick-nav :value="item"></diy-quick-nav>
</template>
<template v-if="item.componentName == 'PaymentQrcode'">
<!-- 付款码 -->
<diy-payment-qrcode :value="item"></diy-payment-qrcode>
</template>
<template v-if="item.componentName == 'HotArea'">
<!-- 热区 -->
<diy-hot-area :value="item"></diy-hot-area>
</template>
<template v-if="item.componentName == 'FollowOfficialAccount'">
<!-- 关注公众号 -->
<diy-follow-official-account :value="item"></diy-follow-official-account>
</template>
<template v-if="item.componentName == 'Map'">
<!-- 地图组件 -->
<diy-map :value="item"></diy-map>
</template>
<template v-if="item.componentName == 'Audio'">
<!-- 音频 -->
<diy-audio :value="item"></diy-audio>
</template>
<!-- 自定义扩展组件 -->
<diy-comp-extend :value="item"></diy-comp-extend>
</view>
</view>
</template>
<script>
export default {
components: {},
props: {
diyData: {
type: Object
},
scrollTop: {
type: [String, Number],
default: '0'
},
haveTopCategory: {
type: Boolean
},
followOfficialAccount: {
type: Object
},
},
data() {
return {
diyGlobalData: null
};
},
created() {
this.diyGlobalData = JSON.parse(JSON.stringify(this.diyData));
},
computed: {
topNavColor() {
var color = '';
if (this.diyData.global.topNavBg) {
color = 'transparent';
if (this.scrollTop > 20) {
color = this.diyData.global.topNavColor;
} else {
color = 'transparent';
}
} else {
color = this.diyData.global.topNavColor;
}
return color;
},
// 修改属性样式
setPagestyle() {
this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = '';
// 给每个组件增加位置属性,用于定位,搜索、分类导航等定位
item.moduleIndex = index + 1;
// 特殊处理搜索框 当显示位置为滚动至顶部固定时,只设置背景颜色
if (item.componentName == 'Search' && item.positionWay == 'fixed') {
// item.pageStyle = 'background-color:' + item.pageBgColor + ';';
return false;
}
item.pageStyle += 'background-color:' + item.pageBgColor + ';';
if (item.margin) {
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';';
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';';
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';';
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';';
}
});
return this.diyGlobalData.value;
},
// 过滤组件的渲染
diyDataArray() {
let data = [],
showModuleData = this.$store.state.diyGroupShowModule ? JSON.parse(this.$store.state
.diyGroupShowModule) : '';
if (showModuleData.length) {
if (showModuleData.includes('null')) return [];
let diyDataArr = this.setPagestyle;
diyDataArr.forEach((item, index) => {
if (showModuleData.includes(item.componentName)) {
data.push(item);
}
});
} else data = this.setPagestyle;
return data;
}
},
methods: {}
};
</script>
<style lang="scss">
.diy-group {
width: 100%;
}
</style>

View File

@@ -0,0 +1,462 @@
<template>
<x-skeleton :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)"
: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)">
</image>
</view>
<view class="content"
v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<button v-if="value.btnStyle.control" :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'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</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)" :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)"/>
<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" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">
已团{{ item.sell_num }}
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">¥{{ item.price }}</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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)"/>
<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" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '' }">已团{{ item.sell_num }}</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ item.groupbuy_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">{{ '.' + item.groupbuy_price.split('.')[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">¥{{ item.price }}</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-groupbuy',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonType: '',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') return this.value.ornament.type == 'shadow' ? '404rpx' :
'392rpx';
return this.value.ornament.type == 'shadow' ? '376rpx' : '368rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/groupbuy/api/goods/lists',
data: data,
success: res => {
if (res.code == 0) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
this.loading = false;
}
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/groupbuy/detail', {
groupbuy_id: e.groupbuy_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-groupbuy {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
padding-bottom: 20rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.goods-name {
margin-top: 6rpx;
line-height: 1.5;
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
.discount-price {
white-space: nowrap;
font-weight: bold;
position: absolute;
bottom: 20rpx;
left: 0;
display: flex;
align-items: baseline;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
button {
position: absolute;
bottom: 10rpx;
right: 20rpx;
margin: 0;
padding: 0 20rpx;
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
.bg {
position: absolute;
width: 100%;
height: 60rpx;
bottom: 0;
left: 0;
z-index: 2;
}
.num {
width: 180rpx;
position: absolute;
bottom: 10rpx;
padding-left: 20rpx;
font-size: 20rpx;
line-height: 1;
color: #ffffff;
z-index: 3;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 158rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
}
}
}
.swiper {
width: 100%;
white-space: nowrap;
padding: 20rpx;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<view :style="horzBlankGaugeWrap"></view>
</template>
<script>
// 辅助空白
export default {
name: 'diy-horz-blank',
props: {
value: {
type: Object
}
},
data() {
return {};
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
horzBlankGaugeWrap: function() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;';
}
obj += 'height:' + this.value.height * 2 + 'rpx';
return obj;
}
},
created() {},
methods: {}
};
</script>
<style></style>

View File

@@ -0,0 +1,25 @@
<template>
<view :style="{ borderTop: '2rpx ' + value.borderStyle + ' ' + value.color }"></view>
</template>
<script>
// 辅助线
export default {
name: 'diy-horz-line',
props: {
value: {
type: Object
}
},
data() {
return {};
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
methods: {}
};
</script>
<style></style>

View File

@@ -0,0 +1,73 @@
<template>
<view :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"/>
<!-- 热区功能 -->
<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>
</view>
</view>
</template>
<script>
export default {
name: 'diy-hot-area',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
hotAreaWarp: function() {
var obj = '';
obj = 'background-color:' + this.value.componentBgColor + ';';
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: {}
};
</script>
<style lang="scss" scoped>
.hot-area-box {
position: relative;
width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.simple-graph-wrap {
line-height: 0;
overflow: hidden;
position: relative;
image {
width: 100%;
}
.heat-map {
position: absolute;
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<view class="diy-icon" :style="iconBgStyle">
<text class="js-icon" :class="iconClass" :style="iconStyle"></text>
</view>
</template>
<script>
export default {
name: 'diy-icon',
props: {
icon: {
type: String,
default: ''
},
value: {
type: Object,
default: function () {
return null;
}
}
},
computed: {
iconClass(){
var _class = ' ' + this.icon;
if (this.value && this.value.iconColor.length > 1) _class += ' gradient';
return _class;
},
iconBgStyle(){
if (!this.value) return {};
var style = {
'border-radius': this.value.bgRadius + '%',
'background': ''
};
if (this.value.iconBgImg) style['background'] += 'url('+ this.$util.img(this.value.iconBgImg) +') no-repeat bottom / contain'
if (this.value.iconBgColor.length) {
if (style.background) style.background += ',';
if (this.value.iconBgColor.length == 1) {
style.background += this.value.iconBgColor[0];
} else {
style['background'] += 'linear-gradient('+ this.value.iconBgColorDeg +'deg, '+ this.value.iconBgColor.join(',') +')';
}
}
return this.$util.objToStyle(style);
},
iconStyle(){
if (!this.value) return {};
var style = {
'font-size': this.value.fontSize + '%'
}
if (this.value.iconColor.length == 1) {
style.color = this.value.iconColor[0];
} else {
style['background'] = 'linear-gradient('+ this.value.iconColorDeg +'deg, '+ this.value.iconColor.join(',') +')';
}
return this.$util.objToStyle(style);
}
}
}
</script>
<style lang="scss">
.diy-icon {
width: 100%;
height: 100%;
font-size: 100%;
color: #000;
display: flex;
align-items: center;
justify-content: center;
.js-icon {
font-size: 50%;
line-height:1;
padding: 1rpx;
&.gradient {
-webkit-background-clip:text!important;
-webkit-text-fill-color:transparent;
}
}
}
</style>

View File

@@ -0,0 +1,301 @@
<template>
<view class="single-graph">
<view :style="imgAdsMarginWarp" class="swiper-box">
<block v-if="imgAdsValue.list.length == 1">
<view class="simple-graph-wrap" :style="imgAdsSwiper" @click="$util.diyRedirectTo(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"/>
</view>
</block>
<swiper v-else class="swiper" :style="{ height: swiperHeight }" :class="{
'swiper-left': imgAdsValue.indicatorLocation == 'left',
'swiper-right': imgAdsValue.indicatorLocation == 'right',
'ns-indicator-dots': imgAdsValue.carouselStyle == 'line'
}" :autoplay="true" :interval="imgAdsValue.interval" circular="true" :indicator-dots="isDots"
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="$util.diyRedirectTo(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"/>
</view>
</swiper-item>
</swiper>
<!-- #ifdef MP-WEIXIN -->
<view v-if="imgAdsValue.list.length > 1 && value.indicatorIsShow" :class="[
'swiper-dot-box',
{ straightLine: imgAdsValue.carouselStyle == 'line' },
{ 'swiper-left': imgAdsValue.indicatorLocation == 'left' },
{ 'swiper-right': imgAdsValue.indicatorLocation == 'right' }
]">
<view v-for="(numItem, numIndex) in imgAdsValue.list.length" :key="numIndex" :class="['swiper-dot', { active: numIndex == swiperIndex }]" :style="[numIndex == swiperIndex && { backgroundColor: imgAdsValue.indicatorColor }]"></view>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
export default {
name: 'diy-img-ads',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
isDots: true,
swiperHeight: 0,
imgAdsValue: null, // 深拷贝一遍数据,防止动态计算图片展示尺寸的时候,影响到父级的数据,导致二次渲染的时候,数据错误
swiperIndex: 0
};
},
created() {
this.calcSingleRow();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
imgAdsMarginWarp: function() {
var obj = '';
obj = 'background-color:' + this.value.componentBgColor + ';';
return obj;
},
imgAdsSwiper: 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;
},
singleGraphBg: function() {
var imgArr = [];
for (let i = 0; i < this.imgAdsValue.list.length; i++) {
let item = this.imgAdsValue.list[i];
imgArr[i] = parseFloat(item.imgHeight);
}
imgArr.sort(function(a, b) {
return b - a;
});
var obj = '';
obj += 'background-color:' + this.imgAdsValue.backgroundColor + ';';
obj += 'height:' + imgArr[0] * (this.imgAdsValue.backgroundHeight / 100) * 2 + 'rpx;';
return obj;
}
},
methods: {
swiperChange(e) {
this.swiperIndex = e.detail.current;
},
calcSingleRow() {
let minHeight = 0;
let systemInfo = uni.getSystemInfoSync()
// 深拷贝一层数据,防止数据更改越权
this.imgAdsValue = JSON.parse(JSON.stringify(this.value));
this.imgAdsValue.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = systemInfo.windowWidth;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
// 获取最大高度 if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight;
if (minHeight == 0 || minHeight > item.imgHeight) minHeight = item.imgHeight;
});
this.imgAdsValue.list.forEach((item, index) => {
item.imgHeight = minHeight + 'px';
this.swiperHeight = minHeight + 'px';
});
this.imgAdsValue.indicatorColor = this.imgAdsValue.indicatorColor || '#fff';
if (this.value.indicatorIsShow === undefined) {
this.value.indicatorIsShow = true; // 控制指示点是否展示
}
// 是否显示指示器
if (this.imgAdsValue.list.length <= 1) {
this.isDots = false;
}
// #ifdef H5
this.isDots = this.value.indicatorIsShow;
// #endif
// #ifdef MP-WEIXIN
this.isDots = false;
// #endif
}
}
};
</script>
<style lang="scss" scoped>
.single-graph {
width: 100%;
line-height: 0;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
box-sizing: border-box;
}
.simple-graph-wrap {
line-height: 0;
overflow: hidden;
position: relative;
image {
width: 100%;
}
.heat-map {
position: absolute;
}
}
.item.active text {
background: rgba(0, 0, 0, 0.3);
position: absolute;
bottom: 0;
color: #ffffff;
font-size: $font-size-tag;
width: 100%;
left: 0;
line-height: 40rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: 0 10rpx;
text-align: center;
}
.swiper-box {
position: relative;
width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.swiper {
margin: 0 auto;
overflow: hidden;
}
.swiper-item {
width: 100%;
height: auto !important;
display: flex;
justify-content: center;
flex-direction: column;
// position: relative;
overflow: hidden;
.item {
width: 100%;
height: auto;
text-align: center;
position: relative;
overflow: hidden;
image {
width: 100%;
max-width: 100%;
height: 100%;
will-change: transform;
}
.heat-map {
position: absolute;
}
}
}
.swiper-dot-box {
position: absolute;
bottom: 20rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 40rpx 8rpx;
box-sizing: border-box;
&.swiper-left {
justify-content: flex-start;
}
&.swiper-right {
justify-content: flex-end;
}
.swiper-dot {
background-color: #b2b2b2;
width: 15rpx;
border-radius: 50%;
height: 15rpx;
margin: 8rpx;
&.active {
background-color: rgba(0, 0, 0, 1);
}
}
&.straightLine {
.swiper-dot {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
&.active {
width: 36rpx;
background-color: rgba(0, 0, 0, 1);
}
}
}
}
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
/deep/.uni-scroll-view::-webkit-scrollbar {
display: none;
}
.swiper /deep/ .uni-swiper-dots-horizontal {
bottom: 25rpx;
}
.swiper-left /deep/ .uni-swiper-dots-horizontal {
left: 40rpx;
transform: translate(0);
}
.swiper-right /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 {
width: 24rpx;
border-radius: 0;
height: 8rpx;
}
.swiper.ns-indicator-dots /deep/ .uni-swiper-dot {
width: 18rpx;
height: 6rpx;
border-radius: 4rpx;
}
.swiper.ns-indicator-dots /deep/ .uni-swiper-dot-active {
width: 36rpx;
}
</style>

View File

@@ -0,0 +1,830 @@
<template>
<view>
<view class="bg" :style="warpCss">
<view class="index-page-content">
<view class="nav-top-category" :style="categoryCss">
<scroll-view v-if="value" scroll-with-animation class="diyIndex" scroll-x="true"
:scroll-into-view="'a' + pageIndex"
: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' }"
:style="{ background: index == pageIndex && value.styleType == 'fill' ? value.selectColor : '' }">
<view class="text-con" :class="index == pageIndex ? 'active' : ''" :style="{
color: index == pageIndex ? '' : value.noColor
}" v-if="value.styleType == 'fill'">
{{ item.short_name ? item.short_name : item.category_name }}
</view>
<view class="text-con" :class="index == pageIndex ? 'active' : ''" :style="{ color: index == pageIndex ? value.selectColor : value.noColor }" v-else>
{{ item.short_name ? item.short_name : item.category_name }}
</view>
<view class="color-base-bg line" v-if="index == pageIndex && value.styleType != 'fill'" :style="{ background: value.selectColor ? value.selectColor + '!important' : 'rgba(0,0,0,0)' + '!important' }">
</view>
</view>
</scroll-view>
<text class="iconfont icon-unfold unfold-arrows" :style="{ color: value.moreColor }" @click="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)">
{{ item.short_name ? item.short_name : item.category_name }}
</text>
</view>
</uni-popup>
<view class="nav_top_category-fill" :style="{ height: moduleHeight }"></view>
<block v-if="pageIndex == 0">
<slot name="components"></slot>
<slot></slot>
</block>
<block v-else>
<slot name="components"></slot>
<view class="index-category-box">
<view class="category-goods" v-show="!isloading">
<mescroll-uni :top="uniPopTop" ref="mescroll" @getData="getGoodsList" :background="'url(' + $util.img(bgUrl) + ') 0px -50px / 100% no-repeat'" :paddingBoth="'30rpx'" @touchmove.prevent.stop>
<block slot="list">
<!-- 二级分类 -->
<view class="twoCategorylist" v-if="twoCategorylist != 'undefined' && twoCategorylist && twoCategorylist.length > 0">
<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)">
<view class="item-box">
<image :src="$util.img(item.image)" v-if="item.image" mode="aspectFill"/>
<image :src="$util.getDefaultImage().goods" v-else mode="aspectFill"/>
<view>{{ item.category_name }}</view>
</view>
</view>
</view>
</view>
<view class="twoCategory base" 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)">
<view class="item-box">
<image :src="$util.img(item.image)" v-if="item.image" mode="aspectFill"/>
<image :src="$util.getDefaultImage().goods" v-else mode="aspectFill"/>
<view>{{ item.category_name }}</view>
</view>
</view>
</view>
</view>
<swiper class="twoCategory big" :duration="500" v-if="twoCategorylist.length > 10" @change="swiperTocategoryChange">
<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)">
<view class="item-box">
<image :src="item.image" mode="aspectFill"/>
<view>{{ item.category_name }}</view>
</view>
</view>
</swiper-item>
</swiper>
<view class="dot-box">
<view class="dot-item" v-for="page in maxPage" v-if="maxPage > 1" :key="page" :class="twoCategorylistId == page - 1 ? 'active color-base-bg' : ''"></view>
</view>
</view>
<!-- 分类广告 -->
<image class="category_adv" v-if="cateList[pageIndex].image_adv" :src="$util.img(cateList[pageIndex].image_adv)" mode="widthFix"/>
<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)">
<view class="goods-img">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"/>
<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)"/>
</view>
</view>
<view class="info-wrap">
<view class="name-wrap">
<view class="goods-name">{{ item.goods_name }}</view>
</view>
<view class="lineheight-clear">
<view class="discount-price">
<text class="unit color-base-text font-size-tag">{{ $lang('common.currencySymbol') }}</text>
<text class="price color-base-text font-size-toolbar">{{ showPrice(item) }}</text>
</view>
<view class="member-price-tag" v-if="item.member_price && item.member_price == showPrice(item)"><image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix"/>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"/>
</view>
</view>
<view class="pro-info">
<view class="delete-price font-size-activity-tag color-tip" v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
<view class="sale font-size-activity-tag color-tip">已售{{ item.sale_num }}{{ item.unit ? item.unit : '件' }}</view>
</view>
</view>
</view>
</view>
<view v-if="!isloading && goodsList[pageIndex].list.length == 0">
<ns-empty text="该分类下暂无商品" :isIndex="false"></ns-empty>
</view>
</block>
</mescroll-uni>
<!-- <ns-empty v-else-if="!isloading" :isIndex="false" text="该分类下暂无商品"></ns-empty> -->
</view>
<view class="loading" v-show="isloading"><ns-loading ref="loading"></ns-loading></view>
</view>
</block>
</view>
</view>
</view>
</template>
<script>
import nsLoading from '@/components/ns-loading/ns-loading.vue';
export default {
props: {
value: {
type: Object
},
bgUrl: {
type: String
},
scrollTop: {
type: [String, Number],
default: '0'
},
diyGlobal: {
type: Object
}
},
components: {
nsLoading
},
data() {
return {
pageIndex: 0, //当前选中分类id
cateList: [{
//header分类
category_name: '首页'
}],
twoCategorylist: [], //二级分类
twoCategorylistId: 0, //二级分类所在的swiper
goodsList: {},
isloading: true,
top: 0,
isUnfold: true, //是否展开菜单
moduleHeight: '' //组件高度
};
},
computed: {
warpCss() {
var obj = '';
obj += (this.bgUrl ? 'background:' + 'url(' + this.$util.img(this.bgUrl) + ') no-repeat 0 0/100%' : '') + ';';
return obj;
},
categoryCss() {
var obj = '';
obj += 'top:' + this.fixedTop + ';';
// obj += 'background-color:' + (this.value.componentBgColor || this.value.pageBgColor) + ';';
obj += 'background-color:' + this.topNavColor + ';';
return obj;
},
maxPage() {
let num = 0;
if (this.twoCategorylist && this.twoCategorylist.length) {
num = Math.ceil(this.twoCategorylist.length / 10);
}
return num;
},
type() {
if (this.value) {
return true;
} else {
return false;
}
},
topNavColor() {
var color = this.value.componentBgColor || this.value.pageBgColor;
if (this.diyGlobal.topNavBg && this.scrollTop > 20) color = this.diyGlobal.topNavColor;
return color;
},
fixedTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = 0;
if (diyPositionObj.diySearch && diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
if (diyPositionObj.diySearch.moduleIndex > diyPositionObj.diyIndexPage.moduleIndex) data =
diyPositionObj.nsNavbar.originalVal + 'px';
else data = diyPositionObj.nsNavbar.originalVal + diyPositionObj.diySearch.originalVal + 'px';
} else if (diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
data = diyPositionObj.nsNavbar.originalVal + 'px';
}
return data;
},
// 分类导航展开菜单的位置
uniPopTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = '0';
if (this.fixedTop && diyPositionObj.diyIndexPage)
data = Number.parseFloat(this.fixedTop) + diyPositionObj.diyIndexPage.originalVal + 'px';
return data;
}
},
watch: {
type(newVal, oldVal) {
if (newVal) {
this.getCategoryList();
}
}
},
mounted() {
this.getCategoryList();
setTimeout(() => {
// 获取组件的高度默认高度为4545是在375屏幕上的高度
const query = uni.createSelectorQuery();
// #ifdef H5
let cssSelect = '.page-header .u-navbar';
this.top = 100;
// #endif
// #ifdef MP
let cssSelect = '.page-header >>> .u-navbar';
this.top = 20;
// #endif
query
.select(cssSelect)
.boundingClientRect(data => {
let height;
if (this.diyGlobal.navBarSwitch) {
height = data ? data.height : 45;
} else {
height = data ? data.height : 0;
}
// #ifdef H5
this.top += height * 2;
// #endif
// #ifdef MP
this.top += height;
// #endif
})
.exec();
});
this.setModuleLocatinoFn();
},
methods: {
initPageIndex() {
this.pageIndex = 0;
this.showModuleFn();
},
//请求分类列表
getCategoryList() {
let url = '/api/goodscategory/tree';
let data = {
level: 3
};
this.$api.sendRequest({
url: url,
data: data,
success: res => {
if (res.code >= 0) {
let arr = [];
let obj = {
list: []
};
obj.category_name = this.value.title ? this.value.title : '首页';
arr.push(obj);
this.cateList = arr.concat(res.data);
Object.keys(this.cateList).forEach((key, index) => {
this.goodsList[key] = {
page: 1,
list: []
};
});
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
}
}
});
},
//修改当前页面id
changePageIndex(e) {
this.isloading = true;
this.pageIndex = e;
this.showModuleFn();
if (e == 0) return;
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
if (this.cateList[this.pageIndex].child_list) {
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
this.twoCategorylist.forEach(v => {
if (v.image) {
v.image = this.$util.img(v.image);
} else {
v.image = this.$util.getDefaultImage().goods;
}
});
} else {
this.twoCategorylist = false;
}
if (this.$refs.mescroll) {
this.$refs.mescroll.refresh();
this.$refs.mescroll.myScrollTo(0);
}
},
//监听二级分类 页面切换
swiperTocategoryChange(e) {
this.twoCategorylistId = e.detail.current;
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/detail', {
goods_id: item.goods_id
});
},
getGoodsList(mescroll) {
let id = this.pageIndex;
var data = {
page: mescroll.num,
page_size: mescroll.size
};
data.category_id = this.cateList[this.pageIndex].category_id_1;
data.category_level = 1;
this.$api.sendRequest({
url: '/api/goodssku/page',
data: data,
success: res => {
this.isloading = false;
let newArr = [];
let msg = res.message;
if (res.code == 0 && res.data) {
this.count = res.data.count;
newArr = res.data.list;
} else {
this.$util.showToast({
title: msg
});
}
mescroll.endSuccess(newArr.length);
//设置列表数据
if (mescroll.num == 1) this.goodsList[id].list = []; //如果是第一页需手动制空列表
this.goodsList[id].list = this.goodsList[id].list.concat(newArr); //追加新数据
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.$forceUpdate();
}
});
},
toCateGoodsList(e, f) {
this.$util.redirectTo('/pages/goods/list', {
category_id: e,
category_level: f
});
},
goodsImg(imgStr) {
let imgs = imgStr.split(',');
return imgs[0] ? this.$util.img(imgs[0], {
size: 'mid'
}) : this.$util.getDefaultImage().goods;
},
imgError(index) {
this.goodsList[index].goods_image = this.$util.getDefaultImage().goods;
},
showPrice(data) {
let price = data.discount_price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
showMarketPrice(item) {
if (item.market_price_show) {
let price = this.showPrice(item);
if (item.market_price > 0) {
return item.market_price;
} else if (parseFloat(item.price) > parseFloat(price)) {
return item.price;
}
}
return '';
},
goodsTag(data) {
return data.label_name || '';
},
// 控制菜单展开关闭
unfoldMenu() {
if (this.isUnfold) this.$refs.navTopCategoryPop.open();
else this.$refs.navTopCategoryPop.close();
this.isUnfold = !this.isUnfold;
},
// 向vuex中的diyIndexPositionObj增加分类导航组件定位位置
setModuleLocatinoFn() {
const query = uni.createSelectorQuery().in(this);
query.select('.nav-top-category')
.boundingClientRect(data => {
let diyIndexPage = {
originalVal: data.height || 0, //自身高度 px
moduleIndex: this.value.moduleIndex //组件在diy-group的位置
};
this.moduleHeight = (data.height || 0) + 'px';
this.$store.commit('setDiyGroupPositionObj', {
diyIndexPage: diyIndexPage
});
}).exec();
},
showModuleFn() {
let searchModule = this.$root.diyData.value.filter((item, index) => {
return item.componentName == 'Search';
});
// setDiyGroupShowModule值为【】表示显示所有组件,为【null】则什么组件也不显示
if (this.pageIndex == 0) this.$store.commit('setDiyGroupShowModule', JSON.stringify([]));
else {
if (searchModule[0].positionWay == 'fixed') this.$store.commit('setDiyGroupShowModule', JSON.stringify(
['Search']));
else this.$store.commit('setDiyGroupShowModule', JSON.stringify(['null']));
}
// 特殊处理,切换分类导航导致页面无法上下滚动
// #ifdef H5
if (this.pageIndex == 0) {
// 标记当前页使用了mescroll (需延时,确保page已切换)
setTimeout(function() {
let uniPageDom = document.getElementsByTagName('uni-page')[0];
uniPageDom && uniPageDom.removeAttribute('use_mescroll');
}, 30);
}
// #endif
}
}
};
</script>
<style lang="scss">
.bg {
width: 100%;
height: 100%;
}
.nav-top-category {
display: flex;
align-items: center;
position: fixed;
z-index: 999;
transition: background 0.3s;
width: 100%;
padding: 0 24rpx;
box-sizing: border-box;
.text-fiexd {
.text-con {
line-height: 60rpx;
}
}
.unfold-arrows {
position: relative;
margin-left: 4rpx;
padding-left: 16rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #fff;
}
}
.nav-topcategory-pop {
padding: 0 10rpx 20rpx;
display: flex;
flex-wrap: wrap;
background-color: #fff;
.category-item {
padding: 0 30rpx;
height: 60rpx;
text-align: center;
line-height: 56rpx;
border-radius: 40rpx;
background-color: #f0f0f0;
margin: 20rpx 10rpx 0;
font-size: 24rpx;
border: 2rpx solid transparent;
box-sizing: border-box;
&.active {
background-color: transparent;
}
}
}
.diyIndex {
width: 100%;
height: 100rpx;
white-space: nowrap;
padding: 20rpx 0 0;
box-sizing: border-box;
&.widthAuto {
width: auto;
}
.item {
position: relative;
margin-right: 40rpx;
display: inline-block;
line-height: 80rpx;
font-size: $font-size-base;
text-align: center;
.text-con {
height: 30px;
line-height: 30px;
&.active {
font-size: $font-size-base;
font-weight: bold;
}
}
.text-con.active {
font-size: $font-size-base;
font-weight: bold;
}
.line {
position: absolute;
left: calc(50% - 14rpx);
width: 28rpx;
height: 5rpx;
border-radius: 5rpx;
}
&.fill {
border-radius: 50rpx;
padding: 0 10rpx;
.text-con.active {
font-size: $font-size-base;
font-weight: 600;
color: #fff;
}
}
}
}
.index-page-box {
width: 100%;
height: calc(100vh - 288rpx);
}
.index-page-content {
width: 100%;
// height: calc(100vh - 144px);
}
.index-category-box.active {
padding-bottom: 160rpx;
padding-bottom: calc(160rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
}
.index-category-box {
width: 100%;
padding-bottom: 110rpx;
padding-bottom: calc(110rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(110rpx + env(safe-area-inset-bottom));
.twoCategorylist {
position: relative;
}
.twoCategory.min {
height: 160rpx;
}
.twoCategory.big {
height: 340rpx;
}
.twoCategory {
width: 100%;
background: #ffffff;
border-radius: 15rpx;
overflow: hidden;
margin-top: 20rpx;
.twoCategory-page {
width: 100%;
height: 100%;
padding: 20rpx;
box-sizing: border-box;
}
.swiper-item {
width: 120rpx;
height: 120rpx;
display: inline-block;
margin-right: calc((100% - 120rpx * 5) / 4);
overflow: hidden;
.item-box {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
image {
width: 88rpx;
height: 88rpx;
}
view {
width: 100%;
font-size: 22rpx;
line-height: 1;
margin-top: 10rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-align: center;
}
}
}
.swiper-item:nth-child(5n) {
margin-right: 0;
}
.swiper-item:nth-child(10n + 6) {
margin-top: 15rpx;
}
}
.dot-box {
width: calc(100% - 40rpx);
height: 50rpx;
position: absolute;
bottom: 0rpx;
left: 20rpx;
background: rgba($color: #000000, $alpha: 0);
display: flex;
justify-content: center;
align-items: center;
.dot-item {
width: 12rpx;
height: 12rpx;
background: #cccccc;
border-radius: 6rpx;
margin-right: 10rpx;
}
.dot-item.active {
width: 24rpx;
}
}
.category_adv {
width: 100%;
margin: 20rpx 0;
border-radius: 15rpx;
}
.category-goods {
width: 100%;
}
}
.loading {
width: 100%;
height: 50rpx;
margin-top: 100rpx;
}
/deep/.uni-scroll-view::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
}
.goods-list.double-column {
display: flex;
flex-wrap: wrap;
margin-top: $margin-updown;
.goods-item {
flex: 1;
position: relative;
background-color: #fff;
flex-basis: 48%;
max-width: calc((100% - 30rpx) / 2);
margin-right: $margin-both;
margin-bottom: $margin-updown;
border-radius: $border-radius;
&:nth-child(2n) {
margin-right: 0;
}
.goods-img {
position: relative;
overflow: hidden;
padding-top: 100%;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
image {
width: 100%;
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
}
.goods-tag {
color: #fff;
line-height: 1;
padding: 8rpx 16rpx;
position: absolute;
border-bottom-right-radius: $border-radius;
top: 0;
left: 0;
font-size: $font-size-goods-tag;
}
.goods-tag-img {
position: absolute;
border-top-left-radius: $border-radius;
width: 80rpx;
height: 80rpx;
top: 0;
left: 0;
z-index: 5;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.info-wrap {
padding: 0 26rpx 26rpx 26rpx;
}
.goods-name {
font-size: $font-size-base;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-top: 20rpx;
height: 68rpx;
}
.discount-price {
display: inline-block;
font-weight: bold;
line-height: 1;
margin-top: 16rpx;
.unit {
margin-right: 6rpx;
}
}
.pro-info {
display: flex;
margin-top: 16rpx;
.delete-price {
text-decoration: line-through;
flex: 1;
.unit {
margin-right: 6rpx;
}
}
&>view {
line-height: 1;
&:nth-child(2) {
text-align: right;
}
}
}
.member-price-tag {
display: inline-block;
width: 60rpx;
line-height: 1;
margin-left: 6rpx;
image {
width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<view class="diy-kefu" :style="style">
<view class="fui-list-group merchgroup" v-for="(item,index) in value.list">
<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>
</view>
<view class="fui-list-inner" >
<view class="title" style="color: ;height: 75rpx;">
<text style="font-weight:600" :style="{color:item.textColor}">{{item.title}}</text>
<view class="subtitle" style="font-size:24rpx" :style="{color:item.textColor}">{{item.desc}}</view>
</view>
</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>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'diy-picture',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
previewSqs(){
var img = this.$util.img(this.value.list[1].imageUrl)
uni.previewImage({
current: img,
urls: [img]
})
}
}
};
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,77 @@
<template>
<view class="diy-picture" :style="style">
<view class="fui-cell-group">
<!-- <image mode="widthFix" style="width: 100%;" :src="$util.img(item.imageUrl)"></image> -->
<view v-for="(item,index) in value.list" @click="redirectTo(item.link)" class="fui-cell" :class="item.iconType == 'img'?'img-cell':''">
<view class="fui-cell-icon" style="color:diyitem.style.iconcolo">
<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>
<image v-if="item.iconType == 'img'" mode="widthFix" :src="$util.img(item.imageUrl)" style="border-radius:6rpx;width: 60rpx;"></image>
</view>
<view class="fui-cell-text" style="color:#333;">{{item.title}}</view>
<view class="fui-cell-remark" style="font-size: 24rpx;">{{lang=='en-us'?'view':'查看'}}</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'diy-listmenu',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
lang:uni.getStorageSync("lang")//en-us 英文
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if(this.value.margin.top > 0)css += 'margin-top:' + this.value.margin.top * 2 + 'rpx;';
return css;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == 'pages/member/index' && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
}
};
</script>
<style lang="scss">
.img-cell{
padding:0 16rpx !important;
}
</style>

View File

@@ -0,0 +1,241 @@
<template>
<x-skeleton type="banner" :loading="loading" :configs="skeletonConfig">
<view class="live-wrap" @click="entryRoom(liveInfo.roomid)" v-if="liveInfo">
<view class="banner-wrap">
<image :src="liveInfo.banner != '' ? $util.img(liveInfo.banner) : $util.img('public/uniapp/live/live_default_banner.png')"
mode="widthFix" @error="liveInfo.banner = $util.img('public/uniapp/live/live_default_banner.png')"/>
<view class="shade"></view>
<view class="wrap">
<view class="room-name">
<text class="status-name font-size-base" :class="{ 'color-base-bg': liveInfo.live_status == '101' }">
<text class="iconfont icon-zhibozhong font-size-sub" v-if="liveInfo.live_status == '101'"></text>
<text class="iconfont icon-zhibojieshu font-size-sub" v-else></text>
{{ liveInfo.status_name }}
</text>
{{ liveInfo.name }}
</view>
</view>
</view>
<view class="room-info" v-if="value.isShowAnchorInfo || value.isShowLiveGood">
<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"/>
<text class="anchor-name">主播{{ liveInfo.anchor_name }}</text>
</block>
<text class="separate" v-if="value.isShowAnchorInfo && value.isShowLiveGood">|</text>
<block v-if="value.isShowLiveGood">
<text class="goods-text">直播商品{{ liveInfo.goods.length }}</text>
</block>
</view>
</view>
</x-skeleton>
</template>
<script>
// 直播
export default {
components: {},
name: 'diy-live',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
loading: true,
skeletonConfig: {
headHeight: '200rpx'
},
liveInfo: {
banner: '',
anchor_img: ''
}
};
},
created() {
this.getLiveInfo();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getLiveInfo();
}
},
methods: {
getLiveInfo() {
this.$api.sendRequest({
url: '/live/api/live/info',
success: res => {
if (res.code == 0 && res.data) {
this.liveInfo = res.data;
this.getLiveStatus();
} else {
this.liveInfo = null;
}
this.loading = false;
}
});
},
entryRoom(roomId) {
// #ifdef MP-WEIXIN
wx.navigateTo({
url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${roomId}`
});
// #endif
},
getLiveStatus() {
// #ifdef MP-WEIXIN
let livePlayer = requirePlugin('live-player-plugin');
livePlayer.getLiveStatus({
room_id: this.liveInfo.roomid
}).then(res => {
const liveStatus = res.liveStatus;
if (liveStatus && liveStatus != this.liveInfo.live_status) {
this.changeLiveStatus(liveStatus);
}
})
.catch(err => {console.log('get live status', err);
});
// 往后间隔1分钟或更慢的频率去轮询获取直播状态
var timer = setInterval(() => {
livePlayer
.getLiveStatus({
room_id: this.liveInfo.roomid
})
.then(res => {
const liveStatus = res.liveStatus;
if (liveStatus && liveStatus != this.liveInfo.live_status) {
this.changeLiveStatus(liveStatus);
}
if (this.$util.inArray(liveStatus, [103, 104, 106, 107])) {
clearInterval(timer);
}
})
.catch(err => {
console.log('get live status', err);
});
}, 60000);
// #endif
},
changeLiveStatus(status) {
this.$api.sendRequest({
url: '/live/api/live/modifyLiveStatus',
data: {
room_id: this.liveInfo.roomid,
status: status
},
success: res => {
if (res.code == 0) {
this.getLiveInfo();
}
}
});
}
}
};
</script>
<style lang="scss">
.live-wrap {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.banner-wrap {
width: 100%;
position: relative;
line-height: 1;
display: flex;
image {
width: 100%;
}
.shade {
width: 100%;
height: 100%;
position: absolute;
background: rgba($color: #888, $alpha: 0.3);
left: 0;
top: 0;
z-index: 5;
}
.wrap {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 10;
padding: 26rpx 20rpx;
box-sizing: border-box;
.room-name {
font-size: $font-size-toolbar;
color: #fff;
line-height: 1;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
.status-name {
display: inline-block;
font-size: $font-size-activity-tag;
color: #fff;
padding: 8rpx 12rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 36rpx;
margin-right: 20rpx;
.icon-zhibozhong {
font-size: $font-size-activity-tag;
color: #fff;
margin-right: 9rpx;
}
}
}
}
}
.room-info {
padding: 20rpx 30rpx;
background: #fff;
display: flex;
.anchor-img {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 20rpx;
}
.anchor-name,
.goods-text {
font-size: $font-size-base;
line-height: 60rpx;
}
.separate {
color: #808080;
margin: 0 10rpx;
line-height: 56rpx;
}
}
</style>
<style scoped>
.coupon-all>>>.uni-scroll-view::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<view class="many-goods-list">
<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="changeCateIndex(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>
<view class="desc" :class="{ 'color-base-bg': index == cateIndex && item.desc }">{{ item.desc }}
</view>
</view>
</view>
</scroll-view>
<view class="many-goods-list-fill" :style="{'height': manyInfo.height}" v-if="fixedTop"></view>
<diy-goods-list class="many-goods-list-body" v-if="goodsValue" :value="goodsValue" ref="diyGoodsList"></diy-goods-list>
</view>
</template>
<script>
export default {
name: 'diy-many-goods-list',
props: {
value: {
type: Object,
default: () => {
return {};
}
},
scrollTop: {
type: [Number, String]
},
global: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
cateIndex: 0, // 当前选中的分类id
goodsValue: null, // 商品列表数据
manyInfo: {
bodyHeight: 0,
bodyTop: 0,
height: 0,
top: 0
}
};
},
created() {
this.changeCateIndex(this.value.list[0], 0, true);
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.changeCateIndex(this.value.list[0], 0, true);
},
scrollTop: function(nval) {
const query = uni.createSelectorQuery().in(this);
query
.select('.many-goods-list')
.boundingClientRect(data => {
if (data) {
this.manyInfo.top = data.top;
}
})
.exec();
query
.select('.many-goods-list .many-goods-list-body')
.boundingClientRect(data => {
if (data) {
this.manyInfo.bodyHeight = (data.height || 0);
this.manyInfo.bodyTop = (data.top || 0);
}
})
.exec();
}
},
computed: {
fixedTop() {
let diyPositionObj = JSON.parse(JSON.stringify(this.$store.state.diyGroupPositionObj));
let positionHeight = 0;
let height = 0;
delete diyPositionObj.diyManyGoodsList;
if (diyPositionObj) {
let arr = Object.values(diyPositionObj);
arr.forEach((item, index) => {
positionHeight += item.originalVal; //定位的高度【搜索框+导航分类+自定义头部】
});
}
if (this.manyInfo.top < positionHeight && (this.manyInfo.bodyTop + this.manyInfo.bodyHeight >
positionHeight + Number.parseFloat(this.manyInfo.height))) {
height = positionHeight;
}
return height;
},
manyWrapCss() {
let html = '';
html += `position: ${this.fixedTop ? 'fixed' : 'initial'};`
html += `top: ${this.fixedTop}px;`
if (!this.global.topNavBg)
html += `background-color: #fff;`
else
html += `background-color: ${this.fixedTop ? this.global.topNavColor : 'transparent'};`
return html;
}
},
mounted() {
const query = uni.createSelectorQuery().in(this);
query
.select('.many-goods-list .many-goods-list-head')
.boundingClientRect(data => {
if (data) {
this.manyInfo.height = (data.height || 0) + 'px';
// 向vuex中的diyIndexPositionObj增加多商品组件定位位置
let diyManyGoodsList = {
originalVal: data.height || 0 //自身高度 px
}
this.$store.commit('setDiyGroupPositionObj', {
diyManyGoodsList: diyManyGoodsList
});
}
})
.exec();
},
methods: {
changeCateIndex(item, index, isFirst) {
this.cateIndex = index;
this.goodsValue = {
sources: item.sources,
categoryId: item.categoryId,
categoryName: item.categoryName,
goodsId: item.goodsId,
componentBgColor: this.value.componentBgColor,
componentAngle: this.value.componentAngle,
topAroundRadius: this.value.topAroundRadius,
bottomAroundRadius: this.value.bottomAroundRadius,
elementBgColor: this.value.elementBgColor,
elementAngle: this.value.elementAngle,
topElementAroundRadius: this.value.topElementAroundRadius,
bottomElementAroundRadius: this.value.bottomElementAroundRadius,
count: this.value.count,
nameLineMode: this.value.nameLineMode,
template: this.value.template,
style: this.value.style,
ornament: this.value.ornament,
sortWay: this.value.sortWay,
saleStyle: this.value.saleStyle,
tag: this.value.tag,
btnStyle: this.value.btnStyle,
goodsNameStyle: this.value.goodsNameStyle,
theme: this.value.theme,
priceStyle: this.value.priceStyle,
slideMode: this.value.slideMode,
imgAroundRadius: this.value.imgAroundRadius,
margin: this.value.margin,
goodsMarginType: this.value.goodsMarginType,
goodsMarginNum: this.value.goodsMarginNum
};
// 如果是第一次加载,不需要执行下面代码
if (isFirst) return;
this.$refs.diyGoodsList.goodsValue = this.goodsValue;
this.$refs.diyGoodsList.getGoodsList();
}
}
};
</script>
<style lang="scss" scoped>
.many-goods-list-head {
left: 0;
right: 0;
z-index: 5;
background-color: #fff;
}
scroll-view {
width: 100%;
white-space: nowrap;
box-sizing: border-box;
padding: 20rpx 0;
.scroll-item {
display: inline-block;
text-align: center;
vertical-align: top;
width: calc(25% - 40rpx);
position: relative;
padding: 0 20rpx;
&:first-child {
width: calc(25% - 20rpx);
padding-left: 0;
}
.split-line {
display: inline-block;
width: 1rpx;
height: 30rpx;
background-color: #e5e5e5;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
&.active {
.name {
font-weight: bold;
}
.desc {
color: #ffffff;
border-radius: 20rpx;
}
}
.name {
font-size: 32rpx;
color: $color-title;
line-height: 1;
}
.cate {
display: inline-block;
}
.desc {
font-size: $font-size-tag;
color: $color-tip;
height: 36rpx;
line-height: 36rpx;
margin-top: 10rpx;
min-width: 120rpx;
max-width: 220rpx;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10rpx;
}
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<view class="diy-map" :style="style">
<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 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="tomap(item)">
<cover-view style="font-size:24rpx">一键导航</cover-view>
</cover-view>
</map>
</view>
</view>
</template>
<script>
export default {
name: 'diy-map',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
markers:[]
};
},
created() {
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
markerst(){
return [{
id:1,
latitude:this.value.list[0].lat,
longitude:this.value.list[0].lng
}]
},
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
tomap(item){
uni.openLocation({
latitude: parseFloat(item.lat),
longitude: parseFloat(item.lng),
name:"一键导航",
})
}
}
};
</script>
<style lang="scss">
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,249 @@
<template>
<view class="common-wrap" :style="warpCss">
<view class="order-wrap">
<view class="status-wrap">
<view class="item-wrap" @click="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"/>
<view class="icon-shade" :style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_pay_shade.png') + ')'"></view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitPay.icon" v-if="value.icon.waitPay" :value="value.icon.waitPay.style ? value.icon.waitPay.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitpay > 0" class="order-num color-base-bg price-font">{{ orderNum.waitpay > 99 ? '99+' : orderNum.waitpay }}</text>
</view>
<view class="title">{{ $lang('waitpay') }}</view>
</view>
<view class="item-wrap" @click="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>
<view class="icon-shade" :style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_send_shade.png') + ')'"></view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitSend.icon" v-if="value.icon.waitSend" :value="value.icon.waitSend.style ? value.icon.waitSend.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitsend > 0" class="order-num color-base-bg price-font">{{ orderNum.waitsend > 99 ? '99+' : orderNum.waitsend }}</text>
</view>
<view class="title">{{ $lang('waitsend') }}</view>
</view>
<view class="item-wrap" @click="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"/>
<view class="icon-shade" :style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_confirm_shade.png') + ')'"></view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitConfirm.icon" v-if="value.icon.waitConfirm" :value="value.icon.waitConfirm.style ? value.icon.waitConfirm.style : null"></diy-icon>
</template>
<text v-if="orderNum.waitconfirm > 0" class="order-num color-base-bg price-font">{{ orderNum.waitconfirm > 99 ? '99+' : orderNum.waitconfirm }}</text>
</view>
<view class="title">{{ $lang('waitconfirm') }}</view>
</view>
<view class="item-wrap" @click="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"/>
<view class="icon-shade" :style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/wait_rate_shade.png') + ')'"></view>
</template>
<template v-else>
<diy-icon :icon="value.icon.waitUse.icon" v-if="value.icon.waitUse" :value="value.icon.waitUse.style ? value.icon.waitUse.style : null"></diy-icon>
</template>
<!-- <text v-if="orderNum.wait_use > 0" class="order-num color-base-bg price-font">{{ orderNum.wait_use > 99 ? '99+' : orderNum.wait_use }}</text> -->
</view>
<view class="title">{{ $lang('completed') }}</view>
</view>
<view class="item-wrap" @click="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"/>
<view class="icon-shade" :style="'-webkit-mask-image: url(' + $util.img('public/uniapp/member/order/refunding_shade.png') + ')'"></view>
</template>
<template v-else>
<diy-icon :icon="value.icon.refunding.icon" v-if="value.icon.refunding" :value="value.icon.refunding.style ? value.icon.refunding.style : null"></diy-icon>
</template>
<text v-if="orderNum.refunding > 0" class="order-num color-base-bg price-font">{{ orderNum.refunding > 99 ? '99+' : orderNum.refunding }}</text>
</view>
<view class="title">{{ $lang('activist') }}</view>
</view>
</view>
</view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 自定义会员中心——我的订单
export default {
name: 'diy-member-my-order',
props: {
value: {
type: Object
}
},
data() {
return {
orderNum: {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
}
};
},
created() {
this.init();
},
watch: {
storeToken(nVal, oVal) {
this.init();
},
// 组件刷新监听
componentRefresh: function(nval) {
this.init();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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: {
init() {
if (this.storeToken) {
this.getOrderNum();
} else {
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
};
}
},
/**
* 获取订单数量
*/
getOrderNum() {
this.$api.sendRequest({
url: '/api/order/num',
data: {
order_status: 'waitpay,waitsend,waitconfirm,refunding,wait_use,waitrate'
},
success: res => {
if (res.code == 0) {
this.orderNum = res.data;
} else {
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0,
waitrate: 0
};
}
}
});
},
/**
* 跳转
* @param {Object} url
*/
redirect(url) {
if (this.storeToken) {
this.$util.redirectTo(url);
} else {
this.$refs.login.open(url);
}
}
}
};
</script>
<style lang="scss">
.common-wrap {
width: 100%;
box-sizing: border-box;
}
.order-wrap {
.status-wrap {
display: flex;
padding: 30rpx 0;
align-items: center;
justify-content: center;
color: #333;
}
.item-wrap {
flex: 1;
text-align: center;
background: #f6f7f9;
padding: 20rpx 0;
.icon-block {
width: 60rpx;
height: 60rpx;
font-size: 60rpx;
margin: 4rpx auto;
position: relative;
&>image {
position: absolute;
top: 5%;
right: 5%;
width: 90%;
height: 90%;
z-index: 5;
}
.icon-shade {
width: 90%;
height: 90%;
position: absolute;
z-index: 4;
top: 5%;
right: 5%;
background: $base-color;
-webkit-mask: no-repeat center / contain;
}
}
.order-num {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
box-sizing: border-box;
color: #ffffff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 6rpx;
min-width: 30rpx;
border-radius: 16rpx;
height: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 26rpx;
}
}
}
</style>

View File

@@ -0,0 +1,350 @@
<template>
<view>
<x-skeleton type="list" :loading="loading" :configs="skeletonConfig" v-if="value.ornament.type == 'default'">
<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="toDetail(item)">
<view class="merch-img">
<image class="cover-img" :src="$util.img(item.merch_image)" mode="widthFix" @error="imgError(index)" />
</view>
<view class="info-wrap">
<text class="title">{{ item.merch_name }}</text>
<text class="desc">{{ item.desc }}</text>
<view class="read-wrap"></view>
</view>
</view>
</view>
</view>
</x-skeleton>
<view v-else :style="warpCss">
<scroll-view :scroll-x="true" :class="['merch-nav', 'singleSlide' ]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="merch-nav-item graphic" v-for="(item, index) in list" :key="index" :style="{ width: 100 / 4 + '%' }" @click="toDetail(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"
style="max-width: 80rpx; max-height: 80rpx; border-radius: 50rpx;"
/>
</view>
<text class="graphic-text" style="font-size: 28rpx; font-weight: normal; color: rgb(48, 49, 51);">
{{ item.merch_name }}
</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
</view>
</view>
</template>
<script>
// 文章
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
data() {
return {
list: [
/*{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
},
{
merch_name:'品牌',
merch_image:'http://saas.cn//upload/4/common/images/20240824/20240824040223172448654356147.jpg'
}*/
],
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '40rpx',
headWidth: '160rpx',
headHeight: '160rpx',
textRows: 2
}
};
},
created() {
this.getList();
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
this.getList();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;';
}
console.log(obj)
return obj;
},
// 子项样式
itemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color;
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color;
}
return obj;
}
},
methods: {
getList() {
console.log(121)
var data = {
num: this.value.count
};
console.log(this.value)
if (this.value.sources == 'diy') {
data.num = 0;
data.merch_id_arr = this.value.merchIds.toString();
}
this.$api.sendRequest({
url: '/merch/api/merch/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
this.list = data;
}
this.loading = false;
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages_promotion/merch/detail', {
merch_id: item.merch_id
});
},
imgError(index) {
if (this.list[index]) this.list[index].merch_image = this.$util.getDefaultImage().article;
}
}
};
</script>
<style>
/* 单行滑动 */
.merch-nav.singleSlide>>>.uni-scroll-view-content {
display: flex;
}
</style>
<style lang="scss">
.merch-nav {
padding: 16rpx;
box-sizing: border-box;
&.singleSlide {
.merch-nav-item {
flex-shrink: 0;
}
}
&.pageSlide {
position: relative;
.merch-nav-wrap {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
}
}
.merch-nav-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 14rpx 0;
box-sizing: border-box;
.graphic-text {
padding-top: 12rpx;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
text-align: center;
&.alone {
padding-top: 0;
}
}
&.text {
.graphic-text {
padding-top: 0;
}
}
.graphic-img {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
font-size: 24rpx;
.tag {
position: absolute;
top: -10rpx;
right: -24rpx;
color: #fff;
border-radius: 24rpx;
border-bottom-left-radius: 0;
transform: scale(0.8);
padding: 8rpx 16rpx;
line-height: 1;
font-size: 24rpx;
}
.icon {
font-size: 50rpx;
color: $color-sub;
}
}
}
}
.merch-wrap {
.list-wrap {
&.style-1 {
.item {
display: flex;
padding: 20rpx;
margin-top: 24rpx;
&:first-of-type {
margin-top: 0;
}
.merch-img {
margin-right: 20rpx;
width: 160rpx;
height: 160rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
}
}
.info-wrap {
// flex: 1;
// display: flex;
// flex-direction: column;
// justify-content: space-between;
.desc{
color:#888
}
.title {
font-weight: bold;
// margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 30rpx;
line-height: 1.5;
}
.abstract {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: $font-size-tag;
}
.read-wrap {
display: flex;
color: #999ca7;
justify-content: flex-start;
align-items: center;
margin-top: 10rpx;
line-height: 1;
text {
font-size: $font-size-tag;
}
.iconfont {
font-size: 36rpx;
vertical-align: bottom;
margin-right: 10rpx;
}
.category-icon {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: $base-color;
margin-right: 10rpx;
}
.date {
margin-left: 20rpx;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,351 @@
<template>
<x-skeleton type="list" :loading="loading" :configs="skeletonConfig">
<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>
</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="toDetail(item.note_id)"
:style="notesItemStyle">
<view class="notes-item" v-if="item.status == 1">
<view class="notes-item-con">
<view class="notes-title">{{ item.note_title }}</view>
<view class="notes-highlights-list" v-if="value.notesLabel == 1 && item.goods_highlights">
<text class="color-base-bg" v-for="(labelItem, labelIndex) in item.label" :key="labelIndex">{{ labelItem }}</text>
</view>
<view class="notes-intro">
<text class="notes-label color-base-text">#{{ item.note_type == 'goods_item' ? '单品介绍' : '掌柜说' }}#</text>
{{ item.note_abstract }}
</view>
</view>
<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"/>
<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"/>
</view>
<view class="notes-item-con">
<view class="notes-info" v-if="(value.readNum == 1 && item.is_show_read_num == 1) || (value.uploadTime == 1 && item.is_show_release_time == 1)">
<view class="notes-num">
<text v-show="value.uploadTime == 1 && item.is_show_release_time == 1">{{ item.update_time_day }}</text>
</view>
<view class="notes-num">
<text v-show="value.readNum == 1 && item.is_show_read_num == 1">阅读 {{ item.initial_read_num + item.read_num }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-notes',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonConfig: {
itemDirection: 'column',
headWidth: '100%',
headHeight: '320rpx',
textRows: 2,
textWidth: ['100%', '80%']
},
dataList: [],
giveLikeFlag: false
};
},
created() {
this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getDataList();
}
},
computed: {
notesItemStyle() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
methods: {
refresh() {
this.getDataList();
},
getDataList() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.note_id_arr = this.value.noteId.toString();
}
this.$api.sendRequest({
url: '/notes/api/notes/lists',
data: data,
success: res => {
var data = res.data;
this.dataList = [];
if (data) {
for (var i = 0; i < data.length; i++) {
var item = {};
item = data[i];
if (data[i].cover_type == 1) {
item.img = data[i].cover_img.split(',');
} else {
item.img = data[i].cover_img;
}
if (data[i].upload_time) {
item.update_time_day = this.$util.timeStampTurnTime(data[i].upload_time)
.split(' ')[0];
} else {
item.update_time_day = this.$util.timeStampTurnTime(data[i].create_time)
.split(' ')[0];
}
item.label = data[i].goods_highlights.split(',');
this.dataList.push(item);
}
}
this.loading = false;
}
});
},
toMore() {
this.$util.redirectTo('/pages_tool/store_notes/note_list');
},
toDetail(id) {
this.$util.redirectTo('/pages_tool/store_notes/note_detail', {
note_id: id
});
},
/* 点赞 */
giveLike(noteId, index) {
if (!this.storeToken) {
this.$refs.login.open('/pages/index/index');
return;
}
if (this.giveLikeFlag) return false;
this.giveLikeFlag = true;
var url = this.dataList[index].is_dianzan == 1 ? '/notes/api/record/delete' : '/notes/api/record/add';
this.$api.sendRequest({
url: url,
data: {
note_id: noteId
},
success: res => {
this.giveLikeFlag = false;
if (res.code == 0 && res.data > 0) {
if (this.noteType != 'goods_item')
this.dataList[index].dianzan_num = this.dataList[index].is_dianzan == 1 ? this.dataList[index].dianzan_num - 1 : this.dataList[index].dianzan_num + 1;
else {
this.dataList[index].dianzan_num = this.dataList[index].is_dianzan == 1 ? this.dataList[index].dianzan_num - 1 : this.dataList[index].dianzan_num + 1;
}
this.dataList[index].is_dianzan = this.dataList[index].is_dianzan == 1 ? 0 : 1;
} else {
this.$util.showToast({
title: res.message
});
}
}
});
},
imageError(index) {
this.dataList[index].img = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-notes {
width: 100%;
box-sizing: border-box;
}
.diy-notes-top {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
.notes-title {
width: 70%;
font-size: 28rpx;
font-weight: 600;
}
.notes-more {
font-size: $font-size-tag;
color: #858585;
}
.notes-more::after {
font-family: 'iconfont';
content: '\e6a3';
font-size: $font-size-tag;
line-height: 1;
position: relative;
margin-left: 4rpx;
}
}
.diy-notes-box {
width: 100%;
padding: 30rpx 0 20rpx;
}
.notes-box-item {
width: calc(100% - 8rpx);
margin: 0 auto;
height: 100%;
margin-bottom: 30rpx;
border-radius: 10rpx;
overflow: hidden;
-moz-box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
-webkit-box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.02);
.notes-item {
width: 100%;
height: 100%;
padding: 20rpx;
box-sizing: border-box;
}
.notes-img-wrap {
position: relative;
height: 300rpx;
.notes-item-image {
width: 100%;
height: 300rpx;
object-fit: cover;
}
.notes-label {
display: inline-block;
position: absolute;
left: 20rpx;
bottom: 20rpx;
max-width: calc(100vh - 40rpx);
background-color: #ffffff;
line-height: 36rpx;
padding: 0 10rpx 0 4rpx;
}
}
.notes-img-wrap-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
height: auto;
image {
width: calc((100% - 40rpx) / 3);
height: 210rpx;
margin-top: 20rpx;
&:nth-child(-n + 3) {
margin-top: 0;
}
}
&:after {
content: '';
width: calc((100% - 40rpx) / 3);
}
}
.notes-item-con {
.notes-title {
font-size: 30rpx;
font-weight: 600;
line-height: 44rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.notes-highlights-list {
text {
display: inline-block;
color: #ffffff;
font-size: 24rpx;
line-height: 36rpx;
padding: 0 10rpx;
border-radius: 4rpx;
margin: 0 5rpx;
}
}
.notes-intro {
margin: 4rpx 0 8rpx;
line-height: 40rpx;
overflow: hidden;
text {
float: left;
margin-right: 16rpx;
}
}
.notes-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
.notes-num {
>text {
display: flex;
align-items: center;
font-size: $font-size-tag;
color: #969799;
text.iconfont {
font-size: 26rpx;
margin-right: 6rpx;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,300 @@
<template>
<view class="diy-notice">
<view :class="['notice', value.contentStyle]" :style="noticeWrapCss">
<image v-if="value.iconType == 'img'" class="notice-img" :src="$util.img(value.imageUrl)" mode="heightFix"/>
<diy-icon v-if="value.iconType == 'icon'" :icon="value.icon" :value="value.style ? value.style : 'null'" :style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx', width: '100%', height: '100%' }"></diy-icon>
<view class="notice-xian"></view>
<view class="main-wrap">
<!-- 横向滚动 -->
<view class="horizontal-wrap" v-if="value.scrollWay == 'horizontal'">
<view class="marquee-wrap">
<view class="marquee" :style="marqueeStyle">
<text v-for="(item, index) in list" :key="index" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ item.title }}</text>
</view>
<view class="marquee" :style="marqueeAgainStyle">
<text v-for="(item, index) in list" :key="index" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 上下滚动 -->
<template v-if="value.scrollWay == 'upDown'">
<swiper :vertical="true" :duration="500" autoplay="true" circular="true">
<swiper-item v-for="(item, index) in list" :key="index" @touchmove.prevent.stop>
<text class="beyond-hiding using-hidden" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">
{{ item.title }}
</text>
</swiper-item>
</swiper>
</template>
</view>
</view>
<view @touchmove.prevent.stop>
<uni-popup ref="noticePopup" type="center">
<view class="notice-popup">
<view class="head-wrap" @click="closeNoticePopup">
<text>公告</text>
<text class="iconfont icon-close"></text>
</view>
<view class="content-wrap">{{ notice }}</view>
<button type="primary" @click="closeNoticePopup">我知道了</button>
</view>
</uni-popup>
</view>
</view>
</template>
<script>
// 公告
export default {
name: 'diy-notice',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
notice: '', // 当前点击的弹框内容
marqueeWrapWidth: 0, // 容器宽度
marqueeWidth: 0, // 公告内容累加宽度
marqueeStyle: '', // 横向滚动样式
marqueeAgainStyle: '', // 横向滚动复制样式
time: 0, // 滚动完成时间
delayTime: 1000 // 动画延迟时间
};
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
if (this.value.sources == 'initial') this.getData();
}
},
created() {},
mounted() {
// 数据源:公告系统
if (this.value.sources == 'initial') {
this.getData();
} else {
this.list = this.value.list;
this.bindCrossSlipEvent();
}
},
computed: {
noticeWrapCss: function() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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: {
getData() {
var data = {
page_size: 0
};
if (this.value.sources == 'initial') {
data.page_size = this.value.count;
}
if (this.value.noticeIds.length) {
data.id_arr = this.value.noticeIds.toString();
data.page_size = 0;
}
this.$api.sendRequest({
url: '/api/notice/page',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data.list;
this.bindCrossSlipEvent();
}
}
});
},
toLink(item) {
if (this.value.sources == 'initial') {
this.$util.redirectTo('/pages_tool/notice/detail', {
notice_id: item.id
});
} else if (!item) {
this.$util.redirectTo('/pages_tool/notice/list');
} else if (Object.keys(item.link).length > 1) {
this.$util.diyRedirectTo(item.link);
} else {
// 如果不设置跳转链接,则点击弹框展示
this.notice = item.title;
this.$refs.noticePopup.open();
}
},
closeNoticePopup() {
this.$refs.noticePopup.close();
},
// 绑定横向滚动事件
bindCrossSlipEvent() {
if (this.value.scrollWay == 'horizontal') {
setTimeout(() => {
this.$nextTick(() => {
uni.createSelectorQuery()
.in(this)
.select('.marquee-wrap')
.boundingClientRect(res => {
this.marqueeWrapWidth = res.width;
const query = uni.createSelectorQuery().in(this);
query
.select('.marquee')
.boundingClientRect(data => {
this.marqueeWidth = data.width + 30; // 30px是间距
this.time = Math.ceil(this.marqueeWidth * 10);
if (this.marqueeWrapWidth > this.marqueeWidth) {
this.marqueeStyle = `animation: none;`;
this.marqueeAgainStyle = 'display:none;';
} else {
this.marqueeStyle = `
width: ${this.marqueeWidth}px;
animation-duration: ${this.time}ms;
animation-delay: ${this.delayTime}ms;
`;
this.marqueeAgainStyle = `
width: ${this.marqueeWidth}px;
left: ${this.marqueeWidth}px;
animation-duration: ${this.time}ms;
animation-delay: ${this.delayTime}ms;
`;
}
})
.exec();
})
.exec();
});
});
}
}
}
};
</script>
<style lang="scss">
.notice {
height: 80rpx;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
padding: 20rpx 0 20rpx 20rpx;
font-size: 70rpx;
box-sizing: border-box;
.notice-img {
width: 44rpx;
height: 80rpx;
}
.notice-xian {
width: 1rpx;
height: 26rpx;
background-color: #e4e4e4;
margin: 0 22rpx;
}
}
.main-wrap {
display: inline-block;
width: calc(100% - 115rpx);
position: relative;
}
swiper {
height: 50rpx;
}
.beyond-hiding {
display: inline-block;
width: 100%;
white-space: nowrap;
}
.notice-popup {
padding: 0 30rpx 40rpx;
background-color: #fff;
.head-wrap {
font-size: $font-size-toolbar;
line-height: 100rpx;
height: 100rpx;
display: block;
text-align: center;
position: relative;
border-bottom: 2rpx solid $color-line;
margin-bottom: 20rpx;
.iconfont {
position: absolute;
float: right;
right: 0;
font-size: $font-size-toolbar;
}
}
.content-wrap {
max-height: 600rpx;
overflow-y: auto;
}
button {
margin-top: 40rpx;
}
}
.horizontal-wrap {
height: 30px;
line-height: 30px;
position: relative;
overflow: hidden;
width: 100%;
}
.marquee-wrap {
display: inline-block;
width: 100%;
height: 100%;
vertical-align: middle;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
.marquee {
display: flex;
position: absolute;
white-space: nowrap;
animation: marquee 0s 0s linear infinite;
text {
margin-left: 40rpx;
&:first-child {
margin-left: 0;
}
}
}
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100%);
}
}
</style>

View File

@@ -0,0 +1,203 @@
<template>
<view class="payment-qrocde-wrap" :style="warpCss">
<view class="payment-qrocde-box">
<view class="qrocde-left">
<view class="qrocde-desc">
<text>门店消费时使用支付时点击下方展示付款码</text>
<!-- <text class="iconfont icon-shuaxin"></text> -->
</view>
<view class="qrocde-action">
<button type="primary" @click="toLink">
<text class="iconfont icon-fukuanma"></text>
<text class="action-name">付款码</text>
</button>
<button type="primary" @click="openPaymentPopup">
<text class="iconfont icon-saomafu"></text>
<text class="action-name">扫码付</text>
</button>
</view>
</view>
<view class="qrocde-right">
<text class="iconfont icon-zhifu"></text>
<text class="name">门店支付</text>
</view>
</view>
<view @touchmove.prevent.stop>
<uni-popup ref="paymentPopup" type="center">
<view class="payment-popup">
<view class="head-wrap" @click="closePaymentPopup">
<text>提示</text>
<text class="iconfont icon-close"></text>
</view>
<view class="content-wrap">扫码付请退出程序后直接使用微信扫一扫或返回上一页使用付款码进行支付</view>
<button type="primary" @click="closePaymentPopup">我知道了</button>
</view>
</uni-popup>
</view>
</view>
</template>
<script>
// 付款码
export default {
name: 'diy-payment-qrcode',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() {},
computed: {
warpCss() {
var obj = '';
return obj;
}
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
methods: {
toLink() {
this.$util.redirectTo('/pages_tool/store/payment_qrcode');
},
scanCodeFn() {
// #ifdef APP-PLUS
this.toLink();
// #endif
// #ifndef H5
// 允许从相机和相册扫码,h5端不起作用
uni.scanCode({
success: function(res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
}
});
// #endif
},
openPaymentPopup() {
this.$refs.paymentPopup.open();
},
closePaymentPopup() {
this.$refs.paymentPopup.close();
}
}
};
</script>
<style lang="scss">
.payment-qrocde-box {
overflow: hidden;
display: flex;
background-color: #fff;
border-radius: 16rpx;
.qrocde-left {
flex: 1;
.qrocde-desc {
margin-top: 20rpx;
margin-bottom: 26rpx;
display: flex;
align-items: center;
justify-content: center;
color: $color-tip;
font-size: $font-size-tag;
.iconfont {
margin-left: 10rpx;
font-size: $font-size-tag;
font-weight: bold;
}
}
.qrocde-action {
padding-bottom: 36rpx;
display: flex;
justify-content: center;
button {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
width: 230rpx;
height: 86rpx;
border-radius: 50rpx;
&:first-of-type {
margin-right: 46rpx;
background-color: $base-color;
}
&:last-of-type {
background-color: #999;
}
.iconfont {
margin-right: 10rpx;
}
.action-name {
font-size: 30rpx;
}
}
}
}
.qrocde-right {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-left: 16rpx;
width: 90rpx;
z-index: 2;
box-sizing: border-box;
.name {
font-size: $font-size-sub;
writing-mode: tb-rl;
color: #fff;
letter-spacing: 6rpx;
}
.iconfont {
color: #fff;
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 500rpx;
height: 500rpx;
border-radius: 50%;
background-color: $base-color;
transform: translateY(-50%);
z-index: -1;
}
}
}
.payment-popup {
padding: 0 30rpx 40rpx;
background-color: #fff;
.head-wrap {
font-size: $font-size-toolbar;
line-height: 100rpx;
height: 100rpx;
display: block;
text-align: center;
position: relative;
border-bottom: 2rpx solid $color-line;
margin-bottom: 20rpx;
.iconfont {
position: absolute;
float: right;
right: 0;
font-size: $font-size-toolbar;
}
}
.content-wrap {
max-height: 600rpx;
overflow-y: auto;
}
button {
margin-top: 40rpx;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<view class="diy-picture" :style="style">
<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="redirectTo(item.link)"></image>
<image mode="widthFix" style="width: 100%;height:auto" :src="$util.img(item.imageUrl)" v-else @click="previewImg(item.imageUrl)"></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>
</view> -->
</view>
</view>
</template>
<script>
export default {
name: 'diy-picture',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
};
},
created() {
// this.getDataList();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
// this.getDataList();
}
},
computed: {
style() {
var css = '';
css += 'background-color:' + this.value.contentBgColor + ';';
if (this.value.elementAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
return css;
}
},
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() == 'pages/member/index' && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
}
};
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,540 @@
<template>
<x-skeleton :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)" :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)"/>
</view>
<view class="content" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="tag-wrap" v-if="value.groupStyle.control || value.saleStyle.control">
<view v-if="value.groupStyle.control" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '', borderColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
<text class="iconfont icon-yonghu3" :style="{ backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }"></text>
<text>{{ item.pintuan_num }}人团</text>
</view>
<view v-if="value.saleStyle.control" :style="{ color: value.theme == 'diy' ? value.saleStyle.color : '', borderColor: value.theme == 'diy' ? value.saleStyle.color : '' }">
<text>已拼{{ item.order_num }}</text>
</view>
</view>
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ item.pintuan_price.split(".")[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ "."+item.pintuan_price.split(".")[1] }}</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
<button v-if="value.btnStyle.control" :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'
}">
<text class="text">{{ value.btnStyle.text }}</text>
<text class="fan" v-if="item.reward_type == 4">{{ item.reward_type_num }}积分</text>
<text class="fan" v-if="item.reward_type == 1 || item.reward_type == 2">{{ item.reward_type_num }}</text>
<text class="fan" v-if="item.reward_type == 3">返优惠券</text>
</button>
</view>
</view>
</view>
</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)" :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)"/>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view v-if="value.groupStyle.control" class="num">
<text class="content-tuan-box" :style="{ color: value.theme == 'diy' ? value.groupStyle.color : '', backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
{{ item.pintuan_num }}人团
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 4">
{{ item.reward_type_num }}积分
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 1 || item.reward_type == 2">
{{ item.reward_type_num }}
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 3">返优惠券</text>
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ item.pintuan_price.split('.')[0] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ "."+item.pintuan_price.split('.')[1] }}</text>
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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)"/>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view v-if="value.groupStyle.control" class="num">
<text class="content-tuan-box" :style="{ color: value.theme == 'diy' ? value.groupStyle.color : '', backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColor : '' }">
{{ item.pintuan_num }}人团
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 4">
{{ item.reward_type_num }}积分
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 1 || item.reward_type == 2">
{{ item.reward_type_num }}
</text>
<text class="content-tuan-price" :style="{ color: value.theme == 'diy' ? value.groupStyle.bgColor : '' }" v-if="item.reward_type == 3">
返优惠券
</text>
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ item.pintuan_price.split('.')[1] }}</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ "."+item.pintuan_price.split('.')[1] }}</text>
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-pinfan',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonType:'',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = "";
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy')
width = this.rpxUpPx(this.value.goodsMarginNum * 2);
else
width = [screenWidth - (this.rpxUpPx(20) * 2) - (this.rpxUpPx(200) * 3) - (this.rpxUpPx(this.value
.margin.both * 2) * 2)] / 6;
obj += 'margin-left:' + width + "px;";
obj += 'margin-right:' + width + "px;";
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple')
return this.value.ornament.type == 'shadow' ? '444rpx' : '432rpx';
return this.value.ornament.type == 'shadow' ? '404rpx' : '396rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 3
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = screenWidth * parseInt(res) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/pinfan/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/pinfan/detail', {
pinfan_id: e.pintuan_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-pinfan {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 6rpx 0;
.goods-name {
line-height: 1.3;
}
.tag-wrap {
display: flex;
align-items: center;
margin-top: 4rpx;
>view {
height: 30rpx;
line-height: 30rpx;
border: 2rpx solid $base-color;
border-radius: 6rpx;
margin-right: 10rpx;
font-size: $font-size-activity-tag;
color: $base-color;
.iconfont {
display: inline-block;
width: 30rpx;
height: 30rpx;
font-size: $font-size-activity-tag;
color: #ffffff;
text-align: center;
margin-right: -6rpx;
background: $base-color;
}
text:last-child {
padding: 0 10rpx;
}
}
}
.price-wrap {
display: flex;
align-items: flex-end;
>view:last-of-type {
flex: 1;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
.unit {
font-size: $font-size-tag;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
text-decoration: line-through;
margin: 0 10rpx 4rpx 10rpx;
}
}
button {
bottom: 10rpx;
height: 90rpx;
line-height: 40rpx;
padding: 0 16rpx;
margin: 0;
text-align: center;
background-color: $base-color;
color: #ffffff;
.text {
display: block;
margin-top: 6rpx;
font-size: $font-size-tag;
}
.fan {
font-size: $font-size-activity-tag;
}
}
}
}
}
&.horizontal-slide {
display: flex;
justify-content: space-around;
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n+3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 188rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.num {
margin-top: auto;
font-size: $font-size-activity-tag;
.content-tuan-box {
border-radius: 4rpx;
color: #ffffff;
background-color: $base-color;
padding: 6rpx;
}
.content-tuan-price {
border-radius: 4rpx;
padding: 4rpx 6rpx;
border: 2rpx solid $base-color;
color: $base-color;
margin-left: -2rpx;
}
}
.price-wrap {
white-space: nowrap;
margin-top: 10rpx;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
height: 32rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
}
}
.swiper {
width: 100%;
white-space: nowrap;
padding: 20rpx;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,776 @@
<template>
<x-skeleton :type="skeletonType" :loading="loading" :configs="skeletonConfig">
<view class="diy-pintuan" :class="[value.template, value.style]" :style="warpCss">
<view v-if="value.titleStyle.isShow && list && list.length" :class="[value.titleStyle.style, 'pintuan-head']" :style="{
backgroundImage:
'url(' + $util.img(value.titleStyle.backgroundImage) + '), linear-gradient(to right,' + value.titleStyle.bgColorStart + ',' + value.titleStyle.bgColorEnd + ')'
}">
<view v-if="value.titleStyle.leftStyle == 'text'" class="left-text" :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"/>
<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)"/>
</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"/>
</block>
</view>
<view :style="{ color: value.titleStyle.textColor }">
<text>{{ value.titleStyle.virtualNum || headData.pintuan_count }}</text>
<text>{{ value.titleStyle.style == 'style-2' ? '人参与' : '人拼团成功' }}</text>
</view>
</view>
<view class="head-right" :style="{ fontSize: value.titleStyle.moreFontSize * 2 + 'rpx', color: value.titleStyle.moreColor }" @click="$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)" :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)"/>
</view>
<view class="content" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.priceStyle.lineControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="tag-wrap" v-if="value.groupStyle.control || value.saleStyle.control">
<view v-if="value.groupStyle.control" :style="{
color: value.theme == 'diy' ? value.groupStyle.bgColorStart : '',
borderColor: value.theme == 'diy' ? value.groupStyle.bgColorStart : ''
}">
<text class="iconfont icon-yonghu3" :style="{ backgroundColor: value.theme == 'diy' ? value.groupStyle.bgColorStart : '' }"></text>
<text>{{ item.pintuan_num }}人团</text>
</view>
<view v-if="value.saleStyle.control"
:style="{ color: value.theme == 'diy' ? value.saleStyle.color : '', borderColor: value.theme == 'diy' ? value.saleStyle.color : '' }">
<text>已拼{{ item.order_num }}</text>
</view>
</view>
<view class="bottom-wrap">
<view class="price-wrap">
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="original-price price-font" v-if="value.priceStyle.lineControl" :style="{ color: value.theme == 'diy' ? value.priceStyle.lineColor : '' }">
¥{{ item.price }}
</view>
</view>
<button v-if="value.btnStyle.control" :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'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</view>
</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)" :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)"/>
<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 + ')' : ''
}">
{{ item.pintuan_num }}人团
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view
v-if="value.goodsNameStyle.control && (value.style == 'style-1' || value.style == 'style-3')"
class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl && value.style != 'style-3'">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="other-info-wrap" v-if="value.style == 'style-3'">
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="sale-action">去参团</view>
</view>
<view class="sale-num" v-if="value.style == 'style-2'">已拼{{ item.sale_num }}</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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)"/>
<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 + ')' : ''
}">
{{ item.pintuan_num }}人团
</view>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control && value.style != 'style-2'" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="price-wrap" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ item.pintuan_price.split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + item.pintuan_price.split('.')[1] }}
</text>
</view>
<view class="sale-num" v-if="value.style == 'style-2'">已拼{{ item.sale_num }}</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-pintuan',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonType:'',
skeletonConfig: {},
list: [],
page: 1,
scrollWidth: 0,
headData: []
};
},
created() {
this.initSkeleton();
this.getData();
this.getHeadData();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
this.getHeadData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else if (this.value.template == 'horizontal-slide' && this.value.style == 'style-2')
width = [screenWidth - this.rpxUpPx(212) * 3 - this.rpxUpPx(this.value.margin.both * 2) * 2] / 6;
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-left:' + width + 'px;';
obj += 'margin-right:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.template == 'horizontal-slide' && this.value.style == 'style-2') {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '360rpx';
else return '342rpx';
}
if (this.value.ornament.type == 'shadow') return '324rpx';
else return '308rpx';
} else {
if (this.value.nameLineMode == 'multiple') {
if (this.value.ornament.type == 'shadow') return '400rpx';
else return '382rpx';
}
if (this.value.ornament.type == 'shadow') return '364rpx';
else return '348rpx';
}
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 3
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getHeadData() {
var data = {
num: 3
};
this.$api.sendRequest({
url: '/pintuan/api/order/pintuanmember',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.headData = res.data;
}
}
});
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 6;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/pintuan/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/pintuan/detail', {
pintuan_id: e.pintuan_id
});
},
imageError(index) {
if (this.list[index]) this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
headImageError(index) {
this.headData.member_list[index].member_img = this.$util.img('public/static/img/default_img/square.png');
this.$forceUpdate();
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-pintuan {
overflow: hidden;
.pintuan-head {
padding: 0 24rpx;
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
box-sizing: border-box;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20rpx;
border-radius: 18rpx 18rpx 0 0;
.left-img {
width: 174rpx;
height: 40rpx;
}
.head-content {
display: flex;
align-items: center;
margin-left: 50rpx;
margin-right: auto;
color: #fff;
.img-warp {
position: relative;
margin-right: 8rpx;
line-height: 1;
image {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #ff3d3d;
margin-left: -14rpx;
}
&:after {
content: '';
width: 2rpx;
height: 28rpx;
background-color: #fff;
position: absolute;
left: -32rpx;
top: 50%;
transform: translateY(-50%);
}
}
}
.head-right {
display: flex;
align-items: center;
font-size: $font-size-sub;
color: #fff;
line-height: 1;
}
&.style-2 {
.head-right {
display: flex;
align-items: center;
justify-content: center;
height: 36rpx;
background: linear-gradient(270deg, #ffbd5b 0%, #fd882e 100%);
border-radius: 24rpx;
padding: 2rpx;
text:nth-child(1) {
position: relative;
left: 6rpx;
transform: scale(0.9);
}
text:nth-child(2) {
padding: 6rpx 4rpx 4rpx;
background-color: #fff;
color: #ffbd5b;
border-radius: 50%;
transform: scale(0.6);
font-weight: bold;
line-height: 1;
}
}
}
}
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 6rpx 0;
.tag-wrap {
display: flex;
align-items: center;
>view {
height: 30rpx;
line-height: 30rpx;
border: 2rpx solid $base-color;
border-radius: 6rpx;
margin-right: 10rpx;
font-size: $font-size-activity-tag;
color: $base-color;
.iconfont {
display: inline-block;
width: 30rpx;
height: 30rpx;
font-size: $font-size-activity-tag;
color: #ffffff;
text-align: center;
margin-right: -6rpx;
background: $base-color;
}
text:last-child {
padding: 0 10rpx;
}
}
}
.goods-name {
line-height: 1.3;
}
.bottom-wrap {
display: flex;
align-items: center;
justify-content: space-between;
}
.price-wrap {
overflow: hidden;
width: 260rpx;
display: flex;
align-items: flex-end;
flex-wrap: wrap;
>view:last-of-type {
flex: 1;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
.original-price {
font-size: $font-size-tag;
color: $color-tip;
line-height: 1;
text-decoration: line-through;
margin: 0 10rpx;
}
}
button {
margin-right: 20rpx;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
padding: 0 20rpx;
margin: 0;
color: var(--btn-text-color);
background-color: $base-color;
color: #fff;
font-size: 24rpx;
}
}
}
}
&.horizontal-slide {
display: flex;
flex-direction: column;
.flex-between {
justify-content: space-between;
}
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 4rpx;
}
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
flex-shrink: 0;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
position: relative;
overflow: hidden;
margin: 0 auto;
>image {
width: 200rpx;
}
.num {
width: 80rpx;
position: absolute;
bottom: 0;
font-size: $font-size-tag;
line-height: 1;
color: #ffffff;
text-align: center;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
padding: 10rpx;
transform: translate(50%);
background: $base-color;
}
}
.content {
padding: 10rpx 10rpx 16rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 142rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
font-size: $font-size-base;
&.multi-hidden {
white-space: break-spaces;
}
}
.price-wrap {
line-height: 1;
white-space: nowrap;
margin-top: 10rpx;
font-weight: bold;
.unit {
font-size: $font-size-tag;
height: 32rpx;
color: var(--price-color);
}
.price {
font-size: $font-size-toolbar;
color: var(--price-color);
}
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
box-sizing: border-box;
}
}
&.style-2 {
.item {
width: 212rpx;
&:nth-child(3n + 3) {
width: 210rpx;
}
.img-wrap {
width: 212rpx;
height: 212rpx;
>image {
width: 212rpx !important;
}
}
.price-wrap {
text-align: center;
margin-top: 0 !important;
margin-bottom: 10rpx;
}
.sale-num {
text-align: center;
color: #666666;
line-height: 1;
}
}
.scroll {
padding: 0;
width: auto;
}
.swiper {
padding: 0;
}
}
&.style-3 {
.other-info-wrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.sale-action {
font-size: $font-size-tag;
width: 94rpx;
height: 44rpx;
background: linear-gradient(131deg, #3edb73 0%, #1db576 100%);
border-radius: 8rpx;
color: #fff;
text-align: center;
line-height: 44rpx;
transform: scale(0.9);
}
}
}
}
</style>

View File

@@ -0,0 +1,435 @@
<template>
<x-skeleton :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="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)"/>
</view>
<view class="content" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl || value.btnStyle.control">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
<button v-if="value.btnStyle.control" :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'
}">
{{ value.btnStyle.text }}
</button>
</view>
</view>
</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)" :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)"/>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
</view>
</view>
</scroll-view>
<swiper v-if="value.slideMode == 'slide'" :autoplay="false" class="swiper" :style="{ height: swiperHeight }">
<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">
<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)"/>
</view>
<view :class="['content', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="value.goodsNameStyle.control || value.priceStyle.mainControl">
<view v-if="value.goodsNameStyle.control" class="goods-name"
:style="{ color: value.theme == 'diy' ? value.goodsNameStyle.color : '', fontWeight: value.goodsNameStyle.fontWeight ? 'bold' : '' }"
:class="[{ 'using-hidden': value.nameLineMode == 'single' }, { 'multi-hidden': value.nameLineMode == 'multiple' }]">
{{ item.goods_name }}
</view>
<view class="discount-price" v-if="value.priceStyle.mainControl">
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">¥</text>
<text class="price price-style large" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ showPrice(item).split('.')[0] }}
</text>
<text class="unit price-style small" :style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor + '!important' : '' }">
{{ '.' + showPrice(item).split('.')[1] }}
</text>
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</x-skeleton>
</template>
<script>
export default {
name: 'diy-presale',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonType:'',
skeletonConfig: {},
list: [],
page: 1
};
},
created() {
this.initSkeleton();
this.getData();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
},
// 商品项样式
goodsItemCss() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.elementAngle == 'round') {
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
}
if (this.value.ornament.type == 'shadow') {
obj += 'box-shadow:' + '0 0 10rpx ' + this.value.ornament.color + ';';
}
if (this.value.ornament.type == 'stroke') {
obj += 'border:' + '2rpx solid ' + this.value.ornament.color + ';';
}
const screenWidth = uni.getSystemInfoSync().windowWidth;
if (this.value.template == 'horizontal-slide') {
var width = '';
if (this.value.slideMode == 'scroll' && this.value.goodsMarginType == 'diy') width = this.rpxUpPx(this
.value.goodsMarginNum * 2);
else width = [screenWidth - this.rpxUpPx(20) * 2 - this.rpxUpPx(200) * 3 - this.rpxUpPx(this.value
.margin.both * 2) * 2] / 6;
obj += 'margin-right:' + width + 'px;';
obj += 'margin-left:' + width + 'px;';
}
return obj;
},
swiperHeight() {
if (this.value.nameLineMode == 'multiple') {
return this.value.ornament.type == 'shadow' ? '390rpx' : '382rpx';
}
return this.value.ornament.type == 'shadow' ? '364rpx' : '348rpx';
}
},
methods: {
initSkeleton() {
if (this.value.template == 'row1-of1') {
// 单列 风格
this.skeletonType = 'list';
this.skeletonConfig = {
textRows: 2
};
} else if (this.value.template == 'horizontal-slide') {
// 横向滑动 风格
this.skeletonType = 'waterfall';
this.skeletonConfig = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
},
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().windowWidth;
var data = (screenWidth * parseInt(res)) / 750;
return Math.floor(data);
},
getData() {
var data = {
num: this.value.count
};
if (this.value.sources == 'diy') {
data.num = 0;
data.goods_id_arr = this.value.goodsId.toString();
}
this.$api.sendRequest({
url: '/presale/api/goods/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
// 切屏滚动,每页显示固定数量
if (this.value.template == 'horizontal-slide' && this.value.slideMode == 'slide') {
let size = 3;
let temp = [];
this.page = Math.ceil(this.list.length / size);
for (var i = 0; i < this.page; i++) {
temp[i] = [];
for (var j = i * size; j < this.list.length; j++) {
if (temp[i].length == size) break;
temp[i].push(this.list[j]);
}
}
this.list = temp;
}
}
this.loading = false;
}
});
},
toDetail(e) {
this.$util.redirectTo('/pages_promotion/presale/detail', {
id: e.presale_id
});
},
imageError(index) {
this.list[index].goods_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
showPrice(data) {
let price = data.price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
}
}
};
</script>
<style lang="scss">
/deep/.uni-scroll-view ::-webkit-scrollbar {
/* 隐藏滚动条,但依旧具备可以滚动的功能 */
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
/deep/::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
background: transparent;
}
scroll-view ::-webkit-scrollbar {
width: 0;
height: 0;
background-color: transparent;
touch-action: none;
}
.diy-presale {
&.row1-of1 {
.item {
display: flex;
margin-bottom: 20rpx;
padding: 16rpx;
&.shadow {
margin: 8rpx 8rpx 20rpx 8rpx;
}
&:last-child {
margin-bottom: 0;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
>image {
width: 200rpx;
}
}
.content {
flex: 1;
margin-left: 20rpx;
position: relative;
padding: 6rpx 0;
.goods-name {
line-height: 1.5;
}
.discount-price {
white-space: nowrap;
font-weight: bold;
position: absolute;
bottom: 10rpx;
left: 0;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
button {
position: absolute;
bottom: 10rpx;
right: 20rpx;
margin: 0;
padding: 0 20rpx;
background-color: $base-color;
color: #fff;
min-width: 112rpx;
height: 52rpx;
line-height: 52rpx;
font-size: $font-size-tag;
}
}
}
}
&.horizontal-slide {
.scroll {
width: calc(100% - 40rpx);
padding: 20rpx;
line-height: 1;
white-space: nowrap;
.item.shadow {
margin-bottom: 8rpx;
}
}
.flex-between {
justify-content: space-between;
}
.item {
display: inline-block;
width: 200rpx;
overflow: hidden;
box-sizing: border-box;
&:nth-child(3n + 3) {
width: 198rpx;
}
&.shadow {
margin-top: 8rpx;
}
.img-wrap {
width: 200rpx;
height: 200rpx;
margin: 0 auto;
>image {
width: 200rpx;
}
}
.content {
padding: 10rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&.multi-content {
height: 134rpx;
box-sizing: border-box;
}
.goods-name {
line-height: 1.3;
&.multi-hidden {
white-space: break-spaces;
}
}
.discount-price {
white-space: nowrap;
margin-top: auto;
font-weight: bold;
line-height: 1;
.unit {
font-size: $font-size-tag;
margin-right: 4rpx;
color: $base-color;
}
.price {
font-size: $font-size-toolbar;
color: $base-color;
}
}
}
}
.swiper {
padding: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
.swiper-item {
display: flex;
align-items: center;
}
.item {
width: 200rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<view :style="componentStyle">
<scroll-view class="quick-nav" scroll-x="true">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view
class="quick-nav-item"
v-for="(item, index) in value.list"
:key="index"
@click="redirectTo(item.link)"
: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>
<diy-icon v-if="item.iconType == 'icon'" :icon="item.icon" :value="item.style ? item.style : null" :style="{ fontSize: '60rpx' }"></diy-icon>
</view>
<text class="quick-text" :style="{ color: item.textColor }">{{ item.title }}</text>
</view>
<!-- #ifdef MP -->
</view>
<!-- #endif -->
</scroll-view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
export default {
name: 'diy-quick-nav',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
componentStyle() {
var css = '';
css += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.componentAngle == 'round') {
css += 'border-top-left-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-top-right-radius:' + this.value.topAroundRadius * 2 + 'rpx;';
css += 'border-bottom-left-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
css += 'border-bottom-right-radius:' + this.value.bottomAroundRadius * 2 + 'rpx;';
}
css += 'box-shadow:' + (this.value.ornament.type == 'shadow' ? '0 0 10rpx ' + this.value.ornament.color : '') + ';';
css += 'border:' + (this.value.ornament.type == 'stroke' ? '2rpx solid ' + this.value.ornament.color : '') + ';';
return css;
}
},
methods: {
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == 'pages/member/index' && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
}
}
};
</script>
<style>
.quick-nav >>> .uni-scroll-view-content {
display: flex;
}
</style>
<style lang="scss">
.quick-nav {
.quick-nav-item {
display: flex;
align-items: center;
padding: 0 18rpx;
box-sizing: border-box;
flex-shrink: 0;
border-radius: 40rpx;
margin-right: 20rpx;
height: 48rpx;
&:first-of-type{
padding-left: 12rpx;
}
&:last-child {
margin-right: 0;
}
.quick-img {
margin-right: 6rpx;
height: 30rpx;
line-height: 1;
image {
width: 30rpx;
height: 30rpx;
}
}
.quick-text {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
font-size: 24rpx;
line-height: 1;
}
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<view class="rich-text-box" :style="richTextWarpCss">
<rich-text :nodes="html"></rich-text>
</view>
</template>
<script>
// 富文本
import htmlParser from '@/common/js/html-parser';
export default {
name: 'diy-rich-text',
props: {
value: {
type: Object
}
},
data() {
return {
html: ''
};
},
created() {
this.html = htmlParser(this.value.html);
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
richTextWarpCss: function() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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;
}
},
mounted() {},
methods: {}
};
</script>
<style lang="scss">
.rich-text-box {
padding: $padding;
box-sizing: border-box;
height: auto;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-all;
}
</style>

View File

@@ -0,0 +1,514 @@
<template>
<view>
<!-- 自定义 -->
<view v-if="value.mode == 'custom-rubik-cube'">
<view style="position: relative;"><rich-text :nodes="customHtml"></rich-text></view>
</view>
<view v-else :class="['rubik-cube', value.mode]" :style="rubikCubeWrapCss">
<!-- 1左2右 -->
<template v-if="value.mode == 'row1-lt-of2-rt'">
<view class="template-left">
<view :class="['item', value.mode]" @click="$util.diyRedirectTo(value.list[0].link)" :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"/>
</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="$util.diyRedirectTo(item.link)" :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"/>
</view>
</template>
</template>
</view>
</template>
<!-- 1左3右 -->
<template v-else-if="value.mode == 'row1-lt-of1-tp-of2-bm'">
<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="$util.diyRedirectTo(value.list[0].link)">
<image :src="$util.img(value.list[0].imageUrl)" :mode="list[0].imageMode || 'scaleToFill'" :style="list[0].pageItemStyle" :show-menu-by-longpress="true"/>
</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="$util.diyRedirectTo(value.list[1].link)">
<image :src="$util.img(value.list[1].imageUrl)" :mode="list[1].imageMode || 'scaleToFill'" :style="list[1].pageItemStyle" :show-menu-by-longpress="true"/>
</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="$util.diyRedirectTo(item.link)"
: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"/>
</view>
</template>
</template>
</view>
</view>
</template>
<template v-else>
<view :class="['item', value.mode]" v-for="(item, index) in list" :key="index"
@click="$util.diyRedirectTo(item.link)"
: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"/>
</view>
</template>
</view>
</view>
</template>
<script>
// 魔方、橱窗
import htmlParser from '@/common/js/html-parser';
export default {
name: 'diy-rubik-cube',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
customHtml: ''
};
},
created() {
if (this.value.mode == 'custom-rubik-cube') {
this.value.diyHtml = this.value.diyHtml.replace(/&quot;/g, '"');
this.customHtml = htmlParser(this.value.diyHtml);
} else {
var singleRow = {
'row1-of2': {
ratio: 2,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) + 'px) / 2)'
},
'row1-of3': {
ratio: 3,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 4) + 'px) / 3)'
},
'row1-of4': {
ratio: 4,
width: 'calc((100% - ' + uni.upx2px(this.value.imageGap * 6) + 'px) / 4)'
}
};
if (singleRow[this.value.mode]) {
this.calcSingleRow(singleRow[this.value.mode]);
} else if (this.value.mode == 'row2-lt-of2-rt') {
this.calcFourSquare();
} else if (this.value.mode == 'row1-lt-of2-rt') {
this.calcRowOneLeftOfTwoRight();
} else if (this.value.mode == 'row1-tp-of2-bm') {
this.calcRowOneTopOfTwoBottom();
} else if (this.value.mode == 'row1-lt-of1-tp-of2-bm') {
this.calcRowOneLeftOfOneTopOfTwoBottom();
}
}
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
list() {
var arr = JSON.parse(JSON.stringify(this.value.list));
arr.forEach((item, index) => {
item.pageItemStyle = this.countBorderRadius(this.value.mode, index);
});
return arr;
},
rubikCubeWrapCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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: {
/**
* 魔方:单行多个,平分宽度
* 公式:
* 宽度:屏幕宽度/2示例375/2=187.5
* 比例:原图高/原图宽示例322/690=0.46
* 高度:宽度*比例示例187.5*0.46=86.25
*/
calcSingleRow(params) {
uni.getSystemInfo({
success: res => {
let maxHeight = 0;
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
let width = res.windowWidth - uni.upx2px(this.value.margin.both *
2); // 减去左右间距
if (this.value.imageGap > 0) {
width -= uni.upx2px(params.ratio * this.value.imageGap * 2); // 减去间隙
}
item.imgWidth = width / params.ratio;
item.imgHeight = item.imgWidth * ratio;
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item
.imgHeight;
})
this.list.forEach((item, index) => {
item.widthStyle = params.width;
item.imgHeight = maxHeight;
});
}
})
},
/**
* 魔方四方型各占50%
*/
calcFourSquare() {
uni.getSystemInfo({
success: res => {
let maxHeightFirst = 0;
let maxHeightTwo = 0;
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = res.windowWidth;
item.imgWidth -= uni.upx2px(this.value.margin.both * 4);
if (this.value.imageGap > 0) {
item.imgWidth -= uni.upx2px(this.value.imageGap * 2);
}
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
// 获取每行最大高度
if (index <= 1) {
if (maxHeightFirst == 0 || maxHeightFirst < item.imgHeight) {
maxHeightFirst = item.imgHeight;
}
} else if (index > 1) {
if (maxHeightTwo == 0 || maxHeightTwo < item.imgHeight) {
maxHeightTwo = item.imgHeight;
}
}
});
this.list.forEach((item, index) => {
item.imgWidth = 'calc((100% - ' + uni.upx2px(this.value.imageGap * 2) +
'px) / 2)';
item.widthStyle = item.imgWidth;
if (index <= 1) {
item.imgHeight = maxHeightFirst;
} else if (index > 1) {
item.imgHeight = maxHeightTwo;
}
});
}
});
},
/**
* 魔方1左2右
*/
calcRowOneLeftOfTwoRight() {
let rightHeight = 0; // 右侧两图平分高度
let divide = 'left'; // 划分规则leftright
if (this.list[1].imgWidth === this.list[2].imgWidth) divide = 'right';
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
rightHeight = (item.imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
item.imgWidth += 'px';
} else {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = rightHeight;
}
});
}
});
},
/**
* 魔方1上2下
*/
calcRowOneTopOfTwoBottom() {
var maxHeight = 0;
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
if (index == 0) {
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4);
} else if (index > 0) {
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
}
item.imgHeight = item.imgWidth * ratio;
// 获取最大高度
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight))
maxHeight = item.imgHeight;
});
this.list.forEach((item, index) => {
item.imgWidth += 'px';
item.widthStyle = item.imgWidth;
if (index > 0) item.imgHeight = maxHeight;
});
}
});
},
/**
* 魔方1左3右
*/
calcRowOneLeftOfOneTopOfTwoBottom() {
uni.getSystemInfo({
success: res => {
this.list.forEach((item, index) => {
// 左图
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例
item.imgWidth = res.windowWidth - uni.upx2px(this.value.margin.both * 4) - uni.upx2px(this.value.imageGap * 2);
item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio;
} else if (index == 1) {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = (this.list[0].imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
} else if (index > 1) {
item.imgWidth = (this.list[0].imgWidth - uni.upx2px(this.value.imageGap * 2)) / 2;
item.imgHeight = this.list[1].imgHeight;
}
});
this.list.forEach((item, index) => {
item.imgWidth += 'px';
item.imgHeight;
});
}
});
},
countBorderRadius(type, index) {
var obj = '';
if (this.value.elementAngle == 'right') {
return obj;
}
var defaultData = {
'row1-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-lt-of1-tp-of2-bm': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-tp-of2-bm': [
['border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row2-lt-of2-rt': [
['border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius']
],
'row1-of4': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of3': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
],
'row1-of2': [
['border-top-right-radius', 'border-bottom-right-radius'],
['border-top-left-radius', 'border-bottom-left-radius']
]
};
defaultData[type][index].forEach((item, index) => {
// obj += item + ':' + this.value.aroundRadius * 2 + 'rpx;';
obj += 'border-top-left-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-top-right-radius:' + this.value.topElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-left-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
obj += 'border-bottom-right-radius:' + this.value.bottomElementAroundRadius * 2 + 'rpx;';
});
return obj;
}
}
};
</script>
<style lang="scss">
.rubik-cube {
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.rubik-cube .item {
text-align: center;
line-height: 0;
overflow: hidden;
}
.rubik-cube .item image {
width: 100%;
max-width: 100%;
height: 100%;
}
// 一行两个
.rubik-cube .item.row1-of2 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(2) {
margin-right: 0 !important;
}
// 一行三个
.rubik-cube .item.row1-of3 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(3) {
margin-right: 0 !important;
}
// 一行四个
.rubik-cube .item.row1-of4 {
box-sizing: border-box;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(1) {
margin-left: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(4) {
margin-right: 0 !important;
}
// 两左两右
.rubik-cube .item.row2-lt-of2-rt {
// width: 50%;
display: inline-block;
box-sizing: border-box;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(1) {
margin-left: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) {
margin-right: 0 !important;
margin-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) {
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) {
margin-right: 0 !important;
margin-bottom: 0 !important;
}
// 一左两右
.rubik-cube .template-left,
.rubik-cube .template-right {
// width: 50%;
box-sizing: border-box;
}
.rubik-cube .template-left .item.row1-lt-of2-rt:nth-child(1) {
margin-bottom: 0;
}
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) {
margin-bottom: 0 !important;
}
.rubik-cube.row1-lt-of2-rt .template-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
// 一上两下
.rubik-cube .item.row1-tp-of2-bm:nth-child(1) {
width: 100%;
box-sizing: border-box;
margin-top: 0 !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) {
// width: 50%;
box-sizing: border-box;
margin-left: 0 !important;
margin-bottom: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) {
// width: 50%;
box-sizing: border-box;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
// 一左三右
.rubik-cube .template-left .item.row1-lt-of1-tp-of2-bm {
width: 100%;
box-sizing: border-box;
}
.rubik-cube .template-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.rubik-cube .template-bottom .item:nth-child(2) {
margin-right: 0 !important;
}
</style>

View File

@@ -0,0 +1,336 @@
<template>
<view class="diy-search">
<view class="diy-search-wrap" :class="value.positionWay" :style="fixedCss">
<view :class="['search-box','search-box-'+value.searchStyle]" :style="searchWrapCss" @click="search()">
<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"/>
</view>
<diy-icon class="icon" v-if="value.searchStyle == 2 && value.iconType == 'icon'" :icon="value.icon"
:value="value.style ? value.style : 'null'"
:style="{ maxWidth: 30 * 2 + 'rpx', maxHeight: 30 * 2 + 'rpx' }"></diy-icon>
<view class="search-content" :style="inputStyle">
<input type="text" class="uni-input ns-font-size-base" maxlength="50" :placeholder="value.title" v-model="searchText" @confirm="search()" disabled="true" :placeholderStyle="placeholderStyle" />
<text class="iconfont icon-sousuo3" @click.stop="search()" :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="search()">
<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="search()" disabled="true" @click.stop="search()" :placeholderStyle="placeholderStyle" />
<text class="search-content-btn" @click.stop="search()" :style="{ 'backgroundColor': value.pageBgColor ? value.pageBgColor : 'rgba(0,0,0,0)' }">搜索</text>
</view>
<view class="img" v-if="value.iconType == 'img'" @click.stop="redirectTo(value.searchLink)"><image :src="$util.img(value.imageUrl)" mode="heightFix"/>
</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="redirectTo(value.searchLink)"></diy-icon>
</block>
</view>
</view>
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<view v-if="value.positionWay == 'fixed'" class="u-navbar-placeholder" :style="{ width: '100%', paddingTop: moduleHeight }"></view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API尚未兼容)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
// 搜索
export default {
name: 'diy-search',
props: {
value: {
type: Object,
default: () => {
return {};
}
},
topNavColor: String,
global: {
type: Object,
default: () => {
return {};
}
},
haveTopCategory: {
type: Boolean
},
followOfficialAccount: {
type: Object
},
},
data() {
return {
searchText: '',
menuButtonInfo: menuButtonInfo,
height: 0,
placeholderHeight: 0,
moduleHeight: 0
};
},
computed: {
fixedCss() {
var obj = '';
if (this.value.positionWay == 'fixed') {
let top = this.fixedTop;
// 固定定位
if (this.global.topNavBg)
obj += 'background-color:' + (this.topNavColor == 'transparent' ? this.value.pageBgColor : this
.topNavColor) + ';';
else
obj += 'background-color:' + this.value.pageBgColor + ';';
obj += 'top:' + top + ';';
obj += 'padding-top:' + this.value.margin.top * 2 + 'rpx;';
obj += 'padding-left:' + this.value.margin.both * 2 + 'rpx;';
obj += 'padding-right:' + this.value.margin.both * 2 + 'rpx;';
obj += 'padding-bottom:' + this.value.margin.bottom * 2 + 'rpx;';
}
return obj;
},
searchWrapCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
obj += 'text-align:' + this.value.textAlign + ';';
return obj;
},
inputStyle() {
var obj = '';
obj += 'background-color:' + this.value.elementBgColor + ';';
if (this.value.borderType == 2) {
obj += 'border-radius:' + '40rpx;';
}
return obj;
},
placeholderStyle() {
var obj = '';
if (this.value.textColor) {
obj += 'color:' + this.value.textColor;
} else {
obj += 'color: rgba(0,0,0,0)';
}
return obj;
},
fixedTop() {
let diyPositionObj = this.$store.state.diyGroupPositionObj;
let data = 0
if (diyPositionObj.diySearch && diyPositionObj.diyIndexPage && diyPositionObj.nsNavbar) {
if (diyPositionObj.diySearch.moduleIndex > diyPositionObj.diyIndexPage.moduleIndex) {
data = diyPositionObj.nsNavbar.originalVal + diyPositionObj.diyIndexPage.originalVal;
} else {
data = diyPositionObj.nsNavbar.originalVal;
}
} else if (diyPositionObj.diySearch && diyPositionObj.nsNavbar) {
data = diyPositionObj.nsNavbar.originalVal;
}
data += 'px';
return data;
}
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) {
}
},
created() {
setTimeout(() => {
// 获取组件的高度默认高度为4545是在375屏幕上的高度
const query = uni.createSelectorQuery();
// #ifdef H5
let cssSelect = '.page-header .u-navbar';
// #endif
// #ifdef MP
let cssSelect = '.page-header >>> .u-navbar';
// #endif
query
.select(cssSelect)
.boundingClientRect(data => {
if (this.global.navBarSwitch) {
this.height = data ? data.height : 45;
} else {
this.height = data ? data.height : 0;
}
// 如果存在分类导航组件,则追加该组件的高度
if (this.haveTopCategory) {
this.height += 49;
}
})
.exec();
});
if (this.value.positionWay == 'fixed') this.navbarPlaceholderHeight();
},
mounted() {
if (this.value.positionWay == 'fixed')
this.setModuleLocatinoFn();
},
methods: {
search() {
this.$util.redirectTo('/pages_tool/goods/search');
},
redirectTo(link) {
if (link.wap_url) {
if (this.$util.getCurrRoute() == 'pages/member/index' && !this.storeToken) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
navbarPlaceholderHeight() {
let height = 0;
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.diy-search-wrap')
.boundingClientRect(data => {
// 获取搜索框自身高度
this.placeholderHeight = data.height;
// 通过搜索框自身高度 - 定位模式下的多出的padding-bottom高度
if (this.placeholderHeight) this.placeholderHeight -= this.value.margin.bottom;
}).exec();
});
},
// 向vuex中的diyIndexPositionObj增加搜索组件定位位置
setModuleLocatinoFn() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.diy-search-wrap')
.boundingClientRect(data => {
let diySearch = {
originalVal: data.height || 0, //自身高度 px
currVal: 0, //定位高度
moduleIndex: this.value.moduleIndex //组件在diy-group的位置
};
this.moduleHeight = (data.height || 0) + 'px';
this.$store.commit('setDiyGroupPositionObj', {
'diySearch': diySearch
});
}).exec();
})
}
}
};
</script>
<style lang="scss">
/deep/ .uni-input-placeholder {
overflow: initial;
}
.diy-search-wrap {
overflow: hidden;
}
.fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
transition: background 0.3s;
}
.search-box {
position: relative;
display: flex;
align-items: center;
.img {
height: 60rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.icon {
width: 170rpx;
height: 60rpx;
margin-right: 20rpx;
}
}
.search-box-3 {
.search-content {
display: flex;
align-items: center;
height: 68rpx;
.iconfont {
position: initial;
transform: translateY(0);
width: auto;
margin-left: 26rpx;
margin-right: 12rpx;
font-size: $font-size-base;
line-height: 1;
}
.uni-input {
flex: 1;
padding-left: 0;
height: 68rpx;
}
.search-content-btn {
margin-right: 8rpx;
width: 116rpx;
height: 54rpx;
line-height: 54rpx;
text-align: center;
color: #fff;
border-radius: 30rpx;
}
}
.diy-icon {
margin-left: 20rpx;
margin-right: 0;
width: auto;
font-size: 74rpx;
}
.img {
margin-left: 20rpx;
margin-right: 0;
}
}
.search-content {
flex: 1;
}
.search-content input {
box-sizing: border-box;
display: block;
height: 64rpx;
width: 100%;
padding: 0 20rpx 0 40rpx;
color: #333333;
background: none;
}
.search-content .iconfont {
position: absolute;
top: 50%;
right: 4rpx;
transform: translateY(-50%);
font-size: 30rpx;
z-index: 10;
width: 80rpx;
font-weight: bold;
text-align: center;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,170 @@
<template>
<x-skeleton type="banner" :loading="loading" :configs="skeletonConfig">
<view class="diy-store-label">
<block v-if="businessConfig.store_business == 'store'">
<scroll-view scroll-x="true" :class="[value.contentStyle, { between: list.length == 3 }]" :style="storeLabelWrapCss" :enable-flex="true">
<view v-for="(item, index) in storeLabel" :class="['item']">
<diy-icon v-if="value.icon" class="icon-box" :icon="value.icon" :value="value.style ? value.style : 'null'"></diy-icon>
<text class="label-name" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ item }}</text>
</view>
</scroll-view>
</block>
<block v-else>
<scroll-view scroll-x="true" :class="[value.contentStyle, { between: list.length == 3 }]" :style="storeLabelWrapCss" :enable-flex="true">
<view v-for="(item, index) in list" :class="['item']">
<diy-icon v-if="value.icon" class="icon-box" :icon="value.icon" :value="value.style ? value.style : 'null'"></diy-icon>
<text class="label-name" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ item.label_name }}</text>
</view>
</scroll-view>
</block>
</view>
</x-skeleton>
</template>
<script>
// 门店标签
export default {
name: 'diy-store-label',
props: {
value: {
type: Object
}
},
data() {
return {
loading: true,
skeletonConfig: {
gridRows: 1,
gridRowsGap: '20rpx',
headHeight: '40rpx',
headBorderRadius: '0'
},
list: [],
notice: '',
storeLabel: [],
businessConfig: ""
};
},
created() {
this.getData();
this.getStoreConfig();
},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {
this.getData();
}
},
computed: {
storeLabelWrapCss: function() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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: {
getData() {
var data = {
page: 1,
page_size: 0
};
if (this.value.sources == 'initial') {
data.page_size = this.value.count;
} else if (this.value.sources == 'diy') {
data.label_id_arr = this.value.labelIds.toString();
}
this.$api.sendRequest({
url: '/store/api/store/labelPage',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data.list;
}
this.loading = false;
}
});
},
getStoreConfig() {
this.$api.sendRequest({
url: '/store/api/config/config',
success: res => {
if (res.code >= 0) {
this.businessConfig = res.data.business_config;
if (res.data.business_config.store_business == "store") {
this.getStoreInfo();
}
}
}
});
},
getStoreInfo() {
this.$api.sendRequest({
url: '/api/store/info',
success: res => {
if (res.data) {
let label_arr = res.data.label_name.split(",");
let label_count = 3;
if (this.value.sources == 'initial') label_count = this.value.count;
for (let i = 0; i < label_arr.length; i++) {
if (this.storeLabel.length < label_count && label_arr[i] != '') {
this.storeLabel.push(label_arr[i])
}
}
}
}
});
},
}
};
</script>
<style lang="scss">
.diy-store-label {
.style-1 {
display: flex;
align-items: baseline;
/deep/ .uni-scroll-view-content {
display: flex;
align-items: center;
}
&.between {
justify-content: space-between;
/deep/.uni-scroll-view-content {
justify-content: space-between;
}
}
.item {
flex-shrink: 0;
display: flex;
align-items: center;
padding-right: 20rpx;
.icon-box {
font-size: 50rpx;
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
margin-top: 2rpx;
}
.label-name {
line-height: 40rpx;
}
&:last-of-type {
padding-right: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<view class="store-wrap">
<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()">
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
<text class="title">{{ globalStoreInfo.store_name }}</text>
<text>
<text class="change margin-left">切换</text>
<text class="iconfont icon-right"></text>
</text>
</block>
<text class="title" v-else>定位中...</text>
</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">{{ globalStoreInfo.show_address }}</text>
<text v-else>获取当前位置...</text>
</view>
</view>
<view class="store-image" @click="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>
</view>
</view>
</block>
<block v-if="value.style == 2">
<view class="store-box store-three" @click="toStoreList()">
<view class="store-info">
<view class="store-image" @click="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>
</view>
<view class="info-box" :style="{ color: value.textColor }">
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
<text class="title">{{ globalStoreInfo.store_name }}</text>
<text>
<text class="change margin-left">切换</text>
<text class="iconfont icon-right"></text>
</text>
</block>
<text class="title" v-else>定位中...</text>
</view>
</view>
<view class="store-icon" @click.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-left-wrap">
<block v-if="globalStoreInfo && globalStoreInfo.store_id">
<text class="iconfont icon-weizhi" :style="{ color: value.textColor }"></text>
<text class="title" :style="{ color: value.textColor }">{{ globalStoreInfo.store_name }}</text>
<text class="iconfont icon-unfold" :style="{ color: value.textColor }"></text>
</block>
<text class="title" v-else>定位中...</text>
</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>
</view>
</view>
</block>
</view>
</template>
<script>
// 门店展示
import Map from '@/common/js/map/openMap.js';
export default {
name: 'diy-store',
props: {
value: {
type: Object
}
},
data() {
return {};
},
computed: {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
created() {},
methods: {
//跳转至门店列表
toStoreList() {
this.$util.redirectTo('/pages_tool/store/list');
},
selectStore() {
if (this.globalStoreInfo) {
this.$util.redirectTo('/pages_tool/store/detail', { store_id: this.globalStoreInfo.store_id });
}
},
search() {
this.$util.redirectTo('/pages_tool/goods/search');
},
mapRoute() {
if (!isNaN(Number(this.globalStoreInfo.latitude)) && !isNaN(Number(this.globalStoreInfo.longitude))) {
Map.openMap(Number(this.globalStoreInfo.latitude), Number(this.globalStoreInfo.longitude), this.globalStoreInfo.store_name, 'gcj02');
}
}
}
};
</script>
<style lang="scss">
.store-wrap {
.store-box {
box-sizing: border-box;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.store-info {
height: 100rpx;
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-around;
margin-right: 20rpx;
.info-box {
display: flex;
align-items: flex-end;
margin-bottom: 6rpx;
text {
line-height: 1.2;
}
.title {
max-width: 480rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: $font-size-toolbar;
flex: 1;
}
.change {
font-size: $font-size-goods-tag;
}
.iconfont {
font-size: $font-size-goods-tag;
}
}
.address-wrap {
line-height: 1.2;
font-size: $font-size-goods-tag;
display: flex;
align-items: center;
.iconfont {
font-size: $font-size-goods-tag;
margin-right: 6rpx;
}
.address {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.store-image {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
}
.store-one,
.store-three {
// padding: 0 20rpx;
}
.store-two {
.store-image {
align-self: flex-start;
margin-right: 14rpx;
}
.info-box {
margin-bottom: 0 !important;
}
.store-info {
height: 106rpx;
}
.switchover {
display: flex;
width: 120rpx;
}
}
.store-three {
.store-info {
height: auto;
justify-content: flex-start;
flex-direction: inherit;
align-items: center;
}
.info-box {
margin-left: 18rpx;
margin-bottom: 0 !important;
}
.store-icon text {
font-size: 36rpx;
color: #fff;
}
}
.store-four {
padding: 0 !important;
.store-left-wrap {
display: flex;
align-items: center;
line-height: 1;
.icon-weizhi {
margin-right: 6rpx;
font-size: 28rpx;
}
.icon-unfold {
margin-left: 6rpx;
}
.title {
display: inline-block;
max-width: 160rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.store-right-search {
width: calc(100% - 260rpx);
position: relative;
input {
width: 100%;
height: 72rpx;
line-height: 72rpx;
background-color: #ffffff;
border: none;
border-radius: 72rpx;
padding-left: 30rpx;
box-sizing: border-box;
}
.icon-sousuo3 {
position: absolute;
right: 30rpx;
top: 10rpx;
font-size: 28rpx;
color: #909399;
}
}
}
</style>

View File

@@ -0,0 +1,981 @@
<template>
<view class="diy-text" @click="$util.diyRedirectTo(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;">
<text :style="{ fontWeight: value.fontWeight}">{{ value.text }}</text>
</block>
<block v-if="value.style == 'style-1'" style="height: 40rpx; line-height: 40rpx;">
<view class="text-left" :style="{ color: value.textColor}"><text></text></view>
<text :style="{ fontWeight: value.fontWeight}">{{ value.text }}</text>
<view class="text-right" :style="{ color: value.textColor}"><text></text></view>
</block>
<block v-else-if="value.style == 'style-2'">
<view class="style2">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="xian" :style="{ background: value.textColor }">
<view class="line-triangle" :style="{ borderColor: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-3'">
<view class="style3">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx' ,fontWeight: value.fontWeight}">{{ value.text }}</text>
<view class="inner-line" :style="{ background: value.textColor }"><view class="line-short" :style="{ background: value.textColor }"></view></view>
</view>
</block>
<block v-else-if="value.style == 'style-4'">
<view class="style4">
<text :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="line-box">
<view class="line-left" :style="{ background: value.textColor }"></view>
<view class="line-center" :style="{ borderColor: value.textColor }"></view>
<view class="line-right" :style="{ background: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-5'">
<view class="style5">
<view class="style5-box" :style="{ color: value.textColor }">
<text class="style5-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx' ,fontWeight: value.fontWeight}">{{ value.text }}</text>
<view class="line-left" :style="{ background: value.textColor }"></view>
<view class="line-right" :style="{ background: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-6'">
<view class="style6">
<view class="style6-box" :style="{ color: value.textColor }">
<text class="style6-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="style6-wrap" :style="{ color: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-7'">
<view class="style7">
<view class="style7-box" :style="{ color: value.textColor }">
<text class="style7-title" :style="{ background: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="style7-wrap" :style="{ color: value.textColor }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-8'">
<view class="style8">
<view class="style8-box" :style="{ color: value.textColor }">
<text class="style8-title" :style="{ color: value.textColor, fontSize: value.fontSize * 2 + 'rpx',fontWeight: value.fontWeight }">{{ value.text }}</text>
<view class="style8-wrap" :style="{ background: value.textColor, height: value.fontSize * 2 + 'rpx' }"></view>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-9'">
<view class="style9">
<view class="style9-box">
<view class="style9-center">
<view class="left-img"><image :src="$util.img('public/uniapp/diy/style9-1.png')"/></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')"/></view>
<view class="style9-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="$util.diyRedirectTo(value.more.link)">
{{ value.more.text }}
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</text>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-10'">
<view class="style10">
<view class="style10-box">
<view class="style10-center">
<view class="left-img"><image :src="$util.img('public/uniapp/diy/style10-1.png')"></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') + ')',
backgroundSize: 100 + '%',
backgroundPositionY: 93 + '%',
backgroundRepeat: 'no-repeat',
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/style10-2.png')"></image></view>
<view class="style10-more" v-if="value.more.isShow" :style="{ color: value.more.color }" @click.stop="$util.diyRedirectTo(value.more.link)">
{{ value.more.text }}
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
<text class="sub-title" :style="{ color: value.subTitle.color }">{{ value.subTitle.text }}</text>
</view>
</view>
</block>
<block v-else-if="value.style == 'style-11'">
<view class="style11" :style="{ backgroundColor: value.backgroundColor }">
<view class="style11-box">
<view class="style11-conter">
<view class="style11-conter-box">
<view class="left">
<view class="style11-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
{{ value.text }}
</view>
<view class="style11-sub" :style="{ color: value.subTitle.color }">{{ 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)">
{{ 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>
<image class="right-img" :src="$util.img('public/uniapp/diy/style11-2.png')" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</block>
<view class="style12" v-if="value.style == 'style-12'">
<view class="style12-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
{{ value.text }}
</view>
<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)">
<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>
<view
class="style13-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
{{ value.text }}
</view>
<image class="right-img" :src="$util.img('public/uniapp/diy/style13-1.png')" mode="widthFix"></image>
</view>
<view class="style14" v-else-if="value.style == 'style-14'">
<view class="title-wrap">
<view class="text">
<text :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
{{ value.text }}
</text>
<text class="zone" :style="{ fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">专区</text>
</view>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="iconfont icon-danxuan-xuanzhong" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<view
class="sub-title"
v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }"
>
{{ value.subTitle.text }}
</view>
</view>
<view v-show="value.more.isShow == 1" class="more" :style="{ color: value.more.color }">
<text>{{ value.more.text }}</text>
<text class="iconfont icon-right"></text>
</view>
</view>
<!-- 图十五 -->
<view class="style15" v-else-if="value.style == 'style-15'">
<view class="title-wrap">
<view class="ornament" style="margin-right: 40rpx;">
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="my">
<text class="yuan" :style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
</text>
</view>
<text :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
{{ value.text }}
</text>
<view class="ornament" style="margin-left: 40rpx;">
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="line" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="my">
<text class="yuan" :style="{ backgroundColor: value.textColor, fontWeight: value.fontWeight }"></text>
<text class="vertical" :style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
</text>
</view>
</view>
<view
class="sub-title"
v-show="value.subTitle.text"
:style="{ fontSize: value.subTitle.fontSize * 2 + 'rpx', color: value.subTitle.color }"
>
{{ value.subTitle.text }}
</view>
</view>
<!-- 图十六 -->
<view class="style16" v-if="value.style == 'style-16'">
<view
class="style16-title"
:style="{
fontSize: value.fontSize * 2 + 'rpx',
color: value.textColor,
fontWeight: value.fontWeight
}"
>
{{ value.text }}
</view>
<view class="style16-sub-title" v-show="value.subTitle.text" :style="{ color: value.subTitle.color, backgroundColor: value.subTitle.bgColor }">
<text :class="['js-icon', value.subTitle.icon]" :style="{ backgroundColor: value.subTitle.bgColor }"></text>
<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)">
<text>{{ value.more.text }}</text>
<view class="iconfont icon-right" :style="{ color: value.more.color }"></view>
</view>
</view>
</view>
</view>
</template>
<script>
// 标题
export default {
name: 'diy-text',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
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: {}
};
</script>
<style lang="scss">
.diy-text {
padding: 0;
display: flex;
justify-content: center;
}
.diy-text .title {
margin: 0;
color: #333;
display: flex;
align-items: center;
width: 100%;
justify-content: center;
}
.diy-text .title2 {
margin: 0;
color: #333;
display: flex;
align-items: center;
width: 100%;
}
.diy-text .title > text {
padding: 0 15rpx;
}
.left {
transform: translateY(0%);
width: 30rpx;
height: 24rpx;
}
.right {
transform: translateY(0%);
width: 30rpx;
height: 24rpx;
}
.style2 {
width: 100%;
text-align: center;
.xian {
width: 100%;
height: 2rpx;
vertical-align: top;
position: relative;
}
.line-triangle {
background: transparent !important;
position: absolute;
border: 12rpx solid #000;
border-top-color: transparent !important;
border-left-color: transparent !important;
left: 50%;
bottom: -10rpx;
margin-left: -12rpx;
transform: rotate(45deg);
}
}
.style3 {
width: 100%;
text-align: center;
.inner-line {
width: 100%;
height: 2rpx;
vertical-align: top;
position: relative;
}
.line-short {
width: 324rpx;
height: 6rpx;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -162rpx;
}
}
.style4 {
text-align: center;
width: 100%;
.line-box {
height: 5rpx;
position: relative;
text-align: center;
margin-top: 5rpx;
width: 100%;
.line-left {
display: inline-block;
position: absolute;
top: 8rpx;
left: 0;
bottom: 0;
width: calc((100% - 44rpx) / 2);
height: 2rpx;
}
.line-center {
width: 12rpx;
height: 12rpx;
border: 2rpx solid;
display: inline-block;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
top: 0;
left: 50%;
bottom: 0;
margin-left: -6rpx;
}
.line-right {
display: inline-block;
position: absolute;
top: 8rpx;
bottom: 0;
right: 0;
width: calc((100% - 44rpx) / 2);
height: 2rpx;
}
}
}
.style5 {
text-align: center;
position: relative;
.style5-box {
display: inline-block;
padding: 10rpx !important;
border: 1rpx solid;
position: relative;
.style5-title {
display: inline-block;
padding: 10rpx 30rpx;
border: 1rpx solid;
line-height: 1;
}
.line-left {
height: 10rpx;
position: absolute;
width: 80rpx;
top: 50%;
margin-top: -4rpx;
left: 0;
margin-left: -60rpx;
}
.line-right {
height: 10rpx;
position: absolute;
width: 80rpx;
top: 50%;
margin-top: -4rpx;
right: 0;
margin-right: -60rpx;
}
}
}
.style6 {
text-align: center;
position: relative;
.style6-box {
display: inline-block;
position: relative;
.style6-title {
display: inline-block;
padding: 6rpx 50rpx;
border: 1rpx solid;
position: relative;
z-index: 2;
background: rgb(255, 255, 255);
line-height: 1.5;
}
.style6-wrap {
position: absolute;
display: inline-block;
width: 100%;
top: 10rpx;
right: 4rpx;
bottom: -10rpx;
left: 10rpx;
border: 1rpx solid;
z-index: 0;
box-sizing: border-box;
}
}
}
.style7 {
text-align: center;
position: relative;
.style7-box {
display: inline-block;
position: relative;
.style7-title {
display: inline-block;
padding: 0rpx 50rpx;
position: relative;
z-index: 2;
padding-bottom: 6rpx;
color: rgb(255, 255, 255);
line-height: 1.5;
}
.style7-wrap {
width: 100%;
box-sizing: border-box;
position: absolute;
top: 10rpx;
right: 4rpx;
bottom: -10rpx;
left: 10rpx;
border: 1rpx solid;
z-index: 0;
}
}
}
.style8 {
position: relative;
text-align: left;
.style8-box {
text-align: left !important;
.style8-title{
margin-left: 20rpx;
}
.style8-wrap {
height: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
width: 4rpx;
}
}
}
.style9 {
width: 100%;
.style9-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
.style9-center {
display: flex;
justify-content: center;
align-items: center;
width: calc(100% - 264rpx);
position: relative;
text {
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.left-img {
width: 40rpx;
height: 40rpx;
text-align: center;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.right-img {
width: 40rpx;
height: 40rpx;
text-align: center;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.style9-more {
display: flex;
position: absolute;
right: -140rpx;
/* #ifdef MP-WEIXIN */
right: -120rpx;
/* #endif */
top: 14rpx;
line-height: 1;
align-items: center;
.iconfont {
line-height: 1;
}
}
.sub-title {
line-height: 1;
}
}
}
}
.style10 {
width: 100%;
.style10-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
.style10-center {
display: flex;
justify-content: center;
align-items: center;
width: calc(100% - 264rpx);
position: relative;
text {
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-bottom: 8rpx;
z-index: 99;
}
.left-img {
padding-bottom: 13rpx;
width: 40rpx;
height: 40rpx;
text-align: center;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.center-img {
width: 198rpx;
height: 18rpx;
text-align: center;
flex-shrink: 0;
position: absolute;
bottom: 26rpx;
z-index: 5;
image {
width: 100%;
height: 100%;
}
}
.right-img {
padding-bottom: 13rpx;
width: 40rpx;
height: 40rpx;
text-align: center;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.style10-more {
display: flex;
position: absolute;
right: -140rpx;
/* #ifdef MP-WEIXIN */
right: -120rpx;
/* #endif */
top: 14rpx;
line-height: 1;
align-items: center;
.iconfont {
line-height: 1;
}
}
.sub-title {
line-height: 1;
}
}
}
}
.style11 {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
.style11-box {
width: 100%;
margin: 10rpx 0;
.style11-conter {
.style11-conter-box {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
.left {
display: flex;
width: 70%;
flex-direction: column;
justify-content: space-between;
height: 100%;
margin: 15rpx 0;
padding-left: 34rpx;
z-index: 9;
.style11-title {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.style11-sub {
letter-spacing: 14rpx;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.style11-more {
display: flex;
margin-right: 20rpx;
}
}
.center-img {
width: 61rpx;
position: absolute;
bottom: -26rpx;
left: 16rpx;
z-index: 0;
}
.right-img {
width: 35rpx;
position: absolute;
top: -14rpx;
left: 172rpx;
z-index: 0;
}
}
}
}
.style12 {
display: flex;
align-items: center;
width: 100%;
padding: 20rpx;
.style12-title {
text-align: left;
max-width: 200rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin-right: 10rpx;
}
.style12-sub-title {
text-align: left;
max-width: 300rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.style12-more {
display: flex;
align-items: center;
margin-left: auto;
text {
padding: 0;
max-width: 160rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 24rpx;
margin-right: 8rpx;
}
.iconfont{
font-size: 24rpx;
}
}
}
.style13 {
display: flex;
align-items: center;
justify-content: center;
.style13-title {
max-width: 420rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin: 0 10rpx;
}
image {
height: 50rpx;
width: 76rpx;
}
.right-img {
transform: rotateY(180deg);
}
}
.style14 {
display: flex;
justify-content: space-between;
text-align: initial;
align-items: center;
flex: 1;
padding: 0 20rpx;
text {
padding: 0 !important;
}
.title-wrap {
.text {
display: inline-block;
}
.iconfont {
font-size: 24rpx;
&:nth-of-type(1) {
margin-left: 20rpx;
}
&:nth-of-type(2) {
opacity: 0.6;
}
&:nth-of-type(3) {
opacity: 0.4;
}
}
.sub-title {
opacity: 0.6;
}
.more {
text:first-child {
font-size: 28rpx;
}
}
}
}
.style15 {
flex: 1;
text {
padding: 0 !important;
line-height: 1;
}
.title-wrap {
display: flex;
justify-content: center;
align-items: center;
}
.ornament {
display: flex;
align-items: flex-end;
.line {
border-left: 8rpx solid;
transform: rotate(40deg);
border-radius: 40rpx;
height: 28rpx;
display: block;
margin-right: 12rpx;
&:nth-of-type(2) {
height: 40rpx;
margin-right: 10rpx;
position: relative;
top: 4rpx;
}
}
.my {
transform: rotate(40deg);
line-height: 0;
position: relative;
top: 4rpx;
.yuan {
font-size: 100rpx;
border-radius: 50%;
width: 8rpx;
height: 8rpx;
display: block;
}
.vertical {
border-left: 8rpx solid;
height: 20rpx;
margin-top: 4rpx;
display: inline-block;
border-radius: 40rpx;
}
}
}
.sub-title {
opacity: 0.6;
text-align: center;
}
}
.style16 {
display: flex;
align-items: center;
width: 100%;
padding: 20rpx;
.style16-more {
display: flex;
align-items: center;
margin-left: auto;
& > text {
max-width: 200rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.style16-title {
text-align: left;
max-width: 200rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.style16-sub-title {
margin-left: 20rpx;
text-align: left;
max-width: 300rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
border-radius: 34rpx;
background-image: radial-gradient(transparent 60%, #fff);
height: 50rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
position: relative;
&> .js-icon {
padding: 8rpx;
background-image: radial-gradient(transparent 30%, #fff);
border-radius: 50%;
margin-left: -20rpx;
margin-right: 6rpx;
font-size: 28rpx;
line-height: 1;
}
}
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<video class="diy-video" :src="$util.img(value.videoUrl)" :poster="$util.img(value.imageUrl)" :style="videoWarpCss" objectFit="cover"></video>
</template>
<script>
// 视频
export default {
name: 'diy-video',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() {},
watch: {
// 组件刷新监听
componentRefresh: function(nval) {}
},
computed: {
videoWarpCss: 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: {}
};
</script>
<style scoped>
video {
width: 100%;
}
.diy-video>>>.uni-video-container {
background-color: transparent;
}
</style>

View File

@@ -0,0 +1,465 @@
// 商品详情业务
import {
Weixin
} from '@/common/js/wx-jssdk.js';
export default {
data() {
return {
skuId: 0,
goodsId: 0,
isIphoneX: false, //判断手机是否是iphoneX以上
whetherCollection: 0,
//是否开启预览0不开启1开启
preview: 0,
videoContext: '',
// 媒体,图片,视频
// 解决每个商品SKU图片数量不同时无法切换到第一个导致轮播图显示不出来
swiperInterval: 1,
swiperAutoplay: false,
swiperCurrent: 1,
switchMedia: 'img',
//评价
goodsEvaluate: [{
member_headimg: '',
member_name: '',
content: '',
images: [],
create_time: 0,
sku_name: ''
}],
evaluateConfig: {
evaluate_audit: 1,
evaluate_show: 0,
evaluate_status: 1
},
// 是否可分享到好物圈
goodsCircle: false,
service: null,
shareUrl: '', // 分享链接
source_member: 0, //分享人的id
isCommunity: false, //社群弹窗
poster: "-1", //海报
posterMsg: "", //海报错误信息
posterHeight: 0,
posterParams: {}, //海报所需参数
goodsRoute: '',
posterApi: '',
goodsAttrShow: false, // 商品属性是否展开
//门店列表
storeList: {
data: [],
page: 1,
page_size: 10
},
isShowStore: false,
latitude: null, // 纬度
longitude: null, // 经度
evaluateCount: 0, // 商品评论数量
deliveryType: null, // 配送方式
isVirtual: 0 //是否为虚拟商品
}
},
created() {
this.isIphoneX = this.$util.uniappIsIPhoneX();
if (this.location) {
this.latitude = this.location.latitude;
this.longitude = this.location.longitude;
} else {
this.$util.getLocation();
}
this.getStoreData();
},
watch: {
location: function (nVal) {
if (nVal) {
this.latitude = nVal.latitude;
this.longitude = nVal.longitude;
this.getStoreData();
}
}
},
methods: {
init(params) {
this.skuId = params.sku_id;
this.goodsId = params.goods_id;
this.preview = params.preview;
this.source_member = params.source_member;
this.whetherCollection = params.whetherCollection;
this.posterParams = params.posterParams;
this.shareUrl = params.shareUrl;
this.goodsRoute = params.goodsRoute;
this.posterApi = params.posterApi;
this.isVirtual = params.isVirtual;
this.deliveryType = params.deliveryType;
this.evaluateConfig = params.evaluateConfig;
if (this.evaluateConfig.evaluate_show == 1) {
//商品评论
this.getGoodsEvaluate(params.evaluateList);
this.evaluateCount = params.evaluateCount;
}
for (let k in this.deliveryType) {
if (k == 'store') {
this.isShowStore = true;
}
}
this.getService();
this.videoContext = uni.createVideoContext('goodsVideo');
// #ifdef MP-WEIXIN
this.goodsSyncToGoodsCircle();
// #endif
},
swiperChange(e) {
this.swiperCurrent = e.detail.current + 1;
},
//-------------------------------------服务-------------------------------------
openMerchantsServicePopup() {
this.$refs.merchantsServicePopup.open();
},
closeMerchantsServicePopup() {
this.$refs.merchantsServicePopup.close();
},
//-------------------------------------门店列表-------------------------------------
openStoreListPopup() {
this.$refs.storeListPopup.open();
},
closeStoreListPopup() {
this.$refs.storeListPopup.close();
},
getStoreData() {
//门店列表
let data = {
page: this.storeList.page,
page_size: this.storeList.page_size
};
if (this.latitude && this.longitude) {
data.latitude = this.latitude;
data.longitude = this.longitude;
}
this.$api.sendRequest({
url: '/api/store/page',
data: data,
success: res => {
if (this.storeList.page == 1) this.storeList.data == [];
if (res.code >= 0 && res.data) {
this.storeList.data = this.storeList.data.concat(res.data.list);
} else {
this.$util.showToast({
title: res.message
});
}
}
});
},
selectStore(item) {
this.$util.redirectTo('/pages_tool/store/detail', {
store_id: item.store_id
});
this.closeStoreListPopup();
},
//-------------------------------------属性-------------------------------------
switchGoodsAttr() {
this.goodsAttrShow = !this.goodsAttrShow;
},
//-------------------------------------评价-------------------------------------
//商品评论列表
getGoodsEvaluate(list) {
if (list) {
this.goodsEvaluate = list;
this.goodsEvaluate.forEach((item, index) => {
if (this.goodsEvaluate[index].images) this.goodsEvaluate[index].images = this.goodsEvaluate[index].images.split(",");
if (this.goodsEvaluate[index].is_anonymous == 1) this.goodsEvaluate[index].member_name = this.goodsEvaluate[index].member_name.replace(this.goodsEvaluate[index].member_name.substring(1, this.goodsEvaluate[index].member_name.length - 1), '***')
})
// if (this.goodsEvaluate.images) this.goodsEvaluate.images = this.goodsEvaluate.images.split(",");
// if (this.goodsEvaluate.is_anonymous == 1) this.goodsEvaluate.member_name = this.goodsEvaluate.member_name.replace(
// this.goodsEvaluate.member_name.substring(1, this.goodsEvaluate.member_name.length - 1), '***')
}
},
// 预览评价图片
previewEvaluate(index, img_index, field) {
var paths = [];
for (let i = 0; i < this.goodsEvaluate[index][field].length; i++) {
paths.push(this.$util.img(this.goodsEvaluate[index][field][i]));
}
uni.previewImage({
current: img_index,
urls: paths
});
},
//-------------------------------------关注-------------------------------------
async collection() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
if (this.storeToken) {
//未关注添加关注
if (this.whetherCollection == 0) {
let res = await this.$api.sendRequest({
url: "/api/goodscollect/add",
data: {
sku_id: this.skuId,
goods_id: this.goodsSkuDetail.goods_id,
sku_name: this.goodsSkuDetail.sku_name,
sku_price: this.goodsSkuDetail.show_price,
sku_image: this.goodsSkuDetail.sku_image
},
async: false,
});
var data = res.data;
if (data > 0) {
this.whetherCollection = 1;
}
} else {
//已关注取消关注
let res = await this.$api.sendRequest({
url: "/api/goodscollect/delete",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
async: false,
});
var data = res.data;
if (data > 0) {
this.whetherCollection = 0;
}
}
return this.whetherCollection;
} else {
if (this.source_member) {
this.$refs.login.open(this.shareUrl + '&source_member=' + this.source_member);
} else {
this.$refs.login.open(this.shareUrl);
}
}
},
//-------------------------------------分享-------------------------------------
// 打开分享弹出层
openSharePopup() {
this.$refs.sharePopup.open();
},
// 关闭分享弹出层
closeSharePopup() {
this.$refs.sharePopup.close();
},
copyUrl() {
let text = this.$config.h5Domain + this.shareUrl;
if (this.memberInfo && this.memberInfo.member_id) text += '&source_member=' + this.memberInfo.member_id;
this.$util.copy(text, () => {
this.closeSharePopup();
});
},
//-------------------------------------海报-------------------------------------
// 打开海报弹出层
openPosterPopup() {
this.getGoodsPoster();
this.$refs.sharePopup.close();
},
// 关闭海报弹出层
closePosterPopup() {
this.$refs.posterPopup.close();
this.poster = ''
},
//生成海报
getGoodsPoster() {
uni.showLoading({
'title': '海报生成中...'
})
//活动海报信息
if (this.memberInfo && this.memberInfo.member_id) this.posterParams.source_member = this.memberInfo.member_id;
this.$api.sendRequest({
url: this.posterApi,
data: {
page: this.goodsRoute,
qrcode_param: JSON.stringify(this.posterParams)
},
success: res => {
if (res.code == 0) {
this.$refs.posterPopup.open();
this.poster = res.data.path + "?time=" + new Date().getTime();
} else {
this.posterMsg = res.message;
this.$util.showToast({
title: res.message
})
}
uni.hideLoading();
},
fail: err => {
uni.hideLoading();
}
});
},
// 预览图片
previewMedia(index) {
var paths = [];
for (let i = 0; i < this.goodsSkuDetail.sku_images.length; i++) {
paths.push(this.$util.img(this.goodsSkuDetail.sku_images[i], {
size: 'big'
}));
}
uni.previewImage({
current: index,
urls: paths,
// longPressActions: {
// itemList: ['发送给朋友', '保存图片', '关注'],
// success: function(data) {
// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
// },
// fail: function(err) {
// console.log(err.errMsg);
// }
// }
});
},
swiperImageError(index) {
this.goodsSkuDetail.sku_images[index] = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
// #ifdef MP || APP-PLUS
//小程序中保存海报
saveGoodsPoster() {
let url = this.$util.img(this.poster);
uni.downloadFile({
url: url,
success: (res) => {
if (res.errMsg == "downloadFile:ok") {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
this.$util.showToast({
title: "保存成功"
});
},
fail: (err) => {
this.$util.showToast({
title: "保存失败,请稍后重试"
});
}
});
}
},
fail: (err) => {
this.$util.showToast({
title: "保存失败,请稍后重试"
});
}
});
},
// #endif
//售后保障查询
getService() {
this.$api.sendRequest({
url: '/api/goods/aftersale',
success: res => {
if (res.code == 0 && res.data) {
this.service = res.data;
}
}
});
},
// #ifdef MP-WEIXIN
/**
* 将商品同步到微信圈子
*/
goodsSyncToGoodsCircle() {
this.$api.sendRequest({
url: '/goodscircle/api/goods/sync',
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
if (res.code == 0) {
this.goodsCircle = true;
}
}
})
},
/**
* 将商品推荐到微信圈子
*/
openBusinessView() {
if (wx.openBusinessView) {
wx.openBusinessView({
businessType: 'friendGoodsRecommend',
extraData: {
product: {
item_code: this.goodsSkuDetail.goods_id,
title: this.goodsSkuDetail.sku_name,
image_list: this.goodsSkuDetail.sku_images.map((ele) => {
return this.$util.img(ele);
})
}
},
success: function (res) {
console.log('success', res);
},
fail: function (res) {
console.log('fail', res);
}
})
}
},
// #endif
toEvaluateDetail(id) {
this.$util.redirectTo('/pages_tool/goods/evaluate', {
goods_id: id
});
},
showImg(e) {
//拿到图片的路径里面的内容放在我们数组中
let contentimg = e.target.dataset.nodes;
let arrImg = [];
for (var i = 0; i < contentimg.length; i++) {
var img = contentimg[i].children;
if (Array.isArray(img)) {
for (var j = 0; j < img.length; j++) {
if (img[j].attrs && img[j].name == "img") {
if (img[j].attrs.src) {
arrImg.push(img[j].attrs.src)
}
}
}
}
}
//最后一步就是把我们的所有图片放在预览的api中就可以了
uni.previewImage({
current: arrImg,
urls: arrImg,
})
},
//-------------------------------------社群-------------------------------------
//添加福利群
onCommunity() {
this.isCommunity = true
},
onCloseCommunity() {
this.isCommunity = false
},
}
}

View File

@@ -0,0 +1,455 @@
<template>
<view>
<view scroll-y="true" class="goods-detail" :class="isIphoneX ? 'active' : ''">
<view class="goods-container">
<!-- 弹幕 -->
<pengpai-fadein-out v-if="goodsSkuDetail.barrage_show && goodsSkuDetail.barrageData" ref="pengpai" :duration="1600" :wait="1900" :top="200" :left="0" :radius="60" :loop="true" :info="goodsSkuDetail.barrageData"/>
<!-- 商品媒体信息 -->
<view class="goods-media" :style="{height: goodsSkuDetail.swiperHeight}">
<!-- 商品图片 -->
<view class="goods-img" :class="{ show: switchMedia == 'img' }">
<swiper class="swiper" @change="swiperChange" :interval="swiperInterval" :autoplay="swiperAutoplay" autoplay="true" interval="4000" circular="true">
<swiper-item v-for="(item, index) in goodsSkuDetail.sku_images" :key="index" :item-id="'goods_id_' + index">
<view class="item" @click="previewMedia(index)">
<image :src="$util.img(item, { size: 'big' })" @error="swiperImageError(index)" mode="aspectFit" />
</view>
</swiper-item>
</swiper>
<view class="img-indicator-dots">
<text>{{ swiperCurrent }}</text>
<text v-if="goodsSkuDetail.sku_images">/{{ goodsSkuDetail.sku_images.length }}</text>
</view>
</view>
<!-- 商品视频 -->
<view class="goods-video" :class="{ show: switchMedia == 'video' }">
<video id="goodsVideo" :src="$util.img(goodsSkuDetail.video_url)" :poster="$util.img(goodsSkuDetail.sku_image, { size: 'big' })" objectFit="cover"></video>
</view>
<!-- 切换视频图片 -->
<view class="media-mode" v-if="goodsSkuDetail.video_url != ''">
<text :class="{ 'color-base-bg': switchMedia == 'video' }" @click="switchMedia = 'video'">{{ $lang('video') }}</text>
<text :class="{ 'color-base-bg': switchMedia == 'img' }" @click="(switchMedia = 'img'), videoContext.pause()">{{ $lang('image') }}</text>
</view>
</view>
<!-- 价格区域 -->
<view class="goods-gression">
<slot name="price"></slot>
</view>
<view class="newdetail margin-bottom" v-if="goodsSkuDetail.isinformation == 0">
<!-- 入口区域 -->
<slot name="entrance"></slot>
<!-- 配送 -->
<!-- @click="$refs.deliveryType.open()" -->
<view class="item delivery-type" v-if="goodsSkuDetail.is_virtual == 0" >
<view class="label">{{$lang('send')}}</view>
<block v-if="deliveryType">
<view class="box">
<block v-for="(item, index) in deliveryType" :key="index">
<text v-if="goodsSkuDetail.support_trade_type.indexOf(index) != -1">{{$lang('express')}}</text>
<!-- {{ item.name }} -->
</block>
</view>
<text class="iconfont icon-right"></text>
</block>
<block v-else>
<view class="box">未配置</view>
</block>
</view>
<!-- 门店 -->
<!-- <view class="item store-wrap" @click="openStoreListPopup()" v-if="addonIsExist.store && globalStoreInfo && isShowStore">
<view class="label">门店</view>
<view class="list-wrap">
<view class="name-wrap">
<text class="icondiy icon-system-shop"></text>
<text class="name">{{globalStoreInfo.store_name}}</text>
</view>
<view class="other-wrap">
<text class="distance" v-if="parseFloat(globalStoreInfo.distance)">距离{{ globalStoreInfo.distance > 1 ? globalStoreInfo.distance + 'km' : globalStoreInfo.distance * 1000 + 'm' }}</text>
<text class="decorate" v-if="parseFloat(globalStoreInfo.distance)">.</text>
<view class="address">{{ globalStoreInfo.full_address + globalStoreInfo.address }}
</view>
</view>
</view>
<text class="iconfont icon-right"></text>
</view> -->
<view class="item service" @click="openMerchantsServicePopup()" v-if="goodsSkuDetail.goods_service.length">
<view class="label">服务</view>
<view class="list-wrap">
<view class="item-wrap" v-for="(item, index) in goodsSkuDetail.goods_service" :key="index" v-if="index < 3">
<view class="item-wrap-box">
<view class="item-wrap-icon">
<text class="iconfont icon-dui" v-if="!item.icon || (!item.icon.imageUrl && !item.icon.icon)"></text>
<image class="icon-img" v-else-if="item.icon.iconType == 'img'" :src=" $util.img(item.icon.imageUrl)" />
<diy-icon class="icon-box" v-else-if="item.icon.iconType == 'icon'" :icon="item.icon.icon" :value="item.icon.style ? item.icon.style : null"></diy-icon>
</view>
<text>{{ item.service_name }}</text>
</view>
</view>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<!--多规格区域-->
<view class="newdetail margin-bottom" v-if="goodsSkuDetail.sku_spec_format">
<!-- 入口区域 -->
<slot name="skuspec"></slot>
</view>
<view class="newdetail margin-bottom" v-if="goodsSkuDetail.merch_id > 0">
<!-- 入口区域 -->
<slot name="entrance"></slot>
<!-- 商家 -->
<view class="item store-wrap" @click="$util.redirectTo('/pages_promotion/merch/detail', { merch_id: goodsSkuDetail.merch_id })">
<view class="list-wrap" style="display: flex;">
<view class="name-wrap">
<image :src="$util.img(goodsSkuDetail.merchinfo.merch_image)" mode="widthFix" style="width: 100rpx;height: 100rpx;border-radius: 50rpx;"></image>
</view>
<view class="other-wrap">
<view class="address" style="margin-left: 30rpx;">
<view>{{goodsSkuDetail.merchinfo.merch_name}}</view>
<view style="font-size: 24rpx;color: #888;">官方认证商家值得信赖</view>
</view>
</view>
</view>
<text class="iconfont icon-right"></text>
</view>
</view>
<!-- 配送方式 -->
<view @touchmove.prevent.stop>
<uni-popup ref="deliveryType" type="bottom">
<view class="deliverytype-popup-layer popup-layer">
<view class="head-wrap" @click="$refs.deliveryType.close()">
<text>配送</text>
<text class="iconfont icon-close"></text>
</view>
<scroll-view scroll-y class="type-body">
<block v-for="(item, index) in deliveryType" :key="index">
<view class="type-item" :class="{ 'not-support': goodsSkuDetail.support_trade_type.indexOf(index) == -1 }">
<text class="iconfont" :class="item.icon"></text>
<view class="content">
<view class="title">{{ item.name }}</view>
<view class="desc">{{ item.desc }}</view>
</view>
</view>
</block>
</scroll-view>
</view>
</uni-popup>
</view>
<!-- 商品服务 -->
<view @touchmove.prevent.stop>
<uni-popup ref="merchantsServicePopup" type="bottom">
<view class="goods-merchants-service-popup-layer popup-layer">
<view class="head-wrap" @click="closeMerchantsServicePopup()">
<text>商品服务</text>
<text class="iconfont icon-close"></text>
</view>
<scroll-view scroll-y>
<view class="item" :class="{ 'empty-desc': !item.desc }" v-for="(item, index) in goodsSkuDetail.goods_service" :key="index">
<view class="item-icon" :class="{'empty-desc':!item.desc}">
<text class="iconfont icon-dui color-base-text" v-if="!item.icon || (!item.icon.imageUrl && !item.icon.icon)"></text>
<image class="icon-img" v-else-if="item.icon.iconType == 'img'" :src=" $util.img(item.icon.imageUrl)" />
<diy-icon class="icon-box" v-else-if="item.icon.iconType == 'icon'" :icon="item.icon.icon" :value="item.icon.style ? item.icon.style : null"></diy-icon>
</view>
<view class="info-wrap">
<text class="title">{{ item.service_name }}</text>
<text class="describe" v-if="item.desc">{{ item.desc }}</text>
</view>
</view>
</scroll-view>
<view class="button-box">
<button type="primary" @click="closeMerchantsServicePopup()">确定</button>
</view>
</view>
</uni-popup>
</view>
<!-- 门店列表 -->
<view @touchmove.prevent.stop>
<uni-popup ref="storeListPopup" type="bottom">
<view class="goods-merchants-service-popup-layer popup-layer store-list-wrap">
<view class="head-wrap" @click="closeStoreListPopup()">
<text>门店列表</text>
<text class="iconfont icon-close"></text>
</view>
<scroll-view scroll-y>
<view class="store-list-content">
<view class="list-item" v-for="(item, index) in storeList.data" :key="index" @click="selectStore(item)">
<view class="item-box">
<view class="item-image">
<image :src="$util.img(item.store_image)" v-if="item.store_image"/>
<image :src="$util.getDefaultImage().store" v-else/>
</view>
<view class="item-info">
<view class="item-title">
<text class="title">{{ item.store_name }}</text>
<text class="distance color-base-text" v-if="item.distance">
距离{{ item.distance > 1 ? item.distance + 'km' : item.distance * 1000 + 'm' }}
</text>
</view>
<view class="item-time" v-if="item.open_date">营业时间{{ item.open_date }}
</view>
<view class="item-address">{{ item.full_address + item.address }}</view>
</view>
<view class="item-right"><text class="iconfont icon-right"></text></view>
</view>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
<!-- 业务区域 -->
<slot name="business"></slot>
<view class="detail-community" v-if="goodsSkuDetail.qr_data && goodsSkuDetail.qr_data.qr_state == 1">
<view class="community-box">
<image :src="$util.img('public/uniapp/goods/detail_erweiImage.png')" mode="aspectFill"></image>
<view class="community-content">
<view class="community-title">{{ goodsSkuDetail.qr_data.qr_name }}</view>
<view class="community-txt">{{ goodsSkuDetail.qr_data.community_describe }}</view>
</view>
</view>
<view class="community-btn" @click="onCommunity()">添加</view>
</view>
<!-- 促销 -->
<!-- <view class="community-model" @touchmove.prevent.stop @click.stop="onCloseCommunity()" v-show="isCommunity">
<view class="community-model-content" @click.stop>
<view class="community-model-content-radius">
<view>添加社群</view>
</view>
<view class="community-model-content-draw" v-if="goodsSkuDetail.qr_data && goodsSkuDetail.qr_data.qr_img">
<image :src="goodsSkuDetail.qr_data.qr_img != '' && goodsSkuDetail.qr_data.qr_state == 1 ? $util.img(goodsSkuDetail.qr_data.qr_img) : $util.img('public/uniapp/goods/detail_erweiImage.png') " mode="aspectFill" show-menu-by-longpress="true"/>
</view>
<view class="community-model-content-text">长按识别二维码添加社群</view>
</view>
<view class="community-model-close" @click.stop="onCloseCommunity()">
<text class="iconfont icon-close"></text>
</view>
</view> -->
<!-- 参与流程 -->
<slot name="articipation"></slot>
<!-- 商品评价 -->
<view class="group-wrap" v-if="evaluateConfig.evaluate_show == 1 && goodsSkuDetail.isinformation == 0" style="display: none;">
<view class="goods-evaluate" @click="toEvaluateDetail(goodsSkuDetail.goods_id)">
<view class="tit">
<!-- <view class="tit" :class="{ active: goodsEvaluate.content }"> -->
<view>
<text class="color-title font-size-base">
评价
<text class="font-size-base">({{ evaluateCount }})</text>
</text>
<text class="evaluate-item-empty" v-if="!evaluateCount">暂无评价</text>
<view class="evaluate-item-empty" v-else>
<text class="font-size-tag">查看全部</text>
<text class="iconfont icon-right font-size-tag"></text>
</view>
</view>
</view>
<view class="evaluate-item" v-for="(item, index) in goodsEvaluate" :key="index"
v-if="item.content">
<view class="evaluator">
<view class="evaluator-info">
<view class="evaluator-face">
<image v-if="item.member_headimg" :src="$util.img(item.member_headimg)" @error="item.member_headimg = $util.getDefaultImage().head" mode="aspectFill" />
<image v-else :src="$util.getDefaultImage().head" @error="item.member_headimg = $util.getDefaultImage().head" mode="aspectFill" />
</view>
<view class="evaluator-name-wrap">
<text class="evaluator-name using-hidden" v-if="item.member_name.length > 2 && item.is_anonymous == 1">
{{ item.member_name[0] }}***{{ item.member_name[item.member_name.length - 1] }}
</text>
<text class="evaluator-name using-hidden" v-else>{{ item.member_name }}</text>
<view v-if="item.scores" class="evaluator-xing">
<xiaoStarComponent :starCount="item.scores * 2"></xiaoStarComponent>
</view>
</view>
</view>
<text class="time color-tip">{{ $util.timeStampTurnTime(item.create_time) }}</text>
</view>
<view class="cont margin-top">{{ item.content }}</view>
<scroll-view scroll-x="true">
<view class="evaluate-img" v-if="item.images">
<view class="img-box" v-for="(img, img_index) in item.images" :key="img_index" @click="previewEvaluate(index, img_index, 'images')">
<image :src="$util.img(img)" mode="aspectFill" />
</view>
</view>
</scroll-view>
</view>
</view>
</view>
<view class="goods-attr" v-if="goodsSkuDetail.goods_attr_format && goodsSkuDetail.goods_attr_format.length > 0">
<view class="title">规格属性</view>
<view class="attr-wrap">
<block v-for="(item, index) in goodsSkuDetail.goods_attr_format" :key="index">
<view class="item" v-if="goodsAttrShow || (!goodsAttrShow && index < 4)">
<text class="attr-name">{{ item.attr_name }}</text>
<text class="value-name">{{ item.attr_value_name }}</text>
</view>
</block>
<view class="attr-action" v-if="goodsSkuDetail.goods_attr_format.length > 4" @click="switchGoodsAttr">
<block v-if="!goodsAttrShow">
展开<text class="iconfont icon-iconangledown"></text>
</block>
<block v-else>
收起<text class="iconfont icon-iconangledown-copy"></text>
</block>
</view>
</view>
</view>
<!-- 详情 -->
<view class="goods-detail-tab">
<view class="detail-tab">
<view class="tab-item">{{$lang('details')}}</view>
</view>
<view class="detail-content active">
<view class="detail-content-item">
<view class="goods-details" v-if="goodsSkuDetail.goods_content">
<!-- <rich-text :nodes="goodsSkuDetail.goods_content" @click="showImg($event)" :data-nodes="goodsSkuDetail.goods_content"></rich-text> -->
<!-- {{goodsSkuDetail.goods_content}} -->
<mp-html :content="goodsSkuDetail.goods_content" />
<!-- :loading="loading" @preview="preview" @navigate="navigate" -->
</view>
<view class="goods-details active" v-else></view>
<view class="goods-details" v-if="service && service.is_display == 1 && service.content">
<rich-text :nodes="service.content" @click="showImg($event)" :data-nodes="service.content"></rich-text>
</view>
</view>
</view>
</view>
<!-- 商品推荐 -->
<ns-goods-recommend ref="goodrecommend" route="goods_detail"></ns-goods-recommend>
<ns-copyright></ns-copyright>
<!-- 海报 -->
<view @touchmove.prevent.stop class="poster-layer">
<uni-popup ref="posterPopup" type="center">
<template v-if="poster != '-1'">
<view class="poster-wrap">
<view class="image-wrap">
<image :src="$util.img(poster)" :show-menu-by-longpress="true" mode="widthFix" />
<view class="close iconfont icon-close" @click="closePosterPopup()"></view>
</view>
<!-- #ifdef MP || APP-PLUS -->
<view class="save-btn" @click="saveGoodsPoster()">保存图片</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="save-btn">长按图片进行保存</view>
<!-- #endif -->
</view>
</template>
</uni-popup>
</view>
<!-- 分享弹窗 -->
<view @touchmove.prevent.stop>
<uni-popup ref="sharePopup" type="bottom" class="share-popup">
<view>
<view class="share-title">分享</view>
<view class="share-content">
<!-- #ifdef MP -->
<view class="share-box">
<button class="share-btn" :plain="true" open-type="share">
<view class="iconfont icon-share-friend"></view>
<text>分享给好友</text>
</button>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view class="share-box" v-if="goodsCircle">
<button class="share-btn" :plain="true" @click="openBusinessView">
<view class="iconfont icon-haowuquan"></view>
<text>分享到好物圈</text>
</button>
</view>
<!-- #endif -->
<view class="share-box" @click="openPosterPopup">
<button class="share-btn" :plain="true">
<view class="iconfont icon-pengyouquan"></view>
<text>生成分享海报</text>
</button>
</view>
<!-- #ifdef H5 -->
<view class="share-box" @click="copyUrl">
<button class="share-btn" :plain="true">
<view class="iconfont icon-fuzhilianjie"></view>
<text>复制链接</text>
</button>
</view>
<!-- #endif -->
</view>
<view class="share-footer" @click="closeSharePopup"><text>取消分享</text></view>
</view>
</uni-popup>
</view>
<slot name="fixedbtn"></slot>
</view>
</view>
<!-- 操作区域 -->
<slot name="action"></slot>
<to-top v-if="showTop" @toTop="scrollToTopNative()"></to-top>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
// 商品详情视图
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import nsGoodsRecommend from '@/components/ns-goods-recommend/ns-goods-recommend.vue';
import pengpaiFadeinOut from '@/components/pengpai-fadein-out/pengpai-fadein-out.vue';
import xiaoStarComponent from '@/components/xiao-star-component/xiao-star-component.vue';
import scroll from '@/common/js/scroll-view.js';
import toTop from '@/components/toTop/toTop.vue';
import detail from './detail.js';
export default {
name: 'goods-detail-view',
props: {
goodsSkuDetail: {
type: Object,
default: () => {
return {};
}
}
},
components: {
uniPopup,
nsGoodsRecommend,
pengpaiFadeinOut,
toTop,
xiaoStarComponent
},
mixins: [scroll, detail]
};
</script>
<style lang="scss">
@import '@/common/css/goods_detail.scss';
</style>
<style scoped></style>

View File

@@ -0,0 +1,142 @@
<template>
<!-- 悬浮按钮 -->
<view v-if="pageCount == 1 || need" class="fixed-box" :style="{ height: fixBtnShow ? '330rpx' : '120rpx' }">
<!-- <view class="btn-item" v-if="fixBtnShow" @click="$util.redirectTo('/pages/index/index')"> -->
<!-- #ifdef MP-WEIXIN -->
<button class="btn-item" v-if="fixBtnShow" hoverClass="none" openType="contact" sessionFrom="weapp" showMessageCard="true" :style="{backgroundImage:'url('+(kefuimg?kefuimg:'')+')',backgroundSize:'100% 100%'}">
<text class="icox icox-kefu" v-if="!kefuimg"></text>
<!-- <view>首页</view> -->
</button>
<!-- #endif -->
<view class="btn-item" v-if="fixBtnShow" @click="call()" :style="{backgroundImage:'url('+(phoneimg?phoneimg:'')+')',backgroundSize:'100% 100%'}">
<text class="iconfont icon-dianhua" v-if="!phoneimg"></text>
<!-- <view>我的</view> -->
</view>
<!-- <view class="btn-item icon-xiala" v-if="fixBtnShow" @click="fixBtnShow ? (fixBtnShow = false) : (fixBtnShow = true)">
<text class="iconfont icon-unfold"></text>
</view>
<view class="btn-item switch" v-else :class="{ show: fixBtnShow }"
@click="fixBtnShow ? (fixBtnShow = false) : (fixBtnShow = true)">
<view class="">快捷</view>
<view>导航</view>
</view> -->
</view>
</template>
<script>
export default {
name: 'hover-nav',
props: {
need: {
type: Boolean,
default: false
},
},
data() {
return {
pageCount: 0,
fixBtnShow: true,
tel:'',
kefuimg:'',
phoneimg:''
};
},
created() {
this.kefuimg = this.$util.getDefaultImage().kefu
this.phoneimg = this.$util.getDefaultImage().phone
this.pageCount = getCurrentPages().length;
var that = this
uni.getStorage({
key:'shopInfo',
success(e){
that.tel = e.data.mobile
}
})
},
methods: {
//拨打电话
call(){
uni.makePhoneCall({
phoneNumber:this.tel+''
})
}
}
};
</script>
<style lang="scss">
.container-box {
width: 100%;
.item-wrap {
border-radius: 10rpx;
.image-box {
border-radius: 10rpx;
}
image {
width: 100%;
height: auto;
border-radius: 10rpx;
will-change: transform;
}
}
}
//悬浮按钮
.fixed-box {
position: fixed;
right: 0rpx;
bottom: 200rpx;
z-index: 10;
// background: #fff;
// box-shadow: 2rpx 2rpx 22rpx rgba(0, 0, 0, 0.3);
border-radius: 120rpx;
padding: 20rpx 0;
display: flex;
justify-content: center;
flex-direction: column;
width: 100rpx;
box-sizing: border-box;
transition: 0.3s;
overflow: hidden;
.btn-item {
display: flex;
justify-content: center;
text-align: center;
flex-direction: column;
line-height: 1;
margin: 14rpx 0;
transition: 0.1s;
background: #fff;
border-radius: 50rpx;
width: 80rpx;
height: 80rpx;
padding: 0;
text {
font-size: 36rpx;
font-weight: bold;
}
view {
font-size: 26rpx;
font-weight: bold;
}
&.show {
transform: rotate(180deg);
}
&.switch {}
&.icon-xiala {
margin: 0;
margin-top: 0.1rpx;
}
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<view class="loading-layer" v-if="isShow">
<view class="loading-anim" v-if="themeStyle">
<view class="box item">
<view class="border out item" :style="{'border-left-color':themeStyle.main_color, 'border-top-color':themeStyle.main_color}"></view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'loading-cover',
props: {
initShow: {
type: Boolean,
default: true
}
},
data() {
return {
isShow: true
};
},
components: {},
created() {
this.isShow = this.initShow;
},
methods: {
show() {
this.isShow = true;
},
hide() {
this.isShow = false;
}
}
};
</script>
<style lang="scss">
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading-layer {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 997;
background: #f8f8f8;
}
.loading-anim {
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
}
.loading-anim > .item {
position: relative;
width: 70rpx;
height: 70rpx;
perspective: 1600rpx;
transform-style: preserve-3d;
transition: all 0.2s ease-out;
}
.loading-anim .border {
position: absolute;
border-radius: 50%;
border: 6rpx solid;
}
.loading-anim .out {
top: 15%;
left: 15%;
width: 70%;
height: 70%;
border-left-color: #FF4646;
border-right-color: #C5C5C5 !important;
// border-right-color: rgba($color: #000000, $alpha: 0) !important;
border-top-color: #FF4646 ;
border-bottom-color: #C5C5C5 !important;
// border-bottom-color: rgba($color: #000000, $alpha: 0) !important;
animation: spin 0.6s linear normal infinite;
}
.loading-anim .in {
top: 25%;
left: 25%;
width: 50%;
height: 50%;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
animation: spin 0.8s linear infinite;
}
.loading-anim .mid {
top: 40%;
left: 40%;
width: 20%;
height: 20%;
border-left-color: transparent;
border-right-color: transparent;
animation: spin 0.6s linear infinite;
}
</style>

View File

@@ -0,0 +1,55 @@
/* 下拉刷新区域 */
.mescroll-downwarp {
position: absolute;
top: -100%;
left: 0;
width: 100%;
height: 100%;
text-align: center;
}
/* 下拉刷新--内容区,定位于区域底部 */
.mescroll-downwarp .downwarp-content {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
min-height: 60rpx;
padding: 20rpx 0;
text-align: center;
}
/* 下拉刷新--提示文本 */
.mescroll-downwarp .downwarp-tip {
display: inline-block;
font-size: 28rpx;
color: gray;
vertical-align: middle;
margin-left: 16rpx;
}
/* 下拉刷新--旋转进度条 */
.mescroll-downwarp .downwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid gray;
border-bottom-color: transparent;
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-downwarp .mescroll-rotate {
animation: mescrollDownRotate 0.6s linear infinite;
}
@keyframes mescrollDownRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,52 @@
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{ 'mescroll-rotate': isDownLoading }" :style="{ transform: downRotate }"></view>
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // down的配置项
type: Number, // 下拉状态inOffset1 outOffset2 showLoading3 endDownScroll4
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption() {
return this.option || {};
},
// 是否在加载中
isDownLoading() {
return this.type === 3;
},
// 旋转的角度
downRotate() {
return 'rotate(' + 360 * this.rate + 'deg)';
},
// 文本提示
downText() {
switch (this.type) {
case 1:
return this.mOption.textInOffset;
case 2:
return this.mOption.textOutOffset;
case 3:
return this.mOption.textLoading;
case 4:
return this.mOption.textLoading;
default:
return this.mOption.textInOffset;
}
}
}
};
</script>
<style>
@import './mescroll-down.css';
</style>

View File

@@ -0,0 +1,90 @@
<!--空布局
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
-->
<template>
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
<image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" />
<view v-if="tip" class="empty-tip">{{ tip }}</view>
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
</view>
</template>
<script>
// 引入全局配置
import GlobalOption from './../mescroll-uni-option.js';
export default {
props: {
// empty的配置项: 默认为GlobalOption.up.empty
option: {
type: Object,
default() {
return {};
}
}
},
// 使用computed获取配置,用于支持option的动态配置
computed: {
// 图标
icon() {
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标
},
// 文本提示
tip() {
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示
}
},
methods: {
// 点击按钮
emptyClick() {
this.$emit('emptyclick');
}
}
};
</script>
<style>
/* 无任何数据的空布局 */
.mescroll-empty {
box-sizing: border-box;
width: 100%;
padding: 100rpx 50rpx;
text-align: center;
}
.mescroll-empty.empty-fixed {
z-index: 99;
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
top: 100rpx;
left: 0;
}
.mescroll-empty .empty-icon {
width: 280rpx;
height: 280rpx;
}
.mescroll-empty .empty-tip {
margin-top: 20rpx;
font-size: $font-size-tag;
color: gray;
}
.mescroll-empty .empty-btn {
display: inline-block;
margin-top: 40rpx;
min-width: 200rpx;
padding: 18rpx;
font-size: $font-size-base;
border: 1rpx solid #e04b28;
border-radius: 60rpx;
color: #e04b28;
}
.mescroll-empty .empty-btn:active {
opacity: 0.75;
}
</style>

View File

@@ -0,0 +1,81 @@
<!-- 回到顶部的按钮 -->
<template>
<image
v-if="mOption.src"
class="mescroll-totop"
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', { 'mescroll-safe-bottom': mOption.safearea }]"
:style="{ 'z-index': mOption.zIndex, left: left, right: right, bottom: addUnit(mOption.bottom), width: addUnit(mOption.width), 'border-radius': addUnit(mOption.radius) }"
:src="mOption.src"
mode="widthFix"
@click="toTopClick"
/>
</template>
<script>
export default {
props: {
// up.toTop的配置项
option: Object,
// 是否显示
value: false
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption() {
return this.option || {};
},
// 优先显示左边
left() {
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
},
// 右边距离 (优先显示左边)
right() {
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
}
},
methods: {
addUnit(num) {
if (!num) return 0;
if (typeof num === 'number') return num + 'rpx';
return num;
},
toTopClick() {
this.$emit('input', false); // 使v-model生效
this.$emit('click'); // 派发点击事件
}
}
};
</script>
<style>
/* 回到顶部的按钮 */
.mescroll-totop {
z-index: 99;
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
right: 46rpx !important;
bottom: 272rpx !important;
width: 72rpx;
height: auto;
border-radius: 50%;
opacity: 0;
transition: opacity 0.5s; /* 过渡 */
margin-bottom: var(--window-bottom); /* css变量 */
}
/* 适配 iPhoneX */
.mescroll-safe-bottom {
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
}
/* 显示 -- 淡入 */
.mescroll-totop-in {
opacity: 1;
}
/* 隐藏 -- 淡出且不接收事件*/
.mescroll-totop-out {
opacity: 0;
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,47 @@
/* 上拉加载区域 */
.mescroll-upwarp {
min-height: 60rpx;
padding: 30rpx 0;
text-align: center;
clear: both;
margin-bottom: 20rpx;
}
/*提示文本 */
.mescroll-upwarp .upwarp-tip,
.mescroll-upwarp .upwarp-nodata {
display: inline-block;
font-size: 28rpx;
color: #b1b1b1;
vertical-align: middle;
}
.mescroll-upwarp .upwarp-tip {
margin-left: 16rpx;
}
/*旋转进度条 */
.mescroll-upwarp .upwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #b1b1b1;
border-bottom-color: transparent;
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-upwarp .mescroll-rotate {
animation: mescrollUpRotate 0.6s linear infinite;
}
@keyframes mescrollUpRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,39 @@
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // up的配置项
type: Number // 上拉加载的状态0loading前1loading中2没有更多了
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption() {
return this.option || {};
},
// 加载中
isUpLoading() {
return this.type === 1;
},
// 没有更多了
isUpNoMore() {
return this.type === 2;
}
}
};
</script>
<style>
@import './mescroll-up.css';
</style>

View File

@@ -0,0 +1,15 @@
page {
-webkit-overflow-scrolling: touch;
/* 使iOS滚动流畅 */
}
.mescroll-body {
position: relative;
/* 下拉刷新区域相对自身定位 */
height: auto;
/* 不可固定高度,否则overflow: hidden, 可通过设置最小高度使列表不满屏仍可下拉*/
overflow: hidden;
/* 遮住顶部下拉刷新区域 */
box-sizing: border-box;
/* 避免设置padding出现双滚动条的问题 */
}

View File

@@ -0,0 +1,298 @@
<template>
<view
class="mescroll-body"
:style="{ minHeight: minHeight, 'padding-top': padTop, 'padding-bottom': padBottom, 'padding-bottom': padBottomConstant, 'padding-bottom': padBottomEnv }"
@touchstart="touchstartEvent"
@touchmove="touchmoveEvent"
@touchend="touchendEvent"
@touchcancel="touchendEvent"
>
<view class="mescroll-body-content mescroll-touch" :style="{ transform: translateY, transition: transition }">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{ 'mescroll-rotate': isDownLoading }" :style="{ transform: downRotate }"></view>
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<!-- <mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> -->
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType === 1">
<view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType === 2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick" v-if="showTop"></mescroll-top>
</view>
</template>
<script>
// 引入mescroll-uni.js,处理核心逻辑
import MeScroll from './mescroll-uni.js';
// 引入全局配置
import GlobalOption from './mescroll-uni-option.js';
// 引入空布局组件
import MescrollEmpty from './components/mescroll-empty.vue';
// 引入回到顶部组件
import MescrollTop from './components/mescroll-top.vue';
export default {
components: {
MescrollEmpty,
MescrollTop
},
data() {
return {
mescroll: { optDown: {}, optUp: {} }, // mescroll实例
downHight: 0, //下拉刷新: 容器高度
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // 下拉刷新状态 inOffset1 outOffset2 showLoading3 endDownScroll4
upLoadType: 0, // 上拉加载状态0loading前1loading中2没有更多了
isShowEmpty: false, // 是否显示空布局
isShowToTop: false, // 是否显示回到顶部按钮
windowHeight: 0, // 可使用窗口的高度
statusBarHeight: 0 // 状态栏高度
};
},
props: {
down: Object, // 下拉刷新的参数配置
up: Object, // 上拉加载的参数配置
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量)
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
showTop: {
type: Boolean,
default: true
}
},
computed: {
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
minHeight() {
return this.toPx(this.height || '100%') + 'px';
},
// 下拉布局往下偏移的距离 (px)
numTop() {
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0);
},
padTop() {
return this.numTop + 'px';
},
// 上拉布局往上偏移 (px)
numBottom() {
return this.toPx(this.bottom);
},
padBottom() {
return this.numBottom + 'px';
},
padBottomConstant() {
return this.safearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this.padBottom;
},
padBottomEnv() {
return this.safearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom;
},
// 是否为重置下拉的状态
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
// 过渡
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
},
// 是否在加载中
isDownLoading() {
return this.downLoadType === 3;
},
// 旋转的角度
downRotate() {
return 'rotate(' + 360 * this.downRate + 'deg)';
},
// 文本提示
downText() {
switch (this.downLoadType) {
case 1:
return this.mescroll.optDown.textInOffset;
case 2:
return this.mescroll.optDown.textOutOffset;
case 3:
return this.mescroll.optDown.textLoading;
case 4:
return this.mescroll.optDown.textLoading;
default:
return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px的数值
toPx(num) {
if (typeof num === 'string') {
if (num.indexOf('px') !== -1) {
if (num.indexOf('rpx') !== -1) {
// "10rpx"
num = num.replace('rpx', '');
} else if (num.indexOf('upx') !== -1) {
// "10upx"
num = num.replace('upx', '');
} else {
// "10px"
return Number(num.replace('px', ''));
}
} else if (num.indexOf('%') !== -1) {
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
let rate = Number(num.replace('%', '')) / 100;
return this.windowHeight * rate;
}
}
return num ? uni.upx2px(Number(num)) : 0;
},
//注册列表touchstart事件,用于下拉刷新
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//注册列表touchmove事件,用于下拉刷新
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//注册列表touchend事件,用于下拉刷新
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
// 点击空布局的按钮回调
emptyClick() {
this.$emit('emptyclick', this.mescroll);
},
// 点击回到顶部的按钮回调
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
}
},
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
created() {
let vm = this;
let diyOption = {
// 下拉刷新的配置
down: {
inOffset(mescroll) {
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
},
outOffset(mescroll) {
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
},
onMoving(mescroll, rate, downHight) {
// 下拉过程中的回调,滑动过程一直在执行;
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
},
endDownScroll(mescroll) {
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
},
// 派发下拉刷新的回调
callback: function(mescroll) {
vm.$emit('down', mescroll);
}
},
// 上拉加载的配置
up: {
// 显示加载中的回调
showLoading() {
vm.upLoadType = 1;
},
// 显示无更多数据的回调
showNoMore() {
vm.upLoadType = 2;
},
// 隐藏上拉加载的回调
hideUpScroll() {
vm.upLoadType = 0;
},
// 空布局
empty: {
onShow(isShow) {
// 显示隐藏的回调
vm.isShowEmpty = isShow;
}
},
// 回到顶部
toTop: {
onShow(isShow) {
// 显示隐藏的回调
vm.isShowToTop = isShow;
}
},
// 派发上拉加载的回调
callback: function(mescroll) {
vm.$emit('up', mescroll);
}
}
};
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
let myOption = JSON.parse(
JSON.stringify({
down: vm.down,
up: vm.up
})
); // 深拷贝,避免对props的影响
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
// 初始化MeScroll对象
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
// init回调mescroll对象
vm.$emit('init', vm.mescroll);
// 设置高度
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使down的bottomOffset生效
vm.mescroll.setBodyHeight(sys.windowHeight);
// 因为使用的是page的scroll,这里需自定义scrollTo
vm.mescroll.resetScrollTo((y, t) => {
uni.pageScrollTo({
scrollTop: y,
duration: t
});
});
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {
} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
}
};
</script>
<style>
@import './mescroll-body.css';
@import './components/mescroll-down.css';
@import './components/mescroll-up.css';
</style>

View File

@@ -0,0 +1,60 @@
// mescroll-body 和 mescroll-uni 通用
// import MescrollUni from "./mescroll-uni.vue";
// import MescrollBody from "./mescroll-body.vue";
const MescrollMixin = {
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
// MescrollUni,
// MescrollBody
// },
data() {
return {
mescroll: null //mescroll实例对象
}
},
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
onPullDownRefresh() {
this.mescroll && this.mescroll.onPullDownRefresh();
},
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onPageScroll(e) {
this.mescroll && this.mescroll.onPageScroll(e);
},
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onReachBottom() {
this.mescroll && this.mescroll.onReachBottom();
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef(); // 兼容字节跳动小程序
},
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
mescrollInitByRef() {
if (!this.mescroll || !this.mescroll.resetUpScroll) {
let mescrollRef = this.$refs.mescrollRef;
if (mescrollRef) this.mescroll = mescrollRef.mescroll
}
},
// 下拉刷新的回调
downCallback() {
// mixin默认resetUpScroll
this.mescroll.resetUpScroll()
},
// 上拉加载的回调
upCallback() {
// mixin默认延时500自动结束加载
setTimeout(() => {
this.mescroll.endErr();
}, 500)
}
},
mounted() {
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
}
}
export default MescrollMixin;

View File

@@ -0,0 +1,35 @@
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '', // 没有更多数据的提示文本
// textNoMore: '— 我是有底线的 —', // 没有更多数据的提示文本
offset: 80, // 距底部多远时,触发upCallback
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
toTop: {
// 回到顶部按钮,需配置src才显示
src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: "http://www.mescroll.com/img/mescroll-empty.png?v=1", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
tip: '~ 暂无相关数据 ~' // 提示
}
}
}
export default GlobalOption

View File

@@ -0,0 +1,33 @@
page {
height: 100%;
box-sizing: border-box;
/* 避免设置padding出现双滚动条的问题 */
}
.mescroll-uni-warp {
height: 100%;
}
.mescroll-uni {
position: relative;
width: 100%;
height: 100%;
min-height: 200rpx;
overflow-y: auto;
box-sizing: border-box;
/* 避免设置padding出现双滚动条的问题 */
}
/* 定位的方式固定高度 */
.mescroll-uni-fixed {
z-index: 1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: auto;
/* 使right生效 */
height: auto;
/* 使bottom生效 */
}

View File

@@ -0,0 +1,841 @@
/* mescroll
* version 1.2.3
* 2020-02-18 wenju
* http://www.mescroll.com
*/
export default function MeScroll(options, isScrollBody) {
let me = this;
me.version = '1.2.3'; // mescroll版本号
me.options = options || {}; // 配置
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
me.isUpScrolling = false; // 是否在执行上拉加载的回调
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
// 初始化下拉刷新
me.initDownScroll();
// 初始化上拉加载,则初始化
me.initUpScroll();
// 自动加载
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
if (me.optDown.use && me.optDown.auto && hasDownCallback) {
if (me.optDown.autoShowLoading) {
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
} else {
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
}
}
// 自动触发上拉加载
setTimeout(function() { // 延时确保先执行down的callback,再执行up的callback,因为部分小程序emit是异步,会导致isUpAutoLoad判断有误
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
}, 100)
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
}
/* 配置参数:下拉刷新 */
MeScroll.prototype.extendDownScroll = function(optDown) {
// 下拉刷新的配置
MeScroll.extend(optDown, {
use: true, // 是否启用下拉刷新; 默认true
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
isLock: false, // 是否锁定下拉刷新,默认false;
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
startTop: 100, // scroll-view滚动到顶部时,此时的scroll-top不一定为0, 此值用于控制最大的误差
fps: 80, // 下拉节流 (值越大每秒刷新频率越高)
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
inited: null, // 下拉刷新初始化完毕的回调
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
outOffset: null, // 下拉的距离大于offset那一刻的回调
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
showLoading: null, // 显示下拉刷新进度的回调
afterLoading: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
endDownScroll: null, // 结束下拉刷新的回调
callback: function(mescroll) {
// 下拉刷新的回调;默认重置上拉加载列表为第一页
mescroll.resetUpScroll();
}
})
}
/* 配置参数:上拉加载 */
MeScroll.prototype.extendUpScroll = function(optUp) {
// 上拉加载的配置
MeScroll.extend(optUp, {
use: true, // 是否启用上拉加载; 默认true
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
isLock: false, // 是否锁定上拉加载,默认false;
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
callback: null, // 上拉加载的回调;function(page,mescroll){ }
page: {
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
size: 10, // 每页数据的数量
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
},
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
offset: 80, // 距底部多远时,触发upCallback
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
inited: null, // 初始化完毕的回调
showLoading: null, // 显示加载中的回调
showNoMore: null, // 显示无更多数据的回调
hideUpScroll: null, // 隐藏上拉加载的回调
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
toTop: {
// 回到顶部按钮,需配置src才显示
src: null, // 图片路径,默认null (绝对路径或网络图)
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
btnClick: null, // 点击按钮的回调
onShow: null, // 是否显示的回调
zIndex: 9990, // fixed定位z-index值
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: null, // 图标路径
tip: '~ 暂无相关数据 ~', // 提示
btnText: '', // 按钮
btnClick: null, // 点击按钮的回调
onShow: null, // 是否显示的回调
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
zIndex: 99 // fixed定位z-index值
},
onScroll: false // 是否监听滚动事件
})
}
/* 配置参数 */
MeScroll.extend = function(userOption, defaultOption) {
if (!userOption) return defaultOption;
for (let key in defaultOption) {
if (userOption[key] == null) {
let def = defaultOption[key];
if (def != null && typeof def === 'object') {
userOption[key] = MeScroll.extend({}, def); // 深度匹配
} else {
userOption[key] = def;
}
} else if (typeof userOption[key] === 'object') {
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
}
}
return userOption;
}
/* -------初始化下拉刷新------- */
MeScroll.prototype.initDownScroll = function() {
let me = this;
// 配置参数
me.optDown = me.options.down || {};
me.extendDownScroll(me.optDown);
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
if (me.isScrollBody && me.optDown.native) {
me.optDown.use = false
} else {
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
}
me.downHight = 0; // 下拉区域的高度
// 在页面中加入下拉布局
if (me.optDown.use && me.optDown.inited) {
// 初始化完毕的回调
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
me.optDown.inited(me);
}, 0)
}
}
/* 列表touchstart事件 */
MeScroll.prototype.touchstartEvent = function(e) {
if (!this.optDown.use) return;
this.startPoint = this.getPoint(e); // 记录起点
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
this.lastPoint = this.startPoint; // 重置上次move的点
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
this.inTouchend = false; // 标记不是touchend
}
/* 列表touchmove事件 */
MeScroll.prototype.touchmoveEvent = function(e) {
if (!this.optDown.use) return;
if (!this.startPoint) return;
let me = this;
// 节流
let t = new Date().getTime();
if (me.moveTime && t - me.moveTime < me.moveTimeDiff) { // 小于节流时间,则不处理
return;
} else {
me.moveTime = t
if (!me.moveTimeDiff) me.moveTimeDiff = 1000 / me.optDown.fps
}
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
let curPoint = me.getPoint(e); // 当前点
let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 向下拉 && 在顶部
// mescroll-body,直接判定在顶部即可
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
// scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
if (moveY > 0 && (
(me.isScrollBody && scrollTop <= 0) ||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)))
)) {
// 可下拉的条件
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
me.optUp.isBoth))) {
// 下拉的角度是否在配置的范围内
let angle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (angle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
me.inTouchend = true; // 标记执行touchend
me.touchendEvent(); // 提前触发touchend
return;
}
me.preventDefault(e); // 阻止默认事件
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
// 下拉距离 < 指定距离
if (me.downHight < me.optDown.offset) {
if (me.movetype !== 1) {
me.movetype = 1; // 加入标记,保证只执行一次
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
// 指定距离 <= 下拉距离
} else {
if (me.movetype !== 2) {
me.movetype = 2; // 加入标记,保证只执行一次
me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
if (diff > 0) { // 向下拉
me.downHight += Math.round(diff * me.optDown.outOffsetRate); // 越往下,高度变化越小
} else { // 向上收
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
}
}
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
}
}
me.lastPoint = curPoint; // 记录本次移动的点
}
/* 列表touchend事件 */
MeScroll.prototype.touchendEvent = function(e) {
if (!this.optDown.use) return;
// 如果下拉区域高度已改变,则需重置回来
if (this.isMoveDown) {
if (this.downHight >= this.optDown.offset) {
// 符合触发刷新的条件
this.triggerDownScroll();
} else {
// 不符合的话 则重置
this.downHight = 0;
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
}
this.movetype = 0;
this.isMoveDown = false;
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 上滑
if (isScrollUp) {
// 需检查滑动的角度
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
if (angle > 80) {
// 检查并触发上拉
this.triggerUpScroll(true);
}
}
}
}
/* 根据点击滑动事件获取第一个手指的坐标 */
MeScroll.prototype.getPoint = function(e) {
if (!e) {
return {
x: 0,
y: 0
}
}
if (e.touches && e.touches[0]) {
return {
x: e.touches[0].pageX,
y: e.touches[0].pageY
}
} else if (e.changedTouches && e.changedTouches[0]) {
return {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY
}
} else {
return {
x: e.clientX,
y: e.clientY
}
}
}
/* 计算两点之间的角度: 区间 [0,90]*/
MeScroll.prototype.getAngle = function(p1, p2) {
let x = Math.abs(p1.x - p2.x);
let y = Math.abs(p1.y - p2.y);
let z = Math.sqrt(x * x + y * y);
let angle = 0;
if (z !== 0) {
angle = Math.asin(y / z) / Math.PI * 180;
}
return angle
}
/* 触发下拉刷新 */
MeScroll.prototype.triggerDownScroll = function() {
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
//return true则处于完全自定义状态
} else {
this.showDownScroll(); // 下拉刷新中...
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
}
/* 显示下拉进度布局 */
MeScroll.prototype.showDownScroll = function() {
this.isDownScrolling = true; // 标记下拉中
if (this.optDown.native) {
uni.startPullDownRefresh(); // 系统自带的下拉刷新
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
} else {
this.downHight = this.optDown.offset; // 更新下拉区域高度
this.optDown.showLoading && this.optDown.showLoading(this, this.downHight); // 下拉刷新中...
}
}
/* 显示系统自带的下拉刷新时需要处理的业务 */
MeScroll.prototype.onPullDownRefresh = function() {
this.isDownScrolling = true; // 标记下拉中
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
/* 结束下拉刷新 */
MeScroll.prototype.endDownScroll = function() {
if (this.optDown.native) { // 结束原生下拉刷新
this.isDownScrolling = false;
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
uni.stopPullDownRefresh();
return
}
let me = this;
// 结束下拉刷新的方法
let endScroll = function() {
me.downHight = 0;
me.isDownScrolling = false;
me.optDown.endDownScroll && me.optDown.endDownScroll(me);
!me.isScrollBody && me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
}
// 结束下拉刷新时的回调
let delay = 0;
if (me.optDown.afterLoading) delay = me.optDown.afterLoading(me); // 结束下拉刷新的延时,单位ms
if (typeof delay === 'number' && delay > 0) {
setTimeout(endScroll, delay);
} else {
endScroll();
}
}
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
MeScroll.prototype.lockDownScroll = function(isLock) {
if (isLock == null) isLock = true;
this.optDown.isLock = isLock;
}
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
MeScroll.prototype.lockUpScroll = function(isLock) {
if (isLock == null) isLock = true;
this.optUp.isLock = isLock;
}
/* -------初始化上拉加载------- */
MeScroll.prototype.initUpScroll = function() {
let me = this;
// 配置参数
me.optUp = me.options.up || {
use: false
};
me.extendUpScroll(me.optUp);
if (!me.optUp.isBounce) me.setBounce(false); // 不允许bounce时,需禁止window的touchmove事件
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
// 初始化完毕的回调
if (me.optUp.inited) {
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
me.optUp.inited(me);
}, 0)
}
}
/*滚动到底部的事件 (仅mescroll-body生效)*/
MeScroll.prototype.onReachBottom = function() {
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
if (!this.optUp.isLock && this.optUp.hasNext) {
this.triggerUpScroll();
}
}
}
/*列表滚动事件 (仅mescroll-body生效)*/
MeScroll.prototype.onPageScroll = function(e) {
if (!this.isScrollBody) return;
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
this.setScrollTop(e.scrollTop);
// 顶部按钮的显示隐藏
if (e.scrollTop >= this.optUp.toTop.offset) {
this.showTopBtn();
} else {
this.hideTopBtn();
}
}
/*列表滚动事件*/
MeScroll.prototype.scroll = function(e, onScroll) {
// 更新滚动条的位置
this.setScrollTop(e.scrollTop);
// 更新滚动内容高度
this.setScrollHeight(e.scrollHeight);
// 向上滑还是向下滑动
if (this.preScrollY == null) this.preScrollY = 0;
this.isScrollUp = e.scrollTop - this.preScrollY > 0;
this.preScrollY = e.scrollTop;
// 上滑 && 检查并触发上拉
this.isScrollUp && this.triggerUpScroll(true);
// 顶部按钮的显示隐藏
if (e.scrollTop >= this.optUp.toTop.offset) {
this.showTopBtn();
} else {
this.hideTopBtn();
}
// 滑动监听
this.optUp.onScroll && onScroll && onScroll()
}
/* 触发上拉加载 */
MeScroll.prototype.triggerUpScroll = function(isCheck) {
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
// 是否校验在底部; 默认不校验
if (isCheck === true) {
let canUp = false;
// 还有下一页 && 没有锁定 && 不在下拉中
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
canUp = true; // 标记可上拉
}
}
if (canUp === false) return;
}
this.showUpScroll(); // 上拉加载中...
this.optUp.page.num++; // 预先加一页,如果失败则减回
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
this.optUp.callback(this); // 执行回调,联网加载数据
}
}
/* 显示上拉加载中 */
MeScroll.prototype.showUpScroll = function() {
this.isUpScrolling = true; // 标记上拉加载中
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
}
/* 显示上拉无更多数据 */
MeScroll.prototype.showNoMore = function() {
this.optUp.hasNext = false; // 标记无更多数据
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
}
/* 隐藏上拉区域**/
MeScroll.prototype.hideUpScroll = function() {
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
}
/* 结束上拉加载 */
MeScroll.prototype.endUpScroll = function(isShowNoMore) {
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
if (isShowNoMore) {
this.showNoMore(); // isShowNoMore=true,显示无更多数据
} else {
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
}
}
this.isUpScrolling = false; // 标记结束上拉加载
}
/* 重置上拉加载列表为第一页
*isShowLoading 是否显示进度布局;
* 1.默认null,不传参,则显示上拉加载的进度布局
* 2.传参true, 则显示下拉刷新的进度布局
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
*/
MeScroll.prototype.resetUpScroll = function(isShowLoading) {
if (this.optUp && this.optUp.use) {
let page = this.optUp.page;
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
page.num = this.startNum; // 重置为第一页
page.time = null; // 重置时间为空
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
if (isShowLoading == null) {
this.removeEmpty(); // 移除空布局
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
} else {
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
}
}
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
}
}
/* 设置page.num的值 */
MeScroll.prototype.setPageNum = function(num) {
this.optUp.page.num = num - 1;
}
/* 设置page.size的值 */
MeScroll.prototype.setPageSize = function(size) {
this.optUp.page.size = size;
}
/* 联网回调成功,结束下拉刷新和上拉加载
* dataSize: 当前页的数据量(必传)
* totalPage: 总页数(必传)
* systime: 服务器时间 (可空)
*/
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
let hasNext;
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
this.endSuccess(dataSize, hasNext, systime);
}
/* 联网回调成功,结束下拉刷新和上拉加载
* dataSize: 当前页的数据量(必传)
* totalSize: 列表所有数据总数量(必传)
* systime: 服务器时间 (可空)
*/
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
let hasNext;
if (this.optUp.use && totalSize != null) {
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
hasNext = loadSize < totalSize; // 是否还有下一页
}
this.endSuccess(dataSize, hasNext, systime);
}
/* 联网回调成功,结束下拉刷新和上拉加载
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
*/
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
let me = this;
// 结束下拉刷新
if (me.isDownScrolling) me.endDownScroll();
// 结束上拉加载
if (me.optUp.use) {
let isShowNoMore; // 是否已无更多数据
if (dataSize != null) {
let pageNum = me.optUp.page.num; // 当前页码
let pageSize = me.optUp.page.size; // 每页长度
// 如果是第一页
if (pageNum === 1) {
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
}
if (dataSize < pageSize || hasNext === false) {
// 返回的数据不满一页时,则说明已无更多数据
me.optUp.hasNext = false;
if (dataSize === 0 && pageNum === 1) {
// 如果第一页无任何数据且配置了空布局
isShowNoMore = false;
me.showEmpty();
} else {
// 总列表数少于配置的数量,则不显示无更多数据
let allDataSize = (pageNum - 1) * pageSize + dataSize;
if (allDataSize < me.optUp.noMoreSize) {
isShowNoMore = false;
} else {
isShowNoMore = true;
}
me.removeEmpty(); // 移除空布局
}
} else {
// 还有下一页
isShowNoMore = false;
me.optUp.hasNext = true;
me.removeEmpty(); // 移除空布局
}
}
// 隐藏上拉
me.endUpScroll(isShowNoMore);
}
}
/* 回调失败,结束下拉刷新和上拉加载 */
MeScroll.prototype.endErr = function(errDistance) {
// 结束下拉,回调失败重置回原来的页码和时间
if (this.isDownScrolling) {
let page = this.optUp.page;
if (page && this.prePageNum) {
page.num = this.prePageNum;
page.time = this.prePageTime;
}
this.endDownScroll();
}
// 结束上拉,回调失败重置回原来的页码
if (this.isUpScrolling) {
this.optUp.page.num--;
this.endUpScroll(false);
// 如果是mescroll-body,则需往回滚一定距离
if (this.isScrollBody && errDistance !== 0) { // 不处理0
if (!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
}
}
}
/* 显示空布局 */
MeScroll.prototype.showEmpty = function() {
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
}
/* 移除空布局 */
MeScroll.prototype.removeEmpty = function() {
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
}
/* 显示回到顶部的按钮 */
MeScroll.prototype.showTopBtn = function() {
if (!this.topBtnShow) {
this.topBtnShow = true;
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
}
}
/* 隐藏回到顶部的按钮 */
MeScroll.prototype.hideTopBtn = function() {
if (this.topBtnShow) {
this.topBtnShow = false;
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
}
}
/* 获取滚动条的位置 */
MeScroll.prototype.getScrollTop = function() {
return this.scrollTop || 0
}
/* 记录滚动条的位置 */
MeScroll.prototype.setScrollTop = function(y) {
this.scrollTop = y;
}
/* 滚动到指定位置 */
MeScroll.prototype.scrollTo = function(y, t) {
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
}
/* 自定义scrollTo */
MeScroll.prototype.resetScrollTo = function(myScrollTo) {
this.myScrollTo = myScrollTo
}
/* 滚动条到底部的距离 */
MeScroll.prototype.getScrollBottom = function() {
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
}
/* 计步器
star: 开始值
end: 结束值
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
t: 计步时长,传0则直接回调end值;不传则默认300ms
rate: 周期;不传则默认30ms计步一次
* */
MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
let diff = end - star; // 差值
if (t === 0 || diff === 0) {
callback && callback(end);
return;
}
t = t || 300; // 时长 300ms
rate = rate || 30; // 周期 30ms
let count = t / rate; // 次数
let step = diff / count; // 步长
let i = 0; // 计数
let timer = setInterval(function() {
if (i < count - 1) {
star += step;
callback && callback(star, timer);
i++;
} else {
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
clearInterval(timer);
}
}, rate);
}
/* 滚动容器的高度 */
MeScroll.prototype.getClientHeight = function(isReal) {
let h = this.clientHeight || 0
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
h = this.getBodyHeight()
}
return h
}
MeScroll.prototype.setClientHeight = function(h) {
this.clientHeight = h;
}
/* 滚动内容的高度 */
MeScroll.prototype.getScrollHeight = function() {
return this.scrollHeight || 0;
}
MeScroll.prototype.setScrollHeight = function(h) {
this.scrollHeight = h;
}
/* body的高度 */
MeScroll.prototype.getBodyHeight = function() {
return this.bodyHeight || 0;
}
MeScroll.prototype.setBodyHeight = function(h) {
this.bodyHeight = h;
}
/* 阻止浏览器默认滚动事件 */
MeScroll.prototype.preventDefault = function(e) {
// 小程序不支持e.preventDefault
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
}
/* 是否允许下拉回弹(橡皮筋效果); true或null为允许; false禁止bounce */
MeScroll.prototype.setBounce = function(isBounce) {
// #ifdef H5
if (isBounce === false) {
this.optUp.isBounce = false; // 禁止
// 标记当前页使用了mescroll (需延时,确保page已切换)
setTimeout(function() {
let uniPageDom = document.getElementsByTagName('uni-page')[0];
uniPageDom && uniPageDom.setAttribute('use_mescroll', true)
}, 30);
// 避免重复添加事件
if (window.isSetBounce) return;
window.isSetBounce = true;
// 需禁止window的touchmove事件才能有效的阻止bounce
window.bounceTouchmove = function(e) {
let el = e.target;
// 当前touch的元素及父元素是否要拦截touchmove事件
let isPrevent = true;
while (el !== document.body && el !== document) {
if (el.tagName === 'UNI-PAGE') { // 只扫描当前页
if (!el.getAttribute('use_mescroll')) {
isPrevent = false; // 如果当前页没有使用mescroll,则不阻止
}
break;
}
let cls = el.classList;
if (cls) {
if (cls.contains('mescroll-touch')) { // 采用scroll-view 此处不能过滤mescroll-uni,否则下拉仍然有回弹
isPrevent = false; // mescroll-touch无需拦截touchmove事件
break;
} else if (cls.contains('mescroll-touch-x') || cls.contains('mescroll-touch-y')) {
// 如果配置了水平或者垂直滑动
let curX = e.touches ? e.touches[0].pageX : e.clientX; // 当前第一个手指距离列表顶部的距离x
let curY = e.touches ? e.touches[0].pageY : e.clientY; // 当前第一个手指距离列表顶部的距离y
if (!this.preWinX) this.preWinX = curX; // 设置上次移动的距离x
if (!this.preWinY) this.preWinY = curY; // 设置上次移动的距离y
// 计算两点之间的角度
let x = Math.abs(this.preWinX - curX);
let y = Math.abs(this.preWinY - curY);
let z = Math.sqrt(x * x + y * y);
this.preWinX = curX; // 记录本次curX的值
this.preWinY = curY; // 记录本次curY的值
if (z !== 0) {
let angle = Math.asin(y / z) / Math.PI * 180; // 角度区间 [0,90]
if ((angle <= 45 && cls.contains('mescroll-touch-x')) || (angle > 45 && cls.contains('mescroll-touch-y'))) {
isPrevent = false; // 水平滑动或者垂直滑动,不拦截touchmove事件
break;
}
}
}
}
el = el.parentNode; // 继续检查其父元素
}
// 拦截touchmove事件:是否可以被禁用&&是否已经被禁用 (这里不使用me.preventDefault(e)的方法,因为某些情况下会报找不到方法的异常)
if (isPrevent && e.cancelable && !e.defaultPrevented && typeof e.preventDefault === "function") e.preventDefault();
}
window.addEventListener('touchmove', window.bounceTouchmove, {
passive: false
});
} else {
this.optUp.isBounce = true; // 允许
if (window.bounceTouchmove) {
window.removeEventListener('touchmove', window.bounceTouchmove);
window.bounceTouchmove = null;
window.isSetBounce = false;
}
}
// #endif
}

View File

@@ -0,0 +1,429 @@
<template>
<view class="mescroll-uni-warp">
<scroll-view :id="viewId" class="mescroll-uni" :class="{ 'mescroll-uni-fixed': isFixed }" :style="{
height: scrollHeight,
'padding-top': padTop,
'padding-bottom': padBottom,
'padding-bottom': padBottomConstant,
'padding-bottom': padBottomEnv,
top: fixedTop,
bottom: fixedBottom,
bottom: fixedBottomConstant,
bottom: fixedBottomEnv,
background: background,
'padding-left': paddingBoth,
'padding-right': paddingBoth
}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" @touchstart="touchstartEvent"
@touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" :scroll-y="isDownReset"
:enable-back-to-top="true">
<view class="mescroll-uni-content" :style="{ transform: translateY, transition: transition }">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp">
<view class="downwarp-content">
<!-- <view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'transform': downRotate}"></view> -->
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<!-- <mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> -->
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType === 1">
<!-- <view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> -->
<ns-loading></ns-loading>
</view>
<!-- 无数据 -->
<view v-if="upLoadType === 2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
</scroll-view>
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
<mescroll-top v-if="showTop" v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
</view>
</template>
<script>
// 引入mescroll-uni.js,处理核心逻辑
import MeScroll from './mescroll-uni.js';
// 引入全局配置
import GlobalOption from './mescroll-uni-option.js';
// 引入空布局组件
import MescrollEmpty from './components/mescroll-empty.vue';
// 引入回到顶部组件
import MescrollTop from './components/mescroll-top.vue';
import nsLoading from '@/components/ns-loading/ns-loading.vue';
export default {
name: 'mescroll-uni',
components: {
MescrollEmpty,
MescrollTop
},
props: {
down: Object, // 下拉刷新的参数配置
up: Object, // 上拉加载的参数配置
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量)
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
fixed: {
// 是否通过fixed固定mescroll的高度, 默认true
type: Boolean,
default () {
return true;
}
},
height: [String,
Number
], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
showTop: {
//是否需要展示返回顶部按钮
type: Boolean,
default () {
return true;
}
},
background: String,
paddingBoth: {
type: [String, Number],
default () {
return 0;
}
}
},
data() {
return {
mescroll: {
optDown: {},
optUp: {}
}, // mescroll实例
viewId: 'id_' +
Math.random()
.toString(36)
.substr(2), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
downHight: 0, //下拉刷新: 容器高度
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // 下拉刷新状态 inOffset1 outOffset2 showLoading3 endDownScroll4
upLoadType: 0, // 上拉加载状态0loading前1loading中2没有更多了
isShowEmpty: false, // 是否显示空布局
isShowToTop: false, // 是否显示回到顶部按钮
scrollTop: 0, // 滚动条的位置
scrollAnim: false, // 是否开启滚动动画
windowTop: 0, // 可使用窗口的顶部位置
windowBottom: 0, // 可使用窗口的底部位置
windowHeight: 0, // 可使用窗口的高度
statusBarHeight: 0 // 状态栏高度
};
},
computed: {
// 是否使用fixed定位 (当height有值,则不使用)
isFixed() {
return !this.height && this.fixed;
},
// mescroll的高度
scrollHeight() {
if (this.isFixed) {
return 'auto';
} else if (this.height) {
return this.toPx(this.height) + 'px';
} else {
return '100%';
}
},
// 下拉布局往下偏移的距离 (px)
numTop() {
// #ifdef H5
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0);
// #endif
// #ifdef MP
return this.toPx(this.top) + (this.topbar ? 44 + this.statusBarHeight : 0);
// #endif
},
fixedTop() {
return this.isFixed ? this.numTop + this.windowTop + 'px' : 0;
},
padTop() {
return !this.isFixed ? this.numTop + 'px' : 0;
},
// 上拉布局往上偏移 (px)
numBottom() {
return this.toPx(this.bottom);
},
fixedBottom() {
return this.isFixed ? this.numBottom + this.windowBottom + 'px' : 0;
},
fixedBottomConstant() {
return this.safearea ? 'calc(' + this.fixedBottom + ' + constant(safe-area-inset-bottom))' : this
.fixedBottom;
},
fixedBottomEnv() {
return this.safearea ? 'calc(' + this.fixedBottom + ' + env(safe-area-inset-bottom))' : this.fixedBottom;
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0;
},
padBottomConstant() {
return this.safearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this.padBottom;
},
padBottomEnv() {
return this.safearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom;
},
// 是否为重置下拉的状态
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
// 过渡
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' :
''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
},
// 是否在加载中
isDownLoading() {
return this.downLoadType === 3;
},
// 旋转的角度
downRotate() {
return 'rotate(' + 360 * this.downRate + 'deg)';
},
// 文本提示
downText() {
switch (this.downLoadType) {
case 1:
return this.mescroll.optDown.textInOffset;
case 2:
return this.mescroll.optDown.textOutOffset;
case 3:
return this.mescroll.optDown.textLoading;
case 4:
return this.mescroll.optDown.textLoading;
default:
return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px的数值
toPx(num) {
if (typeof num === 'string') {
if (num.indexOf('px') !== -1) {
if (num.indexOf('rpx') !== -1) {
// "10rpx"
num = num.replace('rpx', '');
} else if (num.indexOf('upx') !== -1) {
// "10upx"
num = num.replace('upx', '');
} else {
// "10px"
return Number(num.replace('px', ''));
}
} else if (num.indexOf('%') !== -1) {
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
let rate = Number(num.replace('%', '')) / 100;
return this.windowHeight * rate;
}
}
return num ? uni.upx2px(Number(num)) : 0;
},
//注册列表滚动事件,用于下拉刷新和上拉加载
scroll(e) {
this.mescroll.scroll(e.detail, () => {
this.$emit('scroll', this
.mescroll); // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
});
},
//注册列表touchstart事件,用于下拉刷新
touchstartEvent(e) {
this.mescroll.touchstartEvent(e);
},
//注册列表touchmove事件,用于下拉刷新
touchmoveEvent(e) {
this.mescroll.touchmoveEvent(e);
},
//注册列表touchend事件,用于下拉刷新
touchendEvent(e) {
this.mescroll.touchendEvent(e);
},
// 点击空布局的按钮回调
emptyClick() {
this.$emit('emptyclick', this.mescroll);
},
// 点击回到顶部的按钮回调
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
},
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
setClientHeight() {
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
this.isExec = true; // 避免多次获取
this.$nextTick(() => {
// 确保dom已渲染
try {
let view = uni
.createSelectorQuery()
.in(this)
.select('#' + this.viewId);
view.boundingClientRect(data => {
this.isExec = false;
if (data) {
this.mescroll.setClientHeight(data.height);
} else if (this.clientNum != 3) {
// 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
setTimeout(() => {
this.setClientHeight();
}, this.clientNum * 100);
}
}).exec();
} catch (e) {}
});
}
}
},
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
created() {
let vm = this;
let diyOption = {
// 下拉刷新的配置
down: {
inOffset(mescroll) {
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
},
outOffset(mescroll) {
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
},
onMoving(mescroll, rate, downHight) {
// 下拉过程中的回调,滑动过程一直在执行;
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
},
endDownScroll(mescroll) {
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
},
// 派发下拉刷新的回调
callback: function(mescroll) {
vm.$emit('down', mescroll);
}
},
// 上拉加载的配置
up: {
// 显示加载中的回调
showLoading() {
vm.upLoadType = 1;
},
// 显示无更多数据的回调
showNoMore() {
vm.upLoadType = 2;
},
// 隐藏上拉加载的回调
hideUpScroll() {
vm.upLoadType = 0;
},
// 空布局
empty: {
onShow(isShow) {
// 显示隐藏的回调
vm.isShowEmpty = isShow;
}
},
// 回到顶部
toTop: {
onShow(isShow) {
// 显示隐藏的回调
vm.isShowToTop = isShow;
}
},
// 派发上拉加载的回调
callback: function(mescroll) {
vm.$emit('up', mescroll);
// 更新容器的高度 (多mescroll的情况)
vm.setClientHeight();
}
}
};
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
let myOption = JSON.parse(
JSON.stringify({
down: vm.down,
up: vm.up
})
); // 深拷贝,避免对props的影响
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
// 初始化MeScroll对象
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // 附带id
// init回调mescroll对象
vm.$emit('init', vm.mescroll);
// 设置高度
const sys = uni.getSystemInfoSync();
if (sys.windowTop) vm.windowTop = sys.windowTop;
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使down的bottomOffset生效
vm.mescroll.setBodyHeight(sys.windowHeight);
// 因为使用的是scrollview,这里需自定义scrollTo
vm.mescroll.resetScrollTo((y, t) => {
let curY = vm.mescroll.getScrollTop();
vm.scrollAnim = t !== 0; // t为0,则不使用动画过渡
if (t === 0 || t === 300) {
// 当t使用默认配置的300时,则使用系统自带的动画过渡
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = y;
});
} else {
vm.mescroll.getStep(
curY,
y,
step => {
// 此写法可支持配置t
vm.scrollTop = step;
},
t
);
}
});
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
},
mounted() {
// 设置容器的高度
this.setClientHeight();
}
};
</script>
<style>
@import './mescroll-uni.css';
@import './components/mescroll-down.css';
@import './components/mescroll-up.css';
</style>

View File

@@ -0,0 +1,23 @@
/**
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
*/
const MescrollCompMixin = {
// 因为子组件无onPageScroll和onReachBottom的页面生命周期需在页面传递进到子组件
onPageScroll(e) {
let item = this.$refs["mescrollItem"];
if (item && item.mescroll) item.mescroll.onPageScroll(e);
},
onReachBottom() {
let item = this.$refs["mescrollItem"];
if (item && item.mescroll) item.mescroll.onReachBottom();
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh() {
let item = this.$refs["mescrollItem"];
if (item && item.mescroll) item.mescroll.onPullDownRefresh();
}
}
export default MescrollCompMixin;

Some files were not shown because too many files have changed in this diff Show More