This commit is contained in:
liqianjin 2023-02-11 15:13:36 +08:00
commit 3e9a18639c
592 changed files with 146957 additions and 0 deletions

3
.gitignore vendored Normal file
View File

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

221
App.vue Normal file
View File

@ -0,0 +1,221 @@
<script>
import auth from 'common/js/auth.js';
import { Weixin } from 'common/js/wx-jssdk.js';
export default {
mixins: [auth],
onLaunch: function() {
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');
// #ifdef H5
//
if (!uni.getStorageSync('token')) {
this.getAuthInfo();
} else {
this.$api.sendRequest({
url: '/api/member/info',
complete: () => {
if (!uni.getStorageSync('token')) this.getAuthInfo();
}
});
}
// #endif
},
onShow: function() {
// #ifdef MP-WEIXIN
//
if (!uni.getStorageSync('token')) {
this.getAuthInfo();
} else {
this.$api.sendRequest({
url: '/api/member/info',
complete: () => {
if (!uni.getStorageSync('token')) this.getAuthInfo();
}
});
}
// #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.setStorage({
key: 'authInfo',
data: data
});
this.$api.sendRequest({
url: type == 'authLogin' ? '/api/login/auth' : '/api/login/authonlylogin',
data,
success: res => {
if (res.code >= 0) {
uni.setStorage({
key: 'token',
data: res.data.token,
success: () => {
this.$store.dispatch('getCartNumber');
this.$store.commit('setToken', res.data.token);
}
});
}
}
});
},
/**
* 公众号分享设置
*/
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 => {}
});
}
},
watch: {
$route: {
handler(newName, oldName) {
if (this.$util.isWeiXin()) {
this.shareConfig();
}
},
// wacthfirstNamehandler
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'; //
</style>

BIN
common/css/custom.ttf Normal file

Binary file not shown.

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

@ -0,0 +1,123 @@
.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;
}

1498
common/css/goods_detail.scss Normal file

File diff suppressed because it is too large Load Diff

View File

5472
common/css/icondiy.css Normal file

File diff suppressed because it is too large Load Diff

160
common/css/iconfont.css Normal file

File diff suppressed because one or more lines are too long

607
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

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

@ -0,0 +1,64 @@
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: () => {
this.$util.showToast({
title: '请求失败'
});
}
})
// #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
}
}
}

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

@ -0,0 +1,16 @@
var config = {
// api请求地址
baseUrl: '',
// 图片域名
imgDomain: '',
// H5端域名
h5Domain: '',
// 腾讯地图key
mpKey: '',
//客服地址
webSocket : '',
//本地端主动给服务器ping的时间, 0 则不开启 , 单位秒
pingInterval: 1500
};
export default config;

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

@ -0,0 +1,368 @@
import WxMap from 'common/js/map-wx-jssdk.js';
let systemInfo = uni.getSystemInfoSync();
export default {
data() {
return {
diyData: {
global: {
title: '',
popWindow: {
imageUrl: '',
count: -1,
link: {},
imgWidth: '',
imgHeight: ''
}
}
},
memberId: 0,
name: '',
isDefault: '',
store: {}, //首页展示的门店详情
storeId: 0, //首页展示的门店id
pageHeight: '0',
headerHeight: '0',
bottomHeight: '0',
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',
pullDownRefresh: true, //下拉刷新
followOfficialAccount: null, // 关注公众号组件
isLoad: false // 检测是否刷新
};
},
onLoad(data) {
uni.hideTabBar();
this.name = data.name || '';
this.isDefault = data.is_default || '';
if (data.source_member) uni.setStorageSync('source_member', data.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]);
});
}
}
},
onShow() {
if (uni.getStorageSync('token')) {
this.$util.getMemberId().then(resolve => {
this.memberId = resolve;
});
}
this.store = uni.getStorageSync('store') ? uni.getStorageSync('store') : null;
if (this.store) this.storeId = this.store.store_id;
this.getDiyInfo();
this.getHeight();
//记录分享关系
if (uni.getStorageSync('token') && uni.getStorageSync('source_member')) {
this.$util.onSourceMember(uni.getStorageSync('source_member'));
}
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare().then(res => {
this.mpShareData = res;
});
// #endif
},
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;
},
bgNav() {
if (this.diyData.global.topNavColor) {
return {
background: this.diyData.global.topNavColor
};
} else {
return {
background: '#ffffff'
};
}
},
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';
}
},
openBottomNav() {
let str = false;
if (this.diyData && this.diyData.global) {
str = this.diyData.global.openBottomNav;
}
return str;
},
globalS() {
return this.diyData.global;
},
//计算首页弹框的显示宽高
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;
},
scrollHeight() {
if (this.pageHeight != undefined && this.headerHeight != undefined && this.bottomHeight != undefined) {
return 'calc(' + this.pageHeight * 2 + 'rpx - ' + this.headerHeight + ' - ' + this.bottomHeight + ')';
} else {
return '100vh';
}
},
scrollTopHeight() {
if (this.pageHeight != undefined && this.headerHeight != undefined && this.bottomHeight != undefined) {
return 'calc(' + this.pageHeight * 2 + 'rpx - ' + this.headerHeight + ' - ' + this.bottomHeight +
' - 80rpx)';
} else {
return '100vh';
}
}
},
methods: {
scroll(e) {
this.scrollTop = e.detail.scrollTop;
},
callback() {
if (this.$refs.indexPage) {
this.$refs.indexPage.initPageIndex();
}
},
//计算高度
getHeight() {
var self = this;
//获取页面可用区域的高度
uni.getSystemInfo({
success: res => {
this.pageHeight = res.screenHeight;
}
});
// #ifdef MP || APP-PLUS
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.select('.page-header')
.boundingClientRect(data => {
if (data) {
this.headerHeight = data.height * 2 + 'rpx';
// 从状态栏高度开始算
this.paddingTop = data.height + 'px';
this.marginTop = -data.height + 'px';
}
})
.exec();
});
// #endif
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.select('.page-bottom')
.boundingClientRect(data => {
this.bottomHeight = 110 + 'rpx';
})
.exec();
});
},
getDiyInfo() {
this.isLoad = false;
this.$api.sendRequest({
url: '/api/diyview/info',
data: {
name: this.name,
is_default: this.isDefault
},
success: res => {
if (res.code != 0 || !res.data) {
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.$util.showToast({
title: '未配置自定义页面数据'
});
this.diyData = {};
return;
}
let diyDatavalue = res.data;
// console.log(JSON.parse(diyDatavalue.value))
//处理后台组件input输入单引号问题 -- 英文状态下
// diyDatavalue.value = diyDatavalue.value.replace(/\@/g, "'");
if (diyDatavalue.value) {
// uni.setStorageSync(this.name, diyDatavalue.value);
this.diyData = JSON.parse(diyDatavalue.value);
this.$langConfig.title(this.diyData.global.title);
this.mpCollect = this.diyData.global.mpCollect;
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.name +
'_popwindow_count');
if ((this.$refs.uniPopupWindow && popwindow_count == '') || (
this.$refs.uniPopupWindow && popwindow_count == 1)) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.name + '_popwindow_count', 1);
}
} else if (this.diyData.global.popWindow.count == 0) {
this.$refs.uniPopupWindow.open();
uni.setStorageSync(this.name + '_popwindow_count', 0);
}
}, 500);
}
for (var i = 0; i < this.diyData.value.length; i++) {
if (this.diyData.value[i].componentName == 'TopCategory') {
this.topIndexValue = this.diyData.value[i];
break;
}
// 关注公众号组件
if (this.diyData.value[i].componentName == 'FollowOfficialAccount') {
this.followOfficialAccount = this.diyData.value[i];
break;
}
}
// #ifdef MP
//小程序收藏
if (!uni.getStorageSync('isCollect') && this.diyData.global.mpCollect) {
this.$refs.collectPopupWindow.open();
this.showTip = true;
}
// #endif
}
if (!this.pullDownRefresh) {
setTimeout(() => {
this.$refs.diyGroup.refresh(this.diyData);
});
this.pullDownRefresh = true;
}
setTimeout(() => {
uni.stopPullDownRefresh();
if (this.$refs.loadingCover) this.$refs.loadingCover.hide();
this.isLoad = true;
}, 150);
}
});
},
closePopupWindow() {
this.$refs.uniPopupWindow.close();
uni.setStorageSync(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();
}
},
onPageScroll(e) {
if (this.$refs.topNav) {
if (e.scrollTop >= 20) {
this.$refs.topNav.navTopBg();
} else {
this.$refs.topNav.unSetnavTopBg();
}
}
},
onPullDownRefresh() {
this.pullDownRefresh = false;
if (uni.getStorageSync('token')) {
this.$util.getMemberId().then(resolve => {
this.memberId = resolve;
});
}
this.store = uni.getStorageSync('store') ? uni.getStorageSync('store') : null;
if (this.store) this.storeId = this.store.store_id;
this.getDiyInfo();
this.getHeight();
//记录分享关系
if (uni.getStorageSync('token') && uni.getStorageSync('source_member')) {
this.$util.onSourceMember(uni.getStorageSync('source_member'));
}
//小程序分享
// #ifdef MP-WEIXIN
this.$util.getMpShare().then(res => {
this.mpShareData = res;
});
// #endif
},
//分享给好友
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();
}
}

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

@ -0,0 +1,129 @@
export default {
data() {
return {
// 页面样式,动态设置主色调
themeColor: '', //''--base-color:#fa5d14;--base-help-color:#ff7e00;'
tabBarHeight: '0px'
}
},
onLoad() {},
onShow() {
// 刷新多语言
this.$langConfig.refresh();
let time = setInterval(() => {
let theme = uni.getStorageSync('theme_style');
if (theme.main_color) {
this.themeColorSet()
clearInterval(time);
}
}, 50);
if (this.themeColor) this.getTabbarHeight();
},
computed: {
themeStyle() {
return uni.getStorageSync('theme_style');
},
//插件是否存在
addonIsExist() {
return uni.getStorageSync('addon_is_exist');
},
tabBarList() {
// return uni.getStorageSync('bottomNav');
return this.$store.state.tabBarList;
},
siteInfo() {
return uni.getStorageSync('siteInfo');
// return this.$store.state.siteInfo;
},
storeToken() {
return this.$store.state.token;
},
bottomNavHidden() {
return this.$store.state.bottomNavHidden;
// return uni.getStorageSync('bottomNavHidden');
}
},
methods: {
themeColorSet() {
let theme = uni.getStorageSync('theme_style');
this.themeColor =
`--base-color:${theme.main_color};--base-help-color:${theme.aux_color};--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);
},
/**
* 获取tabbar高度
*/
getTabbarHeight() {
try {
const query = uni.createSelectorQuery().in(this);
query.select('#tab-bar').boundingClientRect(data => {
if (data) {
this.tabBarHeight = data.height + 'px'
this.themeColorSet();
}
}).exec();
} catch (e) {}
}
},
filters: {
/**
* 金额格式化输出
* @param {Object} money
*/
moneyFormat(money) {
if (isNaN(parseFloat(money))) return money;
return parseFloat(money).toFixed(2);
}
},
onReady() {
let num = 0;
let timer = setInterval(() => {
this.getTabbarHeight()
num += 1;
if (this.tabBarHeight != '0px' || num == 10) clearInterval(timer)
}, 100)
}
}

View File

@ -0,0 +1,408 @@
// 商品详情业务
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开启
token: "",
//评价
contactData: {
title: '',
path: '',
img: ''
},
shareQuery: '', // 分享参数
shareUrl: '', // 分享链接
source_member: 0, //分享人的id
chatRoomParams: {}, // 联系客服参数
isIphoneX: false, //判断手机是否是iphoneX以上
// cartCount: 0, // 购物车商品数量
whetherCollection: 0,
memberId: 0,
posterParams: {}, //海报所需参数
shareImg: '',
globalS: {
title: '',
topNavColor: "#ffffff",
topNavBg: false,
navBarSwitch: true, // 导航栏是否显示
textNavColor: "#333333",
moreLink: {
name: ""
},
navStyle: 1,
bgUrl: '',
},
}
},
onLoad(data) {
this.preview = data.preview || 0;
this.token = uni.getStorageSync('token');
this.isIphoneX = this.$util.uniappIsIPhoneX();
if (data.source_member) {
uni.setStorageSync('source_member', data.source_member);
this.source_member = data.source_member;
}
//记录分享关系
if (this.token && 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);
});
}
}
},
computed: {
cartCount() {
return this.$store.state.cartNumber;
},
},
onShow() {
if (this.token) {
this.getCartCount();
this.$util.getMemberId().then(resolve => {
this.memberId = resolve;
});
}
},
watch: {
memberId: function() {
if (this.goodsSkuDetail.goods_id) this.setPublicShare();
}
},
methods: {
// 处理商品详情数据
handleGoodsSkuData() {
this.$langConfig.title(this.goodsSkuDetail.goods_name);
this.globalS.title = this.goodsSkuDetail.goods_name;
if (this.goodsSkuDetail.config) {
this.globalS.navBarSwitch = this.goodsSkuDetail.config.nav_bar_switch;
}
this.modifyGoodsInfo();
this.getWhetherCollection();
// 初始化商品详情视图数据
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,
memberId: this.memberId,
goodsRoute: this.goodsRoute,
isVirtual:this.goodsSkuDetail.is_virtual
});
//媒体
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 = '';
this.goodsSkuDetail.goods_image_list.forEach((item, index) => {
if (typeof item.pic_spec == "string")
item.pic_spec = item.pic_spec.split('*');
uni.getSystemInfo({
success: (res) => {
let ratio = item.pic_spec[0] / res.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 = 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();
this.getGoodsForm();
},
/**
* 刷新商品详情数据
* @param {Object} goodsSkuDetail
*/
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.$langConfig.title(this.goodsSkuDetail.sku_name);
if (typeof this.getMemberCardInfo == 'function') this.getMemberCardInfo();
},
goodsDetailViewInit() {
// 初始化商品详情视图数据
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,
memberId: this.memberId,
goodsRoute: this.goodsRoute
});
},
goHome() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/index/index');
},
goCart() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.$util.redirectTo('/pages/goods/cart');
},
//获取购物车数量
getCartCount() {
this.$store.dispatch('getCartNumber');
},
//-------------------------------------关注-------------------------------------
//更新商品信息
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 => {}
});
},
//-------------------------------------关注-------------------------------------
//获取用户是否关注
getWhetherCollection() {
this.$api.sendRequest({
url: "/api/goodscollect/iscollect",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
this.whetherCollection = res.data;
}
});
},
editCollection() {
if (this.$refs.goodsDetailView) {
this.whetherCollection = this.$refs.goodsDetailView.collection();
}
},
openSharePopup() {
if (this.$refs.goodsDetailView) {
this.$refs.goodsDetailView.openSharePopup();
}
},
getMemberId() {
this.$api.sendRequest({
url: "/api/member/id",
success: res => {
if (res.code >= 0) {
this.memberId = res.data;
}
}
});
},
//弹幕
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.memberId) shareUrl += '&source_member=' + this.memberId;
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.memberId) path += '&source_member=' + this.memberId;
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.memberId) query += '&source_member=' + this.memberId;
return {
title: this.goodsSkuDetail.sku_name,
query: query,
imageUrl: this.$util.img(this.goodsSkuDetail.sku_image, {
size: 'big'
})
};
}
// #endif
}

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

@ -0,0 +1,411 @@
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.safeArea.width || info.screenWidth;
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;
}
/**
* 将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属性中的双引号改为单引号
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;

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

@ -0,0 +1,171 @@
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
if (uni.getStorageSync('token')) data.token = uni.getStorageSync('token');
// 门店id
if (uni.getStorageSync('store')) data.store_id = uni.getStorageSync('store').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) {
uni.setStorage({
key: 'token',
data: res.data.refreshtoken
});
}
if (res.data.code == -10009 || res.data.code == -10010) {
uni.removeStorage({
key: 'token'
})
}
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);
}
});
});
} 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) {
uni.setStorage({
key: 'token',
data: res.data.refreshtoken
});
}
if (res.data.code == -10009 || res.data.code == -10010) {
uni.removeStorage({
key: 'token'
})
}
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);
}
});
}
}
}

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

@ -0,0 +1,127 @@
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;
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;
}
}

1123
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'
})) { // 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://'
})) { // 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();
return;
},
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();
}
});
}
}

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',//活动辅色
},
}
}

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

@ -0,0 +1,900 @@
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/goods/cart',
'/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: bigmidsmall
*/
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);
img_path = arr.join(".");
}
if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) {
path = Config.imgDomain + "/" + img_path;
} else {
path = img_path;
}
// 处理商品助手的图片路径
path = path.replace("addons/NsGoodsAssist/", "").replace("shop/goods/", "");
}
// 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} timeStamp
*/
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
* @param {Object} i
*/
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 number num
* @param {Object} params
* @param {Object} callback
* @param string url
* return array
*/
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: uni.getStorageSync('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',
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} message
* @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.wap_url) {
this.redirectTo(link.wap_url);
}
},
/**
* 获取默认图
* @param {Object} link
*/
getDefaultImage() {
let defaultImg = uni.getStorageSync('default_img');
if (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);
return defaultImg;
} else {
return {
goods: '',
head: '',
store: '',
article: ''
};
}
},
/**
* 判断手机是否为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 {Object} 转化时间字符串 转化时分秒
*/
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(string) {
let keywords = string;
Http.sendRequest({
url: '/weapp/api/weapp/messagetmplids',
data: {
keywords: keywords
},
success: res => {
if (res.data.length) {
uni.requestSubscribeMessage({
tmplIds: res.data,
success: (res) => {
console.log("res", res)
},
fail: (res) => {
console.log('fail', res)
}
})
}
}
})
},
/**
* 分享获取memberId,进行上下级绑定
*/
getMemberId() {
return new Promise((resolve, reject) => {
Http.sendRequest({
url: "/api/member/id",
success: res => {
if (res.code >= 0) {
resolve(res.data)
} else {
reject(res)
}
}
});
})
},
/**
* 获取小程序分享内容数据
*/
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 {Object} shareData
*/
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方向上移动的距离
const EACH_MOVE_AD = -(DIST_AB / times);
// E每次在BC方向上移动的距离
const 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
};
}
}

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

@ -0,0 +1,166 @@
/**
数据验证表单验证
*/
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;
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 = /^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][0-9]))[0-9]{8}$/;
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);
}
}

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

@ -0,0 +1,115 @@
/**
* 微信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 {Object} jsApiParame
* @param {Object} callback
*/
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) => {
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,338 @@
<template>
<view class="chat-message" >
<block v-if="message.contentType == 'sendGood'"><ns-chat-goods :skuId="message.sku_id" :goodsDetail="message.goodsDetail" @sendMsg="sendGood"></ns-chat-goods></block>
<block v-if="message.contentType == 'sendOrder'">
<ns-chat-order :orderId="message.order_id" :orderdetails="message.orderDetail" @sendMsg="sendOrder"></ns-chat-order>
</block>
<block v-if="message.contentType == 'goodssku'"><ns-chat-receiveGoods :skuId="message.sku_id"></ns-chat-receiveGoods></block>
<view class="message" v-if="message.contentType == 'string'">
<view class="message-item " :class="message.isItMe ? 'right' : 'left'">
<block v-if="message.isItMe">
<view class="head_img">
<image class="img" :src="myHeadImg" v-if="myHeadImg" @error="myHeadImgError" mode="aspectFit"></image>
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
</view>
</block>
<block v-else>
<view class="head_img">
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar"></image>
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
</view>
</block>
<view class="chat_text">
<text class="iconfont icon-warn margin-right color-base-text" v-if="message.isItMe && !message.sendStatus"></text>
<view class="content"><rich-text :nodes="stringToEmjoy(message.content)"></rich-text></view>
<!-- <text class="iconfont icon-warn margin-left" v-if="!message.isItMe && !message.sendStatus"></text> -->
</view>
</view>
</view>
<view class="message" v-if="message.contentType == 'image'">
<view class="message-item " :class="message.isItMe ? 'right' : 'left'">
<block v-if="message.isItMe">
<view class="head_img">
<image class="img" :src="myHeadImg" v-if="myHeadImg" mode="aspectFit"></image>
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
</view>
</block>
<block v-else>
<view class="head_img">
<image class="img" :src="avatar" mode="aspectFit" v-if="avatar"></image>
<image class="img" :src="defaultHead" mode="aspectFit" v-else></image>
</view>
</block>
<view class="chat_img">
<text class="iconfont icon-warn margin-right color-base-text" v-if="message.isItMe && !message.sendStatus"></text>
<view class="content_img" @click="previewMedia($util.img(message.image))" :style="{ backgroundImage: 'url(' + $util.img(message.image) + ')' }">
<!-- <image class="img_img" :src="$util.img(message.image)" mode="aspectFit"></image> -->
</view>
<!-- <text class="iconfont icon-warn margin-left" v-if="!message.isItMe && !message.sendStatus"></text> -->
</view>
</view>
</view>
<view v-else-if="message.contentType == 'goods'"><ns-chat-goods :isCanSend="false" :skuId="message.sku_id"></ns-chat-goods></view>
<view v-else-if="message.contentType == 'order'"><ns-chat-order :isCanSend="false" :orderId="message.order_id"></ns-chat-order></view>
<view class="no-connect-box" v-if="message.contentType == 'noline'"><view class="no-connect">客服不在线</view></view>
<view class="no-connect-box" v-if="message.contentType == 'online'"><view class="no-connect">客服在线</view></view>
<uni-popup ref="imgPopup" type="center">
<view class="imagePop"><image :src="$util.img(currImg)" mode="aspectFit"></image></view>
</uni-popup>
</view>
</template>
<script>
import nsChatGoods from '@/components/ns-chat/ns-chat-goods.vue';
import nsChatOrder from '@/components/ns-chat/ns-chat-order.vue';
import nsChatReceiveGoods from '@/components/ns-chat/ns-chat-receiveGoods.vue';
import htmlParser from '@/common/js/html-parser';
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import emjoy from '@/common/js/emjoy.js';
export default {
name: 'chat-message',
props: {
message: {
type: Object
},
shopInfo: {
type: Object
},
userInfo: {
type: Object
},
send: {
type: Boolean
}
},
data() {
return {
avatar: '', //
defaultAvatar: this.$util.getDefaultImage().store,
myHeadImg: '', //
defaultHead: this.$util.getDefaultImage().head,
emjoyList: emjoy.emjoyList,
currImg: ''
};
},
components: {
nsChatGoods,
nsChatOrder,
uniPopup,
nsChatReceiveGoods
},
mounted() {
this.avatar = this.$util.img(this.shopInfo.logo_square);
this.myHeadImg = this.$util.img(this.userInfo.headimg);
},
methods: {
//
previewMedia(img_url) {
var paths = [img_url];
uni.previewImage({
current: 0,
urls: paths
});
},
sendGood() {
this.$emit('sendGood', 'goods');
},
sendOrder() {
this.$emit('sendOrder', 'order');
},
//
myHeadImgError() {
this.myHeadImg = this.defaultHead;
},
stringToEmjoy(value) {
if (!value) return;
//
var reg = RegExp(/\[/);
if (reg.test(value)) {
let string = value; //
let reg = new RegExp('\\[emjoy_(.+?)\\]', 'g');
let emjoyString = string.replace(reg, v => {
let emjoy = '';
for (let index in this.emjoyList) {
if (v == index) {
let _url = this.$util.img(this.emjoyList[index]);
emjoy = "<img class='message-img' src='" + _url + "'/>";
break;
}
}
if (emjoy) {
return emjoy;
} else {
return v;
}
});
let content = htmlParser(emjoyString);
content.forEach(v => {
if (v.name == 'img') {
v.attrs.style = 'display: inline-block;width: 32rpx !important;height: 32rpx !important;padding:0 2rpx;';
}
});
return content;
} else {
let content = value;
return content;
}
}
}
};
</script>
<style lang="scss">
/deep/.uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
background-color: #000;
}
/deep/.uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
max-width: 100%;
width: 100%;
}
.imagePop {
height: 50vh;
width: 100vw;
text-align: center;
image {
width: 100%;
height: 100%;
}
}
.chat-message {
width: 100%;
height: 100%;
.message {
padding: 13rpx 20rpx;
position: relative;
}
.left .content {
padding: 20rpx;
max-width: 450rpx;
border-radius: 10rpx;
font-size: 30rpx;
}
.right .content {
padding: 20rpx;
max-width: 450rpx;
border-radius: 10rpx;
font-size: 30rpx;
}
.content_img {
height: 200rpx;
width: 100%;
overflow: hidden;
text-align: right;
margin-left: 28rpx;
background-position: center right;
background-repeat: no-repeat;
background-size: contain;
image {
min-height: 80rpx;
min-width: 80rpx;
height: 100%;
width: 100%;
}
}
.right .content_img {
margin-right: 28rpx;
margin-left: 0;
}
.message-item {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
flex-wrap: nowrap;
flex-direction: row;
.head_img {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
overflow: hidden;
position: relative;
.img {
width: 100%;
height: 100%;
}
}
//
.contentType3 {
padding: 0;
border-radius: 2rpx;
background-color: transparent !important;
.img {
width: 200rpx;
height: auto;
max-width: 300rpx;
max-height: 400rpx;
}
}
.contentType3::after {
border: none !important;
display: none !important;
}
.content-type-right {
flex-direction: row-reverse;
}
&.right {
flex-direction: row-reverse;
.content {
background-color: #4cd964;
margin-right: 28rpx;
word-break: break-all;
line-height: 36rpx;
position: relative;
}
}
&.left {
.content {
background-color: #ffffff;
margin-left: 28rpx;
word-break: break-all;
line-height: 36rpx;
position: relative;
}
}
}
.next {
width: 100%;
height: 20rpx;
}
}
.no-connect-box {
width: 100%;
text-align: center;
margin: 20rpx 0 50rpx;
.no-connect {
display: inline-block;
padding: 0 20rpx;
height: 40rpx;
background: red;
margin: 0 auto;
background: rgba(0, 0, 0, 0.5);
border-radius: 9rpx;
text-align: center;
line-height: 40rpx;
font-size: 22rpx;
color: #ffffff;
}
}
.chat_text,
.chat_img {
display: flex;
align-items: center;
.iconfont {
font-size: 36rpx;
}
}
.chat_img {
width: 30%;
height: 200rpx;
}
</style>

View File

@ -0,0 +1,742 @@
<template>
<view class="order-container" :class="{ 'safe-area': isIphoneX }">
<!-- #ifdef MP -->
<view
class="payment-navbar"
:style="{
'padding-top': menuButtonBounding.top + 'px',
height: menuButtonBounding.height + 'px'
}"
>
<view class="navbar-title">确认订单</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 == 0">
<!-- 配送方式 -->
<view class="delivery-mode" v-if="goodsData.express_type.length > 1">
<view class="action">
<view
:class="{ active: item.name == orderCreateData.delivery.delivery_type }"
v-for="(item, index) in goodsData.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.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.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.local_config && calculateGoodsData.local_config.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.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"></view>
</view>
</view>
<view class="mobile-wrap store-mobile">
<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">
<view class="site-body">
<!-- 商品 -->
<view class="goods-item" v-for="(goodsItem, goodsIndex) in goodsData.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"></image>
</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 &&
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>
<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 && 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="goodsItem.goods_form" @click="editForm(goodsIndex)">
<ns-form
:data="goodsItem.goods_form.json_data"
ref="goodsForm"
:custom-attr="{ sku_id: goodsItem.sku_id, form_id: goodsItem.goods_form.id }"
></ns-form>
<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"></ns-form>
</view>
<view
class="site-wrap"
v-if="
(calculateGoodsData && calculateGoodsData.coupon_list && calculateGoodsData.coupon_list.length) ||
promotionInfo ||
(calculateGoodsData && calculateGoodsData.max_usable_point > 0) ||
goodsData.invoice
"
>
<view class="site-footer">
<view class="order-cell coupon" v-if="calculateGoodsData && calculateGoodsData.coupon_list && calculateGoodsData.coupon_list.length">
<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"></ns-switch>
</view>
<view class="order-cell order-invoice-cell" v-if="goodsData.invoice">
<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">
<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" size="mini" @click="create()" v-if="!surplusStartMoney()">提交订单</button>
<button v-else class="no-submit" 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="invoicePopup" type="bottom" :mask-click="false">
<view :style="orderCreateData.is_invoice == 1 ? 'height: 83vh;' : 'height: 48vh;'" class="invoice-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">发票</text>
<text class="iconfont icon-close" @click="closePopup('invoicePopup')"></text>
</view>
<scroll-view scroll-y="true" class="popup-body" :class="{ 'safe-area': isIphoneX }">
<view>
<view class="invoice-cell" v-if="goodsData.invoice">
<text class="tit">需要发票</text>
<view class="option-grpup">
<view class="option-item" :class="{ 'color-base-bg active': orderCreateData.is_invoice == 0 }" @click="changeIsInvoice">不需要</view>
<view class="option-item" :class="{ 'color-base-bg active': orderCreateData.is_invoice == 1 }" @click="changeIsInvoice">需要</view>
</view>
</view>
<block v-if="orderCreateData.is_invoice == 1">
<view class="invoice-cell">
<text class="tit">发票类型</text>
<view class="option-grpup">
<view
class="option-item"
:class="{ 'color-base-bg active': orderCreateData.invoice_type == item }"
@click="changeInvoiceType(item)"
v-for="(item, index) in goodsData.invoice.invoice_type.split(',')"
:key="index"
>
{{ item == 1 ? '纸质' : '电子' }}
</view>
</view>
</view>
<view class="invoice-cell">
<text class="tit">抬头类型</text>
<view class="option-grpup">
<view
class="option-item"
:class="{ 'color-base-bg active': orderCreateData.invoice_title_type == 1 }"
@click="changeInvoiceTitleType(1)"
>
个人
</view>
<view
class="option-item"
:class="{ 'color-base-bg active': orderCreateData.invoice_title_type == 2 }"
@click="changeInvoiceTitleType(2)"
>
企业
</view>
</view>
</view>
<view class="invoice-cell">
<text class="tit">发票信息</text>
<view class="invoice-form-group">
<input type="text" placeholder="请填写抬头名称" v-model.trim="orderCreateData.invoice_title" />
<input
v-if="orderCreateData.invoice_title_type == 2"
type="text"
placeholder="请填写纳税人识别号"
v-model.trim="orderCreateData.taxpayer_number"
/>
<input
type="text"
placeholder="请填写邮寄地址"
v-model.trim="orderCreateData.invoice_full_address"
v-if="orderCreateData.invoice_type == 1"
/>
<input type="text" placeholder="请填写邮箱" v-model.trim="orderCreateData.invoice_email" v-if="orderCreateData.invoice_type == 2" />
</view>
</view>
<view class="invoice-cell">
<text class="tit">发票内容</text>
<view class="option-grpup">
<view
:key="index"
v-for="(item, index) in goodsData.invoice.invoice_content_array"
:class="{ 'color-base-bg active': item == orderCreateData.invoice_content }"
@click="changeInvoiceContent(item)"
class="option-item content"
>
{{ item }}
</view>
</view>
</view>
</block>
<view class="invoice-tops">发票内容将以根据税法调整具体请以展示为准发票内容显示详细商品名 称及价格信息</view>
</view>
</scroll-view>
<view class="popup-footer" @click="saveInvoice" :class="{ 'bottom-safe-area': isIphoneX }"><view class="confirm-btn color-base-bg">确定</view></view>
</view>
</uni-popup>
<!-- 活动优惠弹窗 -->
<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 }">
<view class="delivery-content">
<block v-if="storeList">
<view class="item-wrap" v-for="(item, index) in storeList" :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-if="!storeList" class="empty">所选择收货地址附近没有可以自提的门店</view>
</view>
</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"
></textarea>
</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 && calculateGoodsData.coupon_list && calculateGoodsData.coupon_list.length" :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 class="coupon-item" v-for="(couponItem, couponIndex) in calculateGoodsData.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')"></image>
<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>
</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="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="goods-card-popup 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>
<!-- 门店自提时间 -->
<ns-select-time @selectTime="selectPickupTime" ref="timePopup"></ns-select-time>
</template>
</scroll-view>
<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';
</style>

View File

@ -0,0 +1,836 @@
export default {
options: { styleIsolation: 'shared' },
data() {
return {
outTradeNo: '',
isIphoneX: false,
orderCreateData: {
is_balance: 0,
is_point: 1,
is_invoice: 0, // 是否需要发票 0 无发票 1 有发票
invoice_type: 0, // 发票类型 1 纸质 2 电子
invoice_title_type: 1, // 抬头类型 1 个人 2 企业
is_tax_invoice: 0, // 是否需要增值税专用发票 0 不需要 1 需要
coupon: {
coupon_id: 0
},
delivery: {},
member_goods_card: {} // 会员次卡
},
paymentData: null,
calculateData: null,
tempData: null,
storeId: 0,
deliveryTime: '', // 提货时间
memberAddress: null, // 会员收货地址
localMemberAddress: null, // 会员本地配送收货地址
isRepeat: false,
promotionInfo: null,
transactionAgreement: {}, // 购买须知
tempFormData: null,
menuButtonBounding: {} ,// 小程序胶囊属性
storeConfig: null,
localConfig: null,
selectGoodsCard: {
skuId: 0,
itemId: 0,
cardList: {}
} // 当前选择的次卡
}
},
inject: ['promotion'],
created(){
// #ifdef MP
this.menuButtonBounding = uni.getMenuButtonBoundingClientRect();
// #endif
this.isIphoneX = this.$util.uniappIsIPhoneX()
if (uni.getStorageSync('token')) {
Object.assign(this.orderCreateData, uni.getStorageSync(this.createDataKey))
if (this.location) {
this.orderCreateData.latitude = this.location.latitude;
this.orderCreateData.longitude = this.location.longitude;
}
this.payment();
} else {
setTimeout(() => {
this.$refs.loadingCover.hide();
this.$refs.login.open(this.$util.getCurrentRoute().path)
})
}
this.getTransactionAgreement();
},
computed: {
storeToken() {
return this.$store.state.token;
},
goodsData(){
if (this.paymentData) {
this.paymentData.shop_goods_list.goods_list.forEach(item => {
if (item.sku_spec_format) item.sku_spec_format = JSON.parse(item.sku_spec_format);
})
return this.paymentData.shop_goods_list;
}
},
calculateGoodsData(){
if (this.calculateData) return this.calculateData.shop_goods_list;
},
// 余额可抵扣金额
balanceDeduct() {
if (this.calculateData) {
if (this.calculateData.member_account.balance_total <= parseFloat(this.calculateData.order_money).toFixed(2)) {
return parseFloat(this.calculateData.member_account.balance_total).toFixed(2);
} else {
return parseFloat(this.calculateData.order_money).toFixed(2);
}
}
},
// 门店列表
storeList(){
let storeList = null;
if (this.orderCreateData.delivery) {
if (this.orderCreateData.delivery.delivery_type == 'local' && this.localConfig) {
storeList = this.localConfig.store_list;
storeList = storeList.reduce((res, item) => {
return {...res, [item.store_id]: item}
}, {})
}
if (this.orderCreateData.delivery.delivery_type == 'store' && this.storeConfig) {
storeList = this.storeConfig.store_list;
storeList = storeList.reduce((res, item) => {
return {...res, [item.store_id]: item}
}, {})
}
}
return storeList;
},
// 门店信息
storeInfo: {
get(){
if (this.storeList && this.orderCreateData.delivery && this.orderCreateData.delivery.delivery_type != 'express' && this.storeId) {
return this.storeList[ this.orderCreateData.delivery.store_id ];
}
},
set(value){
this.storeList[ this.orderCreateData.delivery.store_id ].store_image = value;
}
},
// 会员卡购买周期
cardChargeType(){
if (this.paymentData.recommend_member_card) {
let charge_rule_arr = [];
let charge_rule = this.paymentData.recommend_member_card.charge_rule;
Object.keys(charge_rule).forEach((key, index)=>{
switch (key) {
case 'week':
charge_rule_arr.push({'key': key, 'value': charge_rule[key], 'title' : '周卡', unit: '周'});
break;
case 'month':
charge_rule_arr.push({'key': key, 'value': charge_rule[key], 'title' : '月卡', unit: '月'});
break;
case 'quarter':
charge_rule_arr.push({'key': key, 'value': charge_rule[key], 'title' : '季卡', unit: '季'});
break;
case 'year':
charge_rule_arr.push({'key': key, 'value': charge_rule[key], 'title' : '年卡', unit: '年'});
break;
}
})
return charge_rule_arr;
}
},
// 定位信息
location(){
return this.$store.state.location;
}
},
watch: {
storeToken: function(nVal, oVal) {
this.payment();
},
deliveryTime: function(nVal){
if (!nVal) this.$refs.timePopup.refresh();
},
location: function(nVal){
if (nVal) {
this.orderCreateData.latitude = nVal.latitude;
this.orderCreateData.longitude = nVal.longitude;
this.payment();
}
}
},
methods: {
/**
* 父级页面onShow调用
*/
pageShow(){
if (uni.getStorageSync('addressBack') ) {
uni.removeStorageSync('addressBack');
this.payment();
}
},
/**
* 获取订单结算数据
*/
payment(){
this.$api.sendRequest({
url: this.api.payment,
data: this.orderCreateData,
success: res => {
if (res.code == 0 && res.data) {
let data = res.data;
// #ifdef MP-WEIXIN
var scene = uni.getStorageSync('is_test') ? 1175 : wx.getLaunchOptionsSync().scene;
if([1175, 1176, 1177, 1191, 1195].indexOf(scene) != -1 && data.shop_goods_list.express_type){
data.shop_goods_list.express_type = data.shop_goods_list.express_type.filter(item => item.name == 'express' );
}
// #endif
if (data.shop_goods_list) {
// 配送方式
if (data.shop_goods_list.express_type && data.shop_goods_list.express_type.length) {
let deliveryStorage = uni.getStorageSync('delivery');
data.shop_goods_list.express_type.forEach(item => {
if (deliveryStorage) {
if (item.name == deliveryStorage.delivery_type) {
this.orderCreateData.delivery = deliveryStorage;
this.storeId = deliveryStorage.store_id ?? 0;
if (deliveryStorage.delivery_type == 'store') {
this.orderCreateData.member_address = {mobile: data.member_account.mobile ?? '' }
if (!this.location) this.$util.getLocation();
}
if (deliveryStorage.delivery_type == 'local' && !this.location) this.$util.getLocation();
}
}
if (item.name == 'local') this.localConfig = item;
if (item.name == 'store') this.storeConfig = item;
})
if (!this.orderCreateData.delivery.delivery_type) {
this.selectDeliveryType(data.shop_goods_list.express_type[0], false);
}
if(uni.getStorageSync('deliveryTime') && uni.getStorageSync('deliveryTime')['delivery_type'] && uni.getStorageSync('deliveryTime')['delivery_type'] == this.orderCreateData.delivery.delivery_type){
this.deliveryTime = uni.getStorageSync('deliveryTime')['deliveryTime'];
this.orderCreateData.buyer_ask_delivery_time = uni.getStorageSync('deliveryTime')['buyer_ask_delivery_time'];
}
}
// 优惠券
if (data.shop_goods_list.coupon_list && data.shop_goods_list.coupon_list[0])
this.orderCreateData.coupon = {coupon_id: data.shop_goods_list.coupon_list[0].coupon_id };
}
// 地址、手机号
if (data.is_virtual) {
this.orderCreateData.member_address = '';
}
// 处理表单数据
data = this.handleGoodsFormData(data);
// 该方法在父级组件中
this.promotionInfo = this.promotion(data);
this.paymentData = data;
this.calculate();
}else{
this.$util.showToast({
title: res.message
});
setTimeout(() => {
this.$util.redirectTo('/pages/index/index');
}, 1000)
}
}
})
},
/**
* 处理商品表单数据
* @param {Object} data
*/
handleGoodsFormData(data){
let goodsFormData = uni.getStorageSync('goodFormData');
data.shop_goods_list.goods_list.forEach(item => {
if (item.goods_form) {
let formData = {};
if (item.form_data) {
item.form_data.map(formIem => {
formData[formIem.id] = formIem;
})
} else if (goodsFormData && goodsFormData.goods_id == item.goods_id) {
goodsFormData.form_data.map(formIem => {
formData[formIem.id] = formIem;
})
}
if (Object.keys(formData).length) {
item.goods_form.json_data.forEach(formIem => {
if (formData[formIem.id]) {
formIem.val = formData[formIem.id].val;
}
})
}
}
})
return data;
},
/**
* 订单创建
*/
calculate(){
this.$api.sendRequest({
url: this.api.calculate,
data: this.handleCreateData(),
success: res => {
if (this.$refs.loadingCover && this.$refs.loadingCover.isShow) this.$refs.loadingCover.hide();
if (res.code == 0 && res.data) {
this.calculateData = res.data;
if (res.data.delivery) {
if (res.data.delivery.delivery_type == 'express') this.memberAddress = res.data.member_address;
if (res.data.delivery.delivery_type == 'local') this.localMemberAddress = res.data.member_address;
}
// 次卡
res.data.shop_goods_list.goods_list.forEach(item => {
if (item.member_card_list) {
if (this.orderCreateData.member_goods_card[ item.sku_id ]) {
let itemId = this.orderCreateData.member_goods_card[ item.sku_id ];
if (!item.member_card_list[itemId]) delete this.orderCreateData.member_goods_card[ item.sku_id ];
}
} else if (this.orderCreateData.member_goods_card[ item.sku_id ]) {
delete this.orderCreateData.member_goods_card[ item.sku_id ];
}
})
if (!res.data.shop_goods_list.coupon_id) this.orderCreateData.coupon.coupon_id = 0;
else this.orderCreateData.coupon.coupon_id = res.data.shop_goods_list.coupon_id;
this.$forceUpdate();
} else {
this.$util.showToast({
title: res.message
});
}
}
})
},
/**
* 订单创建
*/
create(){
if (!this.verify() || this.isRepeat) return;
this.isRepeat = true;
uni.showLoading({title: ''})
this.$api.sendRequest({
url: this.api.create,
data: this.handleCreateData(),
success: res => {
uni.hideLoading();
if (res.code == 0) {
this.outTradeNo = res.data;
uni.removeStorageSync('deliveryTime');
uni.removeStorageSync('goodFormData');
uni.setStorageSync('paySource', '');
if (this.calculateData.pay_money == 0) {
// #ifdef MP-WEIXIN
if (this.paymentData.is_virtual || this.orderCreateData.delivery.delivery_type == 'store') {
this.$util.subscribeMessage('ORDER_VERIFY_OUT_TIME,VERIFY_CODE_EXPIRE,VERIFY');
}
// #endif
this.$util.redirectTo('/pages_tool/pay/result', {
code: res.data
}, 'redirectTo');
} else {
this.openChoosePayment();
}
} else {
this.$util.showToast({ title: res.message });
this.isRepeat = false;
}
// 更新购物车数量
this.$store.dispatch('getCartNumber');
}
})
},
/**
* 处理订单计算创建传参
*/
handleCreateData(){
let data = this.$util.deepClone(this.orderCreateData);
// 订单表单
if (this.$refs.form) {
data.form_data = { form_id: this.paymentData.system_form.id, form_data: this.$util.deepClone(this.$refs.form.formData) };
}
// 商品表单
if (this.$refs.goodsForm) {
if (!data.form_data) data.form_data = {};
data.form_data.goods_form = {};
this.$refs.goodsForm.forEach(item => {
data.form_data.goods_form[item._props.customAttr.sku_id] = {
form_id: item._props.customAttr.form_id,
form_data: this.$util.deepClone(item.formData)
}
})
}
Object.keys(data).forEach((key) => {
let item = data[key];
if (typeof item == 'object') data[key] = JSON.stringify(item);
})
if (data.member_address && this.orderCreateData.delivery && this.orderCreateData.delivery.delivery_type != 'store' ) delete data.member_address;
return data;
},
/**
* 打开支付弹窗
*/
openChoosePayment(){
// #ifdef MP-WEIXIN
if (this.paymentData.is_virtual) {
this.$util.subscribeMessage('ORDER_URGE_PAYMENT,ORDER_PAY');
} else {
switch(this.orderCreateData.delivery.delivery_type){
case 'express'://物流配送
this.$util.subscribeMessage('ORDER_URGE_PAYMENT,ORDER_PAY,ORDER_DELIVERY');
break;
case 'store'://门店自提
this.$util.subscribeMessage('ORDER_URGE_PAYMENT,ORDER_PAY');
break;
case 'local'://同城配送
this.$util.subscribeMessage('ORDER_URGE_PAYMENT,ORDER_PAY,ORDER_DELIVERY');
break;
}
}
// #endif
this.$refs.choosePaymentPopup.getPayInfo(this.outTradeNo);
},
verify(){
if (this.paymentData.is_virtual == 0) {
if (!this.orderCreateData.delivery || !this.orderCreateData.delivery.delivery_type) {
this.$util.showToast({ title: '商家未设置配送方式' });
return false;
}
if (
(this.orderCreateData.delivery.delivery_type == 'express' && !this.memberAddress) ||
(this.orderCreateData.delivery.delivery_type == 'local' && !this.localMemberAddress)
) {
this.$util.showToast({ title: '请先选择您的收货地址' });
return false;
}
if (this.orderCreateData.delivery.delivery_type == 'store') {
if (!this.orderCreateData.delivery.store_id) {
this.$util.showToast({
title: '没有可提货的门店,请选择其他配送方式'
});
return false;
}
if (!this.orderCreateData.member_address.mobile) {
this.$util.showToast({
title: '请输入预留手机'
});
return false;
}
var reg = /^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$/;
if (!reg.test(this.orderCreateData.member_address.mobile)) {
this.$util.showToast({
title: '请输入正确的手机号'
});
return false;
}
if (!this.deliveryTime) {
this.$util.showToast({
title: '请选择提货时间'
});
return false;
}
}
if (this.orderCreateData.delivery.delivery_type == 'local') {
if (!this.orderCreateData.delivery.store_id) {
this.$util.showToast({
title: '没有可配送的门店,请选择其他配送方式'
});
return false;
}
if (this.calculateGoodsData.local_config && this.calculateGoodsData.local_config.info.time_is_open == 1 && !this.deliveryTime) {
this.$util.showToast({
title: '请选择送达时间'
});
return false;
}
}
}
if (this.$refs.goodsForm) {
let formVerify = true;
for (let i = 0; i < this.$refs.goodsForm.length; i++) {
let item = this.$refs.goodsForm[i];
formVerify = item.verify();
if (!formVerify) {
break;
}
}
if (!formVerify) return false;
}
if (this.paymentData.system_form) {
let formVerify = this.$refs.form.verify();
if (!formVerify) return false;
}
return true;
},
/**
* 选择收货地址
*/
selectAddress() {
var params = {
back: this.$util.getCurrentRoute().path,
local: 0,
type: 1
}
// 外卖配送需要定位地址
if (this.orderCreateData.delivery.delivery_type == 'local') {
params.local = 1;
params.type = 2;
}
this.$util.redirectTo('/pages_tool/member/address', params);
},
/**
* 选择配送方式
* @param {Object} data
*/
selectDeliveryType(data, calculate = true){
if (this.orderCreateData.delivery && this.orderCreateData.delivery.delivery_type == data.name) return;
let delivery = {
delivery_type: data.name,
delivery_type_name: data.title
}
// 如果是门店配送
if (data.name == 'store' || data.name == 'local') {
if(data.store_list[0]) {
delivery.store_id = data.store_list[0].store_id;
}
this.storeId = delivery.store_id ? delivery.store_id : 0;
if (!this.orderCreateData.member_address) this.orderCreateData.member_address = {mobile: this.paymentData && this.paymentData.member_account.mobile ? this.paymentData.member_account.mobile : '' }
}
this.$set(this.orderCreateData, 'delivery', delivery);
this.orderCreateData.buyer_ask_delivery_time = '';
this.deliveryTime = '';
uni.removeStorageSync('deliveryTime');
uni.setStorageSync('delivery', delivery);
// 配送方式不为门店配送时
if (this.orderCreateData.delivery.delivery_type != 'express' && !this.location) this.$util.getLocation();
if (calculate) this.calculate();
},
/**
* 图片错误
* @param {Object} index
*/
imageError(index){
this.paymentData.shop_goods_list.goods_list[index].sku_image = this.$util.getDefaultImage().goods;
this.$forceUpdate();
},
/**
* 选择门店
* @param {Object} data
*/
selectPickupPoint(data) {
if (data.store_id != this.storeId) {
this.storeId = data.store_id;
this.orderCreateData.delivery.store_id = data.store_id;
this.calculate();
this.resetDeliveryTime();
// 存储所选门店
let delivery = uni.getStorageSync('delivery');
delivery.store_id = data.store_id;
uni.setStorageSync('delivery', delivery)
}
this.$refs.deliveryPopup.close();
},
/**
* 重置提货时间
*/
resetDeliveryTime(){
this.orderCreateData.buyer_ask_delivery_time = '';
this.deliveryTime = '';
uni.removeStorageSync('deliveryTime');
},
/**
* 门店
*/
storetime(type = ''){
if(this.storeInfo){
let data = this.$util.deepClone(this.storeInfo);
if (data.delivery_time) {
data.delivery_time = JSON.parse(data.delivery_time);
data.end_time = data.delivery_time[ (data.delivery_time.length - 1) ].end_time;
} else {
data.delivery_time = [ {start_time: data.start_time, end_time: data.end_time} ]
}
let obj = {
delivery:this.orderCreateData.delivery,
dataTime:data
}
this.$refs.timePopup.open(obj,type);
this.$forceUpdate();
}
},
/**
* 选择自提时间
* @param {Object} data
*/
selectPickupTime(data){
this.deliveryTime = data.data.month + '('+ data.data.time +')';
let nowDate = new Date();
let Year = nowDate.getFullYear();
let timeData = data.data.month.split('月');
let month = timeData[0];
let date = timeData[1].split('日')[0];
this.orderCreateData.buyer_ask_delivery_time = Year + '-' + month + '-' + date + ' ' + data.data.time
//将时间缓存,避免切换地址时重置
uni.setStorageSync('deliveryTime',{
'deliveryTime' : this.deliveryTime,
'buyer_ask_delivery_time' : this.orderCreateData.buyer_ask_delivery_time,
'delivery_type' : this.orderCreateData.delivery.delivery_type
});
},
storeImgError(){
this.storeInfo.store_image = this.$util.getDefaultImage().store;
},
openPopup(ref) {
this.tempData = this.$util.deepClone(this.orderCreateData);
this.$refs[ref].open();
},
closePopup(ref) {
this.orderCreateData = this.$util.deepClone(this.tempData);
this.$refs[ref].close();
this.tempData = null;
},
/**
* 切换发票开关
*/
changeIsInvoice() {
if (this.orderCreateData.is_invoice == 0) {
this.orderCreateData.is_invoice = 1;
if (!this.orderCreateData.invoice_type) this.orderCreateData.invoice_type = this.goodsData.invoice.invoice_type.split(',')[0];
} else {
this.orderCreateData.is_invoice = 0;
}
},
/**
* 切换发票类型
* @param {Object} invoice_type
*/
changeInvoiceType(invoice_type) {
this.orderCreateData.invoice_type = invoice_type;
},
/**
* 切换发票个人还是企业
* @param {Object} invoice_title_type
*/
changeInvoiceTitleType(invoice_title_type) {
this.orderCreateData.invoice_title_type = invoice_title_type;
},
/**
* 切换增值税专用发票开关
*/
changeIsTaxInvoice() {
if (this.orderCreateData.is_tax_invoice == 0) this.orderCreateData.is_tax_invoice = 1;
else this.orderCreateData.is_tax_invoice = 0;
this.$forceUpdate();
},
/**
* 选择发票内容
* @param {Object} invoice_content
*/
changeInvoiceContent(invoice_content) {
this.orderCreateData.invoice_content = invoice_content;
this.$forceUpdate();
},
/**
* 验证发票内容
*/
invoiceVerify() {
if (!this.orderCreateData.invoice_title) {
this.$util.showToast({
title: '请填写发票抬头'
});
return false;
}
if (!this.orderCreateData.taxpayer_number && this.orderCreateData.invoice_title_type == 2) {
this.$util.showToast({
title: '请填写纳税人识别号'
});
return false;
}
if (this.orderCreateData.invoice_type == 1 && !this.orderCreateData.invoice_full_address && this.orderPaymentData.is_virtual ==1) {
this.$util.showToast({
title: '请填写发票邮寄地址'
});
return false;
}
if (this.orderCreateData.invoice_type == 2 && !this.orderCreateData.invoice_email) {
this.$util.showToast({
title: '请填写邮箱'
});
return false;
}
if (this.orderCreateData.invoice_type == 2) {
var reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
if (!reg.test(this.orderCreateData.invoice_email)) {
this.$util.showToast({
title: '请填写正确的邮箱'
});
return false;
}
}
if (!this.orderCreateData.invoice_content) {
this.$util.showToast({
title: '请选择发票内容'
});
return false;
}
return true;
},
/**
* 保存发票设置
*/
saveInvoice(){
if (this.orderCreateData.is_invoice == 1 && !this.invoiceVerify()) return;
this.calculate();
this.$refs.invoicePopup.close();
},
/**
* 保存留言
*/
saveBuyerMessage(){
this.$refs.buyerMessagePopup.close();
},
/**
* 选择会员卡
*/
selectMemberCard() {
this.orderCreateData.is_open_card = this.orderCreateData.is_open_card ? 0 : 1;
if (!this.orderCreateData.member_card_unit) this.orderCreateData.member_card_unit = this.cardChargeType[0].key;
this.calculate();
},
/**
* 选择会员卡充值类型
* @param {Object} key
*/
selectMembercardUnit(key){
this.orderCreateData.member_card_unit = key;
this.calculate();
},
/**
* 使用积分抵扣
*/
usePoint() {
this.orderCreateData.is_point = this.orderCreateData.is_point ? 0 : 1;
this.calculate();
},
/**
* 支付弹窗关闭
*/
payClose(){
this.$util.redirectTo('/pages/order/detail', {order_id: this.$refs.choosePaymentPopup.payInfo.order_id}, 'redirectTo');
},
/**
* 选择优惠券
* @param {Object} data
*/
selectCoupon(data){
if (this.orderCreateData.coupon.coupon_id == data.coupon_id) this.orderCreateData.coupon = {coupon_id: 0};
else this.orderCreateData.coupon = {coupon_id: data.coupon_id};
},
/**
* 使用优惠券
*/
useCpopon(){
this.$refs.couponPopup.close();
this.calculate();
},
/**
* 同城配送送达时间
*/
localtime(type = ''){
let data = this.$util.deepClone(this.calculateGoodsData.local_config.info);
if (data.delivery_time) {
data.end_time = data.delivery_time[ (data.delivery_time.length - 1) ].end_time;
}
let obj = {
delivery: this.orderCreateData.delivery,
dataTime: data
}
this.$refs.timePopup.open(obj,type);
},
/**
* 剩余起送价
*/
surplusStartMoney(){
let money = 0;
if (this.calculateData && this.calculateData.delivery && this.calculateData.delivery.delivery_type == 'local') {
let startDeliveryMoney = this.calculateGoodsData.delivery.start_money ?? 0;
money = parseFloat(startDeliveryMoney) - parseFloat(this.calculateData.goods_money);
money = money < 0 ? 0 : money;
}
return money;
},
/**
* 交易协议
*/
getTransactionAgreement(){
this.$api.sendRequest({
url: '/api/order/transactionagreement',
success: res => {
if (res.data) this.transactionAgreement = res.data;
}
})
},
editForm(index){
this.tempFormData = {
index: index,
json_data: this.$util.deepClone(this.goodsData.goods_list[index].goods_form.json_data)
}
this.$refs.editFormPopup.open();
},
saveForm(){
if (this.$refs.tempForm.verify()) {
this.$set(this.paymentData.shop_goods_list.goods_list[ this.tempFormData.index ].goods_form, 'json_data', this.$refs.tempForm.formData);
this.$refs.editFormPopup.close();
}
},
/**
* 切换次卡
* @param {Object} index
*/
selectMemberGoodsCard(index){
let sku_id = this.goodsData.goods_list[index].sku_id;
this.selectGoodsCard = {
skuId: sku_id,
itemId: this.orderCreateData.member_goods_card[sku_id] ? this.orderCreateData.member_goods_card[sku_id] : 0,
cardList: this.$util.deepClone(this.calculateGoodsData.goods_list[index].member_card_list),
click: (item_id) => {
this.selectGoodsCard.itemId = this.selectGoodsCard.itemId == item_id ? 0 : item_id;
}
}
this.$refs.memberGoodsCardPopup.open();
},
/**
* 选择次卡
*/
saveMemberGoodsCard(){
this.orderCreateData.member_goods_card[ this.selectGoodsCard.skuId ] = this.selectGoodsCard.itemId || 0;
this.$refs.memberGoodsCardPopup.close();
this.calculate();
}
},
filters: {
// 金额格式化输出
moneyFormat(money) {
return parseFloat(money).toFixed(2);
}
}
}

View File

@ -0,0 +1,185 @@
<template>
<view class="article-wrap" :style="warpCss" v-if="list.length > 0">
<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)"></image>
</view>
<view class="info-wrap">
<text class="title">{{ item.article_title }}</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>
</template>
<script>
//
export default {
name: 'diy-article',
props: {
value: {
type: Object
}
},
data() {
return {
list: []
};
},
created() {
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;';
}
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: {
getBrandList() {
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;
}
}
});
},
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,660 @@
<template>
<view class="diy-bargain" v-if="list.length" :class="[value.template, value.style]" :style="warpCss">
<!-- 商品头部 -->
<view v-if="value.titleStyle.isShow" :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" :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)">
</image>
</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')">
</image>
</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="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' : '' }">{{ value.style=='style-2' ? item.price.split(".")[0] : item.floor_price.split(".")[0] }}</text>
<text class="unit price-style small"
:style="{ color: value.theme == 'diy' ? value.priceStyle.mainColor +'!important' : '' }">{{ value.style=='style-2' ? "."+item.price.split(".")[1] : "."+item.floor_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>
<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"></image>
<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[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>
<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"></image>
<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>
</template>
<script>
export default {
name: 'diy-bargain',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1
};
},
components: {},
async created() {
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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: {
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().safeArea.width || uni.getSystemInfoSync().screenWidth;
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 || [];
},
//
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;
}
}
}
});
},
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;
}
}
}
//
&.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;
}
}
}
}
}
&.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,292 @@
<template>
<view v-if="tabBarList">
<view class="tab-bar" :style="{ backgroundColor: tabBarList.backgroundColor }">
<view class="tabbar-border"></view>
<view class="item" v-for="(item, index) in tabBarList.list" :key="index" @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">
<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="cartNumberBtn font-size-activity-tag"
:class="{ max: item.link.wap_url == '/pages/goods/cart' && cartNumber > 99 }"
:style="{ background: 'var(--price-color)' }"
v-if="item.link.wap_url == '/pages/goods/cart' && 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 }"
>
{{ 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' }"
>
{{ 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 {
currentRoute: '', //
jumpFlag: true, //
cartAnimation: {}
};
},
mounted() {
let currentPage = getCurrentPages()[getCurrentPages().length - 1];
this.currentRoute = currentPage.route;
this.$store.dispatch('getCartNumber');
},
computed: {
cartNumber() {
return this.$store.state.cartNumber;
},
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 (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;
flex-direction: column;
justify-content: center;
align-items: center;
.icon {
position: relative;
display: inline-block;
margin-top: 10rpx;
width: 40rpx;
height: 40rpx;
font-size: 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;
line-height: 1.6;
height: 40rpx;
line-height: 40rpx;
}
}
}
.cartNumberBtn {
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

View File

@ -0,0 +1,976 @@
<template>
<view class="" :class="['category-page-wrap', 'category-template-' + value.template]">
<!-- #ifndef H5 -->
<block v-if="value.template == 4">
<view class="search-box u-navbar-content-title" v-if="value.search" @click="$util.redirectTo('/pages_tool/goods/search')" :style="navbarInnerStyle">
<view class="search-content">
<input type="text" class="uni-input font-size-tag" maxlength="50" placeholder="商品搜索" confirm-type="search" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
</view>
<view :style="navbarInnerStyle" v-if="!value.search">商品分类</view>
</block>
<block v-if="value.template != 4">
<view :style="navbarInnerStyle">商品分类</view>
<view class="search-box" v-if="value.search" @click="$util.redirectTo('/pages_tool/goods/search')">
<view class="search-content">
<input type="text" class="uni-input font-size-tag" maxlength="50" placeholder="商品搜索" confirm-type="search" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
</view>
</block>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="search-box" v-if="value.search" @click="$util.redirectTo('/pages_tool/goods/search')">
<view class="search-content">
<input type="text" class="uni-input font-size-tag" maxlength="50" placeholder="商品搜索" confirm-type="search" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
</view>
<!-- #endif -->
<view class="template-four wx" v-if="value.template == 4">
<scroll-view scroll-x="true" class="template-four-wrap" :scroll-with-animation="true" :scroll-into-view="'category-one-' + oneCategorySelect" enable-flex="true">
<view
class="category-item"
:id="'category-one-' + index"
v-for="(item, index) in templateFourData"
:key="index"
:class="{ select: oneCategorySelect == index }"
@click="templateFourOneFn(index)"
>
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]"><image :src="$util.img(item.image)" mode="aspectFill"></image></view>
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{ item.category_name }}</view>
</view>
</scroll-view>
<view class="category-item-all" @click="$refs.templateFourPopup.open()">
<view class="category-item-all-wrap">
<text class="text">展开</text>
<image class="img" :src="$util.img('/public/uniapp/category/unfold.png')" mode="aspectFill"></image>
</view>
</view>
<uni-popup type="top" ref="templateFourPopup" class="aaa">
<view class="template-four-popup">
<scroll-view scroll-y="true" class="template-four-scroll" enable-flex="true">
<view
class="item"
:class="{ selected: oneCategorySelect == index }"
@click="templateFourOneFn(index)"
v-for="(item, index) in templateFourData"
:key="index"
>
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]">
<image :src="$util.img(item.image)" mode="aspectFill"></image>
</view>
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{ item.category_name }}</view>
</view>
</scroll-view>
<view class="pack-up" @click="$refs.templateFourPopup.close()">
<text>点击收起</text>
<text class="iconfont icon-iconangledown-copy"></text>
</view>
</view>
</uni-popup>
</view>
<view class="content-box" v-if="categoryTree">
<block v-if="categoryTree.length">
<scroll-view scroll-y="true" class="tree-wrap">
<view class="category-item-wrap">
<view
class="category-item"
v-for="(item, index) in categoryTree"
:key="index"
:class="[
{ select: select == index },
{ 'border-bottom': value.template == 4 && select + 1 === index },
{ 'border-top': value.template == 4 && select - 1 === index }
]"
@click="switchOneCategory(index)"
>
<view class="">{{ item.category_name }}</view>
</view>
</view>
</scroll-view>
<view class="right-flex-wrap">
<scroll-view
scroll-y="true"
class="content-wrap"
v-if="value.template == 1 || loadType == 'all'"
ref="contentWrap"
:scroll-into-view="categoryId"
:scroll-with-animation="true"
@scroll="listenScroll"
@touchstart="touchStart"
:refresher-enabled="true"
refresher-default-style="none"
:refresher-triggered="triggered"
@refresherrefresh="onRefresh"
@refresherrestore="onRestore"
>
<view class="child-category" v-for="(item, index) in categoryTree" :key="index" :id="'category-' + index">
<diy-category-item
:category="item"
:value="value"
ref="categoryItem"
:index="index"
:select="select"
:oneCategorySelect="oneCategorySelect"
@tologin="toLogin"
@selectsku="selectSku($event, index)"
@addCart="addCartPoint"
@loadfinish="getHeightArea"
></diy-category-item>
</view>
<view class="end-tips" ref="endTips" :style="{ opacity: endTips }">已经到底了~</view>
</scroll-view>
<view class="content-wrap" v-if="(value.template == 2 || value.template == 3 || value.template == 4) && loadType == 'part'">
<view
class="child-category-wrap"
v-for="(item, index) in categoryTree"
:key="index"
:id="'category-' + index"
:style="{ display: select == index ? 'block' : 'none' }"
>
<diy-category-item
:category="item"
:value="value"
ref="categoryItem"
:index="index"
:last="index == categoryTree.length - 1 ? true : false"
:select="select"
:oneCategorySelect="oneCategorySelect"
@tologin="toLogin"
@selectsku="selectSku($event, index)"
@addCart="addCartPoint"
@switch="switchOneCategory"
></diy-category-item>
</view>
</view>
</view>
</block>
<view class="category-empty" v-else>
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix"></image>
<view class="tips">暂时没有分类哦</view>
</view>
</view>
<view class="cart-box" v-if="(value.template == 2 || value.template == 4) && value.quickBuy && token && categoryTree && categoryTree.length">
<view class="left-wrap">
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation" @click="$util.redirectTo('/pages/goods/cart')">
<text class="iconfont icon-ziyuan1"></text>
<view class="num" v-if="cartNumber">{{ cartNumber < 99 ? cartNumber : '99+' }}</view>
</view>
<view class="price">
<text class="title">总计</text>
<text class="unit font-size-tag price-font"></text>
<text class="money font-size-toolbar price-font">{{ cartMoney[0] }}</text>
<text class="unit font-size-tag price-font">.{{ cartMoney[1] ? cartMoney[1] : '00' }}</text>
</view>
</view>
<view class="right-wrap"><button type="primary" class="settlement-btn" @click="settlement">去结算</button></view>
</view>
<view class="cart-point" :style="{ left: item.left + 'px', top: item.top + 'px' }" :key="index" v-for="(item, index) in carIconList"></view>
<ns-goods-sku-category ref="skuSelect" @addCart="addCartPoint"></ns-goods-sku-category>
</view>
</template>
<script>
//
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
import nsGoodsSkuCategory from '@/components/ns-goods-sku/ns-goods-sku-category.vue';
var contentWrapHeight, query, cartPosition;
export default {
components: { nsGoodsSkuCategory },
name: 'diy-category',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
oneCategorySelect: 0,
select: 0,
categoryId: 'category-0',
categoryTree: null,
scrollLock: false,
triggered: true,
heightArea: [],
isSub: false,
token: null,
carIconList: {},
endTips: 0,
cartAnimation: {},
loadType: '',
templateFourData: []
};
},
created() {
this.getCategoryTree();
this.$store.dispatch('getCartNumber');
this.token = uni.getStorageSync('token');
this.loadType = this.value.goodsLevel == 1 && this.value.loadType == 'all' ? 'all' : 'part';
},
mounted() {
query = uni.createSelectorQuery().in(this);
query
.select('.content-wrap')
.boundingClientRect(data => {
if (data) contentWrapHeight = data.height;
})
.exec();
setTimeout(() => {
query
.select('.end-tips')
.boundingClientRect(data => {
if (data && data.top > contentWrapHeight) this.endTips = 1;
})
.exec();
query
.select('.cart-icon')
.boundingClientRect(data => {
if (data) cartPosition = data;
})
.exec();
if (this.value.template == 1) this.getHeightArea(-1);
}, 500);
},
computed: {
cartMoney() {
let cartMoney = parseFloat(this.$store.state.cartMoney).toFixed(2);
return cartMoney.split('.');
},
cartNumber() {
return this.$store.state.cartNumber;
},
//
navbarHeight() {
// #ifdef APP-PLUS || H5
return 44;
// #endif
// #ifdef MP
// = + ()
// (px)
// return menuButtonInfo.height + (menuButtonInfo.top - this.navbarHeight) * 2;//
// let height = systemInfo.platform == 'ios' ? 44 : 48;
let height = menuButtonInfo.top;
// height += systemInfo.navbarHeight;
return height;
// #endif
},
//
navbarInnerStyle() {
let style = '';
//
// style += 'height:' + this.navbarHeight + 'px;';
style += 'height:' + menuButtonInfo.height * 2 + 'rpx;';
// //
style += 'padding-top:' + this.navbarHeight + 'px;';
// #ifdef MP
if (this.value.template == 4 && this.value.search) {
let rightButtonWidth = menuButtonInfo.width ? menuButtonInfo.width * 2 + 'rpx' : '70rpx';
style += 'padding-right:calc(' + rightButtonWidth + ' + 30rpx);';
}
// #endif
if (this.value.template != 4 || (this.value.template == 4 && !this.value.search)) {
style += 'text-align: center;';
style += 'line-height:' + menuButtonInfo.height * 2 + 'rpx;';
style += 'font-size: 24rpx;';
style += 'padding-bottom: 10rpx;';
}
return style;
}
},
methods: {
/**
* 页面显示
*/
pageShow() {
this.$store.dispatch('getCartNumber');
this.token = uni.getStorageSync('token');
if (!this.heightArea.length) this.getHeightArea(-1);
},
/**
* 获取高度区间
*/
getHeightArea(index) {
let heightArea = [];
query
.selectAll('.content-wrap .child-category')
.boundingClientRect(data => {
if (data && data.length) {
data.forEach((item, index) => {
if (index == 0) heightArea.push([0, item.height]);
else heightArea.push([heightArea[index - 1][1], heightArea[index - 1][1] + item.height]);
});
}
})
.exec();
this.heightArea = heightArea;
if (index != -1 && index < this.categoryTree.length - 1) this.$refs.categoryItem[index + 1].getGoodsList();
},
/**
* 获取全部分类
*/
getCategoryTree() {
this.$api.sendRequest({
url: '/api/goodscategory/tree',
data: {
level: 3
},
success: res => {
if (res.code == 0) {
this.categoryTree = res.data;
if (this.value.template == 4) {
this.templateFourData = JSON.parse(JSON.stringify(this.categoryTree));
this.categoryTree = this.templateFourData[0].child_list;
}
}
}
});
},
/**
* 切换一级分类
* @param {Object} index
*/
switchOneCategory(index) {
if (index >= this.categoryTree.length) return;
this.select = index;
this.categoryId = 'category-' + index;
//
this.scrollLock = true;
},
touchStart() {
this.scrollLock = false;
},
/**
* 监听滚动
* @param {Object} event
*/
listenScroll(event) {
if (this.scrollLock) return;
let scrollTop = event.detail.scrollTop;
if (this.heightArea.length) {
for (let i = 0; i < this.heightArea.length; i++) {
if (scrollTop >= this.heightArea[i][0] && scrollTop <= this.heightArea[i][1]) {
this.select = i;
break;
}
}
if (this.value.template != 1 && this.value.loadType == 'all' && this.heightArea[this.select][1] - scrollTop - contentWrapHeight < 300) {
this.$refs.categoryItem[this.select].getGoodsList();
}
}
},
onRefresh() {
this.triggered = false;
},
onRestore() {
this.triggered = 'restore'; //
},
toLogin() {
this.$emit('tologin');
},
/**
* sku选择
* @param {Object} data
* @param {Object} index
*/
selectSku(data, index) {
this.$api.sendRequest({
url: '/api/goodssku/getInfoForCategory',
data: {
sku_id: data.sku_id
},
success: res => {
if (res.code >= 0) {
let data = res.data,
obj = {};
let item = res.data;
item.unit = item.unit || '件';
if (item.sku_images) item.sku_images = item.sku_images.split(',');
else item.sku_images = [];
//
if (item.goods_spec_format && item.goods_image) {
item.goods_image = item.goods_image.split(',');
item.sku_images = item.goods_image.concat(item.sku_images);
}
// SKU
if (item.sku_spec_format) item.sku_spec_format = JSON.parse(item.sku_spec_format);
// SKU
if (item.goods_spec_format) item.goods_spec_format = JSON.parse(item.goods_spec_format);
//
if (item.promotion_type == 1) {
item.discountTimeMachine = this.$util.countDown(item.end_time - res.timestamp);
}
if (item.promotion_type == 1 && item.discountTimeMachine) {
if (item.member_price > 0 && Number(item.member_price) <= Number(item.discount_price)) {
item.show_price = item.member_price;
} else {
item.show_price = item.discount_price;
}
} else if (item.member_price > 0) {
item.show_price = item.member_price;
} else {
item.show_price = item.price;
}
this.$refs.skuSelect.show('join_cart', item, () => {});
}
}
});
},
settlement() {
let cartList = Object.keys(this.$store.state.cartList),
cartIds = [];
if (!cartList.length || this.isSub) return;
this.isSub = true;
cartList.forEach(key => {
cartIds.push(this.$store.state.cartList[key].cart_id);
});
uni.removeStorageSync('delivery');
uni.setStorage({
key: 'orderCreateData',
data: {
cart_ids: cartIds.toString()
},
success: () => {
this.$util.redirectTo('/pages/order/payment');
this.isSub = false;
}
});
},
/**
* 添加点
* @param {Object} left
* @param {Object} top
*/
addCartPoint(left, top) {
if (this.value.template != 2 && !this.value.quickBuy) return;
let key = new Date().getTime();
this.$set(this.carIconList, key, {
left: left,
top: top,
index: 0,
bezierPos: this.$util.bezier([{ x: left, y: top }, { x: left - 200, y: left - 120 }, { x: cartPosition.left + 10, y: cartPosition.top }], 6).bezier_points,
timer: null
});
this.startAnimation(key);
},
/**
* 执行动画
* @param {Object} key
*/
startAnimation(key) {
let bezierPos = this.carIconList[key].bezierPos,
index = this.carIconList[key].index;
this.carIconList[key].timer = setInterval(() => {
if (index < 6) {
this.carIconList[key].left = bezierPos[index].x;
this.carIconList[key].top = bezierPos[index].y;
index++;
} else {
clearInterval(this.carIconList[key].timer);
delete this.carIconList[key];
this.$forceUpdate();
//
setTimeout(() => {
this.$store.commit('setCartChange');
}, 100);
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);
}
}, 50);
},
//
templateFourOneFn(index) {
this.categoryTree = this.templateFourData[index].child_list || [];
this.oneCategorySelect = index;
this.select = 0;
}
}
};
</script>
<style lang="scss">
.category-page-wrap {
width: 100vw;
height: calc(100vh - var(--tab-bar-height, 0));
display: flex;
flex-direction: column;
background-color: #fff;
}
.content-box {
flex: 1;
height: 0;
display: flex;
.tree-wrap {
width: 170rpx;
height: 100%;
background-color: #f5f5f5;
}
.right-flex-wrap {
flex: 1;
width: 0;
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
transform: translateX(0px);
.content-wrap {
display: flex;
flex: 1;
height: 0;
width: 100%;
}
.child-category-wrap {
width: 100%;
height: 100%;
}
}
}
.tree-wrap .category-item-wrap {
height: auto;
background-color: #fff;
}
.tree-wrap .category-item {
line-height: 1.5;
padding: 26rpx 28rpx;
box-sizing: border-box;
position: relative;
background-color: #f5f5f5;
view {
color: #222222;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
}
&.border-top {
border-bottom-right-radius: 12rpx;
}
&.border-bottom {
border-top-right-radius: 12rpx;
}
&.select {
background: #fff;
view {
color: #333;
font-weight: bold;
}
&::before {
content: ' ';
width: 8rpx;
height: 34rpx;
background: var(--base-color);
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
.search-box {
position: relative;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
background: #fff;
.search-content {
position: relative;
height: 70rpx;
border-radius: 40rpx;
flex: 1;
background-color: #f5f5f5;
input {
box-sizing: border-box;
display: block;
height: 70rpx;
width: 100%;
padding: 0 20rpx 0 40rpx;
background: #f5f5f5;
color: #333;
border-radius: 40rpx;
}
.iconfont {
position: absolute;
top: 50%;
right: 10rpx;
transform: translateY(-50%);
font-size: $font-size-toolbar;
z-index: 10;
color: #89899a;
width: 80rpx;
text-align: center;
}
}
}
.cart-box {
height: 100rpx;
width: 100%;
background: #fff;
border-top: 1px solid #f5f5f5;
box-sizing: border-box;
padding: 0 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
.left-wrap {
display: flex;
align-items: center;
}
.cart-icon {
width: 70rpx;
height: 70rpx;
position: relative;
.iconfont {
color: var(--btn-text-color);
width: inherit;
height: inherit;
background-color: $base-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.num {
position: absolute;
top: 0;
right: 0;
transform: translate(60%, 0);
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 6rpx;
min-width: 30rpx;
border-radius: 16rpx;
background-color: var(--price-color);
border: 2rpx solid #fff;
}
}
.price {
margin-left: 30rpx;
.title {
color: #333;
}
.money,
.unit {
font-weight: bold;
color: var(--price-color);
}
}
.settlement-btn {
margin: 0 0 0 20rpx;
width: 200rpx;
font-weight: bold;
border-radius: 50rpx;
}
}
.cart-point {
width: 26rpx;
height: 26rpx;
position: fixed;
z-index: 1000;
background: #f00;
border-radius: 50%;
transition: all 0.05s;
}
.category-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
image {
width: 380rpx;
}
.tips {
font-size: 26rpx;
font-weight: 500;
color: #999;
margin-top: 50rpx;
}
}
.end-tips {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 20rpx 0;
opacity: 0;
}
//
.category-template-4 {
.search-box {
background-color: #f0f5ff;
.search-content input {
background-color: #fff;
}
}
.cart-box {
position: relative;
z-index: 2;
}
/deep/ .template-four {
position: relative;
z-index: 1;
&:after {
content: '';
position: absolute;
bottom: 20rpx;
height: 4rpx;
left: 0;
right: 0;
box-shadow: 0 20rpx 14rpx rgba(0, 0, 0, 0.2);
}
.template-four-wrap {
position: relative;
z-index: 1;
padding-left: 20rpx;
padding-right: 80rpx;
padding-bottom: 10rpx;
display: flex;
height: 180rpx;
align-items: baseline;
box-sizing: border-box;
background-image: linear-gradient(#f0f5ff 45%, #fff);
}
.template-four-popup {
display: flex;
flex-direction: column;
overflow: hidden;
.title {
line-height: 1;
margin-bottom: 20rpx;
font-weight: bold;
}
.template-four-scroll {
display: flex;
flex-wrap: wrap;
align-items: baseline;
align-content: baseline;
white-space: break-spaces;
padding: 20rpx;
white-space: nowrap;
height: 380rpx;
box-sizing: border-box;
.uni-scroll-view-content {
flex-wrap: wrap;
align-items: baseline;
align-content: baseline;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
padding: 4rpx 0;
color: #666;
margin-right: 16rpx;
border-radius: 40rpx;
margin-bottom: 10rpx;
width: calc((100% - 64rpx) / 5);
&:nth-child(5n + 5) {
margin-right: 0;
}
.image-warp {
margin-bottom: 6rpx;
padding: 4rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 42rpx;
border: 4rpx solid transparent;
}
image {
width: 84rpx;
height: 84rpx;
border-radius: 32rpx;
}
.text {
padding: 2rpx 16rpx;
border-radius: 40rpx;
font-size: $font-size-tag;
}
&.selected {
.text {
background-color: $base-color;
color: var(--btn-text-color);
}
}
}
}
.pack-up {
font-size: $font-size-tag;
color: #888888;
height: 74rpx;
display: flex;
align-items: center;
justify-content: center;
border-top: 2rpx solid #f2f2f2;
.iconfont {
font-size: 40rpx;
margin-left: -4rpx;
}
}
}
.category-item-all {
position: absolute;
bottom: 0;
z-index: 1;
right: 0;
top: 0;
width: 72rpx;
line-height: 1;
.category-item-all-wrap {
position: absolute;
bottom: 0;
right: 0;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2;
background-image: linear-gradient(#f0f5ff 45%, #fff);
}
.text {
writing-mode: tb-rl;
margin-bottom: 6rpx;
letter-spacing: 4rpx;
font-size: $font-size-tag;
font-weight: bold;
}
.img {
width: 20rpx;
height: 20rpx;
}
&::after {
content: '';
box-shadow: -4rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);
position: absolute;
left: 0;
width: 10rpx;
// height: 100rpx;
top: 20%;
bottom: 20%;
background-image: linear-gradient(#f0f5ff 45%, #fff);
// transform: translateY(-50%);
// background-color: #F0F5FF;
}
}
.uni-scroll-view-content {
display: flex;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 130rpx;
flex-shrink: 0;
margin-right: 20rpx;
padding: 4rpx 0;
&:last-of-type {
margin-right: 0;
}
.image-warp {
margin-bottom: 6rpx;
padding: 4rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 42rpx;
border: 4rpx solid transparent;
}
image {
width: 84rpx;
height: 84rpx;
border-radius: 32rpx;
}
}
.select {
.text {
padding: 8rpx 16rpx;
border-radius: 40rpx;
color: #fff;
font-size: $font-size-tag;
line-height: 1;
}
}
}
.content-wrap .categoty-goods-wrap .goods-list {
margin-top: 30rpx;
}
.tree-wrap .category-item.select::before {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
}
</style>

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)"></image>
</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 (!uni.getStorageSync('token')) {
this.$util.redirectTo(
'/pages_tool/login/login',
{
back: this.currentRoute
},
'redirectTo'
);
}
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,102 @@
<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)">
<image :src="$util.img(item.imageUrl)" mode="aspectFit" v-if="!item.iconType || item.iconType == 'img'"></image>
<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() {},
components: {},
methods: {},
computed:{
style(){
let style = {}, height = 54;
// #ifdef MP
height = systemInfo.platform == 'ios' ? 54 : 58;
// #endif
switch (this.value.bottomPosition) {
case 1:
style.top = (this.navHeight + this.statusBarHeight + parseInt(value.btnBottom)) * 2 + 'rpx !important'
break;
case 2:
style.top = (this.navHeight + this.statusBarHeight + parseInt(value.btnBottom)) * 2 + 'rpx !important'
break;
case 3:
style.bottom = (100 + parseInt(value.btnBottom)) * 2 + 'rpx !important'
break;
case 4:
style.bottom = (100 + parseInt(value.btnBottom)) * 2 + 'rpx !important'
break;
}
return this.$util.objToStyle(style);
}
}
};
</script>
<style lang="scss">
.float-btn {
position: fixed;
bottom: 20%;
right: 40rpx;
z-index: 999;
&.right_top {
top: 100rpx;
right: 30rpx;
}
&.left_top {
top: 100rpx;
left: 30rpx;
}
&.right_bottom {
bottom: 200rpx;
right: 30rpx;
padding-bottom: constant(safe-area-inset-bottom); /*兼容 IOS<11.2*/
padding-bottom: env(safe-area-inset-bottom); /*兼容 IOS>11.2*/
}
&.left_bottom {
bottom: 200rpx;
left: 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;
width: 80rpx;
height: 80rpx;
font-size: 80rpx;
&:last-child {
margin-bottom: 0;
}
image {
width: 100%;
height: 100%;
}
}
}
</style>

View File

@ -0,0 +1,135 @@
<template>
<view :class="['brand-wrap', value.ornament.type]" :style="warpCss" v-if="list.length > 0">
<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" index="index">
<image class="brand-pic" :src="$util.img(item.image_url)" mode="aspectFit" @click="toDetail(item)" @error="imgError(index)" :style="itemCss"></image>
</view>
</view>
</view>
</view>
</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: []
};
},
created() {
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;
}
}
});
},
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,438 @@
<template>
<view v-if="list.length" :class="['goods-list', goodsValue.style]" :style="goodsListWarpCss">
<view class="top-wrap">
<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"
>
<image
class="goods-img"
:style="{ borderRadius: value.imgAroundRadius * 2 + 'rpx' }"
:src="$util.img(dataItem.goods_image,{size: 'mid'})"
mode="widthFix"
@error="imgError(dataIndex)"
:lazy-load="true"
></image>
<view :class="['info-wrap', { 'multi-content': value.nameLineMode == 'multiple' }]" v-if="goodsValue.goodsNameStyle.control || goodsValue.priceStyle.mainControl || goodsValue.priceStyle.lineControl">
<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="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"
class="delete-price price-font"
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.lineColor : '' }"
>
{{ dataItem.market_price > 0 ? dataItem.market_price : dataItem.price }}
</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>
</template>
<script>
export default {
name: 'diy-goods-recommend',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
list: [],
goodsValue: {},
page: 1
};
},
created() {
this.goodsValue = this.value;
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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-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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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;
}
}
});
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/detail', {
goods_id: item.goods_id
});
},
imgError(index) {
if (this.list[index]) this.list[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;
}
}
};
</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;
}
}
}
}
//
.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 {
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 {
width: 100%;
height: 200rpx;
}
.info-wrap {
padding: 10rpx;
.goods-name {
line-height: 1;
&.using-hidden{
display: block;
}
&.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;
}
}
}
}
}
</style>

View File

@ -0,0 +1,317 @@
<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-WEIXIN -->
<view
class="graphic-nav-item"
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"
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-show="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' }"
></image>
<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-show="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]">
<!-- #ifdef MP -->
<view class="uni-scroll-view-content">
<!-- #endif -->
<view class="graphic-nav-item" v-for="(item, index) in value.list" :key="index" :style="{ width: 100 / value.rowCount + '%' }" @click="redirectTo(item.link)">
<view
class="graphic-img"
v-show="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' }"
></image>
<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-show="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() {
},
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 = 88 * this.value.pageCount; // 88 = +
if (this.value.mode == 'img') height -= 21 * this.value.pageCount; // 21 =
if (this.value.mode == 'text') height -= 50 * this.value.pageCount; // 21 =
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' && !uni.getStorageSync('token')) {
this.$refs.login.open(link.wap_url);
return;
}
}
this.$util.diyRedirectTo(link);
},
swiperChange(e){
this.swiperCurrent = e.detail.current
}
}
};
</script>
<style>
/* 固定显示 */
.graphic-nav.fixed >>> .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;
}
}
.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: -36rpx;
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,268 @@
<template>
<view class="diy-group">
<view v-for="(item, index) in diyGlobalData.value" :key="index" :style="item.pageStyle">
<view v-if="addonIsExist.store && showStore && item.componentName == 'StoreShow'">
<!-- 门店展示 -->
<diy-store :storeId="storeId" :value="item"></diy-store>
</view>
<template v-if="addonIsExist.store && item.componentName == 'StoreInfo'">
<!-- 门店详情 -->
<diy-store-info :value="item" :storeId="storeId"></diy-store-info>
</template>
<template v-if="item.componentName == 'Text'">
<!-- 文本 -->
<diy-text :value="item"></diy-text>
</template>
<template v-if="item.componentName == 'TextNav'">
<!-- 文本导航 -->
<diy-text-nav :value="item"></diy-text-nav>
</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'">
<!-- 图片广告 -->
<view :class="!showStore || !addonIsExist.store ? 'noStore-bg' : ''"><diy-img-ads :value="item"></diy-img-ads></view>
</template>
<template v-if="item.componentName == 'Search'">
<!-- 搜索 -->
<view :class="!showStore || !addonIsExist.store ? 'noStore-bg' : ''"><diy-search :value="item"></diy-search></view>
<!-- <view :class="!showStore || !addonIsExist.store ? 'noStore-bg' : 'isStore-top'">
<diy-search :value="item"></diy-search>
</view> -->
</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"></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>
<view class="diy-goods-level-wrap" v-if="item.componentName == 'GoodsCategory'">
<!-- 商品分类 使用view替代template目的是限制商品分类在自定义首页的高度-->
<diy-category :value="item"></diy-category>
</view>
<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 == 'MemberInfo'">
<!-- 自定义会员中心会员信息 -->
<diy-member-info ref="diyMemberIndex" :value="item" :token="token"></diy-member-info>
</template>
<template v-if="item.componentName == 'MemberMyOrder'">
<!-- 自定义会员中心我的订单 -->
<diy-member-my-order ref="diyMemberMyOrder" :value="item" :token="token"></diy-member-my-order>
</template>
<!-- 自定义扩展组件 -->
<diy-comp-extend :value="item"></diy-comp-extend>
</view>
</view>
</template>
<script>
export default {
components: {},
props: {
diyData: {
type: Object
},
storeId: {
type: [String, Number]
},
token: {
type: String
},
height: {
type: String,
default() {
return '100vh';
}
}
},
data() {
return {
showStore: false,
diyGlobalData: null
};
},
created() {
this.diyGlobalData = JSON.parse(JSON.stringify(this.diyData));
this.setPagestyle();
},
computed: {
bgColor() {
let str = '';
if (this.diyData && this.diyData.global) {
str = this.diyData.global.bgColor;
}
return str;
},
bgUrl() {
let str = '';
if (this.diyData && this.diyData.global) {
str = this.diyData.global.bgUrl;
}
return str;
}
},
mounted() {
if (this.diyData != undefined) {
this.dealData();
}
},
methods: {
setPagestyle() {
this.diyGlobalData.value.forEach((item, index) => {
item.pageStyle = '';
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' + ';';
}
});
},
//
refresh(data) {
this.diyGlobalData = {}; //
setTimeout(() => {
this.diyGlobalData = data;
this.setPagestyle();
}, 1);
},
dealData() {
if (Array.isArray(this.diyData.value)) {
for (var i = 0; i < this.diyData.value.length; i++) {
if (this.diyData.value[i].componentName == 'StoreShow') {
this.showStore = true;
}
}
}
}
}
};
</script>
<style lang="scss">
.diy-group {
width: 100%;
}
// .diy-goods-level-wrap {
// position: relative;
// height: 60vh;
// }
</style>

View File

@ -0,0 +1,412 @@
<template>
<view class="diy-groupbuy" 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)"></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>
<image class="bg" v-if="value.saleStyle.control" :src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix"></image>
<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[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>
<image class="bg" v-if="value.saleStyle.control" :src="$util.img('public/uniapp/groupbuy/bg.png')" mode="widthFix"></image>
<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(".")[1] }}</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>
</template>
<script>
export default {
name: 'diy-groupbuy',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1
};
},
created() {
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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: {
rpxUpPx(res){
const screenWidth = uni.getSystemInfoSync().safeArea.width || uni.getSystemInfoSync().screenWidth;
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;
}
}
}
});
},
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,36 @@
<template>
<view :style="horzBlankGaugeWrap"></view>
</template>
<script>
//
export default {
name: 'diy-horz-blank',
props: {
value: {
type: Object
}
},
data() {
return {};
},
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,21 @@
<template>
<view :style="{ borderTop: '2rpx ' + value.borderStyle + ' ' + value.color }"></view>
</template>
<script>
// 线
export default {
name: 'diy-horz-line',
props: {
value: {
type: Object
}
},
data() {
return {};
},
methods: {}
};
</script>
<style></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,329 @@
<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"></image>
<!-- 热区功能 -->
<view v-if="imgAdsValue.list[0].heatMapData">
<view
class="heat-map"
v-for="(mapItem, mapIndex) in imgAdsValue.list[0].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>
</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"
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="{ height: item.imgHeight }">
<image :src="$util.img(item.imageUrl)" mode="aspectFill"></image>
<!-- 热区功能 -->
<view v-if="item.heatMapData">
<view
class="heat-map"
v-for="(mapItem, mapIndex) in item.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>
</swiper-item>
</swiper>
<!-- #ifdef MP-WEIXIN -->
<view
v-if="imgAdsValue.list.length > 1"
: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();
},
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 maxHeight = 0;
//
this.imgAdsValue = JSON.parse(JSON.stringify(this.value));
this.imgAdsValue.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = res.windowWidth;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
}
});
//
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight;
});
this.imgAdsValue.list.forEach((item, index) => {
item.imgHeight = maxHeight * 2 + 'rpx';
this.swiperHeight = maxHeight * 2 + 'rpx';
});
this.imgAdsValue.indicatorColor = this.imgAdsValue.indicatorColor || '#fff';
//
if (this.imgAdsValue.list.length <= 1) {
this.isDots = false;
}
// #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;
.item {
width: 100%;
height: auto;
text-align: center;
position: relative;
overflow: hidden;
image {
width: 100%;
max-width: 100%;
height: 100%;
}
.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 /deep/ .swiper-item .item uni-image > div {
background-size: cover !important;
}
.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,693 @@
<template>
<scroll-view scroll-y="true" :style="{ height: scrollHeight }" @scroll="scroll" :scroll-top="scrollTop" @scrolltolower="scrolltolower">
<view class="bg" :style="warpCss">
<view class="nav_top_category">
<scroll-view class="diyIndex widthAuto" @click="changePageIndex(0)">
<view
class="item text-fiexd"
:class="{ fill: value.styleType == 'fill' }"
:style="{ background: pageIndex == 0 && value.styleType == 'fill' ? value.selectColor : '' }"
>
<view
class="text-con"
:class="pageIndex == 0 ? 'active' : ''"
:style="{
color: index == pageIndex ? '' : value.noColor
}"
v-if="value.styleType == 'fill'"
>
{{ cateList[0].short_name ? cateList[0].short_name : cateList[0].category_name }}
</view>
<view
class="text-con"
:class="pageIndex == 0 ? 'active' : ''"
:style="{
color: pageIndex == 0 ? value.selectColor : value.noColor
}"
v-else
>
{{ cateList[0].short_name ? cateList[0].short_name : cateList[0].category_name }}
</view>
<view
class="color-base-bg line"
v-if="pageIndex == 0 && value.styleType != 'fill'"
:style="{ background: value.selectColor ? value.selectColor + '!important' : 'rgba(0,0,0,0)' + '!important' }"
></view>
</view>
</scroll-view>
<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% - 102rpx)' }"
>
<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 : '' }"
v-if="index > 0"
>
<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>
</view>
<view class="index-page-content" :style="{ height: scrollTopHeight }">
<block v-if="pageIndex == 0"><slot></slot></block>
<block v-else>
<view class="index-category-box">
<!-- 二级分类 -->
<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>
<image :src="$util.getDefaultImage().goods" v-else mode="aspectFill"></image>
<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>
<image :src="$util.getDefaultImage().goods" v-else mode="aspectFill"></image>
<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"></image>
<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 ns-margin" v-if="cateList[pageIndex].image_adv" :src="$util.img(cateList[pageIndex].image_adv)" mode="widthFix"></image>
<view class="category-goods" v-show="!isloading">
<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)"></image>
<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)" mode=""></image></view>
</view>
<view class="info-wrap">
<view class="name-wrap">
<view class="goods-name">{{ item.goods_name }}</view>
</view>
<view class="pro-info">
<view class="delete-price font-size-activity-tag color-tip">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
{{ item.market_price > 0 ? item.market_price : item.price }}
</view>
<view class="sale font-size-activity-tag color-tip">已售{{ item.sale_num }}{{ item.unit ? item.unit : '件' }}</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"></image>
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</view>
<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>
</scroll-view>
</template>
<script>
import nsLoading from '@/components/ns-loading/ns-loading.vue';
export default {
props: {
value: {
type: Object
},
scrollHeight: {
type: String
},
scrollTopHeight: {
type: String
},
bgUrl: {
type: String
}
},
components: {
nsLoading
},
data() {
return {
pageIndex: 0, //id
cateList: [
{
//header
category_name: '首页'
}
],
twoCategorylist: [], //
twoCategorylistId: 0, //swiper
goodsList: {},
scrollTop: 0,
scrollTopCopy: 0,
isloading: true
};
},
computed: {
warpCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
obj += this.bgUrl ? 'background:' + 'url(' + this.$util.img(this.bgUrl) + ') no-repeat 0 0/100%' : '';
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;
}
}
},
watch: {
type(newVal, oldVal) {
if (newVal) {
this.getCategoryList();
}
}
},
mounted() {
this.getCategoryList();
},
methods: {
initPageIndex() {
this.pageIndex = 0;
},
//
getCategoryList() {
let url = '/api/goodscategory/tree';
let data = {
level: 3,
template: 2
};
this.$api.sendRequest({
url: url,
data: data,
success: res => {
if (res.code >= 0) {
let arr = [];
let obj = {
list: [],
isAll: false
};
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: [],
isAll: false
};
});
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
}
}
});
},
//id
changePageIndex(e) {
this.isloading = true;
this.pageIndex = e;
if (e == 0) return;
this.twoCategorylist = this.cateList[this.pageIndex].child_list;
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;
}
let id = e;
if (this.goodsList[id] && this.goodsList[id].length) return;
this.scrollTop = this.scrollTopCopy;
this.$nextTick(function() {
this.scrollTop = 0;
});
this.getGoodsList();
},
//swiper
swiperChange(e) {
this.changePageIndex(e.detail.current);
},
//
swiperTocategoryChange(e) {
this.twoCategorylistId = e.detail.current;
},
toDetail(item) {
this.$util.redirectTo('/pages/goods/detail', {
goods_id: item.goods_id
});
},
scroll(e) {
this.scrollTopCopy = e.detail.scrollTop;
},
getGoodsList() {
let id = this.pageIndex;
var data = {
page: this.goodsList[id].page,
page_size: 10
};
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;
if (res.code == 0 && res.data) {
this.goodsList[id].list = this.goodsList[id].list.concat(res.data.list);
if (this.goodsList[id].list.length == res.data.count) {
this.goodsList[id].isAll = true;
}
this.goodsList[id].page += 1;
}
this.$forceUpdate();
}
});
},
//
scrolltolower() {
let id = this.pageIndex;
if (this.goodsList[id].isAll) return;
this.isloading = true;
this.getGoodsList();
},
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;
},
goodsTag(data) {
return data.label_name || '';
}
}
};
</script>
<style lang="scss">
.bg {
width: 100%;
height: 100%;
padding: 0 30rpx;
box-sizing: border-box;
}
.nav_top_category {
display: flex;
.text-fiexd {
.text-con {
line-height: 60rpx;
// &.active{
// line-height: 1.8;
// }
}
}
}
.diyIndex {
width: 100%;
height: 100rpx;
white-space: nowrap;
padding: 20rpx 0 0;
box-sizing: border-box;
&.widthAuto {
width: auto;
}
.item {
padding: 0 20rpx;
display: inline-block;
line-height: 80rpx;
font-size: $font-size-base;
text-align: center;
.text-con {
height: 60rpx;
line-height: 60rpx;
}
.text-con.active {
font-size: $font-size-toolbar;
font-weight: 600;
}
.line {
width: 100%;
height: 5rpx;
border-radius: 5rpx;
}
&.fill {
border-radius: 50rpx;
.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: calc(100%);
margin-bottom: 0;
border-radius: 15rpx;
}
.category-goods {
width: 100%;
}
}
.loading {
width: 100%;
height: 50rpx;
}
/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,235 @@
<template>
<view v-if="liveInfo && liveInfo.roomid != undefined">
<view class="live-wrap" @click="entryRoom(liveInfo.roomid)">
<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')"
></image>
<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"
></image>
<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>
</view>
</template>
<script>
//
export default {
components: {},
name: 'diy-live',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
liveInfo: {
banner: '',
anchor_img: ''
}
};
},
created() {
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;
}
}
});
},
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,143 @@
<template>
<view class="many-goods-list">
<scroll-view scroll-x="true" :scroll-into-view="'a' + cateIndex">
<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="item-wrap">
<view class="split-line" v-if="index > 0"></view>
<view class="cate">
<view class="name">{{ item.title }}</view>
<view class="desc" :class="{ 'color-base-bg': index == cateIndex && item.desc }">{{ item.desc }}</view>
</view>
</view>
</view>
</scroll-view>
<diy-goods-list 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 {};
}
}
},
data() {
return {
cateIndex: 0, // id
goodsValue: null //
};
},
created() {
this.changeCateIndex(this.value.list[0], 0, true);
},
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,
cartEvent: this.value.cartEvent
};
//
if (isFirst) return;
this.$refs.diyGoodsList.goodsValue = this.goodsValue;
this.$refs.diyGoodsList.getGoodsList();
}
}
};
</script>
<style lang="scss" scoped>
scroll-view {
width: 100%;
white-space: nowrap;
box-sizing: border-box;
padding: 40rpx 20rpx;
.scroll-item {
display: inline-block;
text-align: center;
vertical-align: top;
.item-wrap {
display: flex;
align-items: center;
}
.split-line {
display: inline-block;
width: 1rpx;
height: 30rpx;
background-color: #e5e5e5;
margin: 0 35rpx;
}
&.active {
.name {
font-weight: bold;
}
.desc {
color: #ffffff;
border-radius: 20rpx;
}
}
.name {
font-size: 32rpx;
color: $color-title;
line-height: 1;
}
.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>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,248 @@
<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')">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_pay.png')" mode="widthFix"></image>
<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">待付款</view>
</view>
<view class="item-wrap" @click="redirect('/pages/order/list?status=waitsend')">
<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">待发货</view>
</view>
<view class="item-wrap" @click="redirect('/pages/order/list?status=waitconfirm')">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_confirm.png')" mode="widthFix"></image>
<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">待收货</view>
</view>
<view class="item-wrap" @click="redirect('/pages/order/list?status=wait_use')">
<view class="icon-block">
<template v-if="value.style == 3">
<image :src="$util.img('public/uniapp/member/order/wait_use.png')" mode="widthFix"></image>
<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">待使用</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"></image>
<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">售后</view>
</view>
</view>
</view>
<ns-login ref="login"></ns-login>
</view>
</template>
<script>
//
export default {
name: 'diy-member-my-order',
props: {
value: {
type: Object
},
token: {
type: String
}
},
data() {
return {
orderNum: {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0
}
};
},
created() {
this.init();
},
watch: {
token(nVal, oVal) {
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 (uni.getStorageSync('token')) {
this.getOrderNum();
} else {
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0
};
}
},
/**
* 获取订单数量
*/
getOrderNum() {
this.$api.sendRequest({
url: '/api/order/num',
data: {
order_status: 'waitpay,waitsend,waitconfirm,refunding,wait_use'
},
success: res => {
if (res.code == 0) {
this.orderNum = res.data;
}else{
this.orderNum = {
waitpay: 0,
waitsend: 0,
waitconfirm: 0,
refunding: 0,
wait_use: 0
};
}
}
});
},
/**
* 跳转
* @param {Object} url
*/
redirect(url) {
if (!uni.getStorageSync('token')) {
this.$refs.login.open(url);
} else {
this.$util.redirectTo(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;
.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%);
display: inline-block;
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;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 26rpx;
}
}
}
</style>

View File

@ -0,0 +1,338 @@
<template>
<view class="diy-notes" v-if="dataList.length" :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>
<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"
></image>
</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>
</template>
<script>
export default {
name: 'diy-notes',
props: {
value: {
type: Object
}
},
data() {
return {
dataList: [],
giveLikeFlag: false
};
},
created() {
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);
}
}
}
});
},
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 (!uni.getStorageSync('token')) {
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,182 @@
<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="aspectFit"></image>
<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">
<text v-if="value.contentStyle == 'style-2'" class="iconfont icon-gonggao" :style="{ color: value.textColor }"></text>
<view class="uni-swiper-msg">
<!-- 横向滚动horizontal -->
<template v-if="value.scrollWay == 'horizontal'">
<swiper :vertical="false" :duration="0" autoplay="true" circular="true">
<swiper-item v-for="(item, index) in list" :key="index" @touchmove.stop>
<text @click="toLink(item)" class="beyond-hiding animate" :style="{ color: value.textColor }">{{ item.title }}</text>
</swiper-item>
</swiper>
</template>
<!-- 上下滚动upDown -->
<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">
<text @click="toLink(item)" class="beyond-hiding using-hidden" :style="{ color: value.textColor }">{{ item.title }}</text>
</swiper-item>
</swiper>
</template>
</view>
<text v-if="value.contentStyle == 'style-2'" class="iconfont icon-right arrows" @click="toLink"></text>
</view>
</view>
</view>
</template>
<script>
//
export default {
name: 'diy-notice',
props: {
value: {
type: Object
}
},
data() {
return {
list: []
};
},
created() {
//
if (this.value.sources == 'default') this.getData();
else this.list = this.value.list;
},
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 = {};
data.id_arr = this.value.noticeIds.toString();
this.$api.sendRequest({
url: '/api/notice/lists',
data: data,
success: res => {
if (res.code == 0 && res.data) {
this.list = res.data;
}
}
});
},
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 this.$util.diyRedirectTo(item.link);
}
}
};
</script>
<style lang="scss">
.notice {
height: 80rpx;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
padding: 20rpx;
font-size: 70rpx;
box-sizing: border-box;
.notice-img {
width: 212rpx;
height: 40rpx;
}
.notice-xian {
width: 1rpx;
height: 26rpx;
background-color: #e4e4e4;
margin: 0 22rpx;
}
&.style-2 {
.main-wrap {
display: flex;
align-items: center;
.uni-swiper-msg {
width: 400rpx;
margin: 0 10rpx;
}
.arrows {
color: #999;
font-size: $font-size-sub;
}
}
}
}
.main-wrap {
display: inline-block;
width: calc(100% - 115rpx);
position: relative;
}
swiper {
height: 50rpx;
}
.beyond-hiding {
display: inline-block;
width: 100%;
white-space: nowrap;
}
.animate {
width: auto;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 40rpx;
font-size: $font-size-base;
color: #000;
display: inline-block;
white-space: nowrap;
animation: 5s wordsLoop linear infinite normal;
}
@keyframes wordsLoop {
0% {
transform: translateX(400rpx);
-webkit-transform: translateX(400rpx);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
@-webkit-keyframes wordsLoop {
0% {
transform: translateX(400rpx);
-webkit-transform: translateX(400rpx);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
</style>

View File

@ -0,0 +1,501 @@
<template>
<view class="diy-pinfan" 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)"></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="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)"></image>
</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[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>
</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>
</template>
<script>
export default {
name: 'diy-pinfan',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1
};
},
created() {
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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: {
rpxUpPx(res){
const screenWidth = uni.getSystemInfoSync().safeArea.width || uni.getSystemInfoSync().screenWidth;
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;
}
}
}
});
},
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,696 @@
<template>
<view class="diy-pintuan" v-if="list.length" :class="[value.template, value.style]" :style="warpCss">
<view v-if="value.titleStyle.isShow" :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"></image>
<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)"></image>
</block>
<block v-else>
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill">
</image>
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill">
</image>
<image :src="$util.img('public/static/img/default_img/square.png')" mode="aspectFill">
</image>
</block>
</view>
<text :style="{color: value.titleStyle.textColor}">{{value.titleStyle.virtualNum || headData.pintuan_count}}人拼团成功</text>
</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)">
</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="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)">
</image>
<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>
</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[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>
<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>
</template>
<script>
export default {
name: 'diy-pintuan',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1,
scrollWidth: 0,
headData: []
};
},
created() {
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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: {
rpxUpPx(res) {
const screenWidth = uni.getSystemInfoSync().safeArea.width || uni.getSystemInfoSync().screenWidth;
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;
}
}
}
});
},
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;
}
}
&.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>

View File

@ -0,0 +1,377 @@
<template>
<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)"></image>
</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)"></image>
</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[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>
</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>
</template>
<script>
export default {
name: 'diy-presale',
props: {
value: {
type: Object
}
},
data() {
return {
list: [],
page: 1
};
},
created() {
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().safeArea.width || uni.getSystemInfoSync().screenWidth;
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: {
rpxUpPx(res){
const screenWidth = uni.getSystemInfoSync().safeArea.width || uni.getSystemInfoSync().screenWidth;
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;
}
}
}
});
},
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,50 @@
<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: ''
};
},
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;
}
},
created() {
this.html = htmlParser(this.value.html);
},
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,542 @@
<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="{ padding: value.imageGap + 'rpx', height: list[0].imgHeight * 2 + 'rpx' }"
>
<image :src="$util.img(value.list[0].imageUrl)" mode="aspectFill" :style="list[0].pageItemStyle"></image>
</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="{ padding: value.imageGap + 'rpx', height: item.imgHeight }"
>
<image :src="$util.img(item.imageUrl)" mode="aspectFill" :style="item.pageItemStyle"></image>
</view>
</template>
</template>
</view>
</template>
<!-- 1左3右 -->
<template v-else-if="value.mode == 'row1-lt-of1-tp-of2-bm'">
<view class="template-left" :style="{ paddingRight: value.imageGap + 'rpx' }">
<view :class="['item', value.mode]" :style="{ height: list[0].imgHeight + 'rpx' }" @click="$util.diyRedirectTo(value.list[0].link)">
<image :src="$util.img(value.list[0].imageUrl)" mode="aspectFill" :style="list[0].pageItemStyle"></image>
</view>
</view>
<view class="template-right" :style="{ paddingLeft: value.imageGap + 'rpx' }">
<view
:class="['item', value.mode]"
:style="{ height: list[1].imgHeight + 'rpx', paddingBottom: value.imageGap + 'rpx' }"
@click="$util.diyRedirectTo(value.list[1].link)"
>
<image :src="$util.img(value.list[1].imageUrl)" mode="aspectFill" :style="list[1].pageItemStyle"></image>
</view>
<view class="template-bottom" :style="{ paddingTop: value.imageGap + 'rpx' }">
<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="{
height: item.imgHeight + 'rpx',
paddingLeft: index == 3 ? value.imageGap * 2 + 'rpx' : 0
}"
>
<image :src="$util.img(item.imageUrl)" mode="aspectFill" :style="item.pageItemStyle"></image>
</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="{ padding: value.imageGap + 'rpx', height: item.imgHeight * 2 + 'rpx' }"
>
<image :src="$util.img(item.imageUrl)" mode="aspectFill" :style="item.pageItemStyle"></image>
</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: '50%'
},
'row1-of3': {
ratio: 3,
width: '33.33%'
},
'row1-of4': {
ratio: 4,
width: '25%'
}
};
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();
}
}
},
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) {
let maxHeight = 0;
this.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = 375 / params.ratio;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
}
});
//
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight;
});
this.list.forEach((item, index) => {
item.imgWidth = params.width;
item.imgHeight = maxHeight;
});
},
/**
* 魔方四方型各占50%
*/
calcFourSquare() {
let maxHeightFirst = 0;
let maxHeightTwo = 0;
this.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = 375 / 2;
item.imgWidth -= this.value.margin.both * 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 = '50%';
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';
this.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth; //
item.imgWidth = 375 / 2;// 使pxrpx
item.imgWidth -= (this.value.margin.both * 2 + this.value.imageGap / 2);
item.imgHeight = item.imgWidth * ratio;
rightHeight = item.imgHeight / 2 - this.value.imageGap / 2;
} else {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = rightHeight * 2;
item.imgHeight += 'rpx';
// if (divide == 'left') {
// item.imgWidth = this.list[0].imgWidth;
// item.imgHeight = rightHeight / 100;
// } else {
// var ratio = item.imgHeight / item.imgWidth; //
// if (isNaN(ratio)) ratio = 0;
// item.imgWidth = res.windowWidth * ratio;
// item.imgHeight = item.imgHeight * ratio;
// if (this.list[1].imgHeight) this.list[0].imgHeight = this.list[1].imgHeight * 2;
// }
}
}
});
});
// this.list.forEach((item, index) => {
// if (index == 0) item.imgHeight += this.value.imageGap;
// item.imgHeight += 'px';
// });
},
/**
* 魔方1上2下
*/
calcRowOneTopOfTwoBottom() {
var maxHeight = 0;
this.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
var ratio = item.imgHeight / item.imgWidth; //
if (index == 0) item.imgWidth = 375;
else if (index > 0) item.imgWidth = 375 / 2;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
//
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight)) maxHeight = item.imgHeight;
}
});
});
this.list.forEach((item, index) => {
if (index == 0) item.imgWidth -= this.value.margin.both * 2; //
if (index > 0) item.imgWidth -= this.value.margin.both * 2; //
item.imgWidth += item.imgWidth * 2 + 'rpx';
if (index > 0) item.imgHeight = maxHeight;
// else item.imgHeight += 'px';
});
},
/**
* 魔方1左3右
*/
calcRowOneLeftOfOneTopOfTwoBottom() {
this.list.forEach((item, index) => {
uni.getSystemInfo({
success: res => {
//
if (index == 0) {
var ratio = item.imgHeight / item.imgWidth;
item.imgWidth = 375 / 2;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = item.imgWidth * ratio;
} else if (index == 1) {
item.imgWidth = 375 / 2;
item.imgWidth -= this.value.margin.both * 2;
item.imgHeight = this.list[0].imgHeight / 2;
} else if (index > 1) {
item.imgWidth = this.list[0].imgWidth / 2;
item.imgHeight = this.list[0].imgHeight / 2;
}
}
});
});
this.list.forEach((item, index) => {
if (index == 0) {
item.imgHeight += this.value.imageGap;
item.imgHeight *= 2;
} else {
item.imgHeight *= 2;
}
if (index > 1) {
item.imgWidth -= this.value.imageGap / 2;
item.imgWidth -= this.value.margin.both; //
item.imgWidth = item.imgWidth * 2 + 'rpx';
} else {
item.imgWidth -= this.value.margin.both * 2; //
item.imgWidth = item.imgWidth * 2 + 'rpx';
}
});
},
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 {
width: 50% !important;
box-sizing: border-box;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(1) {
padding-left: 0 !important;
}
.rubik-cube .item.row1-of2:nth-child(2) {
padding-right: 0 !important;
}
//
.rubik-cube .item.row1-of3 {
width: 33.33%;
box-sizing: border-box;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(1) {
padding-left: 0 !important;
}
.rubik-cube .item.row1-of3:nth-child(3) {
padding-right: 0 !important;
}
//
.rubik-cube .item.row1-of4 {
width: 25%;
box-sizing: border-box;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(1) {
padding-left: 0 !important;
}
.rubik-cube .item.row1-of4:nth-child(4) {
padding-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) {
padding-left: 0 !important;
padding-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) {
padding-right: 0 !important;
padding-top: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) {
padding-left: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) {
padding-right: 0 !important;
padding-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) {
width: auto;
padding-top: 0 !important;
padding-left: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(1) {
padding-top: 0 !important;
padding-right: 0 !important;
}
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) {
padding-right: 0 !important;
padding-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;
padding-top: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) {
width: 50%;
box-sizing: border-box;
padding-left: 0 !important;
padding-bottom: 0 !important;
}
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) {
width: 50%;
box-sizing: border-box;
padding-right: 0 !important;
padding-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.row1-lt-of1-tp-of2-bm {
width: 50%;
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<view class="search-box" :style="searchWrapCss">
<view class="img" v-if="value.searchStyle == 2 && value.iconType == 'img'"><image :src="$util.img(value.imageUrl)" mode="aspectFill"></image></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"
@click="search()"
:placeholderStyle="placeholderStyle"
/>
<text class="iconfont icon-sousuo3" @click="search()" :style="{ color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"></text>
</view>
</view>
</template>
<script>
//
export default {
name: 'diy-search',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
searchText: ''
};
},
computed: {
searchWrapCss() {
var obj = '';
obj += 'background-color:' + this.value.componentBgColor + ';';
if (this.value.borderType == 2) {
obj += 'border-radius:' + '100rpx;';
}
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;
}
},
created() {},
methods: {
search() {
this.$util.redirectTo('/pages_tool/goods/search');
}
}
};
</script>
<style lang="scss">
/deep/ .uni-input-placeholder {
overflow: initial;
}
.search-box {
position: relative;
display: flex;
align-items: center;
.img {
width: 170rpx;
height: 60rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
}
}
.icon {
width: 170rpx;
height: 60rpx;
margin-right: 20rpx;
}
}
.search-content {
flex: 1;
}
.search-content input {
box-sizing: border-box;
display: block;
height: 77rpx;
width: 100%;
padding: 0 20rpx 0 40rpx;
color: #333333;
}
.search-content .iconfont {
position: absolute;
top: 50%;
right: 4rpx;
transform: translateY(-50%);
font-size: 36rpx;
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,223 @@
<template>
<view class="store-info" v-if="store">
<view class="store-info-top">
<view class="info-image">
<image :src="$util.img(store.store_image)" v-if="store.store_image" mode="aspectFit"></image>
<image :src="$util.getDefaultImage().store" v-else></image>
</view>
<view class="info-desc">
<view class="store-name">{{ store.store_name }}</view>
<view class="item" v-if="store.open_date">
<text>营业时间</text>
<text>{{ store.open_date }}</text>
</view>
<view class="item" v-if="store.telphone">
<text>联系电话</text>
<text @click="phoneCall">{{ store.telphone }}</text>
</view>
</view>
<!-- <text class="store-whole-country" @click="clearStore">
全国
<text class="iconfont icon-right"></text>
</text> -->
</view>
<view class="store-map">
<text>门店地图</text>
<map class="map" :latitude="store.latitude" :longitude="store.longitude" :markers="covers"></map>
</view>
<view class="store-info-bottom" @click="mapRoute()" v-if="store.full_address || store.address">
<view class="address-wrap">
<view>{{ store.full_address + store.address }}</view>
<text>距您当前位置{{ store.distance }}km</text>
</view>
<image :src="$util.img('addon/store/component/view/store_info/img/position.png')" />
</view>
</view>
</template>
<script>
import Map from '@/common/js/map/openMap.js';
export default {
props: {
value: {
type: Object
},
storeId: {
type: [String, Number],
default: 0
}
},
data() {
return {
store: null,
covers: []
};
},
computed: {
//
location() {
return this.$store.state.location;
}
},
mounted() {
if (this.location) {
this.latitude = this.location.latitude;
this.longitude = this.location.longitude;
} else {
this.$util.getLocation();
}
this.getInfo();
},
watch: {
location: function(nVal) {
if (nVal) {
this.latitude = nVal.latitude;
this.longitude = nVal.longitude;
this.getInfo();
}
}
},
methods: {
//
phoneCall() {
uni.makePhoneCall({
phoneNumber: this.store.telphone //
});
},
//
getInfo() {
let data = {
store_id: this.storeId
};
if (this.latitude && this.longitude) {
data.latitude = this.latitude;
data.longitude = this.longitude;
}
this.$api.sendRequest({
url: '/api/store/info',
data: data,
success: res => {
if (res.data) {
this.store = res.data;
this.latitude = res.data.latitude;
this.longitude = res.data.longitude;
uni.setStorageSync('store', this.store);
}
}
});
},
mapRoute() {
Map.openMap(Number(this.latitude), Number(this.longitude), this.store_name, 'gcj02');
},
clearStore() {
let _this = getCurrentPages()[getCurrentPages().length - 1];
uni.setStorageSync('store', '');
_this.refresh();
this.$util.redirectTo('/pages/index/index');
}
}
};
</script>
<style lang="scss">
.store-info {
width: 100%;
box-sizing: border-box;
.store-info-top {
display: flex;
align-items: flex-start;
border-radius: 18rpx;
background: #ffffff;
padding: 20rpx 28rpx;
margin-bottom: 32rpx;
.info-image {
width: 128rpx;
height: 128rpx;
float: left;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
border-radius: 5px;
}
}
.info-desc {
flex-grow: 1;
display: flex;
flex-direction: column;
padding-left: 20rpx;
box-sizing: border-box;
width: 70%;
.store-name {
align-items: center;
font-size: $font-size-toolbar;
color: #2b2b2e;
margin-bottom: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.item {
font-size: $font-size-tag;
margin-bottom: 20rpx;
text:first-child {
color: #333333;
}
text:last-child {
color: #666666;
}
}
}
.store-whole-country {
text {
font-size: 26rpx;
color: #666666;
}
}
}
.store-map {
margin: 16px 0;
text {
color: #2b2b2e;
margin-bottom: 10px;
font-weight: bold;
display: block;
font-size: $font-size-toolbar;
}
.map {
width: 100%;
height: 450rpx;
}
}
.store-info-bottom {
background: #ffffff;
padding: 20rpx 28rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
.address-wrap {
flex: 1;
view {
margin-bottom: 8rpx;
font-weight: bold;
}
text {
color: #666666;
font-size: $font-size-tag;
}
}
image {
width: 80rpx;
height: 80rpx;
}
}
}
</style>

View File

@ -0,0 +1,381 @@
<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="store.store_id">
<text class="title">{{ store.store_name }}</text>
<text>
<text class="change margin-left">切换</text>
<text class="iconfont icon-right"></text>
</text>
</block>
<text class="title" v-else>{{load && !storeList.length ? '没有附近门店' : '定位中...'}}</text>
</view>
<view class="address-box" :style="{ color: value.textColor }">
<view class="address" :style="{ color: value.textColor }">
<text class="iconfont icon-dizhi"></text>
<text v-if="store.store_id" @click="mapRoute">{{ store.full_address }}{{ store.address }}</text>
<text v-else>获取当前位置...</text>
</view>
</view>
</view>
<view class="store-image" @click="selectStore()">
<image :src="$util.img(store.store_image)" v-if="store.store_image && store.store_id" 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(store.store_image)" v-if="store.store_image && store.store_id" 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="store.store_id">
<text class="title">{{ store.store_name }}</text>
<text>
<text class="change margin-left">切换</text>
<text class="iconfont icon-right"></text>
</text>
</block>
<text class="title" v-else>{{load && !storeList.length ? '没有附近门店' : '定位中...'}}</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="store.store_id">
<text class="iconfont icon-weizhi" :style="{ color: value.textColor }"></text>
<text class="title" :style="{ color: value.textColor }">{{ store.store_name }}</text>
<text class="iconfont icon-unfold" :style="{ color: value.textColor }"></text>
</block>
<text class="title" v-else :style="{ color: value.textColor }">{{load && !storeList.length ? '没有附近门店' : '定位中...'}}</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: 'ns-store',
props: {
storeId: {
type: [String, Number],
default() {
return 0;
}
},
value: {
type: Object
}
},
data() {
return {
type: 'country',
store: {
store_name: ''
},
showStore: 1,
size: 2,
num: 1,
storeList: [],
latitude: null, //
longitude: null ,//
load: false
};
},
computed: {
//
location() {
return this.$store.state.location;
}
},
watch: {
storeId() {
if (uni.getStorageSync('store')) {
this.store = uni.getStorageSync('store');
} else {
this.getStoreList();
}
},
location: function(nVal) {
if (nVal) {
this.latitude = nVal.latitude;
this.longitude = nVal.longitude;
this.getStoreList();
}
}
},
created() {
if (this.location && this.store.store_id) {
this.latitude = this.location.latitude;
this.longitude = this.location.longitude;
} else {
this.$util.getLocation({
fail:()=>{
this.getStoreList();
}
});
}
},
methods: {
//
toStoreList() {
this.$util.redirectTo('/pages_tool/index/storelist', {});
},
selectStore() {
if (this.store.store_id) {
this.$util.diyRedirectTo({
store_id: this.store.store_id,
wap_url: '/pages_tool/index/diy?name=' + 'DIY_STORE'
});
}
},
search() {
this.$util.redirectTo('/pages_tool/goods/search');
},
//
getStoreList() {
this.load = false;
let data = {};
if (this.latitude && this.longitude) {
data.latitude = this.latitude;
data.longitude = this.longitude;
}
this.$api.sendRequest({
url: '/api/store/page',
data: data,
success: res => {
let newArr = [];
let msg = res.message;
if (res.code == 0 && res.data) {
newArr = res.data.list;
} else {
this.$util.showToast({
title: msg
});
}
this.storeList = newArr;
for (let i = 0; i < this.storeList.length; i++) {
if (this.storeList[i].store_id == res.data.store_id) {
this.store = this.storeList[i];
uni.setStorageSync('store', this.storeList[i]);
this.$forceUpdate();
}
}
this.load = true;
}
});
},
mapRoute() {
if (!isNaN(Number(this.store.latitude)) && !isNaN(Number(this.store.longitude))) {
Map.openMap(Number(this.store.latitude), Number(this.store.longitude), this.store.store_name, 'gcj02');
}
}
}
};
</script>
<style lang="scss">
.store-wrap {
padding: 20rpx 0;
box-sizing: border-box;
.store-box {
// padding: 20rpx 24rpx;
box-sizing: border-box;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.noStoreBox {
width: 100%;
height: 100%;
line-height: 120rpx;
color: #ffffff;
}
.store-info {
height: 100rpx;
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-around;
margin-right: 20rpx;
.info-box {
// color: #ffffff;
display: flex;
align-items: flex-end;
margin-bottom: 6rpx;
text {
line-height: 1.2;
}
.title {
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: $font-size-toolbar;
margin-right: 40rpx;
}
.change {
font-size: $font-size-goods-tag;
}
.iconfont {
font-size: $font-size-goods-tag;
}
}
.address-box {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #ffffff;
}
.address {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// display: flex;
width: 100%;
// align-items: center;
// color: #ffffff;
line-height: 1.2;
font-size: $font-size-goods-tag;
.iconfont {
font-size: $font-size-goods-tag;
margin-right: 6rpx;
}
.store-address-info {
width: calc(100% - 30rpx);
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,100 @@
<template>
<view :style="{ fontSize: value.fontSize * 2 + 'rpx' }">
<view
v-if="value.arrangement == 'vertical'"
class="diy-text-nav"
:style="{ fontSize: value.fontSize * 2 + 'rpx', background: value.backgroundColor, textAlign: value.textAlign }"
@click="redirectTo(value.list[0].link)"
>
<view class="single" :style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }">
{{ value.list[0].text }}
<text class="subhead">{{ value.list[0].secondText }}</text>
<!-- <text :style="{ fontSize: value.fontSize * 2 + 'rpx'}" class="iconfont icon-right font-size-goods-tag"></text> -->
</view>
</view>
<view v-else class="diy-text-nav" :style="{ background: value.backgroundColor }">
<scroll-view scroll-x>
<view
v-for="(item, index) in value.list"
:key="index"
class="item"
@click="redirectTo(item.link)"
:style="{ textAlign: value.textAlign, fontSize: value.fontSize * 2 + 'rpx', color: value.textColor ? value.textColor : 'rgba(0,0,0,0)' }"
>
{{ item.text }}
</view>
</scroll-view>
</view>
</view>
</template>
<script>
//
export default {
name: 'diy-text-nav',
props: {
value: {
type: Object
}
},
data() {
return {};
},
methods: {
redirectTo(link) {
this.$util.diyRedirectTo(link);
}
}
};
</script>
<style>
.control-text-navigation i {
float: right;
margin: 6rpx 20rpx 0 0;
}
.diy-text-nav {
padding: 20rpx;
}
.diy-text-nav scroll-view {
width: 100%;
flex-direction: row;
white-space: nowrap;
}
.diy-text-nav scroll-view .item {
display: inline-block;
margin-left: 16rpx;
position: relative;
min-width: 25%;
text-overflow: ellipsis;
text-align: center;
}
.diy-text-nav .single {
position: relative;
}
.diy-text-nav .single .subhead {
font-size: 24rpx;
color: #777;
margin-left: 20rpx;
}
.diy-text-nav .iconfont {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0;
}
</style>
<style scoped>
.uni-view {
font-size: 0;
}
.diy-text-nav >>> .uni-scroll-view::-webkit-scrollbar {
display: none;
}
</style>

View File

@ -0,0 +1,980 @@
<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-1'" style="height: 40rpx; line-height: 40rpx;">
<view class="text-left" :style="{ color: value.textColor}"><text></text></view>
<text>{{ 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' }">{{ 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' }">{{ 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' }">{{ 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' }">{{ 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' }">{{ 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' }">{{ 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' }">{{ 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')"></image></view>
<text
class="style9-title"
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }"
>
{{ value.text }}
</text>
<view class="right-img"><image :src="$util.img('public/uniapp/diy/style9-2.png')"></image></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() {},
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;
padding-bottom: 6rpx;
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;
}
.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;
}
}
}
.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,38 @@
<template>
<video :src="$util.img(value.videoUrl)" :poster="$util.img(value.imageUrl)" :style="videoWarpCss"></video>
</template>
<script>
//
export default {
name: 'diy-video',
props: {
value: {
type: Object
}
},
data() {
return {};
},
created() {},
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%;
}
</style>

View File

@ -0,0 +1,545 @@
// 商品详情业务
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,
token: "",
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,
memberId: 0,
service: null,
shareUrl: '', // 分享链接
source_member: 0, //分享人的id
isCommunity: false, //社群弹窗
poster: "-1", //海报
posterMsg: "", //海报错误信息
posterHeight: 0,
posterParams: {}, //海报所需参数
detailTab: 0,
goodsRoute: '',
posterApi: '',
goodsAttrShow: false, // 商品属性是否展开
storeList: {
data: [],
page: 1,
page_size: 10
}, //门店列表
latitude: null, // 纬度
longitude: null, // 经度
store: {},
storeId: 0,
evaluateCount: 0, // 商品评论数量
deliveryType: null, // 配送方式
isVirtual: 0 //是否为虚拟商品
}
},
created() {
this.isIphoneX = this.$util.uniappIsIPhoneX();
this.token = uni.getStorageSync('token');
// 门店信息
this.store = uni.getStorageSync('store') ? uni.getStorageSync('store') : {};
if (this.store) this.storeId = this.store.store_id;
if (this.location) {
this.latitude = this.location.latitude;
this.longitude = this.location.longitude;
} else {
this.$util.getLocation();
}
this.getStoreData();
},
computed: {
location() {
return this.$store.state.location;
}
},
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.memberId = params.memberId;
this.goodsRoute = params.goodsRoute;
this.posterApi = params.posterApi;
this.isVirtual = params.isVirtual;
this.getService();
// 评价设置
this.getEvaluateConfig();
this.videoContext = uni.createVideoContext('goodsVideo');
// #ifdef MP-WEIXIN
this.goodsSyncToGoodsCircle();
// #endif
this.getWhetherCollection();
if (this.isVirtual == 0) this.getEnabledExpressType();
},
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.diyRedirectTo({
store_id: item.store_id,
wap_url: '/pages_tool/index/diy?name=' + 'DIY_STORE'
});
this.closeStoreListPopup();
},
//-------------------------------------属性-------------------------------------
switchGoodsAttr() {
this.goodsAttrShow = !this.goodsAttrShow;
},
//获取用户是否关注
async getWhetherCollection() {
this.$api.sendRequest({
url: "/api/goodscollect/iscollect",
data: {
goods_id: this.goodsId
},
success: res => {
this.whetherCollection = res.data;
}
});
},
//-------------------------------------评价-------------------------------------
//商品评论列表
getGoodsEvaluate() {
this.$api.sendRequest({
url: "/api/goodsevaluate/firstinfo",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
let data = res.data;
if (data) {
this.goodsEvaluate = data;
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), '***')
}
}
});
},
//商品评论列表
getGoodsEvaluateCount() {
this.$api.sendRequest({
url: "/api/goodsevaluate/count",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
if (res.code >= 0) {
this.evaluateCount = res.data;
}
}
});
},
// 预览评价图片
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
});
},
//-------------------------------------关注-------------------------------------
editCollection() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
if (this.token) {
//未关注添加关注
if (this.whetherCollection == 0) {
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
},
success: res => {
var data = res.data;
if (data > 0) {
this.whetherCollection = 1;
}
}
});
} else {
//已关注取消关注
this.$api.sendRequest({
url: "/api/goodscollect/delete",
data: {
goods_id: this.goodsSkuDetail.goods_id
},
success: res => {
var data = res.data;
if (data > 0) {
this.whetherCollection = 0;
}
}
});
}
} else {
if (this.source_member) {
this.$refs.login.open(this.shareUrl + '&source_member=' + this.source_member);
} else {
this.$refs.login.open(this.shareUrl);
}
}
},
collection() {
if (this.preview) return; // 开启预览,禁止任何操作和跳转
this.editCollection();
if (this.token) {
return this.whetherCollection ? 0 : 1;
}
},
//-------------------------------------分享-------------------------------------
// 打开分享弹出层
openSharePopup() {
this.$refs.sharePopup.open();
},
// 关闭分享弹出层
closeSharePopup() {
this.$refs.sharePopup.close();
},
copyUrl() {
let text = this.$config.h5Domain + this.shareUrl;
if (this.memberId) text += '&source_member=' + this.memberId;
this.$util.copy(text, () => {
this.closeSharePopup();
});
},
//-------------------------------------海报-------------------------------------
// 打开海报弹出层
openPosterPopup() {
this.getGoodsPoster();
this.$refs.sharePopup.close();
this.$refs.posterPopup.open();
if (this.poster != '-1') {
setTimeout(() => {
let view = uni.createSelectorQuery().in(this).select(".poster-layer .image-wrap");
view.fields({
size: true,
}, data => {
let posterWhith = data.width;
let ratio = parseFloat((740 / posterWhith).toFixed(2));
if (this.token != '') {
this.posterHeight = parseInt(1240 / ratio);
} else {
this.posterHeight = parseInt(1100 / ratio);
}
}).exec();
}, 100);
}
},
// 关闭海报弹出层
closePosterPopup() {
this.$refs.posterPopup.close();
},
//生成海报
getGoodsPoster() {
uni.showLoading({
'title': '海报生成中...'
})
//活动海报信息
if (this.memberId) this.posterParams.source_member = this.memberId;
this.$api.sendRequest({
url: this.posterApi,
data: {
page: this.goodsRoute,
qrcode_param: JSON.stringify(this.posterParams)
},
success: res => {
if (res.code == 0) {
this.poster = res.data.path + "?time=" + new Date().getTime();
} else {
this.posterMsg = 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.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
this.$util.showToast({
title: "保存成功"
});
},
fail: () => {
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
getEvaluateConfig() {
this.$api.sendRequest({
url: '/api/goodsevaluate/config',
success: res => {
if (res.code == 0) {
var data = res.data;
this.evaluateConfig = data;
if (this.evaluateConfig.evaluate_show == 1) {
//商品评论
this.getGoodsEvaluate();
this.getGoodsEvaluateCount();
}
}
}
});
},
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
},
/**
* 查询启用的配送方式
*/
getEnabledExpressType() {
this.$api.sendRequest({
url: "/api/config/enabledexpresstype",
success: res => {
if (res.code == 0 && res.data) this.deliveryType = res.data;
}
});
}
}
}

View File

@ -0,0 +1,436 @@
<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"></pengpai-fadein-out>
<!-- 商品媒体信息 -->
<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">
<!-- 入口区域 -->
<slot name="entrance"></slot>
<!-- 配送 -->
<view class="item delivery-type" v-if="goodsSkuDetail.is_virtual == 0" @click="$refs.deliveryType.open()">
<view class="label">配送</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">{{ item.name }}</text>
</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 && Object.keys(store).length" >
<view class="label">门店</view>
<view class="list-wrap">
<view class="name-wrap">
<text class="icondiy icon-system-shop"></text>
<text class="name">{{store.store_name}}</text>
</view>
<view class="other-wrap">
<text class="distance" v-if="parseFloat(store.distance)">距离{{ store.distance > 1 ? store.distance + 'km' : store.distance * 1000 + 'm' }}</text>
<text class="decorate" v-if="parseFloat(store.distance)">.</text>
<view class="address">{{ store.full_address + store.address }}</view>
</view>
</view>
<text class="iconfont icon-right"></text>
<!-- <view class="img-wrap"><image :src="$util.img('public/uniapp/goods/detail_more.png')" mode="aspectFit" /></view> -->
</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 class="img-wrap"><image :src="$util.img('public/uniapp/goods/detail_more.png')" mode="aspectFit" /></view> -->
</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>
<image :src="$util.getDefaultImage().store" v-else></image>
</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"
></image>
</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">
<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-line"></view> -->
<view class="tab-item">商品详情</view>
<!-- <view class="tab-line"></view> -->
<!-- <view v-if="service.is_display == 1" class="tab-item" :class="detailTab == 1 ? 'active color-base-text' : ''" @click="detailTab = 1">售后保障</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>
</view>
<view class="goods-details active" v-else>该商家暂无上传相关详情哦</view>
</view>
<!-- <block v-if="service">
<view class="detail-content-item" v-show="detailTab == 1 && service.is_display == 1">
<view class="goods-details" v-if="service.content">
<rich-text :nodes="service.content" @click="showImg($event)" :data-nodes="service.content"></rich-text>
</view>
<view class="goods-details active" v-else>该商品暂无相关售后哦</view>
</view>
</block> -->
</view>
</view>
<!-- 商品推荐 -->
<ns-goods-recommend ref="goodrecommend" route="goods_detail"></ns-goods-recommend>
<ns-copyright></ns-copyright>
<!-- 海报 -->
<view @touchmove.prevent.stop>
<uni-popup ref="posterPopup" type="bottom" class="poster-layer">
<template v-if="poster != '-1'">
<view>
<view class="image-wrap"><image :src="$util.img(poster)" :show-menu-by-longpress="true" /></view>
<!-- #ifdef MP || APP-PLUS -->
<view class="save" @click="saveGoodsPoster()">保存图片</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="save">长按保存图片</view>
<!-- #endif -->
</view>
<view class="close iconfont icon-close" @click="closePosterPopup()"></view>
</template>
<view v-else class="msg">{{ posterMsg }}</view>
</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 xiaoStarComponent from '@/components/xiao-star-component/xiao-star-component.vue';
//
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import nsGoodsRecommend from '@/components/ns-goods-recommend/ns-goods-recommend.vue';
import scroll from '@/common/js/scroll-view.js';
import toTop from '@/components/toTop/toTop.vue';
import goodsDetailBase from '@/common/js/goods_detail_base.js';
import pengpaiFadeinOut from '@/components/pengpai-fadein-out/pengpai-fadein-out.vue';
import detail from './detail.js';
export default {
name: 'goods-detail-view',
props: {
goodsSkuDetail: {
type: Object,
default: () => {
return {};
}
}
},
components: {
uniPopup,
nsGoodsRecommend,
pengpaiFadeinOut,
xiaoStarComponent,
toTop
},
mixins: [scroll, detail]
};
</script>
<style lang="scss">
@import '@/common/css/goods_detail.scss';
</style>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
<template>
<text>{{ temp }}</text>
</template>
<script>
import _time_ from './time.js';
export default {
name: 'l-time',
props: {
//
text: {
type: [String, Number, Date],
default: ''
},
//false
maxDate: {
type: Boolean,
default: false
}
},
data() {
return {
textVal: this.text
};
},
watch: {
text() {
this.textVal = this.text;
}
},
computed: {
temp() {
return this.getText();
}
},
methods: {
getText() {
let self = this;
let timeVal = _time_.getFormatTime(self.textVal, self.maxDate);
if (timeVal && (timeVal.endsWith('刚刚') || timeVal.endsWith('分钟前'))) {
setTimeout(() => {
let temp = self.textVal;
self.textVal = '';
self.textVal = temp;
}, 60000);
}
return this.textVal ? timeVal : '';
},
onClick() {
this.$emit('on-tap', this.textVal);
}
}
};
</script>
<style></style>

161
components/l-time/time.js Normal file
View File

@ -0,0 +1,161 @@
Function.prototype.asyAfter = function(afterfn) {
var _self = this;
return function() {
var ret = _self.apply(this, arguments);
if (ret === 'next') {
return afterfn.apply(this, arguments);
}
return ret;
}
}
Date.prototype.pattern = function(fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours() % 12 == 0 ? 12 : this.getHours() % 12, //小时
"H+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
var week = {
"0": "\u65e5",
"1": "\u4e00",
"2": "\u4e8c",
"3": "\u4e09",
"4": "\u56db",
"5": "\u4e94",
"6": "\u516d"
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
}
if (/(E+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "\u661f\u671f" : "\u5468") :
"") +
week[this.getDay() + ""]);
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k])
.length)));
}
}
return fmt;
}
const isType = type => (/^\[object\s(.*)\]$/.exec(Object.prototype.toString.call(type)))[1];
let Time = function() {};
let timeProto = Time.prototype;
//获取当前时间戳
timeProto.getUnix = function() {
return new Date().getTime();
}
//获取当天0点0分0秒时间戳
timeProto.getTodayUnix = function() {
let date = new Date();
let myDate = `${date.getFullYear()}/${(date.getMonth() + 1)}/${date.getDate()} 00:00:00`;
return new Date(myDate).getTime();
}
//获取今年1月1日0点0分0秒时间戳
timeProto.getYearUnix = function() {
let date = new Date();
date.setMonth(0);
date.setDate(1);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date.getTime();
}
//获取当前时间标准年月日
timeProto.getLastDate = function(constTime) {
if (!constTime) {
return;
}
let date = new Date(constTime);
if (date.pattern) {
return date.pattern("yyyy-MM-dd");
}
let month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
let day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
return date.getFullYear() + '-' + month + '-' + day;
}
const resDateStr = function(timer, constTime) {
let _just = function(timer) {
if (timer <= 0 || Math.floor(timer / 60) <= 0) {
return "刚刚"
} else return 'next';
}
let _mm = function(timer) {
if (timer < 3600) {
return Math.floor(timer / 60) + "分钟前"
} else return 'next';
}
let _hh = function(timer, constTime) {
let today = _time_.getTodayUnix();
if (timer >= 3600 && (constTime - today >= 0)) {
//可切换显示模式
// return "今天 " + new Date(constTime).pattern("HH:mm");
return Math.floor(timer / 60 / 60) + "小时前";
} else {
return 'next'
};
}
let _dd = function(timer, constTime) {
let today = _time_.getTodayUnix();
timer = (today - constTime) / 1000;
if (timer / 86400 <= 31) {
return Math.ceil(timer / 86400) + "天前"
} else return 'next';
}
let _dlast = function(timer, constTime) {
return _time_.getLastDate(constTime);
}
let dateFilter = _just.asyAfter(_mm).asyAfter(_hh).asyAfter(_dd).asyAfter(_dlast);
return dateFilter(timer, constTime);
}
//转换时间
const reg = new RegExp("-", "g");
timeProto.getFormatTime = function(constTime, max) {
if (!constTime) {
return "";
}
switch (isType(constTime)) {
case 'Date':
constTime = constTime.getTime();
break;
case 'String':
constTime = constTime.replace(reg, "/");
default:
constTime = new Date(constTime).getTime();
break;
}
let now = this.getUnix();
let year = this.getYearUnix();
let timer = (now - constTime) / 1000;
if (constTime > now && max) {
return this.getLastDate(constTime);
}
let _t = this;
return resDateStr(timer, constTime);
}
const _time_ = new Time();
export default _time_;

View File

@ -0,0 +1,113 @@
<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: {
// ,propdefault
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: {
// ,propdefault
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 // 0loading1loading2
},
computed: {
// ,propdefault
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, // 0loading1loading2
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,fixedmescroll
},
//
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%"windowHeight10%
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); //
}
},
// 使createdmescroll; mountedcssH5
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
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
uni.pageScrollTo({
scrollTop: y,
duration: t
});
});
// up.toTop.safearea,vuesafearea
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,416 @@
<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
}"
: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 {
components: {
MescrollEmpty,
MescrollTop
},
data() {
return {
mescroll: { optDown: {}, optUp: {} }, // mescroll
viewId:
'id_' +
Math.random()
.toString(36)
.substr(2), // mescrollid(,)
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 4, // inOffset1 outOffset2 showLoading3 endDownScroll4
upLoadType: 0, // 0loading1loading2
isShowEmpty: false, //
isShowToTop: false, //
scrollTop: 0, //
scrollAnim: false, //
windowTop: 0, // 使
windowBottom: 0, // 使
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使)
fixed: {
// fixedmescroll, true
type: Boolean,
default() {
return true;
}
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
showTop: {
//
type: Boolean,
default() {
return true;
}
}
},
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() {
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0);
},
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,fixedmescroll
},
//
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%"windowHeight10%
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
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();
});
}
}
},
// 使createdmescroll; mountedcssH5
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
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
let curY = vm.mescroll.getScrollTop();
vm.scrollAnim = t !== 0; // t0,使
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,vuesafearea
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;

View File

@ -0,0 +1,48 @@
/**
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
*/
const MescrollMoreItemMixin = {
props: {
i: Number, // 每个tab页的专属下标
index: { // 当前tab的下标
type: Number,
default () {
return 0
}
}
},
data() {
return {
downOption: {
auto: false // 不自动加载
},
upOption: {
auto: false // 不自动加载
},
isInit: false // 当前tab是否已初始化
}
},
watch: {
// 监听下标的变化
index(val) {
if (this.i === val && !this.isInit) {
this.isInit = true; // 标记为true
this.mescroll && this.mescroll.triggerDownScroll();
}
}
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
// 自动加载当前tab的数据
if (this.i === this.index) {
this.isInit = true; // 标记为true
this.mescroll.triggerDownScroll();
}
},
}
}
export default MescrollMoreItemMixin;

View File

@ -0,0 +1,56 @@
/**
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
*/
const MescrollMoreMixin = {
data() {
return {
tabIndex: 0 // 当前tab下标
}
},
// 因为子组件无onPageScroll和onReachBottom的页面生命周期需在页面传递进到子组件
onPageScroll(e) {
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPageScroll(e);
},
onReachBottom() {
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onReachBottom();
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh() {
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPullDownRefresh();
},
methods: {
// 根据下标获取对应子组件的mescroll
getMescroll(i) {
if (!this.mescrollItems) this.mescrollItems = [];
if (!this.mescrollItems[i]) {
// v-for中的refs
let vForItem = this.$refs["mescrollItem"];
if (vForItem) {
this.mescrollItems[i] = vForItem[i]
} else {
// 普通的refs,不可重复
this.mescrollItems[i] = this.$refs["mescrollItem" + i];
}
}
let item = this.mescrollItems[i]
return item ? item.mescroll : null
},
// 切换tab,恢复滚动条位置
tabChange(i) {
let mescroll = this.getMescroll(i);
if (mescroll) {
// 延时(比$nextTick靠谱一些),确保元素已渲染
setTimeout(() => {
mescroll.scrollTo(mescroll.getScrollTop(), 0)
}, 30)
}
}
}
}
export default MescrollMoreMixin;

View File

@ -0,0 +1,82 @@
<template>
<!-- top="xxx"下拉布局往下偏移,防止被悬浮菜单遮住 -->
<mescroll v-if="isInit" :top="top" :down="downOption" @down="downCallback" :up="upOption" @up="upCallback" @emptyclick="emptyClick" @init="mescrollInit">
<!-- 数据列表 -->
<slot name="list"></slot>
</mescroll>
</template>
<script>
import Mescroll from './mescroll-uni.vue';
export default {
components: {
Mescroll
},
data() {
return {
mescroll: null, //mescroll
downOption: {
auto: false //
},
upOption: {
auto: false, //
page: {
num: 0, // ,0,1,callback(page)1
size: 10 //
},
noMoreSize: 2, //,;(),; 5
empty: {
tip: '~ 空空如也 ~', //
btnText: '去看看'
},
onScroll: true
},
scrollY: 0,
isInit: false
};
},
props: {
top: [String, Number],
size: [String, Number]
},
created() {
if (this.size) this.upOption.page.size = this.size;
this.isInit = true;
},
mounted() {
this.mescroll.resetUpScroll();
this.listenRefresh();
},
methods: {
// mescroll,mescroll
mescrollInit(mescroll) {
this.mescroll = mescroll;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( mescroll.num=1, upCallback )
this.mescroll.resetUpScroll();
this.listenRefresh();
},
/*上拉加载的回调: mescroll携带page的参数, 其中num:当前页 从1开始, size:每页数据条数,默认10 */
upCallback() {
//
this.$emit('getData', this.mescroll);
},
//
emptyClick() {
this.$emit('emptytap', this.mescroll);
},
//
refresh() {
this.mescroll.resetUpScroll();
this.listenRefresh();
},
listenRefresh() {
this.$emit('listenRefresh', true);
}
}
};
</script>

View File

@ -0,0 +1,234 @@
<template>
<view class="code-box">
<view class="flex-box">
<input :value="inputValue" type="number" :focus="autoFocus" :maxlength="maxlength" class="hide-input" @input="getVal" />
<block v-for="(item, index) in ranges" :key="index">
<view :class="['item', { active: codeIndex === item, middle: type === 'middle', bottom: type === 'bottom', box: type === 'box' }]">
<view class="line" v-if="type !== 'middle'"></view>
<view v-if="type === 'middle' && codeIndex <= item" class="bottom-line"></view>
<block v-if="isPwd && codeArr.length >= item"><text class="dot"></text></block>
<block v-else>
<text class="number">{{ codeArr[index] ? codeArr[index] : '' }}</text>
</block>
</view>
</block>
</view>
</view>
</template>
<script>
// 使 v-model
// 使refs
export default {
name: 'mypOneInput',
props: {
// 使v-model
// value
value: {
type: String,
default: ''
},
// 4/6
maxlength: {
type: Number,
default: 4
},
autoFocus: {
type: Boolean,
default: false
},
isPwd: {
type: Boolean,
default: false
},
// middle-middle line, bottom-bottom line, box-square box
type: {
type: String,
default: 'bottom'
}
},
watch: {
maxlength: {
immediate: true,
handler: function(newV) {
if (newV === 6) {
this.ranges = [1, 2, 3, 4, 5, 6];
} else {
this.ranges = [1, 2, 3, 4];
}
}
},
value: {
immediate: true,
handler: function(newV) {
if (newV !== this.inputValue) {
this.inputValue = newV;
this.toMakeAndCheck(newV);
}
}
}
},
data() {
return {
inputValue: '',
codeIndex: 1,
codeArr: [],
ranges: [1, 2, 3, 4]
};
},
methods: {
getVal(e) {
const val = e.detail.value;
this.inputValue = val;
this.$emit('input', val);
this.toMakeAndCheck(val);
},
toMakeAndCheck(val) {
const arr = val.split('');
this.codeIndex = arr.length + 1;
this.codeArr = arr;
if (this.codeIndex > Number(this.maxlength)) {
this.$emit('finish', this.codeArr.join(''));
}
},
// refs v-model
// 使refsvalue
//
set(val) {
this.inputValue = val;
this.toMakeAndCheck(val);
},
// 使refs
clear() {
this.inputValue = '';
this.codeArr = [];
this.codeIndex = 1;
}
}
};
</script>
<style scoped>
@keyframes twinkling {
0% {
opacity: 0.2;
}
50% {
opacity: 0.5;
}
100% {
opacity: 0.2;
}
}
.code-box {
text-align: center;
}
.flex-box {
display: flex;
justify-content: center;
flex-wrap: wrap;
position: relative;
}
.flex-box .hide-input {
position: absolute;
top: 0;
left: -100%;
width: 200%;
height: 100%;
text-align: left;
z-index: 9;
opacity: 1;
}
.flex-box .item {
position: relative;
flex: 1;
margin-right: 18rpx;
font-size: 70rpx;
font-weight: bold;
color: #333333;
line-height: 100rpx;
}
.flex-box .item::before {
content: '';
padding-top: 100%;
display: block;
}
.flex-box .item:last-child {
margin-right: 0;
}
.flex-box .middle {
border: none;
}
.flex-box .box {
box-sizing: border-box;
border: 2rpx solid #cccccc;
border-width: 2rpx 0 2rpx 2rpx;
margin-right: 0;
}
.flex-box .box:first-of-type {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
.flex-box .box:last-child {
border-right: 2rpx solid #cccccc;
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.flex-box .bottom {
box-sizing: border-box;
border-bottom: 2rpx solid #ddd;
}
.flex-box .active {
border-color: #ddd;
}
.flex-box .active .line {
display: block;
}
.flex-box .line {
display: none;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 2rpx;
height: 40rpx;
background: #333333;
animation: twinkling 1s infinite ease;
}
.flex-box .dot,
.flex-box .number {
line-height: 40rpx;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.flex-box .bottom-line {
height: 8rpx;
background: #000000;
width: 80%;
position: absolute;
border-radius: 4rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<view v-if="advList.length" class="adv-wrap">
<swiper :indicator-dots="advList.length > 1" indicator-active-color="#ffffff" :autoplay="true" :interval="3000" :duration="1000" v-if="advList.length > 1" class="ns-adv">
<swiper-item v-for="(item, index) in advList" :key="index" @click="jumppage(item.adv_url)">
<view class="image-box" :style="{ 'background-color': item.background }"><image :src="$util.img(item.adv_image)"></image></view>
</swiper-item>
</swiper>
<view v-else class="adv-wrap ns-adv">
<image :src="$util.img(advList[0]['adv_image'])" mode="widthFix"></image>
</view>
</view>
</template>
<script>
export default {
name: 'ns-advert',
props: {
keyword: {
type: String
}
},
data() {
return {
advList: []
};
},
created() {
this.getAdvList();
},
methods: {
//广
getAdvList() {
var item = {
adv_image: '',
adv_url: ''
};
this.$api.sendRequest({
url: '/api/adv/detail',
data: {
keyword: this.keyword
},
success: res => {
if (res.code == 0) {
var data = res.data.adv_list;
for (var index in data) {
if (data[index].adv_url) data[index].adv_url = JSON.parse(data[index].adv_url);
}
this.advList = res.data.adv_list;
}
}
});
},
jumppage(e) {
this.$util.diyRedirectTo(e);
}
}
};
</script>
<style>
.adv-wrap {
width: 100%;
}
.bg {
width: 100%;
height: 158rpx;
background-color: #fff;
position: fixed;
left: 0;
/* #ifdef H5 */
top: 88rpx;
/* #endif */
/* #ifdef MP-WEIXIN */
top: 0;
/* #endif */
}
.ns-adv {
background: #fff;
height: 300rpx;
border-radius: 10rpx;
/* padding: 8rpx 24rpx 24rpx; */
}
.ns-adv .image-box {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.ns-adv image {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
</style>

View File

@ -0,0 +1,363 @@
<template>
<view>
<view @touchmove.prevent.stop v-if="birthday" class="reward-popup">
<uni-popup ref="birthdayGift" type="center" :maskClick="false">
<view class="reward-wrap">
<view class="wrap" :style="{ backgroundImage: 'url(' + $util.img('public/uniapp/birthday_gift/birthday_gift_bg.png') + ')' }">
<view class="birthday-title-name">Dear {{ birthday.nickname }}</view>
<view class="birthday-title-desc" v-if="birthday.blessing_content">{{ birthday.blessing_content }}</view>
<view class="birthday-title-desc" v-else>感谢您一直以来的支持在您生日到来之际特为您送上最真诚的祝福</view>
<view class="birthday-title-hint">
<image :src="$util.img('public/uniapp/birthday_gift/birthday_gift_left.png')" mode="" class="birthday-img-all"></image>
<view class="font-size-toolbar">生日贺礼</view>
<image :src="$util.img('public/uniapp/birthday_gift/birthday_gift_right.png')" mode="" class="birthday-img-all"></image>
</view>
<scroll-view scroll-y="true" class="register-box">
<view class="reward-content">
<view class="content" v-if="birthday.point > 0">
<view class="info">
<text class="num">
{{ parseFloat(birthday.point) }}
<text class="type">积分</text>
</text>
<view class="desc">用于下单时抵现或兑换商品等</view>
</view>
<view class="tip" @click="closeRewardPopup('1')">立即查看</view>
</view>
<view class="content" v-if="birthday.balance_type == 0 && birthday.balance > 0">
<view class="info">
<text class="num">
{{ parseFloat(birthday.balance) }}
<text class="type">元红包</text>
</text>
<view class="desc">不可提现红包</view>
</view>
<view class="tip" @click="closeRewardPopup('2')">立即查看</view>
</view>
<view class="content" v-if="birthday.balance_type == 1 && birthday.balance_money > 0">
<view class="info">
<text class="num">
{{ parseFloat(birthday.balance_money) }}
<text class="type">元红包</text>
</text>
<view class="desc">可提现红包</view>
</view>
<view class="tip" @click="closeRewardPopup('2')">立即查看</view>
</view>
<block v-if="birthday.coupon_list.length > 0">
<block v-for="(item, index) in birthday.coupon_list" :key="index">
<view class="content">
<view class="info">
<block v-if="item.type == 'reward'">
<text class="num">
{{ parseFloat(item.money) }}
<text class="type">元优惠劵</text>
</text>
</block>
<block v-else-if="item.type == 'discount'">
<text class="num">
{{ item.discount }}
<text class="type"></text>
</text>
</block>
<view class="desc">用于下单时抵现或兑换商品等</view>
</view>
<view class="tip" @click="closeRewardPopup('3')">立即查看</view>
</view>
</block>
</block>
</view>
</scroll-view>
</view>
<view class="close-btn" @click="cancel()"><text class="iconfont icon-close btn"></text></view>
</view>
</uni-popup>
</view>
</view>
</template>
<script>
import uniPopup from '../uni-popup/uni-popup.vue';
//
export default {
name: 'ns-birthday-gift',
components: {
uniPopup
},
data() {
return {
birthday: {
flag: false,
coupon_list: {}
},
record: false,
leatBirthday: false
};
},
computed: {
introduction() {
let bytesCount = 0;
if (this.birthday.blessing_content) {
for (let i = 0, n = this.birthday.blessing_content.length; i < n; i++) {
let c = this.birthday.blessing_content.charCodeAt(i);
if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
bytesCount += 1;
} else {
bytesCount += 2;
}
}
}
return bytesCount;
}
},
async created() {
await this.getBirthdayGift();
//this.getReceiveGift();
this.getBirthdaygiftRecord();
},
methods: {
getReward() {
return this.birthday;
},
async open() {
if (this.leatBirthday) {
this.$refs.birthdayGift.open();
await this.getReceiveGift();
} else {
await this.getBirthdayGift();
await this.getReceiveGift();
this.$refs.birthdayGift.open();
}
},
cancel() {
this.$refs.birthdayGift.close();
},
getRecord() {
return this.record;
},
getBirthday() {
return this.birthday;
},
/**
* 获取生日礼配置
*/
async getBirthdayGift() {
var res = await this.$api.sendRequest({
url: '/birthdaygift/api/Config/config',
async: false
});
if (res.code >= 0) {
if (res.data) {
this.birthday = res.data;
this.leatBirthday = true;
}
}
/* await this.$api.sendRequest({
url: '/birthdaygift/api/Config/Config',
success: res => {
if (res.code >= 0) {
if(res.data){
this.birthday = res.data;
uni.setStorageSync('birthdayFlag',res.data.flag);
}
}
}
}); */
},
//
async getReceiveGift() {
if (this.birthday.flag == true) {
await this.$api.sendRequest({
url: '/birthdaygift/api/Config/receive',
data: {
id: this.birthday.id
},
success: res => {
if (res.code >= 0) {
this.$emit('getBirthday');
uni.removeStorageSync('birthdayFlag');
this.$util.redirectTo('/pages/member/index');
}
}
});
}
},
closeRewardPopup(type) {
if (type == 1) {
this.$util.redirectTo('/pages_tool/member/point_detail', {});
return;
} else if (type == 2) {
this.$util.redirectTo('/pages_tool/member/balance_detail', {});
return;
} else {
this.$util.redirectTo('/pages_tool/member/coupon', {});
return;
}
},
getBirthdaygiftRecord() {
this.$api.sendRequest({
url: '/birthdaygift/api/Config/getRecord',
data: {},
success: res => {
if (res.code >= 0) {
this.record = res.data;
}
}
});
}
}
};
</script>
<style scoped>
.register-box /deep/ .uni-scroll-view {
background: unset !important;
}
.register-box {
max-height: 300rpx;
overflow-y: scroll;
/* margin-top:350rpx; */
}
/deep/ .uni-popup__wrapper-box {
background-color: transparent !important;
}
/deep/ .birthday-title-hint uni-image {
width: 113rpx !important;
height: 24rpx !important;
}
</style>
<style lang="scss">
.reward-wrap {
width: 85vw;
height: auto;
.wrap {
width: 100%;
height: auto;
background-size: 100%;
background-repeat: no-repeat;
padding-bottom: 40rpx;
.birthday-title-name {
font-size: $font-size-toolbar;
font-weight: bold;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
padding-top: 350rpx;
text-align: center;
color: #fff;
line-height: 1;
}
.birthday-title-desc {
font-weight: 500;
margin: 30rpx 70rpx 20rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-align: center;
color: #fff;
}
.birthday-title-hint {
font-size: $font-size-toolbar;
font-weight: bold;
font-family: BDZongYi-A001;
display: flex;
align-items: center;
justify-content: center;
margin: 0 0 40rpx;
line-height: 1;
.birthday-img-all {
width: 100rpx;
height: 20rpx;
}
& > view {
margin: 0 20rpx;
color: #fff;
}
}
}
.reward-content {
margin: 0 40rpx;
}
.head {
color: #fff;
text-align: center;
line-height: 1;
margin: 20rpx 0;
}
& .content:last-child {
margin-bottom: 0;
}
.content {
display: flex;
align-items: center;
padding: 16rpx 26rpx;
background: #fff;
border-radius: 10rpx;
margin-bottom: 20rpx;
.info {
flex: 1;
}
.tip {
color: #fa5b14;
padding: 10rpx 0 10rpx 20rpx;
width: 60rpx;
line-height: 1.5;
letter-spacing: 2rpx;
border-left: 2rpx dashed #e5e5e5;
}
.num {
font-size: 48rpx;
color: #fa5b14;
font-weight: bolder;
line-height: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
max-width: 300rpx;
}
.type {
font-size: $font-size-base;
margin-left: 10rpx;
line-height: 1;
font-weight: normal;
color: #606266;
}
.desc {
margin-top: 8rpx;
color: $color-tip;
font-size: $font-size-tag;
line-height: 1;
}
}
.close-btn {
text-align: center;
margin-top: 40rpx;
.btn {
font-size: 40rpx;
color: #fff;
border: 4rpx solid #fff;
border-radius: 50%;
padding: 10rpx;
font-weight: bold;
width: 40rpx;
height: 40rpx;
margin: 0 auto;
line-height: 40rpx;
/* margin: 20rpx 140rpx;
border: none;
background: linear-gradient(90deg, #FF6A00, #FF3C00);
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center; */
}
}
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<view class="message">
<view class="goods-item" v-if="goodsInfo.goods_name">
<image :src="$util.img(goodsInfo.sku_image)" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-name">{{ goodsInfo.sku_name ? goodsInfo.sku_name : goodsInfo.goods_name }}</view>
<view class="goods-bottom">
<view class="goods-price">
<text class="goods-price-sign color-base-text"></text>
<text class="color-base-text">{{ goodsInfo.price }}</text>
</view>
<view class="goods-option font-size-goods-tag disabled">已发送</view>
</view>
</view>
</view>
<view class="goods-item" v-else-if="goodsDetail">
<image :src="$util.img(goodsDetail.sku_image)" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-name">{{ goodsDetail.sku_name ? goodsDetail.sku_name : goodsDetail.goods_name }}</view>
<view class="goods-bottom">
<view class="goods-price">
<text class="goods-price-sign color-base-text"></text>
<text class="color-base-text">{{ goodsDetail.price }}</text>
</view>
<view class="goods-option font-size-goods-tag color-base-bg" @click="sendMsg('goods')">发送</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ns-chat-goods',
props: {
skuId: {
type: [Number, String]
},
goodsDetail: {
type: [Object]
}
},
data() {
return {
goodsInfo: {}
};
},
mounted() {
this.getGoodsInfo();
},
methods: {
getGoodsInfo() {
this.$api.sendRequest({
url: '/api/goodssku/detail',
data: {
sku_id: this.skuId
},
success: res => {
if (res.code >= 0) {
this.goodsInfo = res.data.goods_sku_detail;
}
}
});
},
sendMsg() {
this.$emit('sendMsg', 'goods');
}
}
};
</script>
<style lang="scss">
.message {
padding: 13rpx 20rpx;
box-sizing: border-box;
width: 100vw;
position: relative;
.goods-item {
width: 100%;
height: 220rpx;
background: #ffffff;
position: relative;
display: flex;
align-items: center;
border-radius: 20rpx;
margin: 0 auto;
padding: $padding;
box-sizing: border-box;
image {
width: 180rpx;
height: 180rpx;
min-width: 180rpx;
}
.goods-info {
width: 100%;
height: 180rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 20rpx;
box-sizing: border-box;
.goods-name {
width: 100%;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.goods-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
text {
line-height: 1;
}
.goods-price {
display: flex;
align-items: flex-end;
padding-bottom: 10rpx;
font-weight: 500;
.goods-price-sign {
font-size: $font-size-activity-tag;
}
}
.goods-option {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
text-align: center;
border-radius: $border-radius;
color: #ffffff;
}
}
}
.disabled {
background: #e5e5e5;
}
}
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<view class="message">
<view class="goods-item" v-if="orderdetails">
<image :src="$util.img(orderdetails.order_goods ? orderdetails.order_goods[0].sku_image : '')" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-name">{{ orderdetails.order_goods ? orderdetails.order_goods[0].sku_name : '' }}</view>
<view class="font-size-goods-tag">订单状态:{{ orderdetails.order_status_name }}</view>
<view class="font-size-goods-tag">配送方式:{{ orderdetails.delivery_type_name }}</view>
<view class="goods-bottom">
<view class="goods-price color-base-text">
<text class="goods-price-sign"></text>
<text>{{ orderdetails.order_goods ? orderdetails.order_goods[0].price : '' }}</text>
</view>
<view class="goods-option font-size-goods-tag color-base-bg" @click="sendMsg('order')">发送</view>
</view>
</view>
</view>
<view class="goods-item" v-else-if="orderInfo">
<image :src="$util.img(orderInfo.order_goods ? orderInfo.order_goods[0].sku_image : '')" mode="aspectFill"></image>
<view class="goods-info">
<view class="goods-name">{{ orderInfo.order_goods ? orderInfo.order_goods[0].sku_name : '' }}</view>
<view class="font-size-goods-tag">订单状态:{{ orderInfo.order_status_name }}</view>
<view class="font-size-goods-tag">配送方式:{{ orderInfo.delivery_type_name }}</view>
<view class="goods-bottom">
<view class="goods-price color-base-text">
<text class="goods-price-sign"></text>
<text>{{ orderInfo.order_goods ? orderInfo.order_goods[0].price : '' }}</text>
</view>
<view class="goods-option font-size-goods-tag disabled">已发送</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ns-chat-order',
props: {
orderId: {
type: [Number, String]
},
isCanSend: Boolean,
orderdetails: {
type: [Object]
}
},
data() {
return {
orderInfo: {}
};
},
mounted() {
this.getGoodsInfo();
},
methods: {
getGoodsInfo() {
if (this.orderId) {
this.$api.sendRequest({
url: '/api/order/detail',
data: {
order_id: this.orderId
},
success: res => {
if (res.code >= 0) {
this.orderInfo = res.data;
}
}
});
}
},
sendMsg() {
this.$emit('sendMsg', 'order');
}
}
};
</script>
<style lang="scss">
.message {
padding: 13rpx 20rpx;
box-sizing: border-box;
width: 100vw;
position: relative;
.goods-item {
width: 100%;
height: 220rpx;
background: #ffffff;
position: relative;
display: flex;
align-items: center;
border-radius: 20rpx;
margin: 0 auto;
padding: $padding;
box-sizing: border-box;
image {
width: 180rpx;
height: 180rpx;
}
.goods-info {
width: 100%;
height: 180rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 20rpx;
box-sizing: border-box;
.goods-name {
width: 100%;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
// font-size: $font-size-tag;
margin-bottom: 10rpx;
}
.goods-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
text {
line-height: 1;
}
.goods-price {
display: flex;
align-items: flex-end;
padding-bottom: 10rpx;
font-weight: 500;
.goods-price-sign {
font-size: 20rpx;
}
}
.goods-option {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
text-align: center;
border-radius: $border-radius;
color: #ffffff;
}
}
}
.disabled {
background: #e5e5e5;
}
}
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<view class="goods">
<view class="goods-msg">
<image :src="$util.img(goodsINfo.sku_image)" mode="aspectFill"></image>
<view class="goods-item">
<view class="title">{{ goodsINfo.goods_name }}</view>
<view class="goods-sku">
库存:{{ goodsINfo.stock }}
<text>销量:{{ goodsINfo.sale_num }}</text>
</view>
<view class="goods-price">
<view class="price color-base-text">
<text class="price-util"></text>
<text class="price-num">{{ goodsINfo.price }}</text>
</view>
<view class="see-shop color-base-text" @click="go_shop()">
查看商品
<text class="iconfont icon-right"></text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ns-chat-receiveGoods',
props: {
skuId: {
type: [Number, String]
}
},
data() {
return {
goodsINfo: {}
};
},
mounted() {
this.getInfo();
},
methods: {
getInfo() {
this.$api.sendRequest({
url: '/api/goodssku/detail',
data: {
sku_id: this.skuId
},
success: res => {
if (res.code >= 0) {
this.goodsINfo = res.data.goods_sku_detail;
this.$emit('upDOM');
// that.setPageScrollTo();
}
}
});
},
go_shop() {
this.$util.redirectTo('/pages/goods/detail?goods_id=' + this.goodsINfo.goods_id);
}
}
};
</script>
<style lang="scss">
.goods {
padding: 13rpx 20rpx;
box-sizing: border-box;
width: 100vw;
position: relative;
.goods-msg {
width: 100%;
height: 220rpx;
background: #ffffff;
position: relative;
display: flex;
align-items: center;
border-radius: 20rpx;
margin: 0 auto;
padding: $padding;
box-sizing: border-box;
image {
width: 180rpx;
height: 180rpx;
min-width: 180rpx;
border-radius: $border-radius;
}
.goods-item {
width: 100%;
height: 180rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 20rpx;
box-sizing: border-box;
.title {
width: 100%;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.goods-sku {
color: $color-sub;
text {
padding-left: $padding;
}
}
.goods-price {
display: flex;
align-items: center;
justify-content: space-between;
.price {
.price-util {
font-size: $font-size-activity-tag;
}
}
.see-shop {
display: flex;
align-items: center;
text {
padding-top: 4rpx;
padding-left: 4rpx;
font-size: $font-size-sub;
}
}
}
}
}
}
</style>

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