源文件

This commit is contained in:
gyq
2025-04-25 09:49:53 +08:00
commit 791d82b9e3
640 changed files with 130029 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
<template>
<view :class="theme_view">
<view v-if="(propConfig || null) != null && (propData || null) != null && propData.length > 0">
<block v-for="(floor, index) in propData" :key="index">
<block v-if="floor.goods_list.length > 0 && floor.home_data_location == propLocation">
<component-goods-list
:propData="floor"
propMoreUrlKey="url"
:propLabel="propLabel"
:propIsAutoPlay="(propConfig.is_home_auto_play || 0) == 1"
:propCurrencySymbol="propCurrencySymbol"
:propIsCartParaCurve="propIsCartParaCurve"
:propSource="propSource"
:propOpenCart="floor.style_type === '2' ? false : true"
></component-goods-list>
</block>
</block>
</view>
</view>
</template>
<script>
const app = getApp();
import componentGoodsList from '@/components/goods-list/goods-list';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
components: {
componentGoodsList,
},
props: {
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
propLocation: {
type: [String, Number],
default: 0,
},
propConfig: {
type: [String, Object],
default: null,
},
propData: {
type: Array,
default: [],
},
propLabel: {
type: [Array, Object, String],
default: null,
},
propIsCartParaCurve: {
type: Boolean,
default: false,
},
// 来源
propSource: {
type: String,
default: '',
},
propOpenCart: {
type: Boolean,
default: true,
},
},
methods: {},
};
</script>
<style></style>

View File

@@ -0,0 +1,242 @@
<template>
<view :class="theme_view">
<!-- 更新 -->
<view v-if="is_update_status && (update_data || null) != null" class="update-container pf left-0 top-0 wh-auto ht-auto">
<view class="update-content auto bg-white pr">
<image :src="update_alert_bg_images" mode="widthFix" class="update-alert-bg-images wh-auto"></image>
<view class="padding-top-xs padding-left-xl padding-right-xl padding-bottom-xl">
<view class="text-size-xl fw-b tc pa name">{{update_data.name}}</view>
<view class="text-size tc pa version">v{{update_data.version_new}}</view>
<scroll-view :scroll-y="true" class="content tl">
<block v-for="(item, index) in update_data.content" :key="index">
<view class="margin-bottom-sm text-size-xs">{{item}}</view>
</block>
</scroll-view>
<view class="margin-top-xl flex-row">
<button v-if="(update_data.is_force_update || 0) == 0" type="default" class="br-main bg-white cr-main round text-size-md" size="mini" @tap="update_close_event">{{$t('common.cancel')}}</button>
<button type="default" class="br-main bg-main cr-white round text-size-md" size="mini" @tap="to_update_event">{{$t('common.now_update_text')}}</button>
</view>
</view>
</view>
</view>
<!-- 评分 -->
<view v-if="is_star_status && (star_url || null) != null && (star_alert_images || null) != null" class="star-container pf left-0 top-0 wh-auto ht-auto tc">
<view class="star-content">
<image :src="star_alert_images" mode="widthFix" class="star-alert-images wh-auto" @tap="to_star_event"></image>
</view>
<view class="padding-sm margin-top-xl">
<view class="dis-inline-block" @tap="close_star_event">
<iconfont name="icon-close-o" size="30rpx" color="#ccc"></iconfont>
</view>
</view>
</view>
<!-- 关于我们中使用 -->
<!-- #ifdef APP -->
<view v-if="is_about_page && !propIsHideStar" class="margin-top">
<text class="cr-grey-9">{{app_version_info}}</text>
<block v-if="is_loading">
<text v-if="(update_data || null) == null" class="cr-grey-c margin-left-lg text-size-xs">{{$t('common.already_latest_text')}}</text>
<text v-else class="cr-blue margin-left-lg text-size-xs cp" @tap="update_event">{{$t('common.to_update_text')}}(v{{update_data.version_new}})</text>
<text v-if="(star_url || null) != null && (star_alert_images || null) != null" class="cr-blue margin-left-lg text-size-xs cp" @tap="star_event">{{$t('common.to_star_text')}}</text>
</block>
</view>
<!-- #endif -->
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
update_tips_cache_key: app.globalData.data.cache_app_update_tips_interval_time_key,
star_tips_cache_key: app.globalData.data.cache_app_star_tips_interval_time_key,
app_version_info: app.globalData.data.app_version_info,
is_about_page: false,
is_loading: false,
update_data: null,
// 更新提示
is_update_status: false,
update_tips_interval_time: 0,
update_alert_bg_images: null,
// 评分提示
is_star_status: false,
star_tips_await_time: 0,
star_tips_interval_time: 0,
star_alert_images: null,
star_url: null
};
},
props: {
propIsHideStar: {
type: Boolean,
default: false,
},
},
// 页面被展示
created: function () {
this.init();
},
methods: {
// 初始化、获取数据
init(is_init = 0) {
// #ifdef APP
// 是否关于我们页面
this.setData({
is_about_page: app.globalData.current_page(false) == 'pages/about/about',
});
// 请求接口获取版本数据
uni.request({
url: app.globalData.get_request_url('index', 'version', 'appadmin'),
method: 'POST',
data: {app_version: app.globalData.data.app_version_info},
dataType: 'json',
success: (res) => {
if(res.data.code == 0) {
var data = res.data.data;
var upd_data = {
is_loading: true,
update_data: data.update_data || null,
// 更新提示
update_alert_bg_images: data.update_alert_bg_images || null,
is_update_status: parseInt(data.is_update_status || 0) == 1,
update_tips_interval_time: parseInt(data.update_tips_interval_time || 600),
// 评分提示
is_star_status: parseInt(data.is_star_status || 0) == 1,
star_tips_await_time: parseInt(data.star_tips_await_time || 600),
star_tips_interval_time: parseInt(data.star_tips_interval_time || 1800),
star_alert_images: data.star_alert_images || null,
star_url: data.star_url || null,
};
// 当前时间
var current_time = Date.parse(new Date()) / 1000;
// 更新提示间隔时间
var update_tips_cache_time = parseInt(uni.getStorageSync(this.update_tips_cache_key) || 0);
if(update_tips_cache_time > 0 && current_time < update_tips_cache_time + upd_data.update_tips_interval_time) {
upd_data.is_update_status = false;
}
// 评分是否可以展示评分
var star_tips_cache_time = parseInt(uni.getStorageSync(this.star_tips_cache_key) || 0);
if(star_tips_cache_time > 0) {
upd_data.is_star_status = current_time > star_tips_cache_time;
}
// 首次则记录评分缓存
if(star_tips_cache_time == 0) {
uni.setStorageSync(this.star_tips_cache_key, (Date.parse(new Date()) / 1000)+upd_data.star_tips_await_time);
// 如果等待时间为0则不需要等待就提示评分
if(upd_data.star_tips_await_time == 0) {
upd_data.is_star_status = true;
}
}
// 如果已经展示更新弹窗则不展示评分弹窗
if(upd_data.is_update_status) {
upd_data.is_star_status = false;
}
// 关于我们页面则不直接展示
if(this.is_about_page) {
upd_data.is_update_status = false;
upd_data.is_star_status = false;
}
this.setData(upd_data);
}
},
fail: () => {
// 失败则再重试一次
if(is_init == 0) {
this.get_data(1);
}
}
});
// #endif
},
// 更新关闭
update_close_event(e) {
this.setData({
is_update_status: false
});
uni.setStorageSync(this.update_tips_cache_key, Date.parse(new Date()) / 1000);
},
// 打开更新事件
update_event(e) {
this.setData({
is_update_status: true
});
},
// 去更新事件
to_update_event(e) {
plus.runtime.openURL(this.update_data.update_url);
},
// 打开评分事件
star_event(e) {
this.setData({
is_star_status: true
});
},
// 去打分事件
to_star_event(e) {
// 先关闭评分
this.close_star_event();
// 打开地址
plus.runtime.openURL(this.star_url);
},
// 关闭评分事件
close_star_event(e) {
this.setData({
is_star_status: false
});
// 增加间隔时间,到时间后才会再提示
uni.setStorageSync(this.star_tips_cache_key, (Date.parse(new Date()) / 1000)+this.star_tips_interval_time);
}
}
};
</script>
<style scoped>
.update-container,
.star-container {
background-color: rgba(0, 0, 0, 0.6);
z-index: 100;
padding-top: 26vh;
}
.update-container .update-content {
width: 75vw;
padding-top: 100rpx;
border-radius: 20rpx;
}
.update-container .update-content .update-alert-bg-images {
margin-top: -180rpx;
}
.update-container .update-content .name {
left: 36rpx;
top: 60rpx;
}
.update-container .update-content .version {
left: 36rpx;
top: 140rpx;
}
.update-container .update-content .content {
max-height: 26vh;
}
.star-container .star-content {
border-radius: 20rpx;
}
.star-container .star-content .star-alert-images {
max-width: 460rpx;
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<view :class="theme_view">
<block v-if="(propData || null) != null && propData.length > 0">
<view v-for="(item, index) in propData" :key="index" class="ask-comment-item">
<view :data-value="item.url" @tap="url_event" class="flex-row cp">
<view class="title cr-white tc">{{$t('goods-list.goods-list.00n7i3')}}</view>
<view class="base-nav flex-1 flex-width margin-left-sm">
<view class="oh nav padding-bottom-sm">
<view class="flex-row jc-sb align-c">
<text class="va-m single-text flex-1 flex-width">{{ item.title || item.content }}</text>
<text class="cr-grey text-size-xs">{{$t('detail.detail.025362')}}{{ item.comments_count }}{{$t('ask-comments-goods.ask-comments-goods.xl51n6')}}</text>
</view>
<view v-if="(item.images || null) != null && item.images.length > 0" class="images oh margin-top-lg">
<block v-for="(iv, ix) in item.images" :key="ix">
<image class="br radius margin-right-sm" @tap="comment_images_show_event" :data-index="index" :data-ix="ix" :src="iv" mode="aspectFit"></image>
</block>
</view>
</view>
</view>
</view>
</view>
</block>
<block v-else>
<view class="cr-grey-d tc spacing-mb flex-row jc-c align-c">
<image :src="ask_static_url + 'no-ask.png'" mode="widthFix" class="no-ask margin-right-main" />{{$t('ask-comments-goods.ask-comments-goods.g6mc44')}}</view>
</block>
</view>
</template>
<script>
const app = getApp();
var ask_static_url = app.globalData.get_static_url('ask', true) + 'app/';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
ask_static_url:ask_static_url,
};
},
props: {
propData: {
type: Array,
default: () => {
return [];
},
},
},
created: function () {},
methods: {
// 评价图片预览
comment_images_show_event(e) {
var index = e.currentTarget.dataset.index;
var ix = e.currentTarget.dataset.ix;
uni.previewImage({
current: this.propData[index]["images"][ix],
urls: this.propData[index]["images"],
});
},
// url事件
url_event(e) {
app.globalData.url_event(e);
}
},
};
</script>
<style scoped>
/**
* 商品评价
*/
.title {
width: 40rpx;
height: 40rpx;
line-height: 40rpx;
background: #fd9525;
border-radius: 4rpx;
}
.ask-comment-item {
padding-bottom: 10rpx;
margin-bottom: 20rpx;
}
.ask-comment-item .avatar {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
border: 1px solid #e2e2e2;
}
.ask-comment-item .base-nav {
border-bottom: 2rpx solid #f5f5f5;
}
.ask-comment-item:last-of-type {
margin-bottom: 0;
}
.ask-comment-item:last-of-type .base-nav {
border: 0;
}
.no-ask {
width: 174rpx;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<view :class="theme_view">
<view v-if="propNumber != 0" class="am-badge">
<view :class="'am-badge-text ' + ((propNumber > 99) ? 'am-badge-text-max' : '')">
<text>{{(propNumber > 99) ? '99+' : propNumber}}</text>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
components: {},
props: {
propNumber: {
type: [Number,String],
default: 0
}
},
methods: {}
};
</script>
<style scoped>
.am-badge {
display: inline-block;
position: relative;
vertical-align: middle;
}
.am-badge-text {
display: inline-block;
position: absolute;
right: 0;
transform: translate(50%, -50%);
top: 0;
min-width: 28rpx;
padding: 0;
height: 28rpx;
line-height: 28rpx;
text-align: center;
background-color: #FF3B30;
border-radius: 40rpx;
color: #fff;
font-size: 20rpx;
padding: 2rpx 2rpx;
box-shadow: 0 0 10rpx rgb(0 0 0 / 30%);
}
.am-badge-text-max {
padding: 2rpx 4rpx;
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<view :class="theme_view">
<view v-if="((propData || null) != null)" class="plugins-binding-container">
<!-- 组合搭配 -->
<block v-if="((propData.binding_list || null) != null)">
<block v-for="(bv, bi) in propData.binding_list" :key="bi">
<view v-if="((bv.goods || null) != null) && bv.goods.length > 0" class="plugins-binding-list padding-horizontal-main padding-top-main border-radius-main oh spacing-mb">
<view class="spacing-nav-title oh">
<text class="text-wrapper">{{bv.title}}</text>
<view v-if="(bv.estimate_discount_price || 0) != 0" class="estimate-discount-price fr">
<text class="discount-icon cr-white text-size-xs">{{$t('detail.detail.6026t4')}}</text>
<text class="cr-green text-size-lg va-m">{{propCurrencySymbol}}{{bv.estimate_discount_price}}</text>
</view>
</view>
<view class="left-content fl">
<component-goods-list :propData="{style_type: 2, goods_list: bv.goods, multiple_items: 2}" :propOpenCart="false" :propLabel="propLabel" :propCurrencySymbol="propCurrencySymbol" :propIsAutoPlay="(propData.config.is_auto_play || 0) == 1"></component-goods-list>
</view>
<view class="right-content fr bs-bb padding-left-main tc">
<button type="default" size="mini" class="bg-main br-main cr-white text-size-xs round" :data-value="'/pages/plugins/binding/detail/detail?id='+bv.id" @tap="url_event">{{bv.buy_button_text}}</button>
<view class="sales-price margin-top-sm">{{propCurrencySymbol}}{{bv.estimate_price}}</view>
<view v-if="(bv.estimate_original_price || 0) != 0" class="original-price margin-top-sm">{{propCurrencySymbol}}{{bv.estimate_original_price}}</view>
</view>
</view>
</block>
</block>
<!-- 商品关联 -->
<view v-if="((propData.relevant_data || null) != null)">
<component-goods-list :propData="{title: propData.relevant_data.name, style_type: 2, goods_list: propData.relevant_data.data}" :propLabel="propLabel" :propCurrencySymbol="propCurrencySymbol" :propIsAutoPlay="(propData.config.is_auto_play_relevant || 0) == 1"></component-goods-list>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import componentGoodsList from "../goods-list/goods-list";
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
components: {
componentGoodsList
},
props: {
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol()
},
propData: {
type: [Array,Object],
default: []
},
propLabel: {
type: [Array,Object,String],
default: null
}
},
methods: {
// url事件
url_event(e) {
app.globalData.url_event(e);
}
}
};
</script>
<style>
.plugins-binding-list {
background: linear-gradient(to right, rgb(255 235 220), rgb(241 235 255));
}
.plugins-binding-list .left-content {
width: 65%;
}
.plugins-binding-list .right-content {
width: 35%;
padding-top: 100rpx;
}
.plugins-binding-list .estimate-discount-price .discount-icon {
border-top-right-radius: 30rpx;
border-bottom-left-radius: 30rpx;
background-image: linear-gradient(45deg,#a3f9a3,#248828,#8bc34a,#d2374c,#9c27b0);
background-size: 400%;
animation: gradient 5s ease infinite;
padding: 0 16rpx;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<view :class="theme_view">
<view v-if="(data_list || null) != null && data_list.length > 0" class="plugins-binding-data-list oh">
<block v-for="(item, index) in data_list" :key="index">
<view class="item border-radius-main bg-white padding-main oh pr spacing-mb">
<view class="oh flex-row" :data-value="item.url" @tap="url_event">
<image :src="item.images" mode="aspectFit" class="images dis-block border-radius-main"></image>
<view class="flex-1 flex-width flex-col jc-sb">
<view class="base-right bs-bb padding-left-main">
<view class="fw-b text-size-lg cr-base single-text">{{ item.title }}</view>
<view class="sales-price margin-top-main single-text">
<text class="text-size-xs">{{ propCurrencySymbol }}</text>
<text class="text-size-lg fw-b">{{ item.estimate_price }}</text>
</view>
<view v-if="(item.estimate_discount_price || 0) != 0" class="margin-top-sm single-text flex-row align-c">
<text class="discount-icon cr-white text-size-xs">{{$t('detail.detail.6026t4')}}</text>
<view class="cr-green single-text">
<text class="text-size-xs">{{ propCurrencySymbol }}</text>
<text class="text-size">{{ item.estimate_discount_price }}</text>
</view>
</view>
</view>
<button type="default" size="mini" class="br-main bg-main cr-white round buy-submit self-e margin-0 text-size-xs">{{ item.type_name }}{{$t('binding-list.binding-list.kh7951')}}</button>
</view>
</view>
<view class="binding-goods-list border-radius-main margin-top-main oh" :style="'height: ' + ((item.is_home_show_goods || 0) == 1 ? Math.ceil(item.goods.length / 2) * 134 + 12 : '0') + 'rpx'">
<view class="padding-horizontal-main padding-top-main padding-bottom-main oh">
<block v-for="(gv, gi) in item.goods" :key="gi">
<view class="goods-item oh margin-bottom-lg" :data-value="gv.goods_url" @tap="url_event">
<image :src="gv.images" mode="aspectFit" class="goods-images fl dis-block radius"></image>
<view class="goods-right fr bs-bb">
<view class="single-text text-size-sm">{{ gv.title }}</view>
<view v-if="(gv.show_field_price_status || 0) == 1" class="single-text">
<text class="sales-price va-m text-size-xss">{{ gv.show_price_symbol }}{{ gv.price }}</text>
<text class="cr-grey va-m text-size-xsss">{{ gv.show_price_unit }}</text>
</view>
<view v-if="(gv.discount_price || null) != null" class="single-text cr-green text-size-xss">{{$t('detail.detail.6026t4')}}{{ gv.show_price_symbol }}{{ gv.discount_price }}</view>
</view>
</view>
</block>
</view>
</view>
<view class="bg-white padding-top-main wh-auto bs-bb bottom-elastic" :class="(item.is_home_show_goods || 0) != 1 ? 'br-t-dashed' : ''" :data-index="index" @tap="item_more_goods_event">
<view class="flex-row jc-sb align-c">
<view class="cr-grey-9 text-size-xs">{{ (item.is_home_show_goods || 0) == 1 ? $t('binding-list.binding-list.2u4v35') : $t('binding-list.binding-list.91d60h') }}{{ item.type_name }}{{$t('recommend-list.recommend-list.x74z3o')}}</view>
<iconfont :name="(item.is_home_show_goods || 0) == 1 ? 'icon-arrow-top' : 'icon-arrow-bottom'" size="24rpx" color="#666"></iconfont>
</view>
</view>
</view>
</block>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
config: {},
data_list: [],
};
},
components: {},
props: {
// 价格符号
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol()
},
propConfig: {
type: [String, Object],
default: null,
},
propData: {
type: Object,
default: () => {
return {};
},
}
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.init();
}
},
// 页面被展示
created: function(e) {
this.init();
},
methods: {
// 初始化
init() {
var config = ((this.propConfig || null) == null ? app.globalData.get_config('plugins_base.binding.data') : this.propConfig) || {};
var data_list = ((this.propData || null) == null || (this.propData.data || null) == null || this.propData.data.length == 0) ? [] : this.propData.data;
this.setData({
config: config,
data_list: data_list,
});
},
// url事件
url_event(e) {
app.globalData.url_event(e);
},
// 商品展开关闭
item_more_goods_event(e) {
var index = e.currentTarget.dataset.index;
var temp_data = this.data_list;
temp_data[index]['is_home_show_goods'] = parseInt(temp_data[index]['is_home_show_goods'] || 0) == 1 ? 0 : 1;
this.setData({ data_list: temp_data });
},
},
};
</script>
<style scoped>
.plugins-binding-data-list .item .images {
width: 256rpx;
height: 256rpx !important;
}
.plugins-binding-data-list .item .base-right .discount-icon {
border-top-right-radius: 30rpx;
border-bottom-left-radius: 30rpx;
background-image: linear-gradient(45deg, #a3f9a3, #248828, #8bc34a, #d2374c, #9c27b0);
background-size: 400%;
animation: gradient 5s ease infinite;
padding: 0 16rpx;
}
.plugins-binding-data-list .item .buy-submit {
padding: 0 20rpx;
height: 46rpx;
line-height: 44rpx;
}
.plugins-binding-data-list .item .binding-goods-list {
background: #f8f8f8;
transition: height 0.25s ease-in-out;
}
.plugins-binding-data-list .item .binding-goods-list .goods-item {
width: calc(50% - 15rpx);
height: 102rpx;
}
.plugins-binding-data-list .item .binding-goods-list .goods-item:nth-child(odd) {
float: left;
}
.plugins-binding-data-list .item .binding-goods-list .goods-item:nth-child(even) {
float: right;
}
.plugins-binding-data-list .item .binding-goods-list .goods-item .goods-right {
width: calc(100% - 115rpx);
}
.plugins-binding-data-list .item .binding-goods-list .goods-item .goods-images {
width: 100rpx;
height: 100rpx !important;
}
.plugins-binding-data-list .item .bottom-elastic {
left: 0;
bottom: 0;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<view :class="theme_view">
<view v-if="(propConfig || null) != null && (propData || null) != null && propData.length > 0">
<block v-for="(floor, index) in propData" :key="index">
<block v-if="floor.blog_list.length > 0 && floor.home_data_location == propLocation">
<view class="plugins-blog" :class="floor.style_type == 2 ? 'bg-white border-radius-main padding-main spacing-mb' : ''">
<view class="spacing-nav-title flex-row align-c jc-sb text-size-xs">
<view class="title-left">
<text class="text-wrapper" :class="floor.style_type == 2 ? '' : 'title-left-border'" :style="'color:' + (floor.color || '#333') + ';'">{{ floor.title }}</text>
<text v-if="(floor.vice_title || null) != null" class="vice-name margin-left-sm cr-grey-9">{{ floor.vice_title }}</text>
</view>
<text :data-value="floor.more_url" @tap="url_event" class="arrow-right padding-right cr-grey cp">{{$t('common.more')}}</text>
</view>
<view class="wh-auto oh pr">
<block v-if="floor.blog_list.length > 0">
<!-- 默认图文 -->
<block v-if="(floor.style_type || 0) == 0">
<view class="plugins-blog-list">
<view v-for="(item, index) in floor.blog_list" :key="index" class="item oh padding-main border-radius-main bg-white spacing-mb">
<view :data-value="item.url" @tap="url_event" class="cp">
<image class="blog-img fl radius" :src="item.cover" mode="aspectFill"></image>
<view class="base fr">
<view class="single-text text-size">{{ item.title }}</view>
<view class="cr-grey-9 margin-top-sm text-size-xs">{{ item.add_time_date_cn }}</view>
<view v-if="(item.describe || null) != null" class="cr-base multi-text margin-top-sm text-size-xs">{{item.describe}}</view>
</view>
</view>
</view>
</view>
</block>
<!-- 九方格 -->
<block v-else-if="floor.style_type == 1">
<view class="plugins-blog-grid-list">
<view v-for="(item, index) in floor.blog_list" :key="index" class="item oh border-radius-main bg-white spacing-mb">
<view :data-value="item.url" @tap="url_event" class="cp">
<image class="blog-img dis-block" :src="item.cover" mode="widthFix"></image>
<view class="base padding-horizontal-sm margin-top-sm">
<view class="goods-title multi-text margin-bottom-sm text-size-xs">{{ item.title }}</view>
<view class="cr-grey text-size-xs">{{ item.add_time_date_cn }}</view>
</view>
</view>
</view>
</view>
</block>
<!-- 滚动 -->
<view v-else-if="floor.style_type == 2" class="rolling-horizontal border-radius-main oh spacing-mb">
<view class="plugins-blog-rolling-list scroll-view-horizontal">
<swiper :vertical="false" :autoplay="(propConfig.is_home_hot_auto_play || 0) == 1" :circular="false" :display-multiple-items="floor.blog_list.length < 3 ? floor.blog_list.length : 3" interval="3000">
<block v-for="(item, index) in floor.blog_list" :key="index">
<swiper-item>
<view :data-value="item.url" @tap="url_event" class="item bg-white border-radius-main margin-right-main oh pr ht-auto pr cp">
<image class="blog-img dis-block wh-auto" :src="item.cover" mode="scaleToFill"></image>
<view class="blog-title pa single-text cr-white padding-sm text-size-xs">{{ item.title }}</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
</block>
</view>
</view>
</block>
</block>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
components: {},
props: {
propLocation: {
type: [String, Number],
default: 0,
},
propConfig: {
type: [String, Object],
default: null,
},
propData: {
type: Array,
default: [],
},
},
methods: {
// url事件
url_event(e) {
app.globalData.url_event(e);
}
}
};
</script>
<style></style>

View File

@@ -0,0 +1,56 @@
<template>
<view :class="theme_view">
<view v-if="(propStatus || false)" class="data-bottom-line">
<view class="bottom-exclude">
<view class="line-item left"></view>
<view class="line-item msg">{{propMsg || $t('bottom-line.bottom-line.44bct2')}}</view>
<view class="line-item right"></view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
components: {},
props: {
propStatus: Boolean,
propMsg: String
},
methods: {}
};
</script>
<style>
.data-bottom-line {
padding: 40rpx;
overflow: hidden;
}
.data-bottom-line .bottom-exclude {
padding-bottom: env(safe-area-inset-bottom);
}
.data-bottom-line .line-item {
width: 33.3%;
}
.data-bottom-line .line-item.left,
.data-bottom-line .line-item.right {
margin-top: 8px;
border-bottom: 1px solid #e1e1e1;
}
.data-bottom-line .line-item.msg {
color: #999;
text-align: center;
font-size: 24rpx;
}
.data-bottom-line .line-item.left,
.data-bottom-line .line-item.msg {
float: left;
}
.data-bottom-line .line-item.right {
float: right;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<view :class="theme_view">
<view v-if="data.length > 0" class="plugins-ordergoodsform-buy oh margin-top-sm">
<view v-for="(item, index) in data" :key="index" class="item pr oh">
<view class="title dis-block single-text text-size-sm pa">{{item.title}}</view>
<view class="value dis-block pa">
<view v-if="propIsRead">{{item.content}}</view>
<input v-else :type="((item.element_arr || null) == null || (item.element_arr[1] || null) == null) ? 'text' : item.element_arr[1]"
:placeholder="(item.placeholder || null) == null ? item.title : item.placeholder"
:data-index="index"
:value="item.default_value || ''"
@blur="input_value_event"
@confirm="input_value_event"
placeholder-class="cr-grey"
:class="'radius text-size-sm padding-horizontal-sm '+((item.check_status === undefined || item.check_status === true) ? 'br' : 'br-red')"
/>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data: [],
};
},
components: {},
props: {
propData: {
type: [Array],
default: []
},
propGoodsID: {
type: [Number,String],
default: 0
},
propIsRead: {
type: Boolean,
default: false
}
},
// 页面被展示
created: function () {
this.setData({
data: this.propData
});
},
methods: {
// 数据值事件
input_value_event(e) {
// 当前表单数据
var index = e.currentTarget.dataset.index;
var item = this.data[index];
item['default_value'] = e.detail.value.trim();
// 数据验证
var check = this.input_value_check(item, index);
// 保存数据
uni.request({
url: app.globalData.get_request_url('save', 'goods', 'ordergoodsform'),
method: 'POST',
data: {
form_id: item.form_id,
goods_id: this.propGoodsID,
title: item.title,
content: check.value
},
dataType: 'json',
success: (res) => {},
fail: (res) => {},
});
},
// 数据值验证
input_value_check(item, index) {
// 是否需要校验
var is_required = parseInt(item.is_required || 0);
var type = ((item.element_arr || null) == null || (item.element_arr[1] || null) == null) ? 'text' : item.element_arr[1];
var value = item.default_value;
var msg = item.validation_msg || item.error_message || this.$t('user-address-save.user-address-save.wkfi45');
var status = null;
// 强制填写数据,但是数据没有则错误
if(status === null && is_required == 1 && value === '')
{
status = false;
}
// 有数据则验证正确格式
if(status === null && value !== '')
{
// 根据类型验证数据
switch(type)
{
// 文本
case 'text' :
var len = value.length;
var minlength = parseInt(item.minlength || 0);
var maxlength = parseInt(item.maxlength || 0);
if((minlength > 0 && len < minlength) || (maxlength > 0 && len > maxlength))
{
status = false;
}
break;
// 整数
case 'number' :
var val = parseInt(value || 0);
var min = parseInt(item.minlength || 0);
var max = parseInt(item.maxlength || 0);
if((min > 0 && val < min) || (max > 0 && val > max))
{
value = (min > 0 && val < min) ? min : max;
status = false;
}
break;
}
// 是否有正则
if(status === null) {
var pattern = (((item.element_arr || null) == null) || (item.element_arr[2] || null) == null) ? null : item.element_arr[2];
if(pattern != null)
{
var regex = new RegExp(pattern);
if(!regex.test(value))
{
status = false;
}
}
}
}
// 是否提示错误
if(status === false) {
app.globalData.showToast(msg);
}
// 数据赋值
var temp_data = this.data;
item['default_value'] = value;
temp_data[index] = item;
temp_data[index]['check_status'] = (status === null) ? true : status;
this.setData({
data: temp_data
});
// 验证信息返回
return {status: status, value: value};
},
// 数据验证
data_check() {
var status = true;
for(var i in this.data) {
var res = this.input_value_check(this.data[i], i);
if(res.status === false) {
status = false;
break;
}
}
return status;
}
}
};
</script>
<style>
.plugins-ordergoodsform-buy .item {
height: 72rpx;
}
.plugins-ordergoodsform-buy .item .title {
width: 134rpx;
left: 0;
bottom: 8rpx;
}
.plugins-ordergoodsform-buy .item .value {
width: calc(100% - 140rpx);
left: 140rpx;
bottom: 0;
}
.plugins-ordergoodsform-buy .item .value input {
height: 50rpx;
line-height: 50rpx;
}
</style>

View File

@@ -0,0 +1,125 @@
<template>
<view :class="theme_view">
<view v-if="cart_icon_data != null && (cart_icon_data.status || 0) == 1" class="cart-para-curve-container pf round" :style="cart_icon_data.style">
<image v-if="(cart_icon_data.icon || null) != null" class="cart-para-curve-icon round br" :src="cart_icon_data.icon"></image>
<view v-else class="cart-para-curve-icon bg-red padding dis-inline-block round br"></view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
cart_icon_data: null
};
},
components: {},
props: {
propBtnHeight: {
type: Number,
default: 30
},
propBtnWidth: {
type: Number,
default: 30
},
propCart: {
type: String,
default: ''
},
},
methods: {
// 初始购物车对象、当前点击对象、图标、支持tabbar位置
init(cart, pos, icon = '', tabbar_pos = null) {
if((pos || null) != null) {
var self = this;
var btn_size = this.propBtnHeight;
var btn_width = this.propBtnWidth;
// 未指定购物车对象则读取tabbar数据自动计算购物车位置
if((cart || null) == null || (cart[0] || null) == null) {
var info = uni.getSystemInfoSync();
// 当前页面
var page = app.globalData.current_page().split('?');
switch(page[0]) {
// 商品搜索
case 'pages/goods-search/goods-search' :
var cart_top = 20;
var cart_width = 35;
var cart_left = info.screenWidth-20;
break;
// 默认购物车
default :
// 无购物车菜单则结束执行
var tabbar = app.globalData.app_tabbar_pages();
if(tabbar_pos === null) {
tabbar_pos = tabbar.indexOf('/pages/cart/cart');
if(tabbar_pos == -1) {
return false;
}
}
// 计算购物车菜单位置
var tabbar_count = tabbar.length;
var cart_top = info.screenHeight;
var cart_width = info.screenWidth/tabbar_count;
var cart_left = cart_width*tabbar_pos;
}
} else {
var temp = cart[0];
var cart_width = temp.width;
var cart_left = temp.left;
var cart_top = temp.top;
}
/* #ifndef MP-ALIPAY */
var left = pos.changedTouches[0].clientX + btn_width/2 - btn_size/2;
var top = pos.changedTouches[0].clientY - btn_size;
/* #endif */
/* #ifdef MP-ALIPAY */
var left = pos.detail.clientX + btn_width/2 - btn_size/2;
var top = pos.detail.clientY - btn_size;
/* #endif */
var x = cart_left + cart_width/2 - btn_size/2 - left;
var y = cart_top - btn_size - top;
if(self.cart_icon_data == null || (self.cart_icon_data.status || 0) == 0) {
self.setData({cart_icon_data: {
status: 1,
style: `--left:${left}px;--top:${top}px;--x:${x}px;--y:${y}px;`,
icon: icon,
}});
setTimeout(function(){
self.setData({ cart_icon_data: {status: 0}});
}, 495);
}
}
}
}
};
</script>
<style>
@keyframes moveY {
to {
transform: translateY(var(--y));
}
}
@keyframes moveX {
to {
transform: translateX(var(--x));
}
}
.cart-para-curve-container {
width: 60rpx;
height: 60rpx;
z-index: 10;
left: var(--left);
top: var(--top);
--duration: 0.5s;
animation: moveY var(--duration) cubic-bezier(0.5, -0.25, 1, 1);
}
.cart-para-curve-container .cart-para-curve-icon {
max-width: 100%;
max-height: 100%;
animation: moveX var(--duration) linear;
}
</style>

1690
components/cart/cart.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
<template>
<view :class="theme_view">
<view v-if="popup_status && (propData || null) != null">
<view class="plugins-categorylimit-warm-tips-mask wh-auto ht-auto pf"></view>
<view class="plugins-categorylimit-warm-tips-content pf bg-white border-radius-main padding-xxl tc">
<image :src="propData.warm_tips_alert_images" class="dis-block wh-auto radius" mode="aspectFit"></image>
<view v-if="(propData.warm_tips_alert_msg || null) != null" class="margin-top-lg cr-base">{{propData.warm_tips_alert_msg}}</view>
<button type="button" class="bg-main br-main cr-white round text-size-md margin-top-xxxl" @tap="close_event">{{$t('common.confirm')}}</button>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
popup_status: true,
};
},
props: {
propData: {
type: [String,Object],
default: ''
}
},
methods: {
// 关闭弹窗
close_event(e) {
this.setData({
popup_status: false
});
}
}
};
</script>
<style scoped>
.plugins-categorylimit-warm-tips-mask {
left: 0;
top: 0;
background: rgb(0, 0, 0, 0.6);
z-index: 10;
}
.plugins-categorylimit-warm-tips-content {
z-index: 11;
width: calc(100% - 240rpx);
left: 50%;
top: 50%;
height: -webkit-max-content;
height: max-content;
transform: translate(-50%, -50%);
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<view :class="theme_view">
<view v-if="propIsShowAddressChoice" class="choice-location pr wh-auto oh" :style="propLocationContainerStyle" @tap.stop="choose_user_location">
<view class="flex-row gap-2 align-c oh" :style="propLocationImgContainerStyle">
<view v-if="propIsLeftIconArrow" class="dis-inline-block va-m lh">
<block v-if="(propLeftImgValue || null) != null && propLeftImgValue.length > 0">
<image :src="propLeftImgValue[0].url" class="dis-block" mode="heightFix"></image>
</block>
<block v-else>
<iconfont :name="propLeftIconValue" :size="propIconLocationSize" propClass="lh" :color="propIconLocationColor || propBaseColor" :propContainerDisplay="propContainerDisplay"></iconfont>
</block>
</view>
<view :class="'va-m dis-inline-block margin-left-xs single-text text' + (propType == 'header' ? ' text-size-md' : ' text-size-xs')" :style="'max-width:' + propTextMaxWidth + ';color:' + (propTextColor || propBaseColor) + ';'">{{ location.text || '' }}</view>
<view v-if="propIsRightIconArrow" class="va-m lh dis-inline-block margin-left-xs">
<block v-if="(propRightImgValue || null) != null && propRightImgValue.length > 0">
<image :src="propRightImgValue[0].url" class="dis-block" mode="heightFix"></image>
</block>
<block v-else>
<iconfont :name="propRightIconValue" :size="propIconArrowSize" propClass="lh-xs" :color="propIconArrowColor || propBaseColor" :propContainerDisplay="propContainerDisplay"></iconfont>
</block>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
location: {},
cloice_location_timer: null,
};
},
props: {
propIsShowAddressChoice: {
type: Boolean,
default: true,
},
propBaseColor: {
type: String,
default: '#fff',
},
propTextDefaultName: {
type: String,
default: '',
},
propTextColor: {
type: String,
default: '',
},
propTextMaxWidth: {
type: String,
default: '100%',
},
propIconLocationColor: {
type: String,
default: '',
},
propIconLocationSize: {
type: String,
default: '28rpx',
},
propIconArrowColor: {
type: String,
default: '',
},
propIconArrowSize: {
type: String,
default: '24rpx',
},
propIsLeftIconArrow: {
type: Boolean,
default: true,
},
propLeftImgValue: {
type: [Array,String],
default: '',
},
propLeftIconValue: {
type: String,
default: 'icon-location',
},
propIsRightIconArrow: {
type: Boolean,
default: true,
},
propRightImgValue: {
type: [Array,String],
default: '',
},
propRightIconValue: {
type: String,
default: 'icon-arrow-bottom',
},
propType: {
type: String,
default: 'header',
},
propLocationContainerStyle: {
type: String,
default: '',
},
propLocationImgContainerStyle: {
type: String,
default: '',
},
propContainerDisplay: {
type: String,
default: 'inline-block',
}
},
// 页面被展示
created: function () {
this.init();
},
methods: {
// 初始化
init() {
let location = app.globalData.choice_user_location_init();
if ((this.propTextDefaultName || null) != null) {
var default_name = this.$t('shopxo-uniapp.app.4v6q86');
if (location.text == default_name) {
location.text = this.propTextDefaultName;
}
}
this.setData({
location: location,
});
},
// 选择位置监听
choose_user_location(e) {
// 定时任务
clearInterval(this.cloice_location_timer);
var self = this;
var timer = setInterval(function () {
var result = app.globalData.choice_user_location_init() || null;
if (result != null && (result.status == 1 || result.status == 3)) {
self.setData({
location: result,
});
clearInterval(self.cloice_location_timer);
// 回调事件
self.$emit('onBack', result);
}
}, 1000);
this.setData({
cloice_location_timer: timer,
});
// 进入位置选择
app.globalData.choose_user_location_event();
},
},
};
</script>
<style scoped>
.choice-location {
height: 56rpx;
line-height: 56rpx;
}
.dis-block {
width: 100%;
height: 56rpx;
}
</style>

View File

@@ -0,0 +1,250 @@
<template>
<view :class="theme_view">
<!-- 底部菜单 -->
<block v-if="is_tabbar">
<component-diy-footer :propKey="key" :propValue="app_tabbar" @onFooterHeight="footer_height_value_event"></component-diy-footer>
<view v-if="propIsFooterSeat && footer_height_value > 0" :style="'height:'+footer_height_value+'rpx;'"></view>
</block>
<!-- 微信隐私提示弹窗 -->
<view v-if="is_show_privacy" class="agreement-page bs-bb pf wh-auto ht-auto left-0 top-0 z-i-deep-must">
<view class="agreement-content border-radius-main bg-white">
<view class="tc">
<image class="logo circle auto dis-block margin-bottom-lg br" :src="logo" mode="widthFix"></image>
<view class="cr-base fw-b text-size-lg">{{ title }}{{$t('common.warm_tips')}}</view>
</view>
<view class="margin-top-lg text-size-sm cr-base content-desc">
<block v-if="(privacy_content || null) == null">{{$t('agreement.agreement.w38e3v')}}{{ title }}{{$t('agreement.agreement.hjn568')}}</block>
<block v-else>{{ privacy_content }}</block>
</view>
<view class="cr-blue margin-top-lg">
<view>
<text @tap="agreement_event" data-value="userregister">{{ title }}{{$t('agreement.agreement.iy7863')}}</text>
</view>
<view class="margin-top-sm">
<text @tap="agreement_event" data-value="userprivacy">{{ title }}{{$t('agreement.agreement.jwi8n1')}}</text>
</view>
</view>
<view class="buttom tc margin-top-xxxl padding-top-lg">
<button type="default" size="mini" class="btn br-grey cr-base bg-white text-size-sm round margin-right-xxxl" @tap="exit_event">{{$t('agreement.agreement.062co8')}}</button>
<button type="default" size="mini" class="btn br-main cr-white bg-main text-size-sm round margin-left-xxxl" open-type="agreePrivacyAuthorization" @agreeprivacyauthorization="agree_privacy_auth_event">{{$t('agreement.agreement.60t34e')}}</button>
</view>
</view>
</view>
<!-- app管理 -->
<component-app-admin ref="app_admin" :propIsHideStar="true"></component-app-admin>
<!-- 用户基础 -->
<component-user-base ref="user_base" :propIsGrayscale="propIsGrayscale"></component-user-base>
<!-- 弹屏广告 -->
<component-popupscreen ref="popupscreen" :propIsGrayscale="propIsGrayscale"></component-popupscreen>
</view>
</template>
<script>
const app = getApp();
import componentDiyFooter from '@/components/diy/footer';
import componentAppAdmin from '@/components/app-admin/app-admin';
import componentUserBase from '@/components/user-base/user-base';
import componentPopupscreen from '@/components/popupscreen/popupscreen';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
logo: app.globalData.get_application_logo_square(),
title: app.globalData.get_application_title(),
is_show_privacy: false,
privacy_content: null,
key: '',
is_tabbar: false,
app_tabbar: null,
footer_height_value: 0,
};
},
props: {
// 是否灰度
propIsGrayscale: {
type: Boolean,
default: false,
},
// 是否显示底部菜单占位
propIsFooterSeat: {
type: Boolean,
default: true,
},
// 是否引入app管理
propIsAppAdmin: {
type: Boolean,
default: true,
},
// 是否引入用户基础信息提示
propIsUserBase: {
type: Boolean,
default: true,
},
// 是否引入弹屏广告
propIsPopupscreen: {
type: Boolean,
default: true,
},
},
components: {
componentDiyFooter,
componentAppAdmin,
componentUserBase,
componentPopupscreen
},
// 页面被展示
created: function () {
// 初始化配置
this.init_config();
},
methods: {
// 显示响应方法
on_show(params = {}) {
//隐藏系统tabbar
if(app.globalData.data.is_use_native_tabbar != 1) {
app.globalData.system_hide_tabbar();
}
// 初始化配置
this.init_config(false, params);
// 系统底部菜单
this.footer_init();
},
// 初始化配置
init_config(status = false, params = {}) {
if ((status || false) == true) {
// 初始化数据
this.init(params);
} else {
app.globalData.is_config(this, 'init_config', params);
}
},
// 初始化数据
init(params = {}) {
// 系统底部菜单
this.footer_init();
// app管理
if (this.propIsAppAdmin && (this.$refs.app_admin || null) != null) {
this.$refs.app_admin.init(params);
}
// 用户头像和昵称设置提示
if (this.propIsUserBase && (this.$refs.user_base || null) != null) {
this.$refs.user_base.init(params);
}
// 弹屏广告
if (this.propIsPopupscreen && (this.$refs.popupscreen || null) != null) {
this.$refs.popupscreen.init(params);
}
// #ifdef MP-WEIXIN
// 微信协议验证
if (app.globalData.data.is_weixin_privacy_setting == 1) {
uni.getPrivacySetting({
success: (res) => {
if (res.needAuthorization) {
this.setData({
is_show_privacy: true,
privacy_content: app.globalData.get_config('config.common_app_mini_weixin_privacy_content', null),
});
}
}
});
}
// #endif
},
// 底部菜单初始化
footer_init(status = 0) {
var is_use_native_tabbar = app.globalData.data.is_use_native_tabbar == 1;
var upd_data = {
is_tabbar: is_use_native_tabbar ? false : app.globalData.is_tabbar_pages()
};
if(upd_data['is_tabbar']) {
upd_data['key'] = Math.random();
upd_data['app_tabbar'] = app.globalData.get_config('app_tabbar') || null;
}
this.setData(upd_data);
// 如果没有菜单数据则读取一次
if(!is_use_native_tabbar && status == 0 && (upd_data['app_tabbar'] || null) == null) {
app.globalData.init_config(0, this, 'footer_init', 1);
}
},
// 底部菜单高度回调事件
footer_height_value_event(value) {
this.setData({
footer_height_value: (value*2)+20
});
this.$emit('onFooterHeight', value);
// 存储底部菜单高度
app.globalData.app_system_tabbar_height_save(value);
},
// 协议事件
agreement_event(e) {
var value = e.currentTarget.dataset.value || null;
if (value == null) {
app.globalData.showToast(this.$t('login.login.4wc3hr'));
return false;
}
// 是否存在协议 url 地址
var key = 'agreement_' + value + '_url';
var url = app.globalData.get_config('config.' + key) || null;
if (url == null) {
app.globalData.showToast(this.$t('login.login.x0nxxf'));
return false;
}
// 打开 webview
app.globalData.open_web_view(url);
},
// 授权回调
agree_privacy_auth_event() {
this.setData({
is_show_privacy: false
});
},
// 退出小程序
exit_event(e) {
uni.exitMiniProgram();
},
}
};
</script>
<style scoped>
.agreement-page {
background-color: rgba(0, 0, 0, 0.6);
height: 100vh;
padding: 40rpx;
}
.agreement-content {
padding: 40rpx;
position: absolute;
top: 15%;
width: calc(100% - 160rpx);
}
.agreement-content .logo {
width: 160rpx;
height: 160rpx;
}
.agreement-content .content-desc {
line-height: 46rpx;
max-height: calc(30vh);
overflow-y: auto;
}
.agreement-content .buttom .btn {
min-width: 200rpx;
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<view :class="theme_view">
<view class="copyright">
<view class="text">{{application_title}} {{version}}</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
application_title: app.globalData.get_application_title(),
version: app.globalData.data.version
};
},
components: {},
props: {},
methods: {}
};
</script>
<style>
.copyright {
color: #cfcfcf;
text-align: center;
padding: 20rpx 0;
}
.copyright .text {
font-size: 26rpx;
font-weight: 400;
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<view :class="theme_view">
<view class="countdown" v-if="is_show && !is_end">
<block v-if="propMsecShow">
<view class="fr time" :style="time_style">{{ msec }}</view>
<view class="fr ds" :style="ds_style">{{ propSecondDs }}</view>
</block>
<view class="fr time" :style="time_style">{{ second }}</view>
<view class="fr ds" :style="ds_style">{{ propMinuteDs }}</view>
<view class="fr time" :style="time_style">{{ minute }}</view>
<view class="fr ds" :style="ds_style">{{ propHourDs }}</view>
<view class="fr time" :style="time_style">{{ hour }}</view>
</view>
<view v-if="is_show && is_end" class="timer-title">{{ propMsg || this.$t('index.index.443683') }}</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
hour: '00',
minute: '00',
second: '00',
msec: 0,
is_show: true,
is_end: false,
timer: null,
timers: null,
};
},
components: {},
props: {
propHour: {
type: [String, Number],
default: '00',
},
propMinute: {
type: [String, Number],
default: '00',
},
propSecond: {
type: [String, Number],
default: '00',
},
propEndShow: {
type: Boolean,
default: false,
},
propMsecShow: {
type: Boolean,
default: false,
},
propMsg: {
type: String,
default: '',
},
propHourDs: {
type: String,
default: ':',
},
propMinuteDs: {
type: String,
default: ':',
},
propSecondDs: {
type: String,
default: ':',
},
propTimePadding: {
type: [Number, String],
default: 0,
},
propTimeSize: {
type: [Number, String],
default: 24,
},
propTimeBackgroundColor: {
type: String,
default: 'linear-gradient(180deg, #FF601B 0%, #FE1B33 100%);',
},
propTimeColor: {
type: String,
default: '#FFF',
},
propTimeWeight: {
type: [Number, String],
default: '400',
},
propDsColor: {
type: String,
default: '#4B5459',
},
propDsSize: {
type: [Number, String],
default: 24,
},
propDsWeight: {
type: [Number, String],
default: '400',
},
},
computed: {
time_style() {
return 'padding: 0 ' + this.propTimePadding + 'rpx;background:' + this.propTimeBackgroundColor + ';color:' + this.propTimeColor + ';font-size:' + this.propTimeSize + 'rpx;font-weight:' + this.propTimeWeight;
},
ds_style() {
return 'color:' + this.propDsColor + ';font-size:' + this.propDsSize + 'rpx;font-weight:' + this.propDsWeight;
},
},
created: function (e) {
// 参数处理
this.hour = this.propHour;
this.minute = this.propMinute;
this.second = this.propSecond;
// 定时处理
this.countdown();
},
// #ifndef VUE2
destroyed() {
clearInterval(this.timer);
clearInterval(this.timers);
},
// #endif
// #ifdef VUE3
unmounted() {
clearInterval(this.timer);
clearInterval(this.timers);
},
// #endif
methods: {
// 倒计时处理
countdown() {
// 销毁之前的任务
clearInterval(this.timer);
clearInterval(this.timers);
// 秒
var self = this;
var hour = parseInt(self.hour);
var minute = parseInt(self.minute);
var second = parseInt(self.second);
self.timer = setInterval(function () {
if (second <= 0) {
if (minute > 0) {
minute--;
second = 59;
} else if (hour > 0) {
hour--;
minute = 59;
second = 59;
}
} else {
second--;
}
self.hour = hour < 10 ? '0' + hour : hour;
self.minute = minute < 10 ? '0' + minute : minute;
self.second = second < 10 ? '0' + second : second;
if (self.propHour <= 0 && self.propMinute <= 0 && self.propSecond <= 0) {
// 停止时间
clearInterval(self.timer);
clearInterval(self.timers);
self.is_end = true;
// 活动已结束、是否结束还展示
if (!self.propEndShow) {
self.is_show = false;
}
}
}, 1000);
// 毫秒
var count = 0;
self.timers = setInterval(function () {
count++;
self.msec = count;
if (count > 9) {
count = 0;
}
if (!self.is_show) {
clearInterval(self.timers);
}
}, 100);
},
},
};
</script>
<style scoped>
.countdown {
line-height: 38rpx;
}
.countdown .timer-title {
color: #666;
margin-right: 10rpx;
}
.countdown .time {
padding: 0;
-moz-border-radius: 8rpx;
border-radius: 8rpx;
color: #fff;
min-width: 40rpx;
text-align: center;
}
.countdown .ds {
color: #4b5459;
padding: 0 8rpx;
}
</style>

View File

@@ -0,0 +1,230 @@
<template>
<view :class="theme_view">
<view class="padding-bottom padding-horizontal-main">
<view class="coupon-card oh pr flex-row">
<view class="card-left flex-col jc-sa align-c" :class="propStatusType > 3 ? 'failure cr-grey-9' : 'cr-white'">
<view class="price">
<text v-if="propData.type == '0'" class="symbol text-size">{{ currency_symbol }}</text>
<text class="num text-size-xxl">{{ propData.discount_value }}</text>
<text v-if="propData.type !== '0'" class="unit text-size-md">{{ propData.type_unit }}</text>
</view>
<text v-if="(propData.desc || null) != null" class="desc text-size-xs single-text">{{ propData.desc }}</text>
</view>
<view class="card-right flex-1 flex-width flex-row jc-sb align-c" :class="propStatusType > 3 ? 'failure cr-grey-9' : ''">
<view class="card-info flex-1 flex-width padding-right-main" :class="propStatusType > 3 ? 'failure cr-grey-9' : 'cr-black'">
<view class="title text-size-lg single-text" :class="propData.time_start">{{ propData.use_limit_type_name }}</view>
<view v-if="propStartTime && propEndTime" class="date text-size-xs cr-grey-9 single-text padding-top-sm">{{ propStartTime }}-{{ propEndTime }}</view>
<view v-if="propIsProgress && propData.process_data" class="progress padding-top-sm flex-row align-c">
<block v-if="propData.process_data.type == 0">
<text class="text-size-xs cr-grey-9"> {{ propData.process_data.msg }} </text>
</block>
<block v-else>
<progress class="flex-1" :percent="propData.process_data.value" stroke-width="6" activeColor="#FF7004" backgroundColor="#fff" border-radius="3" />
<view class="percent text-size-xss cr-grey-9 padding-left-main"> {{ propData.process_data.msg }} </view>
</block>
</view>
<view v-if="propData.expire_tips" class="padding-top-sm text-size-xs cr-red">{{ propData.expire_tips }}</view>
</view>
<view class="card-type">
<!-- 按钮状态 0-领取1-已领取2-已抢完3-去使用,4-已使用5-已过期 -->
<view v-if="propStatusType == 0" class="card-btn dis-inline-block cr-white" @tap="receive">{{ propStatusOperableName || this.$t('coupon-card.coupon-card.m9316y') }}</view>
<view v-else-if="propStatusType == 1" class="card-btn dis-inline-block cr-red br-red received">{{ propStatusOperableName || this.$t('coupon-card.coupon-card.m9316y') }}</view>
<view v-else-if="propStatusType == 2" class="card-btn dis-inline-block cr-white robbed">{{ propStatusOperableName || this.$t('coupon-card.coupon-card.m9316y') }}</view>
<view v-else-if="propStatusType == 3" :data-value="home_page_url" @tap="url_event" class="cp">
<view class="card-btn dis-inline-block cr-white">
{{ propStatusOperableName || this.$t('coupon-card.coupon-card.m9316y') }}
</view>
</view>
<view v-else-if="propStatusType == 4" class="card-image pa top-0 right-0">
<image class="image" :src="coupon_static_url + 'coupon-used.png'" mode="scaleToFill"></image>
</view>
<view v-else-if="propStatusType == 5" class="card-image pa top-0 right-0">
<image class="image" :src="coupon_static_url + 'coupon-expire.png'" mode="scaleToFill"></image>
</view>
<view v-else @tap="receive">{{$t('coupon-card.coupon-card.j318xx')}}</view>
</view>
</view>
<view class="card-circle-top" :style="{ background: `${propBg}` }"></view>
<view class="card-circle-bottom" :style="{ background: `${propBg}` }"></view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
var coupon_static_url = app.globalData.get_static_url('coupon', true);
var tabbar_pages = app.globalData.app_tabbar_pages();
export default {
name: 'coupon-card',
props: {
propData: {
type: Object,
default: () => {
return {
// id: "0",
// // 领取数量
// already_send_count: 0,
// // 总数
// process_data: {
// type: 1, // 0 无限制
// value: 20,
// msg: "已领20%",
// },
// // 日期有效日期 // 2023.08.30-2023.09.1
// date: "",
// expire_tips: "",
// time_start: "",
// time_end: "",
};
},
},
// 监听
propRandom: {
type: Number,
default: 0,
},
// 半圆背景
propBg: {
type: String,
default: '#fff',
},
// 进度条
propIsProgress: {
type: Boolean,
default: false,
},
// 是否可重复领取
propRepeatedClaim: {
type: Boolean,
default: false,
},
// 是否可点击
propDisabled: {
type: Boolean,
default: false,
},
// 下标
propIndex: {
type: [Number,String],
default: 0,
},
// 按钮状态 0-领取1-已领取2-已抢完3-去使用,4-已使用5-已过期
propStatusType: {
type: Number,
default: 0,
},
// 按钮名称: 领取 已领取 已抢完 去使用
propStatusOperableName: {
type: String,
default: '',
},
// 优惠券有效期
propStartTime:{
type: String,
default: '',
},
propEndTime:{
type: String,
default: '',
}
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
coupon_static_url: coupon_static_url + 'app/',
// 符号
currency_symbol: app.globalData.currency_symbol(),
// 首页地址
home_page_url: tabbar_pages[0],
};
},
methods: {
// 领取
receive(e) {
this.$emit('call-back', this.propIndex, this.propData.id);
},
// url事件
url_event(e) {
app.globalData.url_event(e);
}
},
};
</script>
<style scoped>
.coupon-card {
border-radius: 24rpx;
height: 208rpx;
}
.card-left {
width: 176rpx;
padding: 24rpx 12rpx;
background: linear-gradient(95deg, #ff994b 0%, #ff6e00 100%);
}
.card-left.failure {
background: linear-gradient(95deg, #eeeeee 0%, #fafafa 100%);
}
.card-right {
padding: 32rpx 24rpx 32rpx 46rpx;
background-color: #ffe4d1;
}
.card-right.failure {
background: linear-gradient(95deg, #f8f8f8 0%, #e0dede 100%);
}
.card-info.failure {
padding-right: 116rpx;
}
.card-btn {
width: 116rpx;
text-align: center;
padding: 6rpx 0;
background: linear-gradient(93deg, #ff9747 0%, #ff6e01 100%);
border-radius: 13px;
}
.robbed {
background: #fbd3b7;
}
.received {
border-radius: 13px;
background: transparent;
}
::v-deep .uni-progress-bar,
::v-deep .uni-progress-inner-bar {
border-radius: 6rpx;
}
.card-circle-top,
.card-circle-bottom {
width: 40rpx;
height: 40rpx;
background-color: #fff;
border-radius: 50%;
position: absolute;
left: 180rpx;
z-index: 1;
}
.card-circle-top {
top: -20rpx;
}
.card-circle-bottom {
bottom: -20rpx;
}
.card-image .image {
width: 136rpx;
height: 108rpx;
}
</style>

View File

@@ -0,0 +1,430 @@
<template>
<!-- 文章列表 -->
<view class="oh" :style="style_container">
<view class="oh" :style="style_img_container">
<view class="pr oh" :style="style">
<view v-if="!['4'].includes(article_theme)" class="flex-wrap" :class="article_theme_class" :style="article_theme !== '3' ? article_spacing : ''">
<view v-for="(item, index) in data_list" :key="index" class="item oh" :style="article_style" :data-value="item.data.url" @tap="url_event">
<view :class="article_theme == '0' ? 'flex-row oh' : 'flex-col oh ht-auto'" :style="article_img_style">
<template v-if="article_theme !== '3'">
<view class="oh pr flex-row">
<template v-if="item.new_cover.length > 0">
<image :src="item.new_cover[0].url" class="img" :style="img_radius + img_size" mode="aspectFill" />
</template>
<template v-else>
<image :src="item.data.cover" class="img" :style="img_radius + img_size" mode="aspectFill" />
</template>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
</template>
<view v-if="field_show.includes('0') || field_show.includes('1') || field_show.includes('2') || field_show.includes('3')" class="jc-sb flex-1" :class="article_theme == '3' ? 'flex-row align-c' : 'flex-col'" :style="article_theme !== '0' ? content_padding : ''">
<view class="flex-col" :class="article_theme == '3' ? 'flex-1 flex-width' : ''" :style="'gap:' + name_desc_space + 'px;'">
<view v-if="field_show.includes('3')" class="title" :class="article_theme == '3' ? 'text-line-1' : 'text-line-2'" :style="article_name">{{ item.new_title ? item.new_title : item.data.title }}</view>
<view v-if="field_show.includes('2')" :class="'desc ' + field_desc_row == '2' ? 'text-line-2' : 'text-line-1'" :style="article_desc">{{ item.data.describe || '' }}</view>
</view>
<view class="flex-row jc-sb gap-8" :class="article_theme == '3' ? 'margin-left' : 'align-e margin-top'">
<view :style="article_date">{{ field_show.includes('0') ? item.data.add_time : '' }}</view>
<view v-show="field_show.includes('1')" class="flex-row align-c gap-3" :style="article_page_view">
<iconfont name="icon-eye" propContainerDisplay="flex"></iconfont>
<view>
{{ item.data.access_count ? item.data.access_count : '' }}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-else class="oh" :class="article_theme_class">
<swiper class="swiper" circular :autoplay="is_roll ? true : false" :interval="interval_time" :next-margin="next_margin" :display-multiple-items="slides_per_group" :style="'height:' + carousel_height_computer">
<swiper-item v-for="(item1, index1) in article_carousel_list" :key="index1">
<view class="flex-row ht-auto" :style="article_spacing">
<view v-for="(item, index) in item1.carousel_list" :key="index" class="item oh ht-auto" :style="article_style" :data-value="item.data.url" @tap="url_event">
<view class="oh flex-col ht-auto" :style="article_img_style">
<view class="oh pr wh-auto ht-auto flex-row">
<template v-if="item.new_cover.length > 0">
<image :src="item.new_cover[0].url" class="img" :style="img_radius + 'height:100%;'" mode="aspectFill" />
</template>
<template v-else>
<image :src="item.data.cover" class="img" :style="img_radius + 'height:100%;'" mode="aspectFill" />
</template>
<template v-if="field_show.includes('3') && name_float == '1'">
<view class="text-line-1" :style="article_name + float_name_style">{{ item.new_title ? item.new_title : item.data.title }}</view>
</template>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
<view v-if="field_show.includes('0') || field_show.includes('1') || field_show.includes('2') || (field_show.includes('3') && name_float == '0')" class="jc-sb flex-1 flex-col" :style="article_theme !== '0' ? content_padding : ''">
<view class="flex-col" :style="'gap:' + name_desc_space + 'px;'">
<view v-if="field_show.includes('3') && name_float == '0'" class="title text-line-2" :style="article_name + article_name_height_computer">{{ item.new_title ? item.new_title : item.data.title }}</view>
<view v-if="field_show.includes('2')" :class="'desc ' + field_desc_row == '2' ? 'text-line-2' : 'text-line-1'" :style="article_desc">{{ item.data.describe || '' }}</view>
</view>
<view :class="'flex-row jc-sb gap-8 align-e' + ((field_show.includes('3') && name_float == '0') || field_show.includes('2') ? ' margin-top' : '')">
<view :style="article_date">{{ field_show.includes('0') ? item.data.add_time : '' }}</view>
<view v-show="field_show.includes('1')" class="flex-row align-c gap-3" :style="article_page_view">
<iconfont name="icon-eye" propContainerDisplay="flex"></iconfont>
<view>
{{ item.data.access_count ? item.data.access_count : '' }}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { isEmpty, common_styles_computer, common_img_computer, padding_computer, radius_computer, get_math, gradient_handle, background_computer, gradient_computer, margin_computer, box_shadow_computer, border_computer, old_margin } from '@/common/js/common/common.js';
import subscriptIndex from '@/components/diy/modules/subscript/index.vue';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
subscriptIndex,
},
props: {
propValue: {
type: Object,
default: () => {},
},
// 是否使用公共样式
propIsCommonStyle: {
type: Boolean,
default: true,
},
// 关键key
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
// 数据
data_list: [],
// 风格
article_theme: '0',
// 是否显示
field_show: ['0', '1'],
// 文章
article_name: '',
// 描述
article_desc: '',
// 日期
article_date: '',
// 浏览量
article_page_view: '',
// 内容圆角
content_radius: '',
// 图片圆角
img_radius: '',
// 内间距
content_padding: '',
// 内容间距
content_spacing: '',
// 文章间距
article_spacing: '',
// article_item_height: '',
article_style: '',
article_img_style: '',
// 轮播图定时轮播
interval_time: 2000,
// 轮播图是否滚动
is_roll: 1,
article_theme_class: '',
// 轮播高度
carousel_height_computer: '',
// 文章内容高度
article_name_height_computer: '',
// 文章名称浮动样式
float_name_style: '',
name_float: '0',
// 图片大小
img_size: '',
// 文章轮播数据
article_carousel_list: [],
// 文章描述间距
name_desc_space: 0,
// 一行显示的数量
slides_per_group: 1,
next_margin: '0rpx',
field_desc_row: '1',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
// 描述样式
const desc_size = new_style.desc_size;
let desc_style = 'font-size:' + desc_size + 'px;line-height:' + desc_size + 'px;height:' + desc_size + 'px;color:' + new_style.desc_color + ';';
if (new_content.field_desc_row == '2') {
desc_style = 'font-size:' + desc_size + 'px;line-height:' + (desc_size > 0 ? desc_size + 3 : 0 ) + 'px;height:'+ (desc_size > 0 ? (desc_size + 3) * 2 : 0) + 'px;color:' + new_style.desc_color + ';';
}
this.setData({
field_desc_row: new_content.field_desc_row,
name_float: !isEmpty(new_content.name_float) ? new_content.name_float : '0',
// 判断是自动还是手动
data_list:
new_content.data_type == '0'
? new_content.data_list
: new_content.data_auto_list && new_content.data_auto_list.length > 0
? new_content.data_auto_list.map((item) => ({
id: get_math(),
new_title: '',
new_cover: [],
data: item,
}))
: [],
article_theme_class: this.article_theme_class_computer(new_content.theme),
article_theme: new_content.theme,
field_show: new_content.field_show,
// 样式
article_name: 'font-size:' + new_style.name_size + 'px;' + 'font-weight:' + new_style.name_weight + ';' + 'color:' + new_style.name_color + ';',
article_desc: desc_style,
article_date: 'font-size:' + new_style.time_size + 'px;' + 'font-weight:' + new_style.time_weight + ';' + 'color:' + new_style.time_color + ';',
article_page_view: 'font-size:' + new_style.page_view_size + 'px;' + 'font-weight:' + new_style.page_view_weight + ';' + 'color:' + new_style.page_view_color + ';',
content_radius: radius_computer(new_style.content_radius),
img_radius: radius_computer(new_style.img_radius),
// 内间距
content_padding: padding_computer(new_style.padding),
// 内容间距
content_spacing: `gap: ${new_style.content_spacing}px;`,
// 文章间距
article_spacing: `gap: ${new_style.article_spacing}px;`,
// 描述间距
name_desc_space: parseInt(new_style.name_desc_space),
next_margin: new_style.rolling_fashion == 'translation' ? '-' + new_style.article_spacing_margin + 'px' : '0rpx',
// 文章内容高度
slides_per_group: new_style.rolling_fashion == 'translation' ? Number(new_content.carousel_col) + 1 : 1,
});
// 默认数据
const product_style_list = [
{ name: '单列展示', value: '0', width:110, height: 83 },
{ name: '两列展示(纵向)', value: '1', width:180, height: 180 },
{ name: '大图展示', value: '2', width:0, height: 180 },
{ name: '无图模式', value: '3', width:0, height: 0 },
{ name: '左右滑动展示', value: '4', width:0, height: 0 },
];
const scale = sys_width / 390;
let img_style = ``;
if (['0'].includes(new_content.theme)) {
// 图片宽度
if (typeof new_style.content_img_width == 'number') {
img_style += `width: ${ new_style.content_img_width * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_content.theme);
if (list.length > 0) {
img_style += `width: ${ list[0].width * scale }px;`;
} else {
img_style += 'width: auto;';
}
}
}
if (!['3', '4'].includes(new_content.theme)) {
// 图片宽度
if (typeof new_style.content_img_height == 'number') {
img_style += `height: ${ new_style.content_img_height * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_content.theme);
if (list.length > 0) {
img_style += `height: ${ list[0].height * scale }px;`;
} else {
img_style += 'height: auto;';
}
}
}
// 背景图的处理
const article_data = {
background_img_style: new_style?.article_background_img_style || '',
background_img: new_style?.article_background_img || '',
}
const article_margin = new_style.value?.margin || old_margin;
const margin_width = article_margin.margin_left + article_margin.margin_right;
// 渐变效果
const all_style = gradient_handle(new_style?.article_color_list || [], new_style?.article_direction || '') + margin_computer(article_margin) + box_shadow_computer(new_style) + border_computer(new_style);
// 文章样式
if (this.article_theme == '0') {
this.setData({
img_size: img_style,
article_style: this.content_radius + all_style,
article_img_style: this.content_spacing + this.content_padding + background_computer(article_data)
});
} else if (this.article_theme == '1') {
this.setData({
img_size: img_style,
article_style: `width: calc(50% - ${new_style.article_spacing + (margin_width * 2) / 2}px);` + this.content_radius + all_style,
article_img_style: background_computer(article_data)
});
} else if (this.article_theme == '2') {
this.setData({
img_size: img_style,
article_style: this.content_radius + all_style,
article_img_style: background_computer(article_data)
});
} else if (this.article_theme == '3') {
this.setData({
style: `padding: 0 ${new_style.content_spacing}px;background:#fff;` + this.content_radius,
});
} else if (this.article_theme == '4') {
// 更新轮播图的key确保更换时能重新更新轮播图
const temp_carousel_col = new_content.carousel_col || '1';
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = temp_carousel_col !== '0' ? ((new_style.article_spacing * temp_carousel_col - 1) + (margin_width * temp_carousel_col)) / temp_carousel_col : '0';
const multicolumn_columns_width = new_style.rolling_fashion == 'translation' ? `margin-right: ${ new_style.article_spacing + article_margin.margin_right }px;width:100%;` : `width:calc(${100 / (Number(temp_carousel_col) + 1)}% - ${gap * 2}rpx);min-width:calc(${100 / (Number(temp_carousel_col) + 1)}% - ${gap * 2}rpx);`;
const { name_bg_color_list = [], name_bg_direction = '180deg', name_bg_radius, name_bg_padding, name_bg_margin } = new_style;
const data = {
color_list: name_bg_color_list,
direction: name_bg_direction,
}
let location = 'position:absolute;bottom:0;left:0;right:0;'
// 轮播宽度
this.setData({
// 滚动时间
interval_time: (new_style.interval_time || 2) * 1000,
// 是否滚动修改
is_roll: new_style.is_roll,
// article_item_height: `height: ${new_style.article_height }px`,
article_style: this.content_radius + all_style + multicolumn_columns_width,
// 轮播高度
carousel_height_computer: new_style.article_height * scale + 'px',
// 文章内容高度
article_name_height_computer: `height:${new_style.name_size * 2.4 * 2}rpx;line-height:${new_style.name_size * 1.2 * 2}rpx;`,
float_name_style: gradient_computer(data) + (!isEmpty(name_bg_radius) ? radius_computer(name_bg_radius) : '') + (!isEmpty(name_bg_padding) ? padding_computer(name_bg_padding) : '' ) + (!isEmpty(name_bg_padding) ? margin_computer(name_bg_margin) : '') + location,
article_img_style: background_computer(article_data)
});
// 文章轮播数据
const cloneList = JSON.parse(JSON.stringify(this.data_list));
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (new_style.rolling_fashion != 'translation') {
if (cloneList.length > 0) {
// 每页显示的数量
const num = Number(temp_carousel_col) + 1;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({ carousel_list: cloneList.slice(i * num, (i + 1) * num) });
}
this.setData({
article_carousel_list: nav_list,
});
} else {
// 否则的话,就返回全部的信息
this.setData({
article_carousel_list: [{ carousel_list: cloneList }],
});
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
carousel_list: [item],
});
});
this.setData({
article_carousel_list: nav_list,
});
}
}
if (this.propIsCommonStyle) {
this.setData({
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
}
},
// 文章主题样式
article_theme_class_computer(theme) {
switch (theme) {
case '0':
return 'style1 flex-col';
case '1':
return 'style2 flex-row flex-wrap';
case '2':
return 'style3 flex-col';
case '3':
return 'style4 flex-col';
default:
return 'style5';
}
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style lang="scss" scoped>
.style1 {
.item {
max-width: 100%;
}
}
.style2 {
.item {
.img {
width: 100%;
}
}
}
.style3 {
.item {
width: 100%;
.img {
width: 100%;
}
}
}
.style4 {
.item {
width: 100%;
&:not(:last-child) {
border-bottom: 2rpx solid #eee;
}
}
}
.style5 {
.item {
width: 100%;
.img {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,264 @@
<template>
<!-- 文章列表 -->
<view class="article-tabs ou" :class="'article-tabs-' + propKey" :style="style_container">
<view class="ou" :style="style_img_container">
<componentDiyModulesTabsView :propKey="propKey" :propValue="article_tabs" :propIsTop="top_up == '1'" :propTop="sticky_top" :propStyle="tabs_style" :propsTabsContainer="tabs_container" :propsTabsImgContainer="tabs_img_container" :propCustomNavHeight="propIsTabsUseSafeDistance ? (propCustomNavHeight * 2 + 'rpx') : '0rpx'" :propTabsBackground="tabs_background" :propTabsSlidingFixedBg="tabs_sliding_fixed_bg" @onTabsTap="tabs_click_event"></componentDiyModulesTabsView>
<view :style="article_margin_top">
<view :style="article_container">
<view :style="article_img_container">
<componentDiyArticleList :propKey="diy_key" :propValue="article_tabs" :propIsCommonStyle="false"></componentDiyArticleList>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, padding_computer, margin_computer, background_computer, gradient_computer, radius_computer, isEmpty, box_shadow_computer, border_computer, old_border_and_box_shadow, old_margin, old_padding } from '@/common/js/common/common.js';
import componentDiyModulesTabsView from '@/components/diy/modules/tabs-view';
import componentDiyArticleList from '@/components/diy/article-list'; // 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
props: {
propValue: {
type: Object,
default: () => {},
},
// 距离顶部高度
propTop: {
type: Number,
default: 0,
},
// 自定义导航栏高度
propCustomNavHeight: {
type: Number,
default: 33,
},
// 滚动距离
propScrollTop: {
type: Number,
default: 0,
},
// 顶部导航是否开启沉浸模式
propIsImmersionModel: {
type: Boolean,
default: false,
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
// 选项卡是否使用安全距离
propIsTabsUseSafeDistance: {
type: Boolean,
default: true
}
},
components: {
componentDiyModulesTabsView,
componentDiyArticleList,
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
article_tabs: {},
// 是否滑动置顶
top_up: '0',
tabs_style: '',
tabs_top: 0,
tabs_background: 'background:transparent',
custom_nav_height: 33,
diy_key: '',
// 选项卡背景设置
article_margin_top: '',
tabs_container: '',
tabs_img_container: '',
tabs_sliding_fixed_bg: '',
// 商品区域背景设置
article_container: '',
article_img_container: '',
// #ifdef MP
nav_safe_space: bar_height + 5,
// #endif
// #ifdef H5 || MP-TOUTIAO
nav_safe_space: bar_height + 7,
// #endif
// #ifdef APP
nav_safe_space: bar_height + 0,
// #endif
// 选项卡默认数据
tabs_index: 0,
sticky_top: 0,
};
},
watch: {
// 监听滚动距离
propScrollTop(newVal) {
if (newVal + this.sticky_top + this.custom_nav_height > this.tabs_top + this.nav_safe_space && this.top_up == '1') {
let new_style = this.propValue.style || {};
let tabs_bg = new_style.common_style.color_list;
let new_tabs_background = '';
if (tabs_bg.length > 0 && (tabs_bg[0].color || null) != null) {
new_tabs_background = gradient_computer(new_style.common_style);
}
let new_tabs_background_img = background_computer(new_style.common_style);
if (new_tabs_background_img.length > 0) {
new_tabs_background_img += 'background-position: top left;';
}
this.tabs_background = (new_tabs_background.length > 0 ? new_tabs_background : 'background:#fff;') + new_tabs_background_img;
} else {
this.tabs_background = 'background:transparent';
}
},
propTop(val) {
this.init();
},
propKey(val) {
this.setData({
diy_key: val,
});
// 初始化
this.init();
},
},
created() {
this.init();
},
mounted() {
this.$nextTick(() => {
const self = this;
setTimeout(() => {
self.getTop();
});
// #ifdef H5 || MP-TOUTIAO
// 获取自定义导航栏高度
this.setData({
custom_nav_height: this.propCustomNavHeight,
});
// #endif
});
},
methods: {
// 初始化数据
init() {
let new_content = this.propValue.content || {};
let new_style = this.propValue.style || {};
let new_data = JSON.parse(JSON.stringify(this.propValue));
const new_tabs_data = new_data.content.tabs_list[this.tabs_index] || {};
new_data.content.theme = new_data.content.article_theme;
new_data.content.data_type = new_tabs_data.data_type;
new_data.content.category = new_tabs_data.category;
new_data.content.carousel_col = new_data.content.article_carousel_col;
new_data.content.data_list = new_tabs_data.data_list;
new_data.content.data_auto_list = new_tabs_data.data_auto_list;
new_data.content.data_ids = new_tabs_data.data_ids;
new_data.content.number = new_tabs_data.number;
new_data.content.sort = new_tabs_data.sort;
new_data.content.sort_rules = new_tabs_data.sort_rules;
new_data.content.field_show = new_data.content.field_show;
new_data.content.is_cover = new_tabs_data.is_cover;
// 公共样式
const common_style = new_style.common_style;
let tabs_style_obj = {
padding_top: common_style.padding_top - this.propCustomNavHeight < 0 ? 0 : common_style.padding_top - this.propCustomNavHeight,
padding_left: common_style.padding_left,
padding_right: common_style.padding_right,
};
let new_tabs_style = padding_computer(tabs_style_obj) + margin_computer(tabs_style_obj) + `position:relative;left: -${tabs_style_obj.padding_left * 2}rpx;right: -${tabs_style_obj.padding_right * 2}rpx;width:100%;`;
// 如果是历史数据的话,就执行默认添加下边距
if (isEmpty(new_style.tabs_padding)) {
new_tabs_style += 'padding-bottom: 20rpx;';
}
const { tabs_bg_color_list = [], tabs_bg_direction = '', tabs_bg_background_img_style = '', tabs_bg_background_img = [], tabs_radius = old_radius, tabs_padding = old_padding, article_content_color_list = [], article_content_direction = '', article_content_background_img_style = '', article_content_background_img = [], article_content_margin = old_margin, article_content_padding = old_padding, article_content_radius = old_radius } = new_style;
// 选项卡背景设置
const tabs_data = {
color_list: tabs_bg_color_list,
direction: tabs_bg_direction,
background_img_style: tabs_bg_background_img_style,
background_img: tabs_bg_background_img,
};
// 文章区域背景设置
const article_content_data = {
color_list: article_content_color_list,
direction: article_content_direction,
background_img_style: article_content_background_img_style,
background_img: article_content_background_img,
};
const article_content = new_style?.article_content || old_border_and_box_shadow;
const tabs_content = new_style?.tabs_content || old_border_and_box_shadow;
this.setData({
top_up: new_content.tabs_top_up,
sticky_top: this.propTop + (new_style?.tabs_margin?.margin_top || 0),
article_tabs: new_data,
style_container: common_styles_computer(common_style),
style_img_container: common_img_computer(common_style, this.propIndex),
tabs_style: new_tabs_style,
article_margin_top: 'margin-top:' + (new_style?.article_content_spacing || 0) * 2 + 'rpx',
tabs_sliding_fixed_bg: gradient_computer(tabs_data),
tabs_container: gradient_computer(tabs_data) + radius_computer(tabs_radius) + margin_computer(new_style?.tabs_margin || old_margin) + border_computer(tabs_content) + box_shadow_computer(tabs_content) + 'overflow: hidden;',
tabs_img_container: background_computer(tabs_data) + padding_computer(tabs_padding) + 'box-sizing: border-box;overflow: hidden;',
article_container: gradient_computer(article_content_data) + margin_computer(article_content_margin) + radius_computer(article_content_radius) + box_shadow_computer(article_content) + border_computer(article_content) + 'overflow: hidden;',
article_img_container: background_computer(article_content_data) + padding_computer(article_content_padding) + 'box-sizing: border-box;overflow: hidden;',
});
},
// tabs切换事件
tabs_click_event(index) {
let new_data = JSON.parse(JSON.stringify(this.propValue));
new_data.content.theme = new_data.content.article_theme;
new_data.content.data_type = new_data.content.tabs_list[index].data_type;
new_data.content.category = new_data.content.tabs_list[index].category;
new_data.content.carousel_col = new_data.content.article_carousel_col;
new_data.content.data_list = new_data.content.tabs_list[index].data_list;
new_data.content.data_auto_list = new_data.content.tabs_list[index].data_auto_list;
new_data.content.data_ids = new_data.content.tabs_list[index].data_ids;
new_data.content.number = new_data.content.tabs_list[index].number;
new_data.content.sort = new_data.content.tabs_list[index].sort;
new_data.content.sort_rules = new_data.content.tabs_list[index].sort_rules;
new_data.content.field_show = new_data.content.field_show;
new_data.content.is_cover = new_data.content.tabs_list[index].is_cover;
this.setData({
tabs_index: index,
article_tabs: new_data,
diy_key: Math.random(),
});
},
// 获取商品距离顶部的距离
getTop() {
const query = uni.createSelectorQuery().in(this);
query
.select('.article-tabs-' + this.propKey)
.boundingClientRect((res) => {
if ((res || null) != null) {
let new_data = typeof this.propValue == 'string' ? JSON.parse(JSON.stringify(this.propValue)) : this.propValue;
const new_style = new_data.style || {};
this.setData({
tabs_top: res.top - (new_style.common_style?.margin_top || 0) + (new_style?.tabs_margin?.margin_top || 0),
});
}
})
.exec();
},
},
};
</script>
<style lang="scss" scoped>
.tabs-container {
position: relative;
width: 100%;
left: 0;
right: 0;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<view :style="style_container">
<view :style="style_img_container">
<view :style="style"></view>
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
init() {
const { height } = this.propValue.content;
const { line_color, common_style } = this.propValue.style;
this.setData({
style: `height: ${height * 2}rpx;background: ${line_color || 'transparent'};`,
style_container: common_styles_computer(common_style),
style_img_container: common_img_computer(common_style, this.propIndex),
});
},
},
};
</script>
<style scoped lang="scss">
.right-0 {
top: 50%;
transform: translateY(-50%);
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<!-- 横线 -->
<view :style="style_container">
<view :style="style_img_container">
<view :style="style"></view>
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
// key
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
// 边框设置
let border_content = `border-bottom-style: ${new_content?.styles || 'solid'};`;
// 边框颜色设置
let border_style = `border-bottom-width: ${new_style.line_width * 2 || 2}rpx; border-bottom-color: ${new_style.line_color || 'rgba(204, 204, 204, 1)'};`;
this.setData({
style: border_content + border_style,
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
},
};
</script>
<style></style>

371
components/diy/carousel.vue Normal file
View File

@@ -0,0 +1,371 @@
<template>
<view class="pr" :style="style_container + swiper_bg_style">
<view class="pa top-0 wh-auto ht-auto" :style="swiper_bg_img_style"></view>
<view class="pr" :style="style_img_container + (!isEmpty(swiper_bg_img_style) ? swiper_bg_img_style_null : '')">
<swiper circular="true" :autoplay="form.is_roll == '1'" :interval="form.interval_time * 1000" :display-multiple-items="slides_per_group" :duration="500" :style="{ height: swiper_height }" :previous-margin="previousMargin" :next-margin="nextMargin" @change="slideChange">
<block v-if="form.carousel_type == 'card'">
<swiper-item v-for="(item, index) in new_list" :key="index">
<view class="flex-row align-c wt-auto ht-auto" :data-value="item.carousel_link.page" @tap="url_open">
<view class="swiper-item" :style="img_style" :class="['scale-defalt', { 'scale-1': animationData === index }]">
<view class="wh-auto ht-auto">
<imageEmpty :propImageSrc="item.carousel_img[0]" :propStyle="img_style" :propImgFit="img_fit" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
</view>
<view v-if="new_style.video_is_show == '1' && item.carousel_video.length > 0" :class="{ 'x-middle': new_style.video_location == 'center', 'right-0': new_style.video_location == 'flex-end' }" class="video-class pa oh" :style="{'bottom': new_style.video_bottom * 2 + 'rpx'}">
<view class="flex-row gap-5 align-c" :style="video_style" :data-value="item.carousel_video" @tap.stop="video_play">
<block v-if="new_style.video_type == 'img'">
<view class="video_img">
<imageEmpty :propImageSrc="new_style.video_img[0]" propImgFit="aspectFill" propErrorStyle="width: 28rpx;height: 28rpx;"></imageEmpty>
</view>
</block>
<block v-else>
<iconfont :name="!isEmpty(new_style.video_icon_class) ? 'icon-' + new_style.video_icon_class : 'icon-bofang'" size="'28rpx'" :color="new_style.video_icon_color" propContainerDisplay="flex"></iconfont>
</block>
<text v-if="!isEmpty(item.video_title)" :style="{ color: new_style.video_title_color, 'font-size': new_style.video_title_size * 2 + 'rpx', 'text-wrap': 'nowrap' }">{{ item.video_title }}</text>
</view>
</view>
</view>
</swiper-item>
</block>
<block v-else>
<swiper-item v-for="(item, index) in new_list" :key="index">
<view class="ht-auto" :style="['oneDragOne', 'twoDragOne'].includes(form.carousel_type) ? 'padding-right:' + new_style.image_spacing * 2 + 'rpx;' : ''" :data-value="item.carousel_link.page" @tap="url_open">
<view class="wh-auto ht-auto pr" :style="img_style">
<imageEmpty :propImageSrc="item.carousel_img[0]" :propStyle="img_style" :propImgFit="img_fit" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
<view v-if="new_style.video_is_show == '1' && item.carousel_video.length > 0" :class="{ 'x-middle': new_style.video_location == 'center', 'right-0': new_style.video_location == 'flex-end' }" class="video-class pa oh" :style="{'bottom': new_style.video_bottom * 2 + 'rpx'}">
<view class="flex-row gap-5 align-c" :style="video_style" :data-value="item.carousel_video" @tap.stop="video_play">
<block v-if="new_style.video_type == 'img'">
<view class="video_img">
<imageEmpty :propImageSrc="new_style.video_img[0]" propImgFit="aspectFill" propErrorStyle="width: 28rpx;height: 28rpx;"></imageEmpty>
</view>
</block>
<block v-else>
<iconfont :name="!isEmpty(new_style.video_icon_class) ? 'icon-' + new_style.video_icon_class : 'icon-bofang'" size="'28rpx'" :color="new_style.video_icon_color" propContainerDisplay="flex"></iconfont>
</block>
<text v-if="!isEmpty(item.video_title)" :style="{ color: new_style.video_title_color, 'font-size': new_style.video_title_size * 2 + 'rpx', 'text-wrap': 'nowrap' }">{{ item.video_title }}</text>
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
<view v-if="new_style.is_show == '1'" :class="['left', 'right'].includes(new_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="indicator_location_style">
<template v-if="new_style.indicator_style == 'num'">
<view :style="indicator_style" class="dot-item">
<text :style="{ color: new_style.actived_color }">{{ actived_index + 1 }}</text>
<text>/{{ form.carousel_list.length }}</text>
</view>
</template>
<template v-else>
<view v-for="(item, index2) in form.carousel_list" :key="index2" :style="indicator_style + (actived_index == index2 ? 'background:' + new_style.actived_color : '')" class="dot-item" />
</template>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, radius_computer, isEmpty, gradient_computer, padding_computer, get_indicator_location_style, get_indicator_style, background_computer } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
imageEmpty,
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propIsCommon: {
type: Boolean,
default: true,
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propOuterContainerPadding: {
type: Number,
default: 0,
}
},
data() {
return {
form: {},
new_style: {},
// 通用样式显示
style_container: '',
style_img_container: '',
// 图片的设置
img_style: '',
// 指示器的样式
indicator_style: '',
seat_list: [],
new_list: [],
// 指示器选中的位置
actived_index: 0,
interval_types: '',
img_fit: '',
dot_style: '',
video_style: '',
popup_width: '0rpx',
popup_height: '0rpx',
// 样式二的处理
animation: '',
animationData: 0,
previousMargin: '0rpx',
nextMargin: '0rpx',
slides_per_group: 1,
// hackReset: true,
// 轮播图的高度
swiper_height: 50,
// 轮播时的背景样式
swiper_bg_style: '',
swiper_bg_img_style: '',
swiper_bg_img_style_null: `background-image: url('')`
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
isEmpty,
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
// 获取当前手机的宽度
const { windowWidth } = uni.getSystemInfoSync();
// 将80%的宽度分成16份
const block = (windowWidth * 0.8) / 16;
const { common_style, actived_color } = new_style;
// scaleToFill 对应 fill aspectFit 对应 contain aspectFill 对应 cover
let fit = '';
if (new_form.img_fit == 'contain') {
fit = 'aspectFit';
} else if (new_form.img_fit == 'fill') {
fit = 'scaleToFill';
} else if (new_form.img_fit == 'cover') {
fit = 'aspectFill';
}
const { margin_left, margin_right, padding_left, padding_right } = new_style.common_style;
const width = sys_width - margin_left - margin_right - padding_left - padding_right - this.propOuterContainerPadding;
const scale = width / 390;
this.setData({
form: new_form,
new_style: new_style,
seat_list: this.get_seat_list(new_form),
new_list: new_form.carousel_list.concat(this.get_seat_list(new_form)),
popup_width: block * 16 * 2 + 'rpx', // 视频的宽度依照16:9比例来算
popup_height: block * 9 * 2 + 'rpx', // 视频的高度
style_container: this.propIsCommon ? common_styles_computer(common_style) : '', // 公共样式显示
style_img_container: this.propIsCommon ? common_img_computer(common_style, this.propIndex) : '', // 公共样式显示
img_style: radius_computer(new_style), // 图片的设置
indicator_style: get_indicator_style(new_style), // 指示器的样式
indicator_location_style: get_indicator_location_style(new_style),
dot_style: `bottom: ${ new_style.indicator_bottom * scale }px;`, // 指示器位置
img_fit: fit, // 图片风格 默认为aspectFill
video_style: this.get_video_style(new_style), // 视频播放按钮显示逻辑
swiper_height: new_form.height * scale + 'px', // 轮播图高度
swiper_bg_style: this.get_swiper_bg_style(new_form, 0),
swiper_bg_img_style: this.get_swiper_bg_img_style(new_form, 0),
});
// 风格二显示逻辑
if (new_form.carousel_type == 'card') {
// this.$nextTick(() => {
this.setData({
previousMargin: '41px',
nextMargin: '41px',
animationData: 0,
});
// });
} else if (new_form.carousel_type != 'inherit') {
// 风格三,四显示逻辑
// this.$nextTick(() => {
this.setData({
nextMargin: '50px',
slides_per_group: new_form.carousel_type == 'twoDragOne' ? 2 : 1,
});
// });
}
},
get_swiper_bg_style(form, actived_index) {
if (!this.propIsCommon) {
return '';
}
const style = form?.carousel_list?.[actived_index]?.style;
if (style && !isEmpty(style.color_list)) {
const color_list = style.color_list;
const list = color_list.filter((item) => !isEmpty(item.color));
if (list.length > 0) {
try {
return gradient_computer(style);
} catch (error) {
return '';
}
}
return '';
}
return '';
},
get_swiper_bg_img_style(form, actived_index) {
if (!this.propIsCommon) {
return '';
}
const { carousel_img, style = {} } = form?.carousel_list[actived_index] || {};
// 如果是自定义的图片 判断图片是否存在
if (!isEmpty(carousel_img) && style?.background_type == 'carousel') {
// 如果是使用轮播图,判断轮播图是否存在
const data = {
background_img: carousel_img,
background_img_style: style?.background_img_style || '2',
}
return background_computer(data) + (style.is_background_img_blur == '1' ? `filter: blur(14px);opacity: 0.6;` : '');
} else if (!isEmpty(style?.background_img)) {
return background_computer(style) + (style.is_background_img_blur == '1' ? `filter: blur(14px);opacity: 0.6;` : '');
}
return '';
},
get_seat_list(form) {
if (form.carousel_list.length > 3) {
return [];
} else {
let seat_list = [];
const list = JSON.parse(JSON.stringify(form.carousel_list));
switch (list.length) {
case 1:
seat_list = [...list, ...list, ...list];
break;
case 2:
seat_list.push(...list);
break;
case 3:
seat_list.push(...list);
break;
default:
break;
}
return seat_list;
}
},
slideChange(e) {
let actived_index = e.detail.current;
if (e.detail.current > this.form.carousel_list.length - 1) {
const seat_length = this.seat_list.length;
if (this.form.carousel_list.length > 1) {
actived_index = actived_index - seat_length;
} else {
actived_index = 0;
}
}
if (!this.propIsCommon) {
this.$emit('slideChange', actived_index);
}
this.setData({
animationData: e.detail.current,
actived_index: actived_index,
swiper_bg_style: this.get_swiper_bg_style(this.form, actived_index),
swiper_bg_img_style: this.get_swiper_bg_img_style(this.form, actived_index),
});
},
get_video_style(new_style) {
const { video_radius, video_color_list, video_direction, video_title_color, video_padding } = new_style;
let style = ``;
if (!isEmpty(video_radius)) {
style += radius_computer(video_radius);
}
const data = {
color_list: video_color_list,
direction: video_direction,
};
style += gradient_computer(data) + padding_computer(video_padding) + `color: ${video_title_color};box-sizing: border-box;`;
return style;
},
video_play(e) {
const list = e.currentTarget.dataset.value;
if (!isEmpty(list)) {
this.$emit('onVideoPlay', list[0].url, this.popup_width, this.popup_height);
}
},
url_open(link) {
app.globalData.url_event(link);
},
},
};
</script>
<style scoped lang="scss">
.dot-center {
left: 50%;
transform: translateX(-50%);
}
.dot-right {
right: 0;
}
.dot {
z-index: 1;
padding-left: 20rpx;
padding-right: 20rpx;
.dot-item {
margin: 0 6rpx;
}
}
.swiper-container {
display: flex;
align-items: center;
}
.swiper-item {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin-left: auto;
margin-right: auto;
height: 90%;
width: 90%;
text-align: center;
}
.scale-defalt {
position: relative;
border-radius: 20rpx;
transform: scale(1);
transition: -webkit-transform 400ms linear, transform 400ms linear;
transform-origin: 50% 50% 0px;
&.scale-1 {
transform: scale(1.1);
}
}
.video_img {
max-width: 120rpx;
height: 28rpx;
}
.video-class {
max-width: 100%;
padding-right: 20rpx;
padding-left: 20rpx;
}
.x-middle {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
</style>

1013
components/diy/coupon.vue Normal file

File diff suppressed because it is too large Load Diff

337
components/diy/custom.vue Normal file
View File

@@ -0,0 +1,337 @@
<template>
<view :style="style_container">
<view :style="style_img_container">
<view :style="style_content_container">
<view class="w h pr" :style="style_content_img_container">
<template v-if="!isEmpty(form.data_source) && form.data_source_is_loop !== '0'">
<template v-if="data_source_content_list.length > 0 && form.data_source_direction == 'vertical'">
<view class="flex-row flex-wrap" :style="'row-gap:' + new_style.row_gap + 'px;column-gap:' + new_style.column_gap + 'px;'">
<view v-for="(item, index) in data_source_content_list" :key="index" class="ht-auto" :style="gap_width">
<view v-for="(item1, index1) in item.split_list" :key="index1">
<view :style="style_chunk_container">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propFieldList="field_list" :propDataHeight="form.height" :propScale="scale" :propDataIndex="index" :propDataSplitIndex="index1" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</view>
</view>
</template>
<view v-else-if="data_source_content_list.length > 0 && ['vertical-scroll', 'horizontal'].includes(form.data_source_direction)" class="oh ht-auto">
<swiper class="w flex" circular="true" :vertical="form.data_source_direction != 'horizontal'" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :duration="500" :display-multiple-items="slides_per_view" :style="{ width: '100%', height: swiper_height + 'px' }" @change="slideChange">
<swiper-item v-for="(item, index) in data_source_content_list" :key="index">
<view :class="form.data_source_direction != 'horizontal' ? 'ht-auto ' : 'flex-row'" :style="form.data_source_direction == 'horizontal' ? 'column-gap:' + new_style.column_gap + 'px;' : ''">
<view v-for="(item1, index1) in item.split_list" :key="index1" class="wh-auto ht-auto" :style="style_chunk_container + swiper_width + (form.data_source_direction == 'horizontal' ? gap_width : 'margin-bottom:' + content_outer_spacing_magin)">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propFieldList="field_list" :propScale="scale" :propDataIndex="index" :propDataSplitIndex="index1" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="new_style.is_show == '1' && data_source_content_list.length > 1" :class="['left', 'right'].includes(new_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="indicator_location_style">
<block v-if="new_style.indicator_style == 'num'">
<view :style="indicator_style" class="dot-item">
<text :style="{ color: new_style.actived_color }">{{ actived_index + 1 }}</text>
<text>/{{ data_source_content_list.length }}</text>
</view>
</block>
<block v-else>
<view v-for="(item, index) in data_source_content_list" :key="index" :style="indicator_style + (actived_index == index ? 'background:' + new_style.actived_color : '')" class="dot-item" />
</block>
</view>
</view>
<template v-else>
<view :style="style_chunk_container">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propConfigLoop="form.data_source_is_loop || '1'" :propFieldList="field_list" :propDataHeight="form.height" :propScale="scale" @url_event="url_event"></dataRendering>
</view>
</view>
</template>
</template>
<template v-else-if="!isEmpty(form.data_source) && form.data_source_is_loop == '0'">
<view class="h" :style="style_chunk_container">
<view class="w h oh" :style="style_chunk_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propSourceList="{}" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propFieldList="field_list" :propDataHeight="form.height" :propScale="scale" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</template>
<template v-else>
<view :style="style_chunk_container">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propConfigLoop="form.data_source_is_loop || '1'" :propFieldList="field_list" :propDataHeight="form.height" :propScale="scale" @url_event="url_event"></dataRendering>
</view>
</view>
</template>
</view>
</view>
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer, percentage_count, isEmpty, get_indicator_style, get_indicator_location_style, border_width } from '@/common/js/common/common.js';
import dataRendering from '@/components/diy/modules/custom/data-rendering.vue';
const app = getApp();
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
dataRendering
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propIsCommonStyle: {
type: Boolean,
default: true,
},
propOuterContainerPadding: {
type: Number,
default: 0,
}
},
data() {
return {
form: {},
new_style: {},
scale: 1,
style_container: '',
style_img_container: '',
div_width: 0,
div_height: 0,
custom_list_length: 0,
source_list: {
// 存放手动输入的id
data_ids: [],
// 手动输入
data_list: [],
// 自动
data_auto_list: [],
},
data_source_content_list: [],
data_source: '',
// 内容样式
style_content_container: '',
style_content_img_container: '',
// 数据样式
style_chunk_container: '',
style_chunk_img_container: '',
// 指示器选中的下标
actived_index: 0,
// 轮播高度
swiper_height: 0,
swiper_width: 'width: 100%;',
// 指示器样式
indicator_location_style: '',
indicator_style: '',
slides_per_view: 1,
show_data: { data_key: 'id', data_name: 'name' },
old_data_style: {
color_list: [{ color: 'rgb(244, 252, 255)', color_percentage: undefined }],
direction: '180deg',
background_img_style: '2',
background_img: [],
radius: 0,
radius_top_left: 0,
radius_top_right: 0,
radius_bottom_left: 0,
radius_bottom_right: 0,
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
},
content_outer_spacing_magin: '0rpx',
gap_width: '',
field_list: [],
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
methods: {
percentage_count,
isEmpty,
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
// 不包含新创建的数组时,将历史数据放到手动添加数组中
if (!Object.keys(new_form.data_source_content).includes('data_auto_list') && !Object.keys(new_form.data_source_content).includes('data_list')) {
//深拷贝一下,保留历史数据
const data = JSON.parse(JSON.stringify(new_form.data_source_content));
new_form.data_source_content = this.source_list;
// 如果老数组中有数据,将数据放到新数组中
if (!isEmpty(data)) {
new_form.data_source_content.data_list = [ data ];
}
}
// 数据来源的内容
let list = [];
if (new_form.is_custom_data == '1') {
if (Number(new_form.data_source_content.data_type) === 0) {
list = new_form.data_source_content?.data_list || [];
} else {
list = !isEmpty(new_form.data_source_content) ?
new_form.data_source_content.data_auto_list.map(item => ({
id: Math.random(),
new_cover: [],
new_title: '',
data: item,
})) : [];
}
} else {
list = new_form.data_source_content?.data_list || [];
}
// 数组处理
const new_list = list.length > 0 ? this.get_list(list, new_form, new_style) : [];
// 初始化数据
const { common_style, data_content_style, data_style } = new_style;
// 外层左右间距
const outer_spacing = (common_style?.margin_left || 0) + (common_style?.margin_right || 0) + (common_style?.padding_left || 0) + (common_style?.padding_right || 0) + border_width(common_style);
// 内容左右间距
const content_spacing = (data_content_style?.margin_left || 0) + (data_content_style?.margin_right || 0) + (data_content_style?.padding_left || 0) + (data_content_style?.padding_right || 0) + border_width(data_content_style);
// 数据左右间距
const internal_spacing = (data_style?.margin_left || 0) + (data_style?.margin_right || 0) + (data_style?.padding_left || 0) + (data_style?.padding_right || 0) + border_width(data_style);
// 一行显示的数量
const carousel_col = Number(new_form.data_source_carousel_col);
// 数据间距
const data_spacing = ['vertical', 'horizontal'].includes(new_form.data_source_direction) ? new_style.column_gap * (carousel_col - 1) : 0;
// 自定义组件宽度
const width = sys_width - outer_spacing - content_spacing - internal_spacing - data_spacing - this.propOuterContainerPadding;
const new_data_style = !isEmpty(new_style.data_style) ? new_style.data_style : this.old_data_style;
const new_data_content_style = !isEmpty(new_style.data_content_style)? new_style.data_content_style : this.old_data_style;
// 判断是平移还是整屏滚动
const { padding_top = 0, padding_bottom = 0, margin_bottom = 0, margin_top = 0 } = new_data_style;
let swiper_height = 0;
const scale_number = width / 390;
const new_scale = scale_number > 0 ? scale_number : 0;
// 间距
const space_between = new_form.data_source_direction == 'horizontal' ? new_style.column_gap : new_style.row_gap;
let col = Number(new_form.data_source_carousel_col);
// 轮播图高度控制
if (new_form.data_source_direction == 'horizontal') {
swiper_height = new_form.height * new_scale + padding_top + padding_bottom + margin_bottom + margin_top;
} else {
// 商品数量大于列数的时候,高度是列数,否则是当前的数量
col = new_list.length > carousel_col ? carousel_col : new_list.length;
swiper_height = (new_form.height * new_scale + padding_top + padding_bottom + margin_bottom + margin_top) * col + ((Number(new_form.data_source_carousel_col) - 1) * space_between);
}
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = (new_style.column_gap * (carousel_col - 1)) / carousel_col;
// 横向的时候,根据选择的行数和每行显示的个数来区分具体是显示多少个
const swiper_width = (new_form.data_source_direction == 'horizontal' && new_style.rolling_fashion != 'translation') ? `width: ${ 100 / carousel_col }%;`: 'width: 100%;';
this.setData({
form: new_form,
new_style: new_style,
div_width: width,
scale: new_scale,
custom_list_length: new_form.custom_list.length - 1,
style_container: this.propIsCommonStyle ? common_styles_computer(new_style.common_style) + 'box-sizing: border-box;' : '', // 用于样式显示
style_img_container: this.propIsCommonStyle ? common_img_computer(new_style.common_style, this.propIndex) : '',
style_content_container: common_styles_computer(new_data_content_style) + 'box-sizing: border-box;', // 用于样式显示
style_content_img_container: common_img_computer(new_data_content_style),
style_chunk_container: common_styles_computer(new_data_style) + 'box-sizing: border-box;', // 用于样式显示
style_chunk_img_container: common_img_computer(new_data_style),
style_chunk_width: width,
div_height: new_form.height,
data_source_content_list: new_list,
data_source: !isEmpty(new_form.data_source)? new_form.data_source : '',
indicator_style: get_indicator_style(new_style), // 指示器的样式
indicator_location_style: get_indicator_location_style(new_style),
swiper_height: swiper_height,
swiper_width: swiper_width,
content_outer_spacing_magin: space_between + 'px',
gap_width: `width: calc(${100 / carousel_col}% - ${gap}px);`,
slides_per_view: new_style.rolling_fashion == 'translation' ? (new_form.data_source_direction != 'horizontal' ? col : new_form.data_source_carousel_col ) : 1,
show_data: new_form?.show_data || { data_key: 'id', data_name: 'name' },
field_list: new_form?.field_list || [],
});
},
get_list(list, form, new_style) {
// 深拷贝一下,确保不会出现问题
const cloneList = JSON.parse(JSON.stringify(list));
if (new_style.rolling_fashion != 'translation' && form.data_source_direction != 'vertical') {
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (cloneList.length > 0) {
// 每页显示的数量
const num = form.data_source_carousel_col;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: cloneList.slice(i * num, (i + 1) * num),
});
}
return nav_list;
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: cloneList,
},
];
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
}
},
slideChange(e) {
this.setData({
actived_index: e.detail.current,
});
},
url_event(e, index, split_index) {
if (this.data_source == 'goods' && this.data_source_content_list.length > 0) {
const list = this.data_source_content_list[index];
if (!isEmpty(list) && !isEmpty(list.split_list[split_index])) {
const new_list = list.split_list[split_index];
if (!isEmpty(new_list)) {
// 缓存商品数据
app.globalData.goods_data_cache_handle(new_list.data.id, new_list.data);
}
}
}
app.globalData.url_open(e);
},
},
};
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,434 @@
<template>
<view class="img-magic" :style="'height:' + container_size + ';' + style_container">
<view class="magic-container w h pr" :style="style_img_container">
<view class="pr" :style="'width:calc(100% + ' + outer_spacing + ');height:calc(100% + ' + outer_spacing + ');margin:-' + spacing + ';'">
<!-- 风格9 -->
<template v-if="form.style_actived == 7">
<view class="flex-row align-c jc-c style-size flex-wrap">
<view v-for="(item, index) in data_magic_list" :key="index" :style="'margin:' + spacing + ';' + ([0, 1].includes(index) ? 'width:calc(50% - ' + outer_spacing + ');height:calc(50% - ' + outer_spacing + ')' : 'width:calc((100% / 3) - ' + outer_spacing + ');height:calc(50% - ' + outer_spacing + ')')" class="style9">
<view class="w h flex-row" :style="item.data_style.background_style + content_radius">
<view class="re flex-1 oh" :style="item.data_style.background_img_style">
<template v-if="item.data_content.data_type == 'goods'">
<view class="w h flex-col" :style="'gap:'+ item.data_style.title_data_gap * 2 + 'rpx;'">
<view v-if="(!isEmpty(item.data_content.heading_title) || !isEmpty(item.data_content.subtitle)) && [0, 1].includes(index)" :class="'tl' + (item.data_style.title_line == '1' ? ' flex-row align-c' : ' flex-col')" :style="'gap:' + item.data_style.title_gap * 2 + 'rpx;'">
<template v-if="item.data_content.heading_title_type && item.data_content.heading_title_type == 'image'">
<view v-if="item.data_content.heading_title_img.length > 0" class="re oh" :style="'width:100%;height:' + (!isEmpty(item.data_style.heading_img_height) ? item.data_style.heading_img_height * magic_scale : 0) + 'px'">
<image :src="item.data_content.heading_title_img[0].url" class="ht-auto max-w" mode="heightFix" />
</view>
</template>
<template v-else>
<view class="ma-0 w text-word-break text-line-1 flex-basis-shrink" :style="item.data_style.heading_style">{{ item.data_content.heading_title || '' }}</view>
</template>
<template v-if="item.data_content.subtitle_title_type && item.data_content.subtitle_title_type == 'image'">
<view v-if="item.data_content.subtitle_title_img.length > 0" class="re oh" :style="'width:100%;height:' + (!isEmpty(item.data_style.subtitle_img_height) ? item.data_style.subtitle_img_height * magic_scale : 0) + 'px'">
<image :src="item.data_content.subtitle_title_img[0].url" class="ht-auto max-w" mode="heightFix" />
</view>
</template>
<template v-else>
<view class="ma-0 w text-word-break text-line-1 flex-basis-shrink" :style="item.data_style.subtitle_style">{{ item.data_content.subtitle || '' }}</view>
</template>
</view>
<view class="w h flex-1 oh flex-row">
<magic-carousel class="flex-1" :propKey="propKey + index" :propValue="item" :propGoodStyle="item.data_style" :propActived="form.style_actived" propType="product" :propDataIndex="index" @onCarouselChange="carousel_change"></magic-carousel>
</view>
</view>
</template>
<template v-else-if="item.data_content.data_type == 'images'">
<view class="w h flex-1 oh flex-row">
<magic-carousel class="flex-1" :propKey="propKey + index" :propValue="item" propType="img" :propActived="form.style_actived" :propDataIndex="index" @onCarouselChange="carousel_change"></magic-carousel>
</view>
</template>
<template v-else-if="item.data_content.data_type == 'custom'">
<customIndex :propKey="propKey + index" :propValue="item" :propMagicScale="magic_scale" :propDataSpacing="new_style.image_spacing" :propDataIndex="index" @onCarouselChange="carousel_change"></customIndex>
</template>
<template v-else>
<videoIndex :propKey="propKey + index" :propValue="item.data_content" :propDataStyle="item.data_style"></videoIndex>
</template>
<view v-if="item.data_style.is_show == '1' && item.data_content.list.length > 1" :class="['left', 'right'].includes(item.data_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="item.data_style.indicator_location_style">
<template v-if="item.data_style.indicator_style == 'num'">
<view :style="item.data_style.indicator_styles" class="dot-item">
<text class="num-active" :style="{ color: item.data_style.actived_color }">{{ item.actived_index + 1 }}</text
><text>/{{ item.data_content.list.length }}</text>
</view>
</template>
<template v-else>
<view v-for="(item3, index3) in item.data_content.list" :key="index3" :style="item.data_style.indicator_styles + style_actived_color(item, index3)" class="dot-item" />
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(item, index) in data_magic_list" :key="index" class="cr-main cube-selected" :style="selected_style(item) + ';margin:' + spacing + ';'">
<view class="w h flex-row" :style="item.data_style.background_style + content_radius">
<view class="re flex-1 oh" :style="item.data_style.background_img_style">
<template v-if="item.data_content.data_type == 'goods'">
<view class="w h flex-col" :style="'gap:'+ item.data_style.title_data_gap * 2 + 'rpx;'">
<view v-if="!isEmpty(item.data_content.heading_title) || !isEmpty(item.data_content.subtitle)" :class="'tl' + (item.data_style.title_line == '1' ? ' flex-row align-c' : ' flex-col')" :style="'gap:' + item.data_style.title_gap * 2 + 'rpx;'">
<template v-if="item.data_content.heading_title_type && item.data_content.heading_title_type == 'image'">
<view v-if="item.data_content.heading_title_img.length > 0" class="re oh" :style="'width:100%;height:' + (!isEmpty(item.data_style.heading_img_height) ? item.data_style.heading_img_height * magic_scale : 0) + 'px'">
<image :src="item.data_content.heading_title_img[0].url" class="ht-auto max-w" mode="heightFix" />
</view>
</template>
<template v-else>
<view class="ma-0 w text-word-break text-line-1 flex-basis-shrink" :style="item.data_style.heading_style">{{ item.data_content.heading_title || '' }}</view>
</template>
<template v-if="item.data_content.subtitle_title_type && item.data_content.subtitle_title_type == 'image'">
<view v-if="item.data_content.subtitle_title_img.length > 0" class="re oh" :style="'width:100%;height:' + (!isEmpty(item.data_style.subtitle_img_height) ? item.data_style.subtitle_img_height * magic_scale : 0) + 'px'">
<image :src="item.data_content.subtitle_title_img[0].url" class="ht-auto max-w" mode="heightFix" />
</view>
</template>
<template v-else>
<view class="ma-0 w text-word-break text-line-1 flex-basis-shrink" :style="item.data_style.subtitle_style">{{ item.data_content.subtitle || '' }}</view>
</template>
</view>
<view class="w h oh flex-row">
<magic-carousel class="flex-1" :propKey="propKey + index" :propValue="item" :propGoodStyle="item.data_style" propType="product" :propActived="form.style_actived" :propDataIndex="index" @onCarouselChange="carousel_change"></magic-carousel>
</view>
</view>
</template>
<template v-else-if="item.data_content.data_type == 'images'">
<view class="w h oh flex-row">
<magic-carousel class="flex-1" :propKey="propKey + index" :propValue="item" propType="img" :propActived="form.style_actived" :propDataIndex="index" @onCarouselChange="carousel_change"></magic-carousel>
</view>
</template>
<template v-else-if="item.data_content.data_type == 'custom'">
<customIndex :propKey="propKey + index" :propValue="item" :propMagicScale="magic_scale" :propDataSpacing="new_style.image_spacing" :propDataIndex="index" @onCarouselChange="carousel_change"></customIndex>
</template>
<template v-else>
<videoIndex :propKey="propKey + index" :propValue="item.data_content" :propDataStyle="item.data_style"></videoIndex>
</template>
<view v-if="item.data_style.is_show == '1' && item.data_content.list.length > 1" :class="['left', 'right'].includes(item.data_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="item.data_style.indicator_location_style">
<template v-if="item.data_style.indicator_style == 'num'">
<view :style="item.data_style.indicator_styles" class="dot-item">
<text class="num-active" :style="{ color: item.data_style.actived_color }">{{ item.actived_index + 1 }}</text>
<text>/{{ item.data_content.list.length }}</text>
</view>
</template>
<template v-else>
<view v-for="(item3, index3) in item.data_content.list" :key="index3" :style="item.data_style.indicator_styles + style_actived_color(item, index3)" class="dot-item" />
</template>
</view>
</view>
</view>
</view>
</template>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import magicCarousel from '@/components/diy/modules/data-magic/magic-carousel.vue';
import customIndex from '@/components/diy/modules/data-magic/custom/index.vue';
import videoIndex from '@/components/diy/modules/data-magic/video/index.vue';
import { background_computer, common_styles_computer, common_img_computer, gradient_computer, radius_computer, percentage_count, isEmpty, padding_computer, margin_computer, old_border_and_box_shadow, old_margin, old_padding, box_shadow_computer, border_computer, get_indicator_location_style, get_indicator_style, border_width } from '@/common/js/common/common.js';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
magicCarousel,
customIndex,
videoIndex
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propOuterContainerPadding: {
type: Number,
default: 0,
}
},
data() {
return {
form: {},
new_style: {},
outer_spacing: '',
// 图片间距设置
spacing: '',
// 内容圆角设置
content_radius: '',
// 图片圆角设置
// content_img_radius: '',
data_magic_list: [],
cubeCellWidth: 0,
container_size: 0,
style_container: '',
style_img_container: '',
div_width: 0,
magic_scale: 1,
// is_unlimited_size: false,
};
},
computed: {
// 根据当前页面大小计算成百分比
selected_style() {
return (item) => {
return `overflow: hidden;width: calc(${this.percentage(this.getSelectedWidth(item))} - ${this.outer_spacing} ); height: calc(${this.percentage(this.getSelectedHeight(item))} - ${this.outer_spacing} ); top: ${this.percentage(this.getSelectedTop(item))}; left: ${this.percentage(this.getSelectedLeft(item))};`;
};
},
style_actived_color() {
return (item, index) => {
return item.actived_index == index ? `background: ${item.data_style.actived_color};` : '';
};
},
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
methods: {
isEmpty,
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
const container_height = !isEmpty(new_form.container_height) ? new_form.container_height : sys_width;
const density = !isEmpty(new_form.magic_cube_density) ? new_form.magic_cube_density : 4;
const { margin_left, margin_right } = new_style.common_style;
const width = sys_width - margin_left - margin_right - border_width(new_style.common_style) - this.propOuterContainerPadding;
this.setData({
form: new_form,
new_style: new_style,
outer_spacing: new_style.image_spacing * 2 + 'rpx',
spacing: new_style.image_spacing + 'rpx',
content_radius: radius_computer(new_style.data_radius),
// content_img_radius: radius_computer(new_style.img_radius),
data_magic_list: this.get_data_magic_list(new_form.data_magic_list, new_style),
style_container: common_styles_computer(new_style.common_style) + 'box-sizing: border-box;', // 用于样式显示
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
magic_scale: width / 390,
div_width: sys_width,
cubeCellWidth: sys_width / density,
container_size: container_height * (width / 390) + 'px',
});
},
get_data_magic_list(data, new_style) {
data.forEach((item) => {
const data_content = item.data_content;
const data_style = item.data_style;
item.actived_index = 0;
// 指示器样式
data_style.indicator_styles = get_indicator_style(data_style);
data_style.indicator_location_style = get_indicator_location_style(data_style);
// 获取当前的margin
const chunk_margin = data_style?.chunk_margin || old_margin;
// 计算左右间距
const left_right_width_margin = (chunk_margin?.margin_left || 0) + (chunk_margin?.margin_right || 0);
// 计算上下间距
const top_bottom_height_margin = (chunk_margin?.margin_top || 0) + (chunk_margin?.margin_bottom || 0);
data_style.background_style = gradient_computer(data_style) + margin_computer(data_style?.chunk_margin || old_margin) + `width: calc(100% - ${ left_right_width_margin }px);height:calc(100% - ${ top_bottom_height_margin }px);` + box_shadow_computer(data_style?.data_common_style || old_border_and_box_shadow) + border_computer(data_style?.data_common_style || old_border_and_box_shadow);
data_style.background_img_style = background_computer(data_style) + padding_computer(data_style?.chunk_padding || old_padding);
// 商品价格处理
data_style.goods_price_symbol_style = this.goods_trends_config(data_style, 'price_symbol');
data_style.goods_price_unit_style = this.goods_trends_config(data_style, 'price_unit');
let fit = '';
if (data_content.img_fit == 'contain') {
fit = 'aspectFit';
} else if (data_content.img_fit =='fill') {
fit = 'scaleToFill';
} else if (data_content.img_fit == 'cover') {
fit = 'aspectFill';
}
data_content.fit = fit;
// 商品名称和价格样式
data_style.goods_title_style = this.goods_trends_config(data_style, 'title') + `line-height: ${ (item.data_style.goods_title_size + 3) * 2 }rpx;height: ${ (item.data_style.goods_title_size + 3) * 2 }rpx;`;
data_style.goods_price_style = this.goods_trends_config(data_style, 'price') + `line-height: ${ item.data_style.goods_price_size * 2 }rpx;height: ${ (item.data_style.goods_title_size + 3) * 2 }rpx;`;
const radius = !isEmpty(data_style.img_radius) ? data_style.img_radius : { radius: 4, radius_top_left: 4, radius_top_right: 4, radius_bottom_left: 4, radius_bottom_right: 4 };
data_style.get_img_radius = radius_computer(radius);
data_style.chunk_padding_data = padding_computer(data_style.chunk_padding) + 'box-sizing: border-box;';
data_style.heading_style = this.trends_config(data_style, 'heading');
data_style.subtitle_style = this.trends_config(data_style, 'subtitle');
if (data_content.data_type == 'goods') {
data_content.list = this.commodity_list(data_content.goods_list, data_content.goods_num, data_content, data_style);
} else if (data_content.data_type == 'custom' && ['vertical-scroll', 'horizontal'].includes(data_content.data_source_direction)) {
// 是自定义并且是轮播状态的时候,添加数据
const list = this.data_source_content_list(data_content);
const carousel_col = data_content?.data_source_carousel_col || 1;
const num = new_style.rolling_fashion == 'translation' ? list.length : Math.ceil(list.length / carousel_col);
data_content.list = Array(num);
} else {
data_content.list = data_content.images_list;
}
});
return data;
},
// 数据来源的内容
data_source_content_list(data_content){
if (data_content.is_custom_data == '1') {
if (Number(data_content.data_source_content.data_type) === 0) {
return data_content.data_source_content.data_list;
} else {
return data_content.data_source_content.data_auto_list.map((item) => ({
id: Math.random(),
new_cover: [],
new_title: '',
data: item,
}));
}
} else {
return data_content.data_source_content.data_list;
}
},
/*
** 组装产品的数据
** @param {Array} list 商品列表
** @param {Number} num 显示数量
** @return {Array}
*/
commodity_list(list, num, data_content, data_style) {
if (list.length > 0) {
// 深拷贝一下,确保不会出现问题
const goods_list = JSON.parse(JSON.stringify(list)).map((item) => ({
...item.data,
title: !isEmpty(item.new_title) ? item.new_title : item.data.title,
new_cover: item.new_cover,
}));
// 存储数据显示
let nav_list = [];
// 如果是滑动,需要根据每行显示的个数来区分来拆分数据 translation 表示的是平移
if (data_style.rolling_fashion != 'translation') {
// 拆分的数量
const split_num = Math.ceil(goods_list.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({ split_list: goods_list.slice(i * num, (i + 1) * num) });
}
return nav_list;
} else {
return this.rotation_calculation(goods_list, num, data_content, data_style);
}
} else {
return [];
}
},
rotation_calculation(list, num, data_content, data_style) {
// 存储数据显示
let nav_list = [];
const goods_outerflex = data_content.goods_outerflex;
const rotation_direction = data_style.rotation_direction;
// 如果是商品是横排的,轮播也是横排的,就不对商品进行拆分/如果商品是竖排的,轮播也是竖排的,不对商品进行拆分
if ((goods_outerflex == 'row' && rotation_direction == 'horizontal') || (goods_outerflex == 'col' && rotation_direction == 'vertical')) {
list.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
} else {
// 拆分的数量
const split_num = Math.ceil(list.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({ split_list: list.slice(i * num, (i + 1) * num) });
}
return nav_list;
}
return nav_list;
},
getSelectedWidth(item) {
return (item.end.x - item.start.x + 1) * this.cubeCellWidth;
},
//计算选中层的高度。
getSelectedHeight(item) {
return (item.end.y - item.start.y + 1) * this.cubeCellWidth;
},
//计算选中层的右边距离。
getSelectedTop(item) {
return (item.start.y - 1) * this.cubeCellWidth;
},
//计算选中层的左边距离。
getSelectedLeft(item) {
return (item.start.x - 1) * this.cubeCellWidth;
},
// 计算成百分比
percentage(num) {
return percentage_count(num, this.div_width);
},
goods_trends_config(style, key) {
return this.text_style(style[`goods_${key}_typeface`], style[`goods_${key}_size`], style[`goods_${key}_color`]);
},
// 根据传递的参数,从对象中取值
trends_config(style, key) {
return this.text_style(style[`${key}_typeface`], style[`${key}_size`], style[`${key}_color`]);
},
text_style(typeface, size, color) {
return `font-weight:${typeface}; font-size: ${size * 2}rpx; color: ${color};`;
},
carousel_change(actived_index, index) {
if (this.data_magic_list[index]) {
this.data_magic_list[index].actived_index = actived_index;
}
},
},
};
</script>
<style lang="scss" scoped>
// 图片魔方是一个正方形,根据宽度计算高度
.img-magic {
overflow: hidden;
}
.cube-selected {
position: absolute;
text-align: center;
box-sizing: border-box;
}
.text-line-1 {
word-break: break-word;
overflow-wrap: break-word;
word-wrap: break-word;
}
.style-size {
height: 100%;
width: 100%;
.style9 {
position: relative;
overflow: hidden;
}
}
.dot-center {
left: 50%;
transform: translateX(-50%);
}
.dot-right {
right: 0;
}
.dot {
z-index: 3;
.dot-item {
margin: 0 6rpx;
}
}
.gap-20 {
gap: 40rpx;
}
.w {
width: 100%;
}
.h {
height: 100%;
}
.flex-basis-shrink {
flex-basis: content;
flex-shrink: 1;
}
</style>

View File

@@ -0,0 +1,334 @@
<template>
<view class="data-tabs ou" :class="'data-tabs-' + propKey" :style="style_container">
<view class="ou" :style="style_img_container">
<componentDiyModulesTabsView :propKey="propKey" :propValue="data_tabs" :propIsTop="top_up == '1'" :propTop="sticky_top" :propStyle="tabs_style" :propsTabsContainer="tabs_container" :propsTabsImgContainer="tabs_img_container" :propCustomNavHeight="propIsTabsUseSafeDistance ? (propCustomNavHeight * 2 + 'rpx') : '0rpx'" :propTabsSlidingFixedBg="tabs_sliding_fixed_bg" :propTabsBackground="tabs_background" @onTabsTap="tabs_click_event"></componentDiyModulesTabsView>
<view :style="data_margin_top">
<view :style="data_container">
<view :style="data_img_container">
<template v-if="tabs_data_type == 'goods'">
<view class="oh" :style="data_content_container">
<view class="oh" :style="data_content_img_container">
<componentGoodsList ref="diy_goods_list" :propKey="diy_key" :propDiyIndex="propDiyIndex" :propValue="tabs_list" :propIsCommonStyle="false" @goods_buy_event="goods_buy_event"></componentGoodsList>
</view>
</view>
</template>
<template v-else-if="tabs_data_type == 'article'">
<view class="oh" :style="data_content_container">
<view class="oh" :style="data_content_img_container">
<componentArticleList :propKey="diy_key" :propValue="tabs_list" :propIsCommonStyle="false"></componentArticleList>
</view>
</view>
</template>
<template v-else-if="tabs_data_type == 'custom'">
<componentCustomList :propKey="diy_key" :propValue="tabs_list" :propOuterContainerPadding="outer_container_width" :propIsCommonStyle="false"></componentCustomList>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, padding_computer, margin_computer, background_computer, gradient_computer, isEmpty, old_border_and_box_shadow, old_margin, old_radius, old_padding, border_computer, box_shadow_computer, radius_computer, get_math } from '@/common/js/common/common.js';
import componentDiyModulesTabsView from '@/components/diy/modules/tabs-view';
import componentGoodsList from '@/components/diy/goods-list';
import componentArticleList from '@/components/diy/article-list';
import componentCustomList from '@/components/diy/custom';
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
components: {
componentDiyModulesTabsView,
componentGoodsList,
componentArticleList,
componentCustomList,
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propTop: {
type: Number,
default: 0,
},
propCustomNavHeight: {
type: Number,
default: 33,
},
propScrollTop: {
type: Number,
default: 0,
},
// 顶部导航是否开启沉浸模式
propIsImmersionModel: {
type: Boolean,
default: false,
},
propKey: {
type: [String, Number],
default: '',
},
propDiyIndex: {
type: Number,
default: 0,
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 0,
},
// 选项卡是否使用安全距离
propIsTabsUseSafeDistance: {
type: Boolean,
default: true
}
},
data() {
return {
style_container: '',
style_img_container: '',
data_tabs: {},
tabs_list: {},
// 是否滑动置顶
top_up: '0',
tabs_top: 0,
tabs_background: 'background:transparent',
custom_nav_height: 33,
diy_key: '',
// 选项卡背景设置
tabs_container: '',
tabs_img_container: '',
// 商品区域背景设置
data_margin_top: '',
data_container: '',
data_img_container: '',
// #ifdef MP
nav_safe_space: bar_height + 5,
// #endif
// #ifdef H5 || MP-TOUTIAO
nav_safe_space: bar_height + 7,
// #endif
// #ifdef APP
nav_safe_space: bar_height + 0,
// #endif
tabs_style: '',
// 选项卡默认数据
tabs_index: 0,
sticky_top: 0,
tabs_data_type: 'goods',
outer_container_width: 0,
// 数据样式
data_content_container: '',
data_content_img_container: '',
tabs_sliding_fixed_bg: '',
// 滑动固定的背景
tabs_sliding_fixed_bg: '',
data_content_style: {
color_list: [{ color: '', color_percentage: undefined }],
direction: '180deg',
background_img_style: '2',
background_img: [],
radius: 0,
radius_top_left: 0,
radius_top_right: 0,
radius_bottom_left: 0,
radius_bottom_right: 0,
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
// 边框样式
border_is_show: '0',
border_color: '#FF3F3F',
border_style: 'solid',
border_size: {
padding: 1,
padding_top: 1,
padding_right: 1,
padding_bottom: 1,
padding_left: 1,
},
// 阴影
box_shadow_color: '',
box_shadow_x: 0,
box_shadow_y: 0,
box_shadow_blur: 0,
box_shadow_spread: 0,
}
};
},
watch: {
propScrollTop(newVal) {
if (newVal + this.sticky_top + this.custom_nav_height > this.tabs_top + this.nav_safe_space && this.top_up == '1') {
let new_style = this.propValue.style || {};
let tabs_bg = new_style.common_style.color_list;
let new_tabs_background = '';
if (tabs_bg.length > 0 && (tabs_bg[0].color || null) != null) {
new_tabs_background = gradient_computer(new_style.common_style);
}
let new_tabs_background_img = background_computer(new_style.common_style);
if (new_tabs_background_img.length > 0) {
new_tabs_background_img += 'background-position: top left;';
}
this.tabs_background = (new_tabs_background.length > 0 ? new_tabs_background : 'background:#fff;') + new_tabs_background_img;
} else {
this.tabs_background = 'background:transparent';
}
},
propTop(val) {
this.init();
},
propKey(val) {
// 初始化
this.setData({
diy_key: val,
});
this.init();
},
},
created() {
this.init();
},
mounted() {
this.$nextTick(() => {
const self = this;
setTimeout(() => {
self.getTop();
});
// #ifdef H5 || MP-TOUTIAO
// 获取自定义导航栏高度
this.setData({
custom_nav_height: this.propCustomNavHeight,
});
// #endif
});
},
methods: {
init() {
let new_data = typeof this.propValue == 'string' ? JSON.parse(JSON.stringify(this.propValue)) : this.propValue;
const new_content = new_data.content || {};
const new_style = new_data.style || {};
// 公共样式
const common_style = new_style.common_style;
let tabs_style_obj = {
padding_top: (common_style.padding_top - this.propCustomNavHeight) < 0 ? 0 : common_style.padding_top - this.propCustomNavHeight,
padding_left: common_style.padding_left,
padding_right: common_style.padding_right,
};
let new_tabs_style = padding_computer(tabs_style_obj) + `position:relative;left: -${tabs_style_obj.padding_left * 2}rpx;right: -${tabs_style_obj.padding_right * 2}rpx;width:100%;`;
// 如果是历史数据的话,就执行默认添加下边距
if (isEmpty(new_style.tabs_padding)) {
new_tabs_style += 'padding-bottom: 20rpx;';
}
const { tabs_bg_color_list = [], tabs_bg_direction = '', tabs_bg_background_img_style = '', tabs_bg_background_img = [], tabs_radius = old_radius, tabs_padding = old_padding, data_content_color_list = [], data_content_direction = '', data_content_background_img_style = '', data_content_background_img = [], data_content_margin = old_margin, data_content_padding = old_padding, data_content_radius = old_radius } = new_style;
// 选项卡背景设置
const tabs_data = {
color_list: tabs_bg_color_list,
direction: tabs_bg_direction,
background_img_style: tabs_bg_background_img_style,
background_img: tabs_bg_background_img,
};
// 商品区域背景设置
const data_content_data = {
color_list: data_content_color_list,
direction: data_content_direction,
background_img_style: data_content_background_img_style,
background_img: data_content_background_img,
};
const data_content = new_style?.data_content || old_border_and_box_shadow;
const tabs_content = new_style?.tabs_content || old_border_and_box_shadow;
//显示的数据处理
this.tabs_click_event(0);
this.setData({
top_up: new_content.tabs_top_up,
sticky_top: this.propTop + (new_style?.tabs_margin?.margin_top || 0),
data_tabs: new_data,
// 自定义需要做等比缩放,因此宽度需要减去 外层通用的宽度和内容区域的宽度
outer_container_width: common_style.margin_left + common_style.margin_right + common_style.padding_left + common_style.padding_right + new_style.data_content_margin.margin_left + new_style.data_content_margin.margin_right + new_style.data_content_padding.padding_left + new_style.data_content_padding.padding_right,
style_container: common_styles_computer(common_style),
style_img_container: common_img_computer(common_style, this.propIndex),
tabs_style: new_tabs_style,
tabs_sliding_fixed_bg: gradient_computer(tabs_data),
tabs_container: gradient_computer(tabs_data) + radius_computer(tabs_radius) + margin_computer(new_style?.tabs_margin || old_margin) + border_computer(tabs_content) + box_shadow_computer(tabs_content) + 'overflow: hidden;',
tabs_img_container: background_computer(tabs_data) + padding_computer(tabs_padding) + 'box-sizing: border-box;overflow: hidden;',
data_margin_top: 'margin-top:' + (new_style?.data_content_spacing || 0) * 2 + 'rpx;',
data_container: gradient_computer(data_content_data) + margin_computer(data_content_margin) + radius_computer(data_content_radius) + box_shadow_computer(data_content) + border_computer(data_content) + 'overflow: hidden;',
data_img_container: background_computer(data_content_data) + padding_computer(data_content_padding) + 'box-sizing: border-box;overflow: hidden;',
});
},
tabs_click_event(index) {
let new_data = JSON.parse(JSON.stringify(this.propValue));
// 显示的数据处理
const tabs_data_list = new_data.content.tabs_list[index] || {};
const tabs_data_type = tabs_data_list?.tabs_data_type || '';
let tabs_list = {};
// 内容样式
let data_content_container = '';
let data_content_img_container = '';
if (tabs_data_type === 'goods') {
tabs_list = tabs_data_list.goods_config;
const new_style = tabs_data_list.goods_config.style;
// 内容样式
data_content_container = common_styles_computer(new_style?.data_content_style || this.data_content_style);
data_content_img_container = common_img_computer(new_style?.data_content_style || this.data_content_style);
} else if (tabs_data_type === 'article') {
tabs_list = tabs_data_list.article_config;
const new_style = tabs_data_list.article_config.style;
// 内容样式
data_content_container = common_styles_computer(new_style?.data_content_style || this.data_content_style);
data_content_img_container = common_img_computer(new_style?.data_content_style || this.data_content_style);
} else if (tabs_data_type === 'custom') {
tabs_list = tabs_data_list.custom_config;
}
this.setData({
tabs_data_type: tabs_data_type,
tabs_index: index,
tabs_list: tabs_list,
data_content_container: data_content_container,
data_content_img_container: data_content_img_container,
diy_key: get_math(),
});
},
// 获取商品距离顶部的距离
getTop() {
const query = uni.createSelectorQuery().in(this);
query
.select('.data-tabs-' + this.propKey)
.boundingClientRect((res) => {
if ((res || null) != null) {
let new_data = typeof this.propValue == 'string' ? JSON.parse(JSON.stringify(this.propValue)) : this.propValue;
const new_style = new_data.style || {};
this.setData({
tabs_top: res.top - (new_style.common_style?.margin_top || 0),
});
}
})
.exec();
},
goods_buy_event(index, goods = {}, params = {}, back_data = null) {
this.$emit('goods_buy_event', index, goods, params, back_data);
},
goods_cart_back_event(e) {
if ((this.$refs.diy_goods_list || null) != null) {
this.$refs.diy_goods_list.goods_cart_back_event(e);
}
},
},
};
</script>
<style scoped lang="scss"></style>

784
components/diy/diy.vue Normal file
View File

@@ -0,0 +1,784 @@
<template>
<view :style="page_style">
<view :style="page_img_style">
<scroll-view :scroll-y="true" class="ht" @scroll="on_scroll_event" @scrolltolower="on_scroll_lower_event" @scrolltoupper="on_scroll_upper_event" lower-threshold="60" scroll-with-animation="true">
<!-- 头部小程序兼容 -->
<view class="pr header">
<componentDiyHeader :propKey="header_data.id" :propValue="header_data.com_data" :propScrollTop="head_scroll_top" @onImmersionModelCallBack="immersion_model_call_back" @onLocationBack="choice_location_back"></componentDiyHeader>
</view>
<view :style="content_padding">
<view class="content flex-col" :style="'padding-top:calc(' + (temp_is_header_top ? temp_header_top + temp_sticky_top + 'px)' : '0px)')">
<view v-for="item in tabs_data" :key="item.key">
<template v-if="item.is_enable == '1'">
<componentDiyTabs v-if="item.key == 'tabs'" :propIndex="is_immersive_style_and_general_safe_distance_value ? item.index : -1" :propContentPadding="content_padding" :propValue="item.com_data" :propTop="get_tabs_data_prop_top" :propStickyTop="get_tabs_data_prop_sticky_top" :propIsImmersionModel="is_immersion_model && is_the_safe_distance_enabled && item.com_data.content.tabs_top_up == '1'" :propNavIsTop="is_header_top" :propTabsIsTop="true" @onComputerHeight="tabs_height_event" @onTabsTap="tabs_click_event"></componentDiyTabs>
<componentDiyTabsCarousel v-else-if="item.key == 'tabs-carousel'" :propIndex="is_immersive_style_and_general_safe_distance_value ? item.index : -1" :propContentPadding="content_padding" :propValue="item.com_data" :propTop="get_tabs_data_prop_top" :propStickyTop="get_tabs_data_prop_sticky_top" :propIsImmersionModel="is_immersion_model && is_the_safe_distance_enabled" :propScrollTop="scroll_top" :propTabsIsTop="true" @onComputerHeight="tabs_height_event" @onTabsTap="tabs_click_event" @onVideoPlay="video_play"></componentDiyTabsCarousel>
</template>
</view>
<template v-if="is_tabs_type">
<template v-if="diy_data.length > 0">
<view v-for="(item, index) in diy_data" :key="index" :style="'margin-top:' + (['float-window'].includes(item.key) ? '0rpx;z-index:1' : -(item.com_data.style.common_style.floating_up * 2 || 0) + 'rpx;z-index:' + (!isEmpty(item.com_data.style.common_style.module_z_index) ? item.com_data.style.common_style.module_z_index : 0))">
<!-- 基础组件 -->
<template v-if="item.is_enable == '1'">
<componentDiySearch v-if="item.key == 'search'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiySearch>
<componentDiyCarousel v-else-if="item.key == 'carousel'" :propOuterContainerPadding="outer_container_padding" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data" @onVideoPlay="video_play"></componentDiyCarousel>
<componentDiyNavGroup v-else-if="item.key == 'nav-group'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyNavGroup>
<componentDiyUserInfo v-else-if="item.key == 'user-info'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyUserInfo>
<componentDiyNotice v-else-if="item.key == 'notice'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyNotice>
<componentDiyVideo v-else-if="item.key == 'video'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyVideo>
<componentDiyArticleList v-else-if="item.key == 'article-list'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyArticleList>
<componentDiyArticleTabs v-else-if="item.key == 'article-tabs'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data" :propTop="get_diy_prop_top(item.com_data)" :propScrollTop="scroll_top" :propCustomNavHeight="get_diy_custom_nav_height(item.com_data)" :propIsTabsUseSafeDistance="getPropIsTabsUseSafeDistance"></componentDiyArticleTabs>
<componentDataTabs v-else-if="item.key == 'data-tabs'" :ref="'diy_goods_buy' + index" :propIndex="get_prop_index(item)" :propDiyIndex="index" :propKey="item.id + index" :propValue="item.com_data" :propTop="get_diy_prop_top(item.com_data)" :propScrollTop="scroll_top" :propCustomNavHeight="get_diy_custom_nav_height(item.com_data)" :propIsTabsUseSafeDistance="getPropIsTabsUseSafeDistance" @goods_buy_event="goods_buy_event"></componentDataTabs>
<componentDiyGoodsTabs v-else-if="item.key == 'goods-tabs'" :ref="'diy_goods_buy' + index" :propIndex="get_prop_index(item)" :propDiyIndex="index" :propKey="item.id + index" :propValue="item.com_data" :propTop="get_diy_prop_top(item.com_data)" :propScrollTop="scroll_top" :propCustomNavHeight="get_diy_custom_nav_height(item.com_data)" :propIsTabsUseSafeDistance="getPropIsTabsUseSafeDistance" @goods_buy_event="goods_buy_event"></componentDiyGoodsTabs>
<componentDiyGoodsList v-else-if="item.key == 'goods-list'" :ref="'diy_goods_buy' + index" :propIndex="get_prop_index(item)" :propDiyIndex="index" :propKey="item.id + index" :propValue="item.com_data" @goods_buy_event="goods_buy_event"></componentDiyGoodsList>
<componentDiyDataMagic v-else-if="item.key == 'data-magic'" :propOuterContainerPadding="outer_container_padding" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyDataMagic>
<componentDiyCustom v-else-if="item.key == 'custom'" :propOuterContainerPadding="outer_container_padding" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyCustom>
<componentDiyImgMagic v-else-if="item.key == 'img-magic'" :propOuterContainerPadding="outer_container_padding" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyImgMagic>
<componentDiyHotZone v-else-if="item.key == 'hot-zone'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyHotZone>
<componentDiySeckill v-else-if="item.key == 'seckill'" :propOuterContainerPadding="outer_container_padding" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiySeckill>
<!-- 插件 -->
<componentDiyCoupon v-else-if="item.key == 'coupon'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyCoupon>
<!-- 工具组件 -->
<componentDiyFloatWindow v-else-if="item.key == 'float-window'" :propKey="item.id + index" :propValue="item.com_data"></componentDiyFloatWindow>
<componentDiyTitle v-else-if="item.key == 'title'" :propKey="item.id + index" :propIndex="get_prop_index(item)" :propValue="item.com_data"></componentDiyTitle>
<componentDiyAuxiliaryLine v-else-if="item.key == 'row-line'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyAuxiliaryLine>
<componentDiyRichText v-else-if="item.key == 'rich-text'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyRichText>
<componentDiyAuxiliaryBlank v-else-if="item.key == 'auxiliary-blank'" :propIndex="get_prop_index(item)" :propKey="item.id + index" :propValue="item.com_data"></componentDiyAuxiliaryBlank>
</template>
</view>
</template>
<!-- diy底部卡槽 -->
<slot name="diy-bottom-content"></slot>
<slot name="diy-bottom-common"></slot>
</template>
<template v-else>
<!-- 商品九宫格列表 -->
<view v-if="goods_list.length > 0" class="padding-horizontal-main padding-top-main oh">
<component-goods-list :propData="{ style_type: goods_show_type_value, goods_list: goods_list, random: random_value }" :propLabel="plugins_label_data" :propCurrencySymbol="currency_symbol"></component-goods-list>
</view>
<view v-else class="pr">
<!-- 提示信息 -->
<component-no-data :propStatus="goods_list_loding_status" :propMsg="goods_list_loding_msg" propLoadingLogoTop="30%"></component-no-data>
</view>
<!-- diy底部卡槽 -->
<template v-if="goods_bottom_line_status">
<slot name="diy-bottom-content"></slot>
</template>
<slot name="diy-bottom-common"></slot>
</template>
<view class="z-i-deep">
<!-- 商品购买 -->
<component-goods-buy ref="goods_buy" v-on:CartSuccessEvent="goods_cart_back_event"></component-goods-buy>
<!-- 视频播放 -->
<uni-popup ref="popup" type="center" border-radius="20rpx" :mask-click="false">
<view class="flex-col align-c jc-c gap-10">
<video :src="video_src" id="carousel_video" :autoplay="true" :controls="true" show-fullscreen-btn class="radius-md" :style="{ width: popup_width, height: popup_height }"></video>
<iconfont name="icon-qiandao-tancguanbi" size="56rpx" color="#ccc" propContainerDisplay="flex" @tap="video_close"></iconfont>
</view>
</uni-popup>
</view>
</view>
</view>
<!-- 当前diy页面底部菜单非公共底部菜单 -->
<block v-if="is_show_footer">
<componentDiyFooter :propKey="footer_data.id" :propValue="footer_data.com_data" @onFooterHeight="footer_height_value_event"></componentDiyFooter>
<view v-if="footer_height_value > 0" :style="'height:' + footer_height_value + 'rpx;'"></view>
</block>
<!-- 底部卡槽 -->
<slot name="bottom"></slot>
</scroll-view>
</view>
</view>
</template>
<script>
const app = getApp();
import { isEmpty, common_styles_computer, background_computer, padding_computer } from '@/common/js/common/common.js';
import componentDiyHeader from '@/components/diy/header';
import componentDiyFooter from '@/components/diy/footer';
import componentDiyTabs from '@/components/diy/tabs';
import componentDiySearch from '@/components/diy/search';
import componentDiyCarousel from '@/components/diy/carousel';
import componentDiyUserInfo from '@/components/diy/user-info';
import componentDiyNotice from '@/components/diy/notice';
import componentDiyVideo from '@/components/diy/video';
import componentDiyArticleList from '@/components/diy/article-list';
import componentDiyArticleTabs from '@/components/diy/article-tabs';
import componentDiyHotZone from '@/components/diy/hot-zone';
import componentDiyCoupon from '@/components/diy/coupon';
import componentDiyFloatWindow from '@/components/diy/float-window';
import componentDiyTitle from '@/components/diy/title';
import componentDiyAuxiliaryLine from '@/components/diy/auxiliary-line';
import componentDiyRichText from '@/components/diy/rich-text';
import componentDiyAuxiliaryBlank from '@/components/diy/auxiliary-blank';
import componentDiyNavGroup from '@/components/diy/nav-group';
import componentDiyGoodsList from '@/components/diy/goods-list';
import componentDiyGoodsTabs from '@/components/diy/goods-tabs';
import componentDiyDataMagic from '@/components/diy/data-magic';
import componentDiyCustom from '@/components/diy/custom';
import componentDiyImgMagic from '@/components/diy/img-magic';
import componentDiySeckill from '@/components/diy/seckill';
import componentDiyTabsCarousel from '@/components/diy/tabs-carousel';
import componentDataTabs from '@/components/diy/data-tabs';
import componentGoodsList from '@/components/goods-list/goods-list';
import componentNoData from '@/components/no-data/no-data';
import componentBottomLine from '@/components/bottom-line/bottom-line';
import componentGoodsBuy from '@/components/goods-buy/goods-buy';
import componentSearch from '@/components/search/search';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
name: 'diy',
props: {
propValue: {
type: Object,
default: () => ({}),
},
propDataId: {
type: [String, Number],
default: '',
},
propKey: {
type: [String, Number],
default: 0,
},
},
components: {
componentDiyHeader,
componentDiyFooter,
componentDiyTabs,
componentDiySearch,
componentDiyCarousel,
componentDiyUserInfo,
componentDiyNotice,
componentDiyVideo,
componentDiyArticleList,
componentDiyArticleTabs,
componentDiyHotZone,
componentDiyCoupon,
componentDiyAuxiliaryLine,
componentDiyRichText,
componentDiyFloatWindow,
componentDiyTitle,
componentDiyAuxiliaryBlank,
componentDiyNavGroup,
componentDiyGoodsList,
componentDiyGoodsTabs,
componentDiyDataMagic,
componentDiyCustom,
componentDiyImgMagic,
componentDiySeckill,
componentDiyTabsCarousel,
componentDataTabs,
componentGoodsList,
componentNoData,
componentBottomLine,
componentGoodsBuy,
componentSearch,
},
data() {
return {
// 基础配置
currency_symbol: app.globalData.currency_symbol(),
// 是否有选项卡
is_tabs: false,
// 是否是模块数据或者是九宫格商品分类样式数据, 默认模块数据
is_tabs_type: true,
// 是否开启沉浸模式
is_immersion_model: false,
// 5,7,0 是误差,, 10 是下边距66是高度bar_height是不同小程序下的导航栏距离顶部的高度
// #ifdef MP
sticky_top: bar_height + 5 + 10,
// #endif
// #ifdef H5 || MP-TOUTIAO
sticky_top: bar_height + 7 + 10,
// #endif
// #ifdef APP
sticky_top: bar_height + 0 + 10,
// #endif
header_top: '',
temp_sticky_top: 0,
temp_header_top: '0px',
is_header_top: false,
temp_is_header_top: false,
scroll_top: 0,
// 选项卡高度
tabs_height: 0,
header_data: {},
footer_data: {},
// 选项卡数据
tabs_data: {},
diy_data: [],
page_style: '',
page_img_style: '',
is_show_footer: false,
tabs_home_id: this.propDataId,
// 商品列表
goods_list: [],
goods_total: 0,
goods_page_total: 0,
goods_page: 1,
// 数据展示样式0图文、1九方格
goods_show_type_value: 1,
// 增加随机数,避免无法监听数据列表内部数据更新
random_value: 0,
// 标签插件
plugins_label_data: null,
goods_list_loding_status: 1,
goods_list_loding_msg: '',
goods_bottom_line_status: false,
// 判断数据是否在加载中
data_is_loading: 0,
// 缓存key
cache_key: app.globalData.data.cache_diy_data_key,
// 底部导航高度
footer_height_value: 0,
// 商品ref索引
goods_index: 0,
// 视频播放逻辑
video_src: '',
popup_width: '0rpx',
popup_height: '0rpx',
// 顶部导航是否换行
is_search_alone_row: false,
data_alone_row_space: 0,
content_padding: '',
outer_container_padding: 0,
// 滚动延迟器
head_scroll_top: 0,
scroll_throttle_timeout: null,
// 是否开启安全距离
is_the_safe_distance_enabled: false,
// 是否开启置顶
is_tabs_data_topped: false,
};
},
computed: {
get_tabs_data_prop_top() {
// 开启了沉浸式时的处理
if (this.is_immersion_model) {
// 并且开启了安全距离
return this.is_the_safe_distance_enabled ? this.temp_header_top : 0;
} else {
return this.temp_header_top;
}
},
get_tabs_data_prop_sticky_top() {
// 开启了沉浸式时的处理
if (this.is_immersion_model) {
// 并且开启了安全距离
return this.is_the_safe_distance_enabled ? this.temp_sticky_top : 0;
} else {
return this.temp_sticky_top;
}
},
get_prop_index() {
return (item) => {
return this.is_the_safe_distance_enabled && this.tabs_data.length == 0 ? item.index : -1;
}
},
get_diy_prop_top() {
return (item) => {
// 不开启沉浸模式时的处理
if (!this.is_immersion_model) {
return this.temp_sticky_top + this.tabs_height;
} else {
// 开启沉浸模式且开启选项卡置顶时
if (this.is_tabs_data_topped) {
return this.tabs_height;
} else {
// 开启安全距离
let is_general_safe_distance_num = this.temp_sticky_top;
// #ifdef H5 || MP-TOUTIAO
is_general_safe_distance_num = this.is_header_top ? this.temp_sticky_top : 0
// #endif
if (this.is_the_safe_distance_enabled) {
return is_general_safe_distance_num;
} else {
if (item?.content?.is_general_safe_distance == '1') {
return is_general_safe_distance_num;
} else {
return 0;
}
}
}
}
}
},
get_diy_custom_nav_height() {
return (item) => {
let header_height = (this.is_search_alone_row ? 66 + this.data_alone_row_space : 33);
// #ifdef H5 || MP-TOUTIAO
header_height = this.is_header_top ? header_height : 0;
// #endif
// 不开启沉浸模式时的处理
if (!this.is_immersion_model) {
return header_height;
} else {
// 开启沉浸模式且开启选项卡置顶时
if (this.is_tabs_data_topped) {
return 0;
} else {
// 开启沉浸模式时并且开启安全距离
if (this.is_the_safe_distance_enabled) {
return this.is_search_alone_row ? 66 + this.data_alone_row_space : 33;
} else {
if (item?.content?.is_general_safe_distance == '1') {
return header_height;
} else {
return 0;
}
}
}
}
}
},
getPropIsTabsUseSafeDistance() {
let is_tabs_use_safe_distance = this.is_immersion_model;
// #ifdef H5 || MP-TOUTIAO
is_tabs_use_safe_distance = this.is_immersion_model && this.is_header_top;
// #endif
return is_tabs_use_safe_distance || !this.is_immersion_model;
}
},
watch: {
propKey(val) {
// 如果当前存在别的diy或者商品分类tabs则不更新数据
if ((this.tabs_id || null) == null) {
// 初始化
this.init();
}
},
},
created() {
// 初始化配置
this.init_config();
// 初始化
this.init();
},
methods: {
isEmpty,
// 初始化配置
init_config(status) {
if ((status || false) == true) {
// 是否显示底部菜单如果当前地址已经存在系统底部菜单中则不显示当前diy页面自定义的底部菜单
var is_show_footer = parseInt(this.propValue.header.com_data.content.bottom_navigation_show || 0) == 1;
var is_tabbar = app.globalData.is_tabbar_pages();
this.setData({
is_show_footer: is_show_footer && !is_tabbar,
});
// diy页面不显示底部菜单则设置底部菜单高度为0
if(!this.is_show_footer) {
// 存储diy页面底部菜单高度
if(app.globalData.current_page(false) == 'pages/diy/diy') {
app.globalData.app_diy_tabbar_height_save(0);
}
}
} else {
app.globalData.is_config(this, 'init_config');
}
},
// 初始化
init() {
const { header = {}, diy_data = [], tabs_data = [] } = this.propValue;
// 头部的样式
let header_style = header.com_data.style;
let new_diy_index = 0;
let new_tabs_data = [];
let new_diy_data = [];
if (tabs_data.length > 0) {
tabs_data.forEach((item) => {
// 修改item的内容
item = this.get_index_content(new_diy_index, header, header_style, item);
new_tabs_data.push(item);
new_diy_index++;
});
new_diy_data = diy_data;
} else {
new_tabs_data = tabs_data;
// 过滤数据
diy_data.forEach((item) => {
// 判断是否是商品列表
if (item.com_name == 'float-window') {
item.index = -1;
} else {
// 修改item的内容
item = this.get_index_content(new_diy_index, header, header_style, item);
new_diy_data.push(item);
new_diy_index++;
}
});
}
const { padding_right = 0, padding_left = 0 } = header_style.common_style;
const new_is_search_alone_row = header.com_data.content.data_alone_row_value.length > 0 ? true : false;
const new_data_alone_row_space = parseInt(header_style.data_alone_row_space || 0);
// tabs选项卡数据过滤
this.setData({
header_data: header,
footer_data: this.propValue.footer,
diy_data: new_diy_data,
tabs_data: new_tabs_data,
page_style: common_styles_computer(header_style.common_style),
page_img_style: background_computer(header_style.common_style),
// 内间距
content_padding: `padding: 0px ${padding_right}px 0px ${padding_left}px;` + 'box-sizing:border-box;',
outer_container_padding: padding_right + padding_left,
// 判断顶部导航是否置顶
is_header_top: parseInt(header_style.up_slide_display) == 1 ? true : false,
is_tabs_data_topped: new_tabs_data[0]?.com_data?.content?.tabs_top_up == '1' || false,
temp_sticky_top: this.sticky_top,
temp_header_top: (new_is_search_alone_row ? 66 + new_data_alone_row_space : 33),
header_top: (new_is_search_alone_row ? 66 + new_data_alone_row_space : 33),
is_immersion_model: header_style.header_background_type !== 'color_image' && header_style.immersive_style == '1',
// 顶部导航高度是否变化--------------------------------------------------
is_search_alone_row: new_is_search_alone_row,
data_alone_row_space: new_data_alone_row_space,
is_immersive_style_and_general_safe_distance_value: header_style.immersive_style == '1' && header_style.general_safe_distance_value == '1',
is_the_safe_distance_enabled: header_style.immersive_style == '1' && header_style.general_safe_distance_value == '1',// diy_data是否开启安全距离
});
// 缓存数据
uni.setStorageSync(this.cache_key + this.tabs_home_id, diy_data);
},
// 顶部导航沉浸模式回调
// immersion_model_call_back(bool) {
// this.setData({
// is_immersion_model: bool,
// });
// },
get_index_content(new_diy_index, header, header_style, item) {
item.index = new_diy_index;
if (new_diy_index == 0) {
// 判断是否开启沉浸模式和是否开启安全距离 如果为true则除了选项卡和选项卡轮播外 第一个组件则加上安全距离样式的padding_top加上顶部导航的高度和安全距离的高度
if (header_style.immersive_style == '1' && header_style.general_safe_distance_value == '1') {
let new_data = JSON.parse(JSON.stringify(item));
// 顶部导航的高度
let header_top_height = (header.com_data.content.data_alone_row_value.length > 0 ? parseInt(header.com_data.style.data_alone_row_space || 5) : 0) + 33 + (header.com_data.content.data_alone_row_value.length > 0 ? 33 : 0);
new_data.com_data.style.common_style.padding_top = parseInt(new_data.com_data.style.common_style.padding_top) + header_top_height;
return new_data;
}
return item;
}
return item;
},
// 选项卡回调更新数据
tabs_click_event(tabs_id, bool, params = {}) {
let new_data = [];
this.setData({
is_tabs_type: bool,
tabs_id: tabs_id,
});
let new_params = {
...params,
id: tabs_id,
};
if (tabs_id) {
new_data = uni.getStorageSync(this.cache_key + tabs_id) || [];
if (new_data.length > 0) {
// 先使用缓存数据展示
this.setData({
diy_data: new_data,
});
// 已有本地缓存则直接取远程有效数据(默认首次取的是远程缓存数据)
new_params['is_cache'] = 0;
}
// diy数据
if (bool) {
uni.showLoading({
title: this.$t('common.loading_in_text'),
mask: true,
});
uni.request({
url: app.globalData.get_request_url('index', 'diy'),
method: 'POST',
data: new_params,
dataType: 'json',
success: (res) => {
uni.hideLoading();
// 数据处理
let data = res.data.data.data;
if (res.data.code == 0) {
new_data = data?.config.diy_data || [];
uni.setStorageSync(this.cache_key + tabs_id, new_data);
this.setData({
diy_data: new_data,
});
// 是否需要重新加载数据
if (parseInt(data.is_result_data_cache || 0) == 1) {
this.tabs_click_event(tabs_id, bool, { is_cache: 0 });
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
} else {
this.setData({
goods_page: 1,
goods_list: [],
goods_list_loding_status: 1,
goods_bottom_line_status: false,
});
this.get_goods_list(1);
}
} else {
if (tabs_id == '') {
new_data = uni.getStorageSync(this.cache_key + this.tabs_home_id) || [];
}
// 先使用缓存数据展示
this.setData({
diy_data: new_data,
});
}
},
// 选项卡高度
tabs_height_event(height) {
let new_tabs_height = 0;
// 判断是否有选项卡切选项卡数组数据内的字段is_enable值是否为1
if (this.tabs_data.length > 0) {
this.tabs_data.forEach((item, index) => {
if (item.is_enable == '1') {
new_tabs_height = height;
}
});
}
this.setData({
tabs_height: new_tabs_height,
});
},
// 滚动加载
on_scroll_lower_event(e) {
if (!this.is_tabs_type) {
this.get_goods_list();
}
},
// 滚动到顶部
on_scroll_upper_event() {
setTimeout(() => {
this.head_scroll_top = 0;
});
},
// 查询商品
get_goods_list(is_mandatory) {
// 分页是否还有数据
if ((is_mandatory || 0) == 0) {
if (this.goods_bottom_line_status == true) {
uni.stopPullDownRefresh();
return false;
}
}
// 是否加载中
if (this.data_is_loading == 1) {
return false;
}
this.setData({
data_is_loading: 1,
});
// 获取数据
if (this.goods_page > 1) {
uni.showLoading({
title: this.$t('common.loading_in_text'),
});
}
let new_data = {
category_id: this.tabs_id,
page: this.goods_page,
};
// 九宫格数据
uni.request({
url: app.globalData.get_request_url('datalist', 'search'),
method: 'POST',
data: new_data,
dataType: 'json',
success: (res) => {
if (this.goods_page > 1) {
uni.hideLoading();
}
uni.stopPullDownRefresh();
if (res.data.code == 0) {
var data = res.data.data;
if (data.data.length > 0) {
if (this.goods_page <= 1) {
var temp_goods_list = data.data;
} else {
var temp_goods_list = this.goods_list || [];
var temp_data = data.data;
for (var i in temp_data) {
temp_goods_list.push(temp_data[i]);
}
}
this.setData({
goods_list: temp_goods_list,
random_value: Math.random(),
goods_total: data.total,
goods_page_total: data.page_total,
goods_list_loding_status: 3,
goods_list_loding_msg: '',
goods_page: this.goods_page + 1,
data_is_loading: 0,
});
// 是否还有数据
this.setData({
goods_bottom_line_status: this.goods_page > 1 && this.goods_page > this.goods_page_total,
});
} else {
this.setData({
goods_list_loding_status: 0,
goods_list_loding_msg: res.data.msg,
goods_total: 0,
data_is_loading: 0,
});
if (this.goods_page <= 1) {
this.setData({
goods_list: [],
goods_bottom_line_status: false,
});
}
}
} else {
this.setData({
goods_list_loding_status: 0,
goods_list_loding_msg: res.data.msg,
data_is_loading: 0,
});
app.globalData.is_login_check(res.data, this, 'get_goods_list', is_mandatory);
}
},
fail: () => {
if (this.goods_page > 1) {
uni.hideLoading();
}
uni.stopPullDownRefresh();
this.setData({
goods_list_loding_status: 2,
goods_list_loding_msg: this.$t('common.internet_error_tips'),
data_is_loading: 0,
});
},
});
},
// 页面滚动事件
on_scroll_event(e) {
const scroll_num = parseInt(e.detail.scrollTop);
if (scroll_num <= 20) {
this.head_scroll_top = 0;
} else {
if (scroll_num / (this.sticky_top + 33) <= 1) {
// 更新数据的逻辑
this.head_scroll_top = scroll_num;
} else {
this.head_scroll_top = this.sticky_top + 100;
}
}
// #ifdef H5 || MP-TOUTIAO
// 判断顶部导航是否置顶
if (!this.is_header_top && !this.is_immersion_model) {
if (scroll_num >= this.sticky_top + 33 + (this.is_search_alone_row ? 0 : 33 + this.data_alone_row_space)) {
this.temp_sticky_top = 0;
this.temp_header_top = 0;
this.temp_sticky_no_h5_top = 0;
this.temp_is_header_top = true;
} else {
this.temp_header_top = this.header_top;
this.temp_sticky_top = this.sticky_top;
this.temp_sticky_no_h5_top = this.sticky_top;
this.temp_is_header_top = false;
}
}
//#endif
this.scroll_timer_compute(scroll_num);
},
scroll_timer_compute(scroll_num) {
// 使用节流技术减少事件触发的处理次数
if (!this.scroll_throttle_timeout) {
const self = this;
this.scroll_throttle_timeout = setTimeout(() => {
this.scroll_top = scroll_num;
// 清除定时器
this.scroll_throttle_timeout = null;
}, 20); // 可以根据实际情况调整延时时间
}
},
// 底部菜单高度
footer_height_value_event(value) {
this.setData({
footer_height_value: (value*2)+20,
});
// 存储diy页面底部菜单高度
if(app.globalData.current_page(false) == 'pages/diy/diy') {
app.globalData.app_diy_tabbar_height_save(value);
}
},
// 商品数量更新回调
goods_buy_event(index, goods = {}, params = {}, back_data = null) {
if ((this.$refs.goods_buy || null) != null) {
this.goods_index = index;
this.$refs.goods_buy.init(goods, params, back_data);
}
},
// 商品加购回调
goods_cart_back_event(e) {
if ((this.$refs[`diy_goods_buy${this.goods_index}`][0] || null) != null) {
this.$refs[`diy_goods_buy${this.goods_index}`][0].goods_cart_back_event(e);
}
},
// 视频播放
video_play(url, width, height) {
this.setData({
video_src: url,
popup_width: width,
popup_height: height,
});
this.$refs.popup.open();
const videoContext = uni.createVideoContext('carousel_video');
if (!isEmpty(videoContext)) {
videoContext.play();
}
},
// 视频关闭
video_close() {
const videoContext = uni.createVideoContext('carousel_video');
if (!isEmpty(videoContext)) {
videoContext.pause();
}
this.$refs.popup.close();
},
// 位置回调
choice_location_back(e) {
// 如果存在tabs_id则表示当前有选择tab数据则仅当前模块更新无需给上级回调位置
if ((this.tabs_id || null) == null) {
this.$emit('onLocationBack', e);
} else {
this.tabs_click_event(this.tabs_id, this.is_tabs_type);
}
},
},
};
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,164 @@
<template>
<movable-area class="float-window-movable-container">
<movable-view :x="x" :y="y" direction="all" class="float-window-spread flex-row align-c jc-c" @tap="btn_event">
<block v-if="style.float_style == 'diffuse'">
<view class="ring" :style="content_style"></view>
<view class="ring" :style="content_style"></view>
</block>
<view class="img oh" :style="content_style">
<block v-if="(form || null) != null && form.button_jump == 'customer_service'">
<component-online-service :propChatImage="img_url" :propIsSpread="false" :propIsMovable="false"></component-online-service>
</block>
<block v-else>
<imageEmpty :propImageSrc="img_url" propImgFit="aspectFill" propErrorStyle="width: 60rpx;height: 60rpx;"></imageEmpty>
</block>
</view>
</movable-view>
<component-quick-nav ref="quick_nav" :propIsBtn="false"></component-quick-nav>
</movable-area>
</template>
<script>
const app = getApp();
import { isEmpty } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
import componentOnlineService from '@/components/online-service/online-service';
import componentQuickNav from '@/components/quick-nav/quick-nav';
export default {
components: {
imageEmpty,
componentOnlineService,
componentQuickNav
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
}
},
data() {
return {
form: {},
style: {},
img_url: '',
x: 0,
y: 0,
content_style: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
// 获取内容
let form = this.propValue.content || {};
// 获取图片
let img_url = (form.button_img || null) != null ? (form.button_img[0] || null) : null;
if (img_url != null) {
img_url = img_url.url || null;
}
const { float_style, float_style_color, display_location, offset_number_percentage } = this.propValue.style;
// 获取当前手机的宽度和高度
const { windowWidth, windowHeight } = uni.getSystemInfoSync();
// 计算出距离左边的距离
let x = display_location == 'left' ? 10 : windowWidth - 60;
// 计算出距离顶部的距离
const y = Math.ceil(windowHeight * (1 - Number(offset_number_percentage)) - 20);
this.setData({
form: form,
style: this.propValue.style,
img_url: img_url,
content_style: float_style == 'shadow' ? `box-shadow: 0 0 40rpx ${float_style_color};border-radius: 50%;` : `background-color: ${float_style_color};border-radius: 50%;`,
x: x,
y: y
});
},
// 按钮事件
btn_event() {
const { button_jump, button_link } = this.form;
switch(button_jump) {
// 链接
case 'link' :
if (!isEmpty(button_link)) {
app.globalData.url_open(button_link.page);
}
break;
// 快捷导航
case 'quick_nav' :
if ((this.$refs.quick_nav || null) != null) {
this.$refs.quick_nav.quick_open_event();
}
break;
}
},
},
};
</script>
<style scoped lang="scss">
.img {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
z-index: 2;
}
.float-window-movable-container {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: transparent;
pointer-events: none;
z-index: 103;
}
/**
* 呼吸灯
*/
.float-window-spread {
position: relative;
pointer-events: auto;
z-index: 1;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
}
.float-window-spread .ring {
/* 速度为1.5 * 层数 = 实际运行速度,速度修改则 animation-delay 属性也修改相同速度 */
animation: pulsing 1.5s ease-out infinite;
border-radius: 100%;
width: 100rpx;
height: 100rpx;
position: absolute;
}
/* 速度为1*层数 */
.float-window-spread .ring:nth-of-type(1) {
-webkit-animation-delay: -1.5s;
animation-delay: -1.5s;
}
/* 速度为1*层数 */
.float-window-spread .ring:nth-of-type(2) {
-webkit-animation-delay: -2s;
animation-delay: -2s;
}
@keyframes pulsing {
100% {
transform: scale(1.35);
opacity: 0;
}
}
</style>

201
components/diy/footer.vue Normal file
View File

@@ -0,0 +1,201 @@
<template>
<!-- 底部导航 -->
<view v-if="(propValue || null) !== null" class="footer-nav flex-row jc-c align-c" :class="nav_type == 1 ? 'bottom-line-exclude' : ''">
<view class="flex-1 wh-auto" :style="style_container">
<view class="footer-nav-content flex-row jc-c align-c wh-auto" :style="style_img_container">
<view class="flex-row jc-c align-c wh-auto" :class="nav_type == 0 ? 'bottom-line-exclude' : ''">
<view class="flex-row jc-sa align-c wh padding-0">
<block v-for="(item, index) in nav_content" :key="index">
<view class="flex-1 flex-col jc-c align-c gap-5 pr" :data-value="item.link.page || ''" @tap="url_event">
<view v-if="nav_style != 2" class="img-content pr">
<view class="img-item pa border-radius-xs animate-linear" :class="active_index != index ? 'active' : ''">
<template v-if="item.img.length > 0">
<image :src="item.img[0].url" class="img dis-block" model="widthFix"></image>
</template>
</view>
<view class="img-item pa border-radius-xs animate-linear" :class="active_index == index ? 'active' : ''">
<template v-if="item.img_checked.length > 0">
<image :src="item.img_checked[0].url" class="img dis-block" model="widthFix"></image>
</template>
</view>
</view>
<text v-if="nav_style != 1" class="animate-linear text-size-xs pr z-i" :style="active_index == index ? text_color_checked : default_text_color">{{ item.name }}</text>
<view v-if="(item.badge || null) != null" class="pa badge-icon">
<component-badge :propNumber="item.badge"></component-badge>
</view>
</view>
</block>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
let app = getApp();
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
import componentBadge from '@/components/badge/badge';
export default {
props: {
propKey: {
type: [String,Number],
default: '',
},
propValue: {
type: Object,
default: null,
},
// 底部选中索引
propFooterActiveIndex: {
type: Number,
default: 0,
}
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
nav_content: [],
nav_type: 0,
nav_style: 0,
default_text_color: '',
text_color_checked: '',
active_index: 0,
};
},
components: {
componentBadge,
},
// 属性值改变监听
watch: {
// 唯一key
propKey(value, old_value) {
this.init();
},
// 选中索引
propFooterActiveIndex(value, old_value) {
this.init();
},
},
// 页面被展示
created: function () {
this.init();
},
methods: {
// 初始化
init() {
if ((this.propValue || null) !== null) {
let new_content = this.propValue.content || {};
let new_style = this.propValue.style || {};
let nav_content = new_content.nav_content || [];
let page = app.globalData.current_page() || null;
let active_index = this.propFooterActiveIndex;
if (page != null) {
// 角标链接定义
let badge_arr = {
'/pages/cart/cart': 'cart',
'/pages/cart-page/cart-page': 'cart',
};
for (var i in nav_content) {
if ((nav_content[i]['link'] || null) != null && (nav_content[i]['link']['page'] || null) != null) {
// 选中索引
if (nav_content[i]['link']['page'] == '/' + page) {
active_index = i;
}
// 获取角标数据
var badge_key = badge_arr[nav_content[i]['link']['page']];
if (badge_key !== undefined) {
nav_content[i]['badge'] = app.globalData.get_tab_bar_badge(badge_key);
}
}
}
}
this.setData({
nav_content: nav_content,
nav_type: new_content.nav_type || 0,
nav_style: new_content.nav_style || 0,
active_index: active_index,
default_text_color: 'color:' + new_style.default_text_color || 'rgba(0, 0, 0, 1)',
text_color_checked: 'color:' + new_style.text_color_checked || 'rgba(204, 204, 204, 1)',
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style),
});
// 高度计算
let nav_min_height = 70;
var nav_height = parseInt(new_style.common_style.padding_top) + parseInt(new_style.common_style.padding_bottom) + 44;
if (nav_height < nav_min_height) {
nav_height = nav_min_height;
}
let footer_height = nav_height + parseInt(new_style.common_style.margin_top) + parseInt(new_style.common_style.margin_bottom);
// #ifndef H5
// 底部菜单距离底部的安全距离减去20、默认的安全距离太高了
var safe_areaInsets = uni.getSystemInfoSync().safeAreaInsets || {};
var bottom = parseInt(safe_areaInsets.bottom || 0);
if (bottom > 0) {
bottom -= 24;
}
footer_height += bottom;
// #endif
// 回调高度
this.$emit('onFooterHeight', footer_height);
}
},
// 跳转链接
url_event(e) {
let index = e.currentTarget.dataset.index;
let item = this.nav_content[index];
app.globalData.url_event(e);
this.$emit('onFooterTap', index, item);
},
},
};
</script>
<style lang="scss" scoped>
.footer-nav {
z-index: 102;
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
margin: 0 auto;
background-color: transparent;
.footer-nav-content {
min-height: 140rpx;
box-sizing: border-box;
.img-content {
width: 44rpx;
height: 44rpx;
.img-item {
width: 44rpx;
height: 44rpx;
opacity: 0;
&.active {
opacity: 1;
}
.img {
width: 44rpx;
height: 44rpx;
}
}
}
.badge-icon {
margin-top: -70rpx;
margin-right: -70rpx;
}
}
}
/* #ifdef H5 */
@media only screen and (min-width: 1600rpx) {
.footer-nav {
max-width: 1600rpx;
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,607 @@
<template>
<view v-if="!isEmpty(list)" class="oh" :style="style_container" @tap.stop="onTap">
<view class="oh" :style="style_img_container">
<view :class="outer_class" :style="onter_style">
<block v-if="!['5'].includes(theme)">
<view v-for="(item, index) in list" :key="index" class="pr oh" :style="layout_style" :data-index="index" :data-value="item.goods_url" @tap.stop="url_event">
<view :class="layout_type" :style="layout_img_style">
<block v-if="theme == '6'">
<view :class="['flex-row align-c jc-sb ptb-15 mlr-10 gap-20', { 'br-b-e': index != list.length - 1 }]">
<view v-if="is_show('title')" :class="text_line" :style="title_style">{{ item.title }}</view>
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num nowrap" :style="'color:' + new_style.shop_price_color">
<text class="identifying">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" class="identifying">{{ item.show_price_unit }}</text>
</view>
</view>
</block>
<block v-else>
<block v-if="!isEmpty(item)">
<view class="oh pr" :class="img_size">
<view v-if="!isEmpty(item.new_cover)" :style="img_size">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="content_img_radius" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
<view v-else :style="img_size">
<imageEmpty :propImageSrc="item.images" :propStyle="content_img_radius" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
</block>
<view v-if="is_show('title') || is_show('simple_desc') || is_show('price') || is_show('original_price') || is_show('sales_count') || is_show('plugins_view_icon') || form.is_shop_show == '1'" class="flex-col flex-1 jc-sb content gap-10" :style="content_style">
<view class="flex-col gap-10 top-title">
<view v-if="is_show('title') || (['0', '1', '2', '3', '5'].includes(theme) && is_show('simple_desc'))" class="flex-col" :style="{ gap: new_style.title_simple_desc_spacing * 2 + 'rpx' }">
<view v-if="is_show('title')" :class="text_line" :style="title_style">{{ item.title }}</view>
<view v-if="['0', '1', '2', '3', '5'].includes(theme) && is_show('simple_desc')" :class="form.simple_desc_row == '2' ? 'text-line-2' : 'text-line-1'" :style="simple_desc">{{ item.simple_desc }}</view>
</view>
<view v-if="show_content && is_show('plugins_view_icon') && !isEmpty(item.plugins_view_icon_data)" class="flex-row gap-5 align-c">
<view v-for="(icon_data, icon_index) in item.plugins_view_icon_data" :key="icon_index" class="radius text-size-xsss padding-horizontal-xs" :style="{ background: icon_data.bg_color, color: icon_data.color, border: '1rpx solid' + (!isEmpty(icon_data.br_color) ? icon_data.br_color : icon_data.bg_color) }">{{ icon_data.name }}</view>
</view>
</view>
<view v-if="!['3', '4', '5'].includes(form.theme)" class="flex-col gap-5">
<view :class="[form.is_price_solo == '1' ? 'flex-row align-c nowrap' : 'flex-col gap-5']">
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num" :style="'color:' + new_style.shop_price_color">
<text :style="price_symbol">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" :style="price_unit">{{ item.show_price_unit }}</text>
</view>
<view v-if="show_content && is_show('original_price') && !isEmpty(item.min_original_price)" class="text-size-xss flex-row">
<!-- <image v-if="form.static_img.length > 0" class="original-price-left" :src="form.static_img[0].url" model="widthFix"></image> -->
<text :class="['original-price text-line-1', { 'flex-1': form.is_price_solo == '1' }]" :style="original_price">
{{ item.show_original_price_symbol }}{{ item.min_original_price }}
<block v-if="is_show('original_price_unit')">
{{ item.show_original_price_unit }}
</block>
</text>
</view>
</view>
<view class="flex-row jc-sb align-e">
<view>
<view v-if="show_content" class="flex-row align-c text-size-xss">
<view v-if="is_show('sales_count') && !isEmpty(item.sales_count)" class="pr-5" :style="sold_number_style">已售{{ item.sales_count || 0 }}</view>
<!-- <view v-if="is_show('sales_count')" :class="['pr-5', {'br-r-e': is_show('sales_count') && is_show('4')}]" :style="sold_number_style>已售{{ item.sales_count }}件</view> -->
<!-- <view v-if="is_show('4')" class="pl-5" :style="score_style">评分0</view> -->
</view>
</view>
<view v-if="(form.is_shop_show == '1' && form.shop_button_effect == '1' && item.is_error == 0) || (form.is_shop_show == '1' && form.shop_button_effect == '0')" class="pr" :data-index="index" @tap.stop="goods_button_event">
<block v-if="form.shop_type == 'text'">
<view class="plr-11 padding-vertical-xs round" :style="button_style + ('color:' + new_style.shop_button_text_color)">{{ form.shop_button_text }}</view>
</block>
<view v-else class="round padding-horizontal-sm ptb-5" :style="button_gradient">
<iconfont :name="'icon-' + (!isEmpty(form.shop_button_icon_class) ? form.shop_button_icon_class : 'cart')" :color="new_style.shop_icon_color" :size="new_style.shop_icon_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
<view v-if="form.shop_button_effect == '1'" class="cart-badge-icon pa badge-style">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</view>
</view>
<view v-else class="flex-row align-c jc-sb">
<view class="flex-row align-c nowrap">
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num" :style="'color:' + new_style.shop_price_color">
<text :style="price_symbol">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" :style="price_unit">{{ item.show_price_unit }}</text>
</view>
<view v-if="show_content && is_show('original_price') && !isEmpty(item.min_original_price)" class="text-size-xss flex-row">
<!-- <image v-if="form.static_img.length > 0" class="original-price-left" :src="form.static_img[0].url" model="widthFix"></image> -->
<text :class="['original-price text-line-1', { 'flex-1': form.is_price_solo == '1' }]" :style="original_price">
{{ item.show_original_price_symbol }}{{ item.min_original_price }}
<block v-if="is_show('original_price_unit')">
{{ item.show_original_price_unit }}
</block>
</text>
</view>
</view>
<view v-if="(form.is_shop_show == '1' && form.shop_button_effect == '1' && item.is_error == 0) || (form.is_shop_show == '1' && form.shop_button_effect == '0')" class="pr" :data-index="index" @tap.stop="goods_button_event">
<block v-if="form.shop_type == 'text'">
<view class="plr-11 padding-vertical-xs round" :style="button_style + ('color:' + new_style.shop_button_text_color)">{{ form.shop_button_text }}</view>
</block>
<view v-else class="round padding-horizontal-sm ptb-5" :style="button_gradient">
<iconfont :name="'icon-' + (!isEmpty(form.shop_button_icon_class) ? form.shop_button_icon_class : 'cart')" :color="new_style.shop_icon_color" :size="new_style.shop_icon_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
<view v-if="form.shop_button_effect == '1'" class="cart-badge-icon pa badge-style">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</view>
</view>
</block>
</view>
</view>
</block>
<block v-else>
<swiper circular="true" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :duration="500" :next-margin="new_style.rolling_fashion == 'translation' ? '-' + content_outer_spacing_magin : '0rpx'" :display-multiple-items="slides_per_group" :style="{ width: '100%', height: new_style.content_outer_height * new_scale + 'px' }">
<swiper-item v-for="(item1, index1) in shop_content_list" :key="index1">
<view class="flex-row wh-auto ht-auto" :style="onter_style">
<view v-for="(item, index) in item1.split_list" :key="index" class="pr oh" :style="layout_style" :data-index="index1" :data-split-index="index" :data-value="item.goods_url" @tap.stop="url_event">
<view :class="layout_type" :style="layout_img_style">
<block v-if="!isEmpty(item)">
<view class="oh pr" :class="'flex-img' + theme">
<view v-if="!isEmpty(item.new_cover)" :class="'flex-img' + theme">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="content_img_radius" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
<view v-else :class="'flex-img' + theme">
<imageEmpty :propImageSrc="item.images" :propStyle="content_img_radius" propErrorStyle="width: 100rpx;height: 100rpx;"></imageEmpty>
</view>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
</block>
<view v-if="is_show('title') || is_show('simple_desc') || is_show('price') || is_show('plugins_view_icon') || is_show('original_price') || form.is_shop_show == '1'" class="flex-col flex-1 jc-sb content gap-10" :style="content_style">
<view class="flex-col gap-10 top-title">
<view v-if="is_show('title') || (['0', '1', '2', '3', '5'].includes(theme) && is_show('simple_desc'))" class="flex-col" :style="{ gap: new_style.title_simple_desc_spacing * 2 + 'rpx' }">
<view v-if="is_show('title')" :class="text_line" :style="title_style">{{ item.title }}</view>
<view v-if="['0', '1', '2', '3', '5'].includes(theme) && is_show('simple_desc')" :class="form.simple_desc_row == '2' ? 'text-line-2' : 'text-line-1'" :style="simple_desc">{{ item.simple_desc }}</view>
</view>
<view v-if="show_content && is_show('plugins_view_icon') && !isEmpty(item.plugins_view_icon_data)" class="flex-row gap-5 align-c">
<view v-for="(icon_data, icon_index) in item.plugins_view_icon_data" :key="icon_index" class="radius text-size-xsss padding-horizontal-xs" :style="{ background: icon_data.bg_color, color: icon_data.color, border: '1rpx solid' + (!isEmpty(icon_data.br_color) ? icon_data.br_color : icon_data.bg_color) }">{{ icon_data.name }}</view>
</view>
</view>
<view class="flex-row align-c jc-sb">
<view class="flex-row align-c nowrap">
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num" :style="'color:' + new_style.shop_price_color">
<text :style="price_symbol">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" :style="price_unit">{{ item.show_price_unit }}</text>
</view>
<view v-if="show_content && is_show('original_price') && !isEmpty(item.min_original_price)" class="text-size-xss flex">
<!-- <image v-if="form.static_img.length > 0" class="original-price-left" :src="form.static_img[0].url" model="widthFix"></image> -->
<text :class="['original-price text-line-1', { 'flex-1': form.is_price_solo == '1' }]" :style="original_price">
{{ item.show_original_price_symbol }}{{ item.min_original_price }}
<block v-if="is_show('original_price_unit')">
{{ item.show_original_price_unit }}
</block>
</text>
</view>
</view>
<view v-if="(form.is_shop_show == '1' && form.shop_button_effect == '1' && item.is_error == 0) || (form.is_shop_show == '1' && form.shop_button_effect == '0')" class="pr" :data-index="index1" :data-split-index="index" @tap.stop="goods_button_event">
<block v-if="form.shop_type == 'text'">
<view class="plr-11 padding-vertical-xs round" :style="button_style + ('color:' + new_style.shop_button_text_color)">{{ form.shop_button_text }}</view>
</block>
<view v-else class="round padding-horizontal-sm ptb-5" :style="button_gradient">
<iconfont :name="'icon-' + (!isEmpty(form.shop_button_icon_class) ? form.shop_button_icon_class : 'cart')" :color="new_style.shop_icon_color" :size="new_style.shop_icon_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
<view v-if="form.shop_button_effect == '1'" class="cart-badge-icon pa badge-style">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</block>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { isEmpty, common_styles_computer, common_img_computer, gradient_handle, padding_computer, radius_computer, background_computer, border_computer, box_shadow_computer, old_margin, margin_computer } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
import subscriptIndex from '@/components/diy/modules/subscript/index.vue';
import componentBadge from '@/components/badge/badge';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
imageEmpty,
componentBadge,
subscriptIndex
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propIsCommonStyle: {
type: Boolean,
default: true,
},
propKey: {
type: [String, Number],
default: '',
},
propDiyIndex: {
type: Number,
default: 0,
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 0,
},
},
data() {
return {
form: {},
new_style: {},
propIsCartParaCurve: false,
list: [],
content_radius: '', // 圆角设置
content_img_radius: '', // 图片圆角设置
content_padding: '', // 内边距设置
theme: '', // 选择的风格
content_outer_spacing: '', // 商品间距
content_outer_spacing_magin: '', // 商品间距
// 最外层不同风格下的显示
outer_class: '',
onter_style: '',
// 不同风格下的样式
layout_type: '',
layout_style: '',
layout_img_style: '',
content_style: '', // 内容区域的样式
show_content: false, // 显示除标题外的其他区域
text_line: '', // 超过多少行隐藏
style_container: '', // 公共样式
style_img_container: '',
shop_content_list: [],
slides_per_group: 1,
// 内容样式
title_style: '',
price_style: '',
sold_number_style: '',
score_style: '',
button_style: '',
simple_desc: '',
price_symbol: '',
price_unit: '',
original_price: '',
// 按钮背景色
button_gradient: '',
// 图片大小
img_size: '',
new_scale: 1,
};
},
watch: {
propKey(val) {
this.init();
},
propValue(new_value, old_value) {
this.init();
},
},
created() {
this.init();
},
methods: {
isEmpty,
init() {
const new_form = this.propValue.content || null;
const new_style = this.propValue.style || null;
if (new_form != null && new_style != null) {
let new_list = [];
// 指定商品并且指定商品数组不为空
if (!isEmpty(new_form.data_list) && new_form.data_type == '0') {
new_list = new_form.data_list.map((item) => ({
...item.data,
title: !isEmpty(item.new_title) ? item.new_title : item.data.title,
new_cover: item.new_cover,
}));
} else if (!isEmpty(new_form.data_auto_list) && new_form.data_type == '1') {
// 筛选商品并且筛选商品数组不为空
new_list = new_form.data_auto_list;
}
// 最外层不同风格下的显示
const flex = ['0', '2', '6'].includes(new_form.theme) ? 'flex-col ' : 'flex-row ';
const wrap = new_form.theme == '5' ? '' : 'flex-wrap ';
const background = ['6'].includes(new_form.theme) ? 'bg-white ' : '';
const button_gradient = gradient_handle(new_style.shop_button_color, '180deg');
// 默认数据
const product_style_list = [
{ name: '单列展示', value: '0', width: 110, height: 120 },
{ name: '大图展示', value: '2', width: 166, height: 166 },
{ name: '无图模式', value: '6', width: 0, height: 0 },
{ name: '两列展示(纵向)', value: '1', width: 180, height: 180 },
{ name: '两列展示(横向)', value: '4', width: 70, height: 70 },
{ name: '三列展示', value: '3', width: 116, height: 114 },
{ name: '左右滑动展示', value: '5', width: 0, height: 0 },
];
const scale = sys_width / 390;
let img_style = ``;
if (['0', '4'].includes(new_form.theme)) {
// 图片宽度
if (typeof new_style.content_img_width == 'number') {
img_style += `width: ${ new_style.content_img_width * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_form.theme);
if (list.length > 0) {
img_style += `width: ${ list[0].width * scale }px;`;
} else {
img_style += 'width: auto;';
}
}
}
if (!['5', '6'].includes(new_form.theme)) {
// 图片宽度
if (typeof new_style.content_img_height == 'number') {
img_style += `height: ${ new_style.content_img_height * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_form.theme);
if (list.length > 0) {
img_style += `height: ${ list[0].height * scale }px;`;
} else {
img_style += 'height: auto;';
}
}
}
this.setData({
form: new_form,
new_style: new_style,
outer_class: flex + wrap + background + 'oh',
list: new_list,
new_scale: scale,
content_radius: radius_computer(new_style.shop_radius), // 圆角设置
content_img_radius: radius_computer(new_style.shop_img_radius), // 图片圆角设置
content_padding: padding_computer(new_style.shop_padding) + 'box-sizing: border-box;', // 内边距设置
theme: new_form.theme, // 选择的风格
content_outer_spacing: new_style.content_outer_spacing, // 商品间距
content_outer_spacing_magin: new_style.content_outer_spacing * 2 + 'rpx',
onter_style: new_form.theme == '6' ? radius_computer(new_style.shop_radius) : `gap: ${new_style.content_outer_spacing * 2 + 'rpx'};`,
// 不同风格下的样式
layout_type: ['0', '4'].includes(new_form.theme) ? 'flex-row wh-auto ht-auto oh' : 'flex-col wh-auto ht-auto oh',
layout_style: this.get_layout_style(new_style, new_form),
layout_img_style: this.get_layout_img_style(new_style, new_form),
content_style: this.get_content_style(new_style, new_form), // 内容区域的样式
show_content: ['0', '1', '2'].includes(new_form.theme), // 显示除标题外的其他区域
text_line: this.get_text_line(new_form), // 超过多少行隐藏
style_container: this.propIsCommonStyle ? common_styles_computer(new_style.common_style) : '', // 公共样式
style_img_container: this.propIsCommonStyle ? common_img_computer(new_style.common_style, this.propIndex) : '', // 图片样式
// 内容样式设置
button_gradient: button_gradient,
title_style: this.trends_config(new_style, 'title', 'title', new_form),
price_style: this.trends_config(new_style, 'price'),
sold_number_style: this.trends_config(new_style, 'sold_number'),
score_style: this.trends_config(new_style, 'score'),
button_style: this.trends_config(new_style, 'button', 'buy_button') + button_gradient,
simple_desc: this.trends_config(new_style, 'simple_desc', 'desc', new_form),
price_symbol: !isEmpty(new_style.shop_price_symbol_color) ? this.trends_config(new_style, 'price_symbol') : 'font-size: 18rpx;color: #EA3323;' ,
price_unit: !isEmpty(new_style.shop_price_unit_color) ? this.trends_config(new_style, 'price_unit') : 'font-size: 18rpx;color: #EA3323;',
original_price: this.trends_config(new_style, 'original_price'),
shop_content_list: this.get_shop_content_list(new_list, new_form, new_style),
slides_per_group: new_style.rolling_fashion == 'translation' ? new_form.carousel_col : 1,
img_size: img_style,
});
}
},
get_shop_content_list(list, form, new_style) {
// 深拷贝一下,确保不会出现问题
const cloneList = JSON.parse(JSON.stringify(list));
if (new_style.rolling_fashion != 'translation') {
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (cloneList.length > 0) {
// 每页显示的数量
const num = form.carousel_col;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: cloneList.slice(i * num, (i + 1) * num),
});
}
return nav_list;
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: cloneList,
},
];
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
}
},
get_text_line(form) {
let line = '';
if (['1', '6'].includes(form.theme)) {
line = 'text-line-1';
} else if (['0', '2', '3', '4', '5'].includes(form.theme)) {
line = 'text-line-2';
}
return line;
},
// 不同风格下的样式
get_layout_style(new_style, form) {
const { shop_margin = old_margin } = new_style;
const radius = form.theme == '6' ? '' : radius_computer(new_style.shop_radius);
const style = form.theme != '6' ? gradient_handle(new_style?.shop_color_list || [], new_style?.shop_direction || '') + margin_computer(shop_margin) + border_computer(new_style) + box_shadow_computer(new_style) : '';
let size_style = ``;
const shop_left_right_width = shop_margin.margin_left + shop_margin.margin_right;
if (['1', '4'].includes(form.theme)) {
size_style = `width: calc((100% - ${new_style.content_outer_spacing * 2 + (shop_left_right_width * 4) + 'rpx'}) / 2);`;
} else if (form.theme == '3') {
size_style = `width: calc((100% - ${new_style.content_outer_spacing * 4 + (shop_left_right_width * 6) + 'rpx'}) / 3);`;
} else if (form.theme == '5') {
// 如果不是平移的时候执行
if (new_style.rolling_fashion != 'translation') {
size_style = `width: ${this.get_multicolumn_columns_width(new_style, form)};min-width: ${this.get_multicolumn_columns_width(new_style, form)};height: ${new_style.content_outer_height * (sys_width / 390) + 'px'};`;
} else {
size_style = `margin-right: ${ (new_style.content_outer_spacing * 2) + (shop_margin.margin_right * 2) }rpx;width: 100%;height: 100%;`;
}
} else if (form.theme == '0') {
size_style = `width: calc(100% - ${ shop_left_right_width }px);`;
}
return `${radius} ${ style } ${size_style}`;
},
get_layout_img_style(new_style, form) {
const padding = ['0', '4'].includes(form.theme) ? padding_computer(new_style.shop_padding) + 'box-sizing: border-box;' : '';
const data = {
background_img_style: new_style?.shop_background_img_style || '',
background_img: new_style?.shop_background_img || '',
}
const background = form.theme != '6' ? background_computer(data) : '';
return `${padding} ${background}`;
},
get_multicolumn_columns_width(new_style, form) {
const { carousel_col } = form;
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = (new_style.content_outer_spacing * (carousel_col - 1)) / carousel_col;
return `calc(${100 / carousel_col}% - ${gap * 2}rpx)`;
},
get_content_style(new_style, form) {
const spacing_value = new_style.content_spacing;
let spacing = '';
if (['0', '4'].includes(form.theme)) {
spacing = `margin-left: ${spacing_value * 2}rpx;`;
} else {
spacing = padding_computer(new_style.shop_padding) + 'box-sizing: border-box;';
}
return `${spacing}`;
},
// 判断是否显示对应的内容
is_show(index) {
return this.form.is_show.includes(index);
},
// 根据传递的参数,从对象中取值
trends_config(new_style, key, type, new_form) {
return this.style_config(new_style[`shop_${key}_typeface`], new_style[`shop_${key}_size`], new_style[`shop_${key}_color`], type, new_form);
},
// 根据传递的值,显示不同的内容
style_config(typeface, size, color, type, new_form) {
let style = `font-weight:${typeface}; font-size: ${size * 2}rpx;`;
if (type == 'title') {
if (['1', '6'].includes(new_form.theme)) {
style += `line-height: ${size * 2}rpx;height: ${size * 2}rpx;color: ${color};`;
} else if (['0', '2', '3', '4', '5'].includes(new_form.theme)) {
style += `line-height: ${size > 0 ? (size + 3) * 2 : 0}rpx;height: ${size > 0 ? (size + 3) * 4 : 0}rpx;color: ${color};`;
}
} else if (type == 'desc') {
if (new_form.simple_desc_row == '2') {
style += `line-height: ${size > 0 ? (size + 3) * 2 : 0}rpx;height: ${size > 0 ? (size + 3) * 4 : 0}rpx;color: ${color};`;
} else {
style += `line-height: ${size * 2}rpx;height: ${size * 2}rpx;color: ${color};`;
}
} else {
if (type != 'buy_button') {
style += `color: ${color};`;
}
}
return style;
},
// icon标志显示样式
icon_style(item) {
let style = `background: ${item.bg_color};color: ${item.color};`;
if (!isEmpty(item.br_color)) {
style += `border: 2rpx solid ${item.br_color};`;
} else {
style += `border: 2rpx solid ${item.bg_color};`;
}
return style;
},
url_event(e) {
let index = e.currentTarget.dataset.index || 0;
let goods = this.list[index];
let split_index = 0;
if (this.theme == '5') {
split_index = e.currentTarget.dataset.splitIndex || 0;
goods = this.shop_content_list[index].split_list[split_index];
}
app.globalData.goods_data_cache_handle(goods.id, goods);
app.globalData.url_event(e);
},
goods_button_event(e) {
this.goods_cart_event(e);
},
// 加入购物车
goods_cart_event(e) {
let index = e.currentTarget.dataset.index || 0;
let split_index = 0;
let goods = this.list[index];
if (this.theme == '5') {
split_index = e.currentTarget.dataset.splitIndex || 0;
goods = this.shop_content_list[index].split_list[split_index];
}
if (this.form.shop_button_effect == '0') {
app.globalData.goods_data_cache_handle(goods.id, goods);
app.globalData.url_open(goods.goods_url);
} else {
// 开启购物车抛物线效果则展示提示操作
let is_success_tips = this.propIsCartParaCurve ? 0 : 1;
this.$emit(
'goods_buy_event',
this.propDiyIndex,
goods,
{
buy_event_type: 'cart',
is_direct_cart: 1,
is_success_tips: is_success_tips,
},
{
index: index,
split_index: split_index,
pos: e,
}
);
}
},
// 加入购物车成功回调
goods_cart_back_event(e) {
app.globalData.showToast('加入成功', 'success');
// 增加数量
var { index, split_index } = e.back_data;
let new_data = this.list;
let goods = new_data[index];
if (this.theme == '5') {
new_data = this.shop_content_list;
goods = new_data[index][split_index];
}
goods['user_cart_count'] = parseInt(goods['user_cart_count'] || 0) + parseInt(e.stock);
if (goods['user_cart_count'] > 99) {
goods['user_cart_count'] = '99+';
}
if (this.theme != '5') {
new_data[index] = goods;
this.setData({
list: new_data,
});
} else {
new_data[index][split_index] = goods;
this.setData({
shop_content_list: new_data,
});
}
},
},
};
</script>
<style scoped lang="scss">
.identifying {
font-size: 18rpx;
}
.original-price {
padding: 0 20rpx;
}
.flex-img5 {
width: 100%;
height: 100%;
}
// .original-price-left {
// width: 20rpx;
// height: 28rpx;
// }
.br-b-e {
border-bottom: 2rpx solid #eee;
}
.badge-style {
top: -20rpx;
right: 10rpx;
}
</style>

View File

@@ -0,0 +1,253 @@
<template>
<view class="goods-tabs ou" :class="'goods-tabs-' + propKey" :style="style_container">
<view class="ou" :style="style_img_container">
<componentDiyModulesTabsView :propKey="propKey" :propValue="goods_tabs" :propIsTop="top_up == '1'" :propTop="sticky_top" :propStyle="tabs_style" :propsTabsContainer="tabs_container" :propsTabsImgContainer="tabs_img_container" :propCustomNavHeight="propIsTabsUseSafeDistance ? (propCustomNavHeight * 2 + 'rpx') : '0rpx'" :propTabsSlidingFixedBg="tabs_sliding_fixed_bg" :propTabsBackground="tabs_background" @onTabsTap="tabs_click_event"></componentDiyModulesTabsView>
<view :style="shop_margin_top">
<view :style="shop_container">
<view :style="shop_img_container">
<componentGoodsList ref="diy_goods_list" :propKey="diy_key" :propDiyIndex="propDiyIndex" :propValue="goods_tabs" :propIsCommonStyle="false" @goods_buy_event="goods_buy_event"></componentGoodsList>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, padding_computer, margin_computer, background_computer, gradient_computer, isEmpty, old_border_and_box_shadow, old_margin, old_radius, old_padding, border_computer, box_shadow_computer, radius_computer } from '@/common/js/common/common.js';
import componentDiyModulesTabsView from '@/components/diy/modules/tabs-view';
import componentGoodsList from '@/components/diy/goods-list';
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
components: {
componentDiyModulesTabsView,
componentGoodsList,
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propTop: {
type: Number,
default: 0,
},
propCustomNavHeight: {
type: Number,
default: 33,
},
propScrollTop: {
type: Number,
default: 0,
},
// 顶部导航是否开启沉浸模式
propIsImmersionModel: {
type: Boolean,
default: false,
},
propKey: {
type: [String, Number],
default: '',
},
propDiyIndex: {
type: Number,
default: 0,
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 0,
},
propIsTabsUseSafeDistance: {
type: Boolean,
default: true
}
},
data() {
return {
style_container: '',
style_img_container: '',
goods_tabs: {},
// 是否滑动置顶
top_up: '0',
tabs_top: 0,
tabs_background: 'background:transparent',
custom_nav_height: 33,
diy_key: '',
// 选项卡背景设置
tabs_container: '',
tabs_img_container: '',
tabs_sliding_fixed_bg: '',
// 商品区域背景设置
shop_margin_top: '',
shop_container: '',
shop_img_container: '',
// #ifdef MP
nav_safe_space: bar_height + 5,
// #endif
// #ifdef H5 || MP-TOUTIAO
nav_safe_space: bar_height + 7,
// #endif
// #ifdef APP
nav_safe_space: bar_height + 0,
// #endif
// 选项卡默认数据
tabs_index: 0,
sticky_top: 0,
};
},
watch: {
propScrollTop(newVal) {
if (newVal + this.sticky_top + this.custom_nav_height > this.tabs_top + this.nav_safe_space && this.top_up == '1') {
let new_style = this.propValue.style || {};
let tabs_bg = new_style.common_style.color_list;
let new_tabs_background = '';
if (tabs_bg.length > 0 && (tabs_bg[0].color || null) != null) {
new_tabs_background = gradient_computer(new_style.common_style);
}
let new_tabs_background_img = background_computer(new_style.common_style);
if (new_tabs_background_img.length > 0) {
new_tabs_background_img += 'background-position: top left;';
}
this.tabs_background = (new_tabs_background.length > 0 ? new_tabs_background : 'background:#fff;') + new_tabs_background_img;
} else {
this.tabs_background = 'background:transparent';
}
},
propTop(val) {
this.init();
},
propKey(val) {
// 初始化
this.setData({
diy_key: val,
});
this.init();
},
},
created() {
this.init();
},
mounted() {
this.$nextTick(() => {
const self = this;
setTimeout(() => {
self.getTop();
});
// #ifdef H5 || MP-TOUTIAO
// 获取自定义导航栏高度
this.setData({
custom_nav_height: this.propCustomNavHeight,
});
// #endif
});
},
methods: {
init() {
let new_data = typeof this.propValue == 'string' ? JSON.parse(JSON.stringify(this.propValue)) : this.propValue;
const new_content = new_data.content || {};
const new_style = new_data.style || {};
const new_tabs_data = new_data.content.tabs_list[this.tabs_index] || {};
// 产品的值
new_data.content.data_type = new_tabs_data.data_type;
new_data.content.category = new_tabs_data.category;
new_data.content.brand = new_tabs_data.brand;
new_data.content.number = new_tabs_data.number;
new_data.content.sort = new_tabs_data.sort;
new_data.content.sort_rules = new_tabs_data.sort_rules;
new_data.content.data_list = new_tabs_data.data_list;
new_data.content.data_auto_list = new_tabs_data.data_auto_list;
// 公共样式
const common_style = new_style.common_style;
let tabs_style_obj = {
padding_top: common_style.padding_top - this.propCustomNavHeight < 0 ? 0 : common_style.padding_top - this.propCustomNavHeight,
padding_left: common_style.padding_left,
padding_right: common_style.padding_right,
};
let new_tabs_style = padding_computer(tabs_style_obj) + `position:relative;left: -${tabs_style_obj.padding_left * 2}rpx;right: -${tabs_style_obj.padding_right * 2}rpx;width:100%;`;
// 如果是历史数据的话,就执行默认添加下边距
if (isEmpty(new_style.tabs_padding)) {
new_tabs_style += 'padding-bottom: 20rpx;';
}
const { tabs_bg_color_list = [], tabs_bg_direction = '', tabs_bg_background_img_style = '', tabs_bg_background_img = [], tabs_radius = old_radius, tabs_padding = old_padding, shop_content_color_list = [], shop_content_direction = '', shop_content_background_img_style = '', shop_content_background_img = [], shop_content_margin = old_margin, shop_content_padding = old_padding, shop_content_radius = old_radius } = new_style;
// 选项卡背景设置
const tabs_data = {
color_list: tabs_bg_color_list,
direction: tabs_bg_direction,
background_img_style: tabs_bg_background_img_style,
background_img: tabs_bg_background_img,
};
// 商品区域背景设置
const shop_content_data = {
color_list: shop_content_color_list,
direction: shop_content_direction,
background_img_style: shop_content_background_img_style,
background_img: shop_content_background_img,
};
const shop_content = new_style?.shop_content || old_border_and_box_shadow;
const tabs_content = new_style?.tabs_content || old_border_and_box_shadow;
this.setData({
top_up: new_content.tabs_top_up,
sticky_top: this.propTop + (new_style?.tabs_margin?.margin_top || 0),
goods_tabs: new_data,
style_container: common_styles_computer(common_style),
style_img_container: common_img_computer(common_style, this.propIndex),
tabs_style: new_tabs_style,
tabs_sliding_fixed_bg: gradient_computer(tabs_data),
tabs_container: gradient_computer(tabs_data) + radius_computer(tabs_radius) + margin_computer(new_style?.tabs_margin || old_margin) + border_computer(tabs_content) + box_shadow_computer(tabs_content) + 'overflow: hidden;',
tabs_img_container: background_computer(tabs_data) + padding_computer(tabs_padding) + 'box-sizing: border-box;overflow: hidden;',
shop_margin_top: 'margin-top:' + (new_style?.shop_content_spacing || 0) * 2 + 'rpx;',
shop_container: gradient_computer(shop_content_data) + margin_computer(shop_content_margin) + radius_computer(shop_content_radius) + box_shadow_computer(shop_content) + border_computer(shop_content) + 'overflow: hidden;',
shop_img_container: background_computer(shop_content_data) + padding_computer(shop_content_padding) + 'box-sizing: border-box;overflow: hidden;',
});
},
tabs_click_event(index) {
let new_data = JSON.parse(JSON.stringify(this.propValue));
new_data.content.data_type = new_data.content.tabs_list[index].data_type;
new_data.content.category = new_data.content.tabs_list[index].category;
new_data.content.brand = new_data.content.tabs_list[index].brand;
new_data.content.number = new_data.content.tabs_list[index].number;
new_data.content.sort = new_data.content.tabs_list[index].sort;
new_data.content.sort_rules = new_data.content.tabs_list[index].sort_rules;
new_data.content.data_list = new_data.content.tabs_list[index].data_list;
new_data.content.data_auto_list = new_data.content.tabs_list[index].data_auto_list;
this.setData({
tabs_index: index,
goods_tabs: new_data,
diy_key: Math.random(),
});
},
// 获取商品距离顶部的距离
getTop() {
const query = uni.createSelectorQuery().in(this);
query
.select('.goods-tabs-' + this.propKey)
.boundingClientRect((res) => {
if ((res || null) != null) {
let new_data = typeof this.propValue == 'string' ? JSON.parse(JSON.stringify(this.propValue)) : this.propValue;
const new_style = new_data.style || {};
this.setData({
tabs_top: res.top - (new_style.common_style?.margin_top || 0),
});
}
})
.exec();
},
goods_buy_event(index, goods = {}, params = {}, back_data = null) {
this.$emit('goods_buy_event', index, goods, params, back_data);
},
goods_cart_back_event(e) {
if ((this.$refs.diy_goods_list || null) != null) {
this.$refs.diy_goods_list.goods_cart_back_event(e);
}
},
},
};
</script>
<style scoped lang="scss"></style>

420
components/diy/header.vue Normal file
View File

@@ -0,0 +1,420 @@
<template>
<view v-if="(propValue || null) !== null" class="header-container">
<view class="header-around wh-auto" :style="roll_style + position">
<view class="wh-auto" :style="roll_img_style">
<view class="wh-auto ht-auto pa up_slide_bg" :style="up_slide_style">
<view class="wh-auto ht-auto" :style="up_slide_img_style"></view>
</view>
<view :style="top_content_style">
<view class="header-content flex-row align-s">
<view class="model-top flex-1 mt-1">
<view class="roll pr z-i">
<view class="model-head pr padding-left-main padding-right-main" style="box-sizing: border-box">
<view class="flex-col" :style="'gap:' + data_alone_row_space">
<view class="model-head-content flex-row align-c jc-sb gap-16 wh-auto pr" :style="header_style">
<!-- 支付宝小程序自带返回按钮这里就不给返回按钮了这里给留出一点空间就行 -->
<!-- #ifndef MP-ALIPAY -->
<view v-if="!is_tabbar_pages" class="z-i dis-inline-block margin-top-xs" @tap="top_nav_left_back_event">
<iconfont name="icon-arrow-left" size="40rpx" propContainerDisplay="flex" :color="form.style.left_back_btn_color || '#333'"></iconfont>
</view>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<view class="dis-inline-block padding-left-sm"></view>
<!-- #endif -->
<view v-if="['1', '2', '3'].includes(form.content.theme)" class="flex-1">
<view class="flex-row align-c jc-c ht-auto gap-16" :class="position_class" :style="text_style + 'justify-content:' + form.content.indicator_location || 'center'">
<template v-if="['2', '3'].includes(form.content.theme)">
<view v-if="form.content.logo.length > 0" class="logo-outer-style re flex-row align-c">
<template v-if="form.style.up_slide_logo && form.style.up_slide_logo.length > 0">
<!-- 有上滑logo的处理逻辑 -->
<view v-if="(propScrollTop - 5) / (header_top + 33) < 1" class="logo-style" :style="up_slide_old_logo_style">
<image class="logo-style" :src="form.content.logo[0].url" mode="heightFix" />
</view>
<view :class="['logo-style left-0', { pa: (propScrollTop - 5) / (header_top + 33) < 1 }]" :style="'opacity:0;' + up_slide_opacity">
<image class="logo-style" :src="form.style.up_slide_logo[0].url" mode="heightFix" />
</view>
</template>
<template v-else>
<!-- 没有上滑logo的处理逻辑 -->
<image class="logo-style" :src="form.content.logo[0].url" mode="heightFix" />
</template>
</view>
</template>
<view v-if="['1', '2', '3'].includes(form.content.theme) && !isEmpty(form.content.title)">{{ form.content.title }}</view>
<template v-if="['3'].includes(form.content.theme) && !is_search_alone_row">
<view class="flex-1 fw-n">
<componentDiySearch :propValue="form" :propIsPageSettings="true"></componentDiySearch>
</view>
</template>
</view>
</view>
<view v-else-if="['4', '5'].includes(form.content.theme)" class="flex-1 flex-row align-c">
<view v-if="form.content.positioning_name_float !== '1'" class="flex-row align-c gap-2">
<view :style="location_margin">
<component-choice-location :propLocationContainerStyle="style_location_container" :propLocationImgContainerStyle="style_location_img_container" :propBaseColor="location_color" :propTextDefaultName="form.content.positioning_name" :propIsLeftIconArrow="form.content.is_location_left_icon_show == '1'" :propLeftImgValue="form.content.location_left_img" :propLeftIconValue="'icon-' + form.content.location_left_icon" :propIconLocationSize="location_left_size" :propIconArrowSize="location_right_size" :propIsRightIconArrow="form.content.is_location_right_icon_show == '1'" :propRightImgValue="form.content.location_right_img" :propRightIconValue="'icon-' + form.content.location_right_icon" :propTextMaxWidth="location_name_style" propContainerDisplay="flex" @onBack="choice_location_back"></component-choice-location>
</view>
</view>
<template v-if="['5'].includes(form.content.theme) && !is_search_alone_row">
<view class="flex-1">
<componentDiySearch :propValue="form" :propIsPageSettings="true" :propLocationMargin="location_margin" propSearchType="header" :propLocationContainerStyle="style_location_container" :propLocationImgContainerStyle="style_location_img_container" :propBaseColor="location_color" :propIconLocationSize="location_left_size" :propIconArrowSize="location_right_size" @onBack="choice_location_back"></componentDiySearch>
</view>
</template>
</view>
<view v-if="!isEmpty(form.content.icon_setting) && !is_icon_alone_row" class="flex-row align-c z-i" :class="['1'].includes(form.content.theme) ? 'right-0' : ''" :style="{ gap: form.style.img_space * 2 + 'rpx' }">
<view v-for="(item, index) in form.content.icon_setting" :key="index" class="pr" :style="{ width: form.style.img_size * 2 + 'rpx', height: form.style.img_size * 2 + 'rpx' }" :data-value="item.link.page" @tap="url_event">
<imageEmpty v-if="item.img.length > 0" :propImageSrc="item.img[0].url" :propErrorStyle="'width: ' + Number(form.style.img_size) * 2 + 'rpx;height:' + Number(form.style.img_size) * 2 + 'rpx;'"></imageEmpty>
<iconfont v-else :name="'icon-' + item.icon" :size="form.style.img_size * 2 + 'rpx'" :color="form.style.img_color" propContainerDisplay="flex"></iconfont>
<view v-if="!isEmpty(item.badge) && item.badge !== 0" class="pa badge-style">
<component-badge :propNumber="item.badge || 0"></component-badge>
</view>
</view>
</view>
</view>
<view v-if="is_search_alone_row || is_icon_alone_row" class="model-head-content flex-row align-c gap-16">
<template v-if="['3', '5'].includes(form.content.theme) && is_search_alone_row">
<view class="flex-1">
<componentDiySearch :propValue="form" :propIsPageSettings="true" :propLocationMargin="location_margin" propSearchType="header" :propLocationContainerStyle="style_location_container" :propLocationImgContainerStyle="style_location_img_container" :propBaseColor="location_color" :propIconLocationSize="location_left_size" :propIconArrowSize="location_right_size" @onBack="choice_location_back"></componentDiySearch>
</view>
</template>
<view v-if="!isEmpty(form.content.icon_setting) && is_icon_alone_row" class="flex-row align-c z-i" :class="['1'].includes(form.content.theme) ? 'right-0' : ''" :style="{ gap: form.style.img_space * 2 + 'rpx' }">
<view v-for="(item, index) in form.content.icon_setting" :key="index" class="pr" :style="{ width: form.style.img_size * 2 + 'rpx', height: form.style.img_size * 2 + 'rpx' }" :data-value="item.link.page" @tap="url_event">
<imageEmpty v-if="item.img.length > 0" :propImageSrc="item.img[0].url" :propErrorStyle="'width: ' + Number(form.style.img_size) * 2 + 'rpx;height:' + Number(form.style.img_size) * 2 + 'rpx;'"></imageEmpty>
<iconfont v-else :name="'icon-' + item.icon" :size="form.style.img_size * 2 + 'rpx'" :color="form.style.img_color" propContainerDisplay="flex"></iconfont>
<view v-if="!isEmpty(item.badge) && item.badge !== 0" class="pa badge-style">
<component-badge :propNumber="item.badge || 0"></component-badge>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<block v-if="!is_immersion_model">
<view v-if="!is_positon_realative" class="nav-seat" :style="top_content_style">
<view :style="'height:' + (is_search_alone_row || is_icon_alone_row ? 'calc(132rpx + ' + data_alone_row_space + ');' : '66rpx;')"></view>
</view>
</block>
<!-- #ifndef H5 || MP-TOUTIAO -->
<!-- <view v-if="is_positon_realative" class="wh-auto pf top-0 left-0 right-0" :style="roll_style">
<view :style="top_content_style">
<view :style="'height:' + (is_search_alone_row || is_icon_alone_row ? 'calc(132rpx + ' + data_alone_row_space + ');' : '66rpx;')"></view>
</view>
</view> -->
<!-- #endif -->
</view>
</template>
<script>
const app = getApp();
import componentDiySearch from '@/components/diy/search';
import imageEmpty from '@/components/diy/modules/image-empty';
import componentChoiceLocation from '@/components/choice-location/choice-location';
import componentBadge from '@/components/badge/badge';
import { isEmpty, background_computer, gradient_computer, margin_computer, padding_computer, radius_computer } from '@/common/js/common/common.js';
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
props: {
propValue: {
type: [String, Number, Object],
default: '',
},
// 滚动距离
propScrollTop: {
type: Number,
default: 0,
},
propKey: {
type: [String, Number],
default: '',
},
},
components: {
componentDiySearch,
imageEmpty,
componentChoiceLocation,
componentBadge,
},
data() {
return {
form: {},
new_style: {},
position: '',
roll_style: '',
text_style: '',
header_style: 'max-width:100%',
common_app_is_header_nav_fixed: 0,
// 5,7,0 是误差,, 10 是下边距66是高度bar_height是不同小程序下的导航栏距离顶部的高度
// #ifdef MP
top_content_style: 'padding-top:' + (bar_height + 5) + 'px;padding-bottom:10px;',
// #endif
// #ifdef H5 || MP-TOUTIAO
top_content_style: 'padding-top:' + (bar_height + 7) + 'px;padding-bottom:10px;',
// #endif
// #ifdef APP
top_content_style: 'padding-top:' + bar_height + 'px;padding-bottom:10px;',
// #endif
is_positon_realative: false,
// 顶部背景样式类别
header_background_type: 'color_image',
// #ifdef MP
header_top: bar_height + 5 + 10 + 33,
// #endif
// #ifdef H5 || MP-TOUTIAO
header_top: bar_height + 7 + 10 + 33,
// #endif
// #ifdef APP
header_top: bar_height + 0 + 10 + 33,
// #endif
// 判断是否是沉浸模式
is_immersion_model: false,
up_slide_opacity: '',
up_slide_old_logo_style: '',
up_slide_style: '',
up_slide_img_style: '',
// 当前页面是否在底部菜单中
is_tabbar_pages: app.globalData.is_tabbar_pages(),
// 判断header的查询是否独行
is_search_alone_row: false,
is_icon_alone_row: false,
data_alone_row_space: '0rpx',
// 定位设置
style_location_container: '',
style_location_img_container: '',
location_left_size: '24rpx',
location_right_size: '24rpx',
location_margin: '', // 悬浮之后有间距所以要将margin设置成外padding
location_color: '', // 定位颜色
location_name_style: '', // 定位样式
// 默认数据
old_radius: { radius: 0, radius_top_left: 0, radius_top_right: 0, radius_bottom_left: 0, radius_bottom_right: 0 },
old_padding: { padding: 0, padding_top: 0, padding_bottom: 0, padding_left: 0, padding_right: 0 },
old_margin: { margin: 0, margin_top: 0, margin_bottom: 0, margin_left: 0, margin_right: 0 },
};
},
watch: {
// 监听滚动距离
propScrollTop(newVal) {
const { up_slide_background_color_list, up_slide_background_direction, up_slide_background_img, up_slide_background_img_style, up_slide_display } = this.propValue.style || {};
if (up_slide_display == '1') {
// 渐变
const gradient = { color_list: up_slide_background_color_list, direction: up_slide_background_direction };
// 背景图
const back = { background_img: up_slide_background_img, background_img_style: up_slide_background_img_style };
const up_slide_opacity = 'opacity:' + ((newVal - 20) / this.header_top > 1 ? 1 : ((newVal - 20) / this.header_top).toFixed(2)) + ';';
this.up_slide_opacity = up_slide_opacity;
// 来的logo要比新的隐藏的快所以要比原来的logo快一点
this.up_slide_old_logo_style = 'opacity:' + ((newVal - 5) / this.header_top > 1 ? 0 : (1 - (newVal - 5) / this.header_top).toFixed(2)) + ';';
// =0是大小误差
this.up_slide_style = gradient_computer(gradient) + up_slide_opacity;
this.up_slide_img_style = background_computer(back);
}
},
propKey(val) {
if ((this.propValue || null) !== null) {
this.init();
}
},
},
created() {
// 判断是否有值初始化
if ((this.propValue || null) !== null) {
this.init();
}
},
methods: {
// 判断是否为空
isEmpty,
// 初始化
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
let new_roll_style = '';
let new_roll_img_style = '';
const { header_background_img, header_background_img_style, header_background_color_list, header_background_direction, header_background_type, immersive_style } = new_style;
if (header_background_type === 'color_image') {
// 渐变
const gradient = { color_list: header_background_color_list, direction: header_background_direction };
// 背景图
const back = { background_img: header_background_img, background_img_style: header_background_img_style };
new_roll_style += gradient_computer(gradient);
new_roll_img_style += background_computer(back);
} else {
new_roll_style += `background: transparent;`;
}
// 小程序下,获取小程序胶囊的宽度
let menu_button_info = 'max-width:100%';
let new_text_style = `font-weight:${new_style.header_background_title_typeface}; font-size: ${new_style.header_background_title_size * 2}rpx; color: ${new_style.header_background_title_color};`;
// #ifndef MP-TOUTIAO
// #ifdef MP
// 判断是否有胶囊
const is_current_single_page = app.globalData.is_current_single_page();
// 如果有胶囊的时候,做处理
if (is_current_single_page == 0) {
const custom = uni.getMenuButtonBoundingClientRect();
menu_button_info = `max-width:calc(100% - ${custom.width + 10}px);`;
new_text_style += `right:-${custom.width + 10}px;`;
}
// #endif
// #endif
const { location_margin = this.old_margin } = new_style;
this.setData({
form: this.propValue,
position: new_style.up_slide_display == '1' ? 'position:fixed;' : new_style.immersive_style === '1' ? 'position:absolute;' : 'position:relative;',
is_positon_realative: new_style.up_slide_display == '1' ? false : true,
roll_style: new_roll_style,
roll_img_style: new_roll_img_style,
text_style: new_text_style,
position_class: new_content.indicator_location == 'center' ? `indicator-center` : '',
header_style: menu_button_info,
header_background_type: header_background_type,
is_immersion_model: header_background_type !== 'color_image' && immersive_style == '1',
data_alone_row_space: new_style.data_alone_row_space * 2 + 'rpx',
is_search_alone_row: new_content.data_alone_row_value && new_content.data_alone_row_value.includes('search'),
is_icon_alone_row: new_content.data_alone_row_value && new_content.data_alone_row_value.includes('icon'),
style_location_container: this.get_style_location_container(new_style),
style_location_img_container: this.get_style_location_img_container(new_style),
location_left_size: !isEmpty(new_style.location_left_icon_size) ? new_style.location_left_icon_size * 2 + 'rpx' : '24rpx',
location_right_size: !isEmpty(new_style.location_right_icon_size) ? new_style.location_right_icon_size * 2 + 'rpx' : '24rpx',
location_color: !isEmpty(new_style.location_color) ? new_style.location_color : new_style?.position_color || '',
location_name_style: this.get_location_name_style(new_content),
location_margin: `padding: ${location_margin.margin_top * 2}rpx ${location_margin.margin_right * 2}rpx ${location_margin.margin_bottom * 2}rpx ${location_margin.margin_left * 2}rpx;`, // 悬浮之后有间距所以要将margin设置成外padding
});
// this.$emit('onImmersionModelCallBack', this.is_immersion_model);
},
get_location_name_style(new_content) {
const is_search_alone_row = new_content.data_alone_row_value && new_content.data_alone_row_value.includes('search');
const is_icon_alone_row = new_content.data_alone_row_value && new_content.data_alone_row_value.includes('icon');
let width = 0;
if (is_search_alone_row && is_icon_alone_row) {
width = 200;
} else if (is_search_alone_row || is_icon_alone_row) {
width = 100;
}
if (new_content.theme == '4') {
return `${(150 + width) * 2}rpx;`;
} else {
return `${(100 + width) * 2}rpx;`;
}
},
// 定位设置
get_style_location_container(new_style) {
const { location_margin = this.old_margin, location_radius = this.old_radius } = new_style;
const style = {
color_list: new_style?.location_color_list || [],
direction: new_style?.location_direction || '',
};
const height = 32 - location_margin.margin_top - location_margin.margin_bottom;
return gradient_computer(style) + radius_computer(location_radius) + `height: ${height * 2}rpx;line-height: ${height * 2}rpx;`;
},
// 背景图片
get_style_location_img_container(new_style) {
const { location_background_img = [], location_background_img_style = '2', location_border_show = '0', location_padding = this.old_padding, location_margin = this.old_margin, location_border_size = this.old_padding, location_border_color = '', location_border_style = 'solid' } = new_style;
const style = {
background_img: location_background_img,
background_img_style: location_background_img_style,
};
let border = ``;
if (location_border_show == '1') {
border += `border-width: ${location_border_size.padding_top}px ${location_border_size.padding_right}px ${location_border_size.padding_bottom}px ${location_border_size.padding_left}px;border-style: ${location_border_style};border-color: ${location_border_color};`;
}
const height = 32 - (location_margin.margin_top || 0) - (location_margin.margin_bottom || 0);
return background_computer(style) + padding_computer(location_padding) + border + `height: ${(height > 0 ? height : 0) * 2}rpx;line-height: ${(height > 0 ? height : 0) * 2}rpx;box-sizing: border-box;`;
},
// 获取顶部导航高度
get_nav_height() {
const query = uni.createSelectorQuery().in(this);
query
.select('.article-tabs')
.boundingClientRect((res) => {
if ((res || null) != null) {
this.setData({
tabs_top: res.top,
});
}
})
.exec();
},
// 位置回调
choice_location_back(e) {
this.$emit('onLocationBack', e);
},
// 打开地址
url_event(e) {
app.globalData.url_event(e);
},
// 返回事件
top_nav_left_back_event() {
app.globalData.page_back_prev_event();
},
},
};
</script>
<style lang="scss" scoped>
.header-container {
width: 100%;
.header-around {
z-index: 12;
}
.model-top {
.roll {
width: 100%;
height: 100%;
margin: 0 auto;
}
.img {
width: 680rpx;
}
}
.model-head {
.model-head-content {
height: 66rpx;
// overflow: hidden;
top: -1rpx;
/* #ifdef H5 || MP-TOUTIAO */
top: 4rpx;
/* #endif */
}
}
.model-head-icon {
position: absolute;
right: 0;
bottom: 0;
height: 66rpx;
.function-icon {
height: 66rpx;
}
}
.logo-outer-style {
height: 56rpx;
.logo-style {
height: 56rpx;
width: 100%;
}
}
}
.indicator-center {
position: absolute;
left: 0;
right: 0;
text-align: center;
top: 0;
padding-left: 0;
}
.up_slide_bg {
z-index: -1;
}
.badge-style {
top: -20rpx;
right: 5rpx;
}
</style>

122
components/diy/hot-zone.vue Normal file
View File

@@ -0,0 +1,122 @@
<template>
<!-- 热区 -->
<view ref="containerRef" class="oh container" :style="style_container">
<view :style="style_img_container">
<view ref="hotRef" class="hot pr" :style="style">
<image :src="img" class="wh-auto dis-block" mode="widthFix" @load="on_load_img" />
<view v-for="(item, index) in hot_data" :key="index" class="hot_box" :style="'left: ' + item.drag_start.x * w_scale1 * w_scale2 + 'px;top:' + item.drag_start.y * h_scale1 * h_scale2 + 'px;width: ' + Math.max(item.drag_end.width * w_scale1 * w_scale2, 1) + 'px;height: ' + Math.max(item.drag_end.height * h_scale1 * h_scale2, 1) + 'px;display: flex;'" :data-value="item.link.page" @tap="url_event"> </view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
img: '',
hot_data: [],
img_width: 1,
img_height: 1,
container_ref_h: 0,
hot_ref_h: 0,
hot_ref_w: 0,
w_scale1: 1,
h_scale1: 1,
w_scale2: 1,
h_scale2: 1,
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
this.setData({
img: new_content.img[0].url,
img_width: new_content.hot.img_width || 1,
img_height: new_content.hot.img_height || 1,
hot_data: new_content.hot.data || [],
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
// 图片加载完成 获取宽高
on_load_img(e) {
const query = uni.createSelectorQuery();
// 选择我们想要的元素
query
.in(this)
.select('.container')
.boundingClientRect((res) => {
if ((res || null) != null) {
// data包含元素的宽度、高度等信息
this.setData({
container_ref_h: res.height,
w_scale1: res.height / this.img_width,
h_scale1: res.height / this.img_height,
});
}
})
.exec(); // 执行查询
query
.in(this)
.select('.hot')
.boundingClientRect((res) => {
if ((res || null) != null) {
// data包含元素的宽度、高度等信息
this.setData({
w_scale2: res.width / this.container_ref_h,
h_scale2: res.height / this.container_ref_h,
});
}
})
.exec(); // 执行查询
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style lang="scss" scoped>
.hot {
min-height: 20rpx;
.hot_box {
// background: rgba(42, 148, 255, 0.15);
// border: 2rpx dashed rgba(142, 198, 255, 0.5);
position: absolute;
opacity: 0.4;
}
}
</style>

View File

@@ -0,0 +1,228 @@
<template>
<!-- 图片魔方 -->
<view class="img-magic" :style="style_container + 'height:' + (form.style_actived == 10 ? '100%' : container_size)">
<view class="magic-container wh-auto ht-auto" :style="style_img_container">
<view class="pr" :style="outer_style">
<!-- 风格3 -->
<template v-if="form.style_actived == 2">
<view class="flex-row align-c jc-c style-size">
<view v-for="(item, index) in form.img_magic_list" :key="index" class="three flex-row" :style="img_spacing" :data-value="item.img_link ? item.img_link.page : ''" @tap="url_event">
<view class="wh-auto flex-row" :style="content_img_container">
<view class="flex-1" :style="content_img_style_container">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="dis-block wh-auto ht-auto" :mode="img_fit" :style="content_img_radius"></image>
</view>
</view>
</view>
</view>
</template>
<!-- 风格9 -->
<template v-else-if="form.style_actived == 8">
<view class="flex-row align-c jc-c style-size flex-wrap">
<view v-for="(item, index) in form.img_magic_list" :key="index" :class="['flex-row', { 'style9-top': [0, 1].includes(index), 'style9-bottom': ![0, 1].includes(index) }]" :style="img_spacing" :data-value="item.img_link ? item.img_link.page : ''" @tap="url_event">
<view class="wh-auto flex-row" :style="content_img_container">
<view class="flex-1" :style="content_img_style_container">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="dis-block wh-auto ht-auto" :mode="img_fit" :style="content_img_radius"></image>
</view>
</view>
</view>
</view>
</template>
<template v-else-if="form.style_actived == 10">
<template v-if="form.limit_size == '0'">
<view v-for="(item, index) in form.img_magic_list" :key="index" class="cr-main flex-row" :style="img_spacing + selected_style(item)" :data-value="item.img_link ? item.img_link.page : ''" @tap="url_event">
<view class="wh-auto flex-row" :style="content_img_container">
<view class="flex-1" :style="content_img_style_container">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="dis-block wh-auto" mode="widthFix" :style="content_img_radius"></image>
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(item, index) in form.img_magic_list" :key="index" class="cr-main flex-row" :style="img_spacing + selected_style(item) + ';height:' + image_height" :data-value="item.img_link ? item.img_link.page : ''" @tap="url_event">
<view class="wh-auto flex-row" :style="content_img_container">
<view class="flex-1" :style="content_img_style_container">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="dis-block wh-auto ht-auto" :mode="img_fit" :style="content_img_radius"></image>
</view>
</view>
</view>
</template>
</template>
<template v-else>
<view v-for="(item, index) in form.img_magic_list" :key="index" class="cube-selected cr-main flex-row" :style="img_spacing + selected_style(item)" :data-value="item.img_link ? item.img_link.page : ''" @tap="url_event">
<view class="wh-auto flex-row" :style="content_img_container">
<view class="flex-1" :style="content_img_style_container">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="dis-block wh-auto ht-auto" :mode="img_fit" :style="content_img_radius"></image>
</view>
</view>
</view>
</template>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, radius_computer, percentage_count, isEmpty, margin_computer, padding_computer, old_padding, old_margin, border_width } from '@/common/js/common/common.js';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propOuterContainerPadding: {
type: Number,
default: 0,
}
},
data() {
return {
form: {},
style_container: '',
style_img_container: '',
// 外部样式
outer_style: '',
// 图片间距设置
img_spacing: '',
// 图片圆角
content_img_radius: '',
cube_cell: '',
container_size: '',
div_width: 0,
image_height: '',
img_fit: '',
content_img_container: '',
content_img_style_container: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
computed: {
// 根据当前页面大小计算成百分比
selected_style() {
return (item) => {
return `overflow: hidden;width: ${this.percentage(this.getSelectedWidth(item))}; height: ${this.percentage(this.getSelectedHeight(item))}; top: ${this.percentage(this.getSelectedTop(item))}; left: ${this.percentage(this.getSelectedLeft(item))};`;
};
},
},
mounted() {
this.init();
},
methods: {
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
const new_style_spacing = new_content.style_actived === 10 ? 0 : new_style.image_spacing;
// 外部样式
const outer_spacing = `calc(100% + ${new_style_spacing * 2}rpx)`;
const outer_sx = `-${new_style_spacing}rpx`;
// 图片间距设置
const spacing = `${new_style_spacing}rpx`;
// scaleToFill 对应 cover aspectFit 对应 contain center 对应 none
let fit = '';
if (new_content.img_fit == 'contain') {
fit = 'aspectFit';
} else if (new_content.img_fit == 'fill') {
fit = 'scaleToFill';
} else if (new_content.img_fit == 'cover') {
fit = 'aspectFill';
}
const container_height = !isEmpty(new_content.container_height) ? new_content.container_height : sys_width;
const density = !isEmpty(new_content.magic_cube_density) ? new_content.magic_cube_density : 4;
const { margin_left, margin_right } = new_style.common_style;
const width = sys_width - margin_left - margin_right - border_width(new_style.common_style) - this.propOuterContainerPadding;
const scale = width / 390;
this.setData({
form: this.propValue.content,
new_style: this.propValue.style,
outer_style: `width:${outer_spacing};height:${outer_spacing};margin:${outer_sx};`,
img_spacing: `padding:${spacing};`,
content_img_radius: radius_computer(new_style),
style_container: common_styles_computer(new_style.common_style) + 'box-sizing: border-box;',
style_img_container: common_img_computer(new_style.common_style, this.propIndex) + 'box-sizing: border-box;',
img_fit: fit,
div_width: sys_width,
container_size: container_height * scale + 'px',
cube_cell: sys_width / density,
image_height: this.propValue.content.image_height * scale + 'px',
content_img_container: common_styles_computer(new_style) + margin_computer(new_style?.margin || old_margin) + 'box-sizing: border-box;',
content_img_style_container: common_img_computer(new_style) + padding_computer(new_style?.padding || old_padding) + 'box-sizing: border-box;',
});
},
getSelectedWidth(item) {
return (item.end.x - item.start.x + 1) * this.cube_cell;
},
//计算选中层的高度。
getSelectedHeight(item) {
return (item.end.y - item.start.y + 1) * this.cube_cell;
},
//计算选中层的右边距离。
getSelectedTop(item) {
return (item.start.y - 1) * this.cube_cell;
},
//计算选中层的左边距离。
getSelectedLeft(item) {
return (item.start.x - 1) * this.cube_cell;
},
// 计算成百分比
percentage(num) {
return percentage_count(num, this.div_width);
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style lang="scss" scoped>
// 图片魔方是一个正方形,根据宽度计算高度
.img-magic {
overflow: hidden;
box-sizing: border-box;
}
.cube-selected {
position: absolute;
text-align: center;
box-sizing: border-box;
}
.style-size {
height: 100%;
width: 100%;
box-sizing: border-box;
.three {
width: 33%;
height: 100%;
position: relative;
box-sizing: border-box;
}
.style9-top {
width: 50%;
height: 50%;
position: relative;
box-sizing: border-box;
}
.style9-bottom {
width: calc(100% / 3);
height: 50%;
position: relative;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<view :class="'wh-auto pr allSignList-' + propIndex + propKey" :style="'height:' + propDataHeight * propScale + 'px;'">
<view v-for="(item, index) in new_list" :key="index" :data-id="item.id" :data-location-x="item.location.x" :data-location-y="item.location.y" :class="'sign-' + propIndex + propKey + ' main-content ' + get_animation_class(item.com_data)" :style="'left:' + get_percentage_count(item.location.x, item.com_data.data_follow, 'left') + ';top:' + get_percentage_count(item.location.y, item.com_data.data_follow, 'top') + ';width:' + get_percentage_count(item.com_data.com_width, item.com_data.data_follow, 'width', item.com_data.is_width_auto, item.com_data.max_width, item.key) + ';height:' + get_percentage_count(item.com_data.com_height, item.com_data.data_follow, 'height', item.com_data.is_height_auto, item.com_data.max_height, item.key) + ';z-index:' + (new_list.length - 1 > 0 ? (new_list.length - 1) - index : 0)">
<template v-if="item.key == 'text'">
<model-text :propKey="propKey" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" :propTitleParams="propShowData.data_name" @url_event="url_event"></model-text>
</template>
<template v-else-if="item.key == 'img'">
<model-image :propKey="propKey" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" :propImgParams="propShowData.data_logo" @url_event="url_event"></model-image>
</template>
<template v-else-if="item.key == 'auxiliary-line'">
<model-lines :propKey="propKey" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId"></model-lines>
</template>
<template v-else-if="item.key == 'icon'">
<model-icon :propKey="propKey" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" @url_event="url_event"></model-icon>
</template>
<template v-else-if="item.key == 'panel'">
<model-panel :propKey="propKey" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" @url_event="url_event"></model-panel>
</template>
</view>
</view>
</template>
<script>
import modelText from '@/components/diy/modules/custom/model-text.vue';
import modelLines from '@/components/diy/modules/custom/model-lines.vue';
import modelImage from '@/components/diy/modules/custom/model-image.vue';
import modelIcon from '@/components/diy/modules/custom/model-icon.vue';
import modelPanel from '@/components/diy/modules/custom/model-panel.vue';
import { location_compute, isEmpty } from '@/common/js/common/common.js';
export default {
components: {
modelText,
modelLines,
modelImage,
modelIcon,
modelPanel,
},
props: {
propCustomList: {
type: Array,
default: () => {
return [];
},
required: true,
},
propIndex: {
type: Number,
default: 0,
},
propSourceList: {
type: Object,
default: () => {
return {};
}
},
propDataHeight: {
type: Number,
default: 0,
},
propScale: {
type: Number,
default: 1,
},
propDataIndex: {
type: Number,
default: 1,
},
propDataSplitIndex: {
type: Number,
default: 1,
},
propIsCustom: {
type: Boolean,
default: false,
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propShowData: {
type: Object,
default: () => ({
data_key: 'id',
data_name: 'name'
}),
},
propKey: {
type: [String, Number],
default: '',
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: () => {
return [];
}
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
new_list: [],
custom_width: 0,
};
},
watch: {
propKey(val) {
// 初始化
this.init(this.propCustomList);
},
propCustomList(val) {
this.init(val);
}
},
computed: {
get_percentage_count() {
return (num, data_follow, type, is_auto = '0', max_size = 0, key = '') => {
// 检查类型是否为'left'或'top',如果是,则根据跟随数据计算样式
if (['left', 'top'].includes(type)) {
const { id = '', type: follow_type = 'left' } = data_follow || { id: '', type: 'left' };
// 如果id不为空且follow_type与type匹配则返回原始值的字符串表示
if (id !== '' && follow_type === type) {
return `${num}px`;
}
// 如果条件不满足则根据比例缩放num并返回
return `${num * this.propScale}px`;
} else {
// 如果is_auto设置为'1'则根据type和max_size计算自动样式
if (is_auto === '1') {
if (type === 'width' || type === 'height') {
if (typeof max_size === 'number' && max_size >= 0) {
const scaledMaxSize = max_size * this.propScale;
const autoStyle = 'auto;';
const maxSizeStyle = scaledMaxSize > 0 ? ` max-${type}: ${scaledMaxSize}px;` : '';
const whiteSpaceStyle = type === 'width' && scaledMaxSize <= 0 ? ' white-space:nowrap;' : '';
return `${ autoStyle }${ maxSizeStyle }${ whiteSpaceStyle }`;
} else {
return 'auto;';
}
}
} else {
// 微信小程序图片等比缩放对小数点后的内容支持的不是特别的好,需要取向上取整数
if (key == 'img' && ['width', 'height'].includes(type)) {
// 如果is_auto未设置或条件不满足则根据比例缩放num并返回
return `${ Math.round(num * this.propScale) }px`;
} else {
// 如果is_auto未设置或条件不满足则根据比例缩放num并返回
return `${num * this.propScale}px`;
}
}
}
};
},
get_animation_class() {
return (data) => {
const { type = 'none', number = 'infinite' } = data?.animation_style || {};
if (type != 'none') {
return type + (number == 'infinite' ? `-${number}` : '');
} else {
return '';
}
};
}
},
mounted() {
this.init(this.propCustomList);
},
methods: {
async init(val) {
// 如果为空就不进行渲染
if (isEmpty(val)) {
return;
}
await this.get_custom_width();
this.set_new_list(val);
},
get_custom_width() {
// 获取当前容器的宽度
const query = uni.createSelectorQuery().in(this);
query.select('.allSignList-' + this.propIndex + this.propKey)
.boundingClientRect((res) => {
if (res) {
this.setData({
custom_width: res.width,
});
}
})
.exec();
},
async set_new_list(val) {
// 第一次渲染先渲染全部数据
this.setData({
new_list: val
});
// 判断是否有跟随的数据
const follow_list = val.filter(item => item.com_data.data_follow && item.com_data?.data_follow?.id !== '');
if (follow_list.length > 0) {
// 等待页面渲染完成之后再获取内容
await this.$nextTick();
// 第二次如果有跟随数据,更新对应数据的内容, 如果有超出容器范围的数据,限制其超出容器范围
const query = uni.createSelectorQuery().in(this);
query.selectAll('.sign-' + this.propIndex + this.propKey)
.boundingClientRect((rect) => {
if (rect) {
// 将返回的内容转成map对象方便快速查找节省性能
const idMap = new Map(rect.map(item => [item.dataset.id, item]));
// 历史数据拷贝,方便后续操作避免每次都更新数据,统一重新渲染
const val = JSON.parse(JSON.stringify(this.new_list));
val.forEach((item1) => {
const { data_follow } = item1.com_data;
const targetItem = idMap.get(data_follow?.id);
if (targetItem) {
const text_item = item1.key == 'text' ? idMap.get((item1?.id || '')+ '') : undefined;
if (data_follow?.type === 'left') {
// 更新位置信息
const location_x = this.updateLocation(targetItem, data_follow, this.propScale, true);
// 获取组件的宽度,如果是宽度自适应,则需要重新计算位置
let item_width = item1.com_data.com_width;
// 如果是宽度自适应,需要重新判断一下处理逻辑
if (item1.com_data?.is_width_auto === '1' && text_item) {
item_width = text_item.width;
}
// 根据容器信息更新位置信息
item1.location.x = location_compute(item_width, location_x, this.custom_width);
} else if (data_follow?.type === 'top') {
// 更新位置信息
const location_y = this.updateLocation(targetItem, data_follow, this.propScale, false);
// 获取组件的宽度,如果是宽度自适应,则需要重新计算位置
let item_height = item1.com_data.com_height;
// 如果是高度自适应,需要重新判断一下处理逻辑
if (item1.com_data?.is_height_auto === '1' && text_item) {
item_height = text_item.height;
}
// 根据容器信息更新位置信息
item1.location.y = location_compute(item_height, location_y, this.propDataHeight * this.propScale);
}
}
});
this.setData({
new_list: val
});
}
})
.exec();
}
},
updateLocation(targetItem, data_follow, scale, isX) {
try {
const locationValueStr = targetItem.dataset[`location${isX ? 'X' : 'Y'}`];
if (locationValueStr == null) {
return;
}
const locationValue = parseFloat(locationValueStr);
if (isNaN(locationValue) || scale <= 0 || (isX ? targetItem.width < 0 : targetItem.height < 0)) return;
return ((locationValue + (data_follow?.spacing || 0)) * scale) + (isX ? targetItem.width : targetItem.height);
} catch (error) {
console.error(`Error updating location ${isX ? 'X' : 'Y'}:`, error);
}
},
url_event(e) {
this.$emit('url_event', e, this.propDataIndex, this.propDataSplitIndex);
}
},
};
</script>
<style lang="scss" scoped>
.main-content {
position: absolute;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<view :class="'wh-auto pr allSignList-' + propIndex + propKey" :style="'height:' + propDataHeight * propScale + 'px;'">
<view v-for="(item, index) in new_list" :key="index" :data-id="item.id" :data-location-x="item.location.x" :data-location-y="item.location.y" :class="'sign-' + propIndex + propKey + ' main-content ' + get_animation_class(item.com_data)" :style="'left:' + get_percentage_count(item.location.x, item.com_data.data_follow, 'left') + ';top:' + get_percentage_count(item.location.y, item.com_data.data_follow, 'top') + ';width:' + get_percentage_count(item.com_data.com_width, item.com_data.data_follow, 'width', item.com_data.is_width_auto, item.com_data.max_width, item.key) + ';height:' + get_percentage_count(item.com_data.com_height, item.com_data.data_follow, 'height', item.com_data.is_height_auto, item.com_data.max_height, item.key) + ';z-index:' + (new_list.length - 1 > 0 ? (new_list.length - 1) - index : 0)">
<template v-if="item.key == 'text'">
<model-text :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" :propTitleParams="propShowData.data_name" @url_event="url_event"></model-text>
</template>
<template v-else-if="item.key == 'img'">
<model-image :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" :propImgParams="propShowData.data_logo" @url_event="url_event"></model-image>
</template>
<template v-else-if="item.key == 'auxiliary-line'">
<model-lines :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId"></model-lines>
</template>
<template v-else-if="item.key == 'icon'">
<model-icon :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" @url_event="url_event"></model-icon>
</template>
<template v-else-if="item.key == 'panel'">
<model-panel :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propSourceList="propSourceList" :propConfigLoop="propConfigLoop" :propIsCustom="propIsCustom" :propIsCustomGroup="propIsCustomGroup" :propCustomGroupFieldId="propCustomGroupFieldId" @url_event="url_event"></model-panel>
</template>
<template v-else-if="item.key == 'custom-group'">
<model-custom-group :propKey="propKey + item.id" :propValue="item.com_data" :propScale="propScale" :propFieldList="propFieldList" :propConfigLoop="propConfigLoop" :propDataWidth="item.com_data.com_width" :propDataHeight="item.com_data.custom_height" :propSourceList="propSourceList" :propGroupSourceList="propGroupSourceList" :propIsCustom="propIsCustom" :propShowData="propShowData"></model-custom-group>
</template>
</view>
</view>
</template>
<script>
import modelText from '@/components/diy/modules/custom/model-text.vue';
import modelLines from '@/components/diy/modules/custom/model-lines.vue';
import modelImage from '@/components/diy/modules/custom/model-image.vue';
import modelIcon from '@/components/diy/modules/custom/model-icon.vue';
import modelPanel from '@/components/diy/modules/custom/model-panel.vue';
import modelCustomGroup from '@/components/diy/modules/custom/model-custom-group.vue';
import { location_compute, isEmpty } from '@/common/js/common/common.js';
export default {
components: {
modelText,
modelLines,
modelImage,
modelIcon,
modelPanel,
modelCustomGroup
},
props: {
propCustomList: {
type: Array,
default: () => {
return [];
},
required: true,
},
propIndex: {
type: Number,
default: 0,
},
propSourceList: {
type: Object,
default: () => {
return {};
}
},
propGroupSourceList: {
type: Array,
default: () => {
return [];
}
},
propDataHeight: {
type: Number,
default: 0,
},
propScale: {
type: Number,
default: 1,
},
propDataIndex: {
type: Number,
default: 1,
},
propDataSplitIndex: {
type: Number,
default: 1,
},
propIsCustom: {
type: Boolean,
default: false,
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propShowData: {
type: Object,
default: () => ({
data_key: 'id',
data_name: 'name'
}),
},
propKey: {
type: [String, Number],
default: '',
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: () => {
return [];
}
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
new_list: [],
custom_width: 0,
};
},
watch: {
propKey(val) {
// 初始化
this.init(this.propCustomList);
},
propCustomList(val) {
this.init(val);
}
},
computed: {
get_percentage_count() {
return (num, data_follow, type, is_auto = '0', max_size = 0, key = '') => {
// 检查类型是否为'left'或'top',如果是,则根据跟随数据计算样式
if (['left', 'top'].includes(type)) {
const { id = '', type: follow_type = 'left' } = data_follow || { id: '', type: 'left' };
// 如果id不为空且follow_type与type匹配则返回原始值的字符串表示
if (id !== '' && follow_type === type) {
return `${num}px`;
}
// 如果条件不满足则根据比例缩放num并返回
return `${num * this.propScale}px`;
} else {
// 如果is_auto设置为'1'则根据type和max_size计算自动样式
if (is_auto === '1') {
if (type === 'width' || type === 'height') {
if (typeof max_size === 'number' && max_size >= 0) {
const scaledMaxSize = max_size * this.propScale;
const autoStyle = 'auto;';
const maxSizeStyle = scaledMaxSize > 0 ? ` max-${type}: ${scaledMaxSize}px;` : '';
const whiteSpaceStyle = type === 'width' && scaledMaxSize <= 0 ? ' white-space:nowrap;' : '';
return `${ autoStyle }${ maxSizeStyle }${ whiteSpaceStyle }`;
} else {
return 'auto;';
}
}
} else {
// 微信小程序图片等比缩放对小数点后的内容支持的不是特别的好,需要取向上取整数
if (key == 'img' && ['width', 'height'].includes(type)) {
// 如果is_auto未设置或条件不满足则根据比例缩放num并返回
return `${ Math.round(num * this.propScale) }px`;
} else {
// 如果is_auto未设置或条件不满足则根据比例缩放num并返回
return `${num * this.propScale}px`;
}
}
}
};
},
get_animation_class() {
return (data) => {
const { type = 'none', number = 'infinite' } = data?.animation_style || {};
if (type != 'none') {
return type + (number == 'infinite' ? `-${number}` : '');
} else {
return '';
}
};
}
},
mounted() {
this.init(this.propCustomList);
},
methods: {
async init(val) {
// 如果为空就不进行渲染
if (isEmpty(val)) {
return;
}
await this.get_custom_width();
this.set_new_list(val);
},
get_custom_width() {
// 获取当前容器的宽度
const query = uni.createSelectorQuery().in(this);
query.select('.allSignList-' + this.propIndex + this.propKey)
.boundingClientRect((res) => {
if (res) {
this.setData({
custom_width: res.width,
});
}
})
.exec();
},
async set_new_list(val) {
// 第一次渲染先渲染全部数据
this.setData({
new_list: val
});
// 判断是否有跟随的数据
const follow_list = val.filter(item => item.com_data.data_follow && item.com_data?.data_follow?.id !== '');
if (follow_list.length > 0) {
await this.$nextTick();
// 第二次如果有跟随数据,更新对应数据的内容, 如果有超出容器范围的数据,限制其超出容器范围
const query = uni.createSelectorQuery().in(this);
query.selectAll('.sign-' + this.propIndex + this.propKey)
.boundingClientRect((rect) => {
if (rect) {
// 将返回的内容转成map对象方便快速查找节省性能
const idMap = new Map(rect.map(item => [item.dataset.id, item]));
// 历史数据拷贝,方便后续操作避免每次都更新数据,统一重新渲染
const val = JSON.parse(JSON.stringify(this.new_list));
val.forEach((item1) => {
const { data_follow } = item1.com_data;
const targetItem = idMap.get(data_follow?.id);
if (targetItem) {
const text_item = item1.key == 'text' ? idMap.get((item1?.id || '')+ '') : undefined;
if (data_follow?.type === 'left') {
// 更新位置信息
const location_x = this.updateLocation(targetItem, data_follow, this.propScale, true);
// 获取组件的宽度,如果是宽度自适应,则需要重新计算位置
let item_width = item1.com_data.com_width;
// 如果是宽度自适应,需要重新判断一下处理逻辑
if (item1.com_data.is_width_auto === '1' && text_item) {
item_width = text_item.width;
}
// 根据容器信息更新位置信息
item1.location.x = location_compute(item_width, location_x, this.custom_width);
} else if (data_follow?.type === 'top') {
// 更新位置信息
const location_y = this.updateLocation(targetItem, data_follow, this.propScale, false);
// 获取组件的宽度,如果是宽度自适应,则需要重新计算位置
let item_height = item1.com_data.com_height;
// 如果是高度自适应,需要重新判断一下处理逻辑
if (item1.com_data?.is_height_auto === '1' && text_item) {
item_height = text_item.height;
}
// 根据容器信息更新位置信息
item1.location.y = location_compute(item_height, location_y, this.propDataHeight * this.propScale);
}
}
});
this.setData({
new_list: val
});
}
})
.exec();
}
},
updateLocation(targetItem, data_follow, scale, isX) {
try {
const locationValueStr = targetItem.dataset[`location${isX ? 'X' : 'Y'}`];
if (locationValueStr == null) {
return;
}
const locationValue = parseFloat(locationValueStr);
if (isNaN(locationValue) || scale <= 0 || (isX ? targetItem.width < 0 : targetItem.height < 0)) return;
const computedValue = ((locationValue + (data_follow?.spacing || 0)) * scale) + (isX ? targetItem.width : targetItem.height);
return computedValue;
} catch (error) {
console.error(`Error updating location ${isX ? 'X' : 'Y'}:`, error);
}
},
url_event(e) {
this.$emit('url_event', e, this.propDataIndex, this.propDataSplitIndex);
}
},
};
</script>
<style lang="scss" scoped>
.main-content {
position: absolute;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,378 @@
<template>
<view v-if="is_show" class="ht-auto" :style="style_container">
<view :style="style_img_container">
<view :style="style_content_container">
<view class="w h pr" :style="style_content_img_container">
<template v-if="data_source_content_list.length > 0 && form.data_source_direction == 'vertical'">
<view class="flex-row flex-wrap" :style="'row-gap:' + new_style.row_gap + 'px;column-gap:' + new_style.column_gap + 'px;'">
<view v-for="(item, index) in data_source_content_list" :key="index" class="ht-auto" :style="gap_width">
<view v-for="(item1, index1) in item.split_list" :key="index1">
<view :style="style_chunk_container">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataGroupRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propFieldList="propFieldList" :propDataHeight="propDataHeight" :propScale="custom_scale" :propIsCustom="propIsCustom" :propIsCustomGroup="true" :propShowData="propShowData" :propConfigLoop="propConfigLoop !== '1' ? form.is_use_parent_data : '1'" :propDataIndex="index" :propDataSplitIndex="index1" @url_event="url_event"></dataGroupRendering>
</view>
</view>
</view>
</view>
</view>
</template>
<view v-else-if="data_source_content_list.length > 0 && ['vertical-scroll', 'horizontal'].includes(form.data_source_direction)" class="oh ht-auto">
<swiper class="w flex" circular="true" :vertical="form.data_source_direction != 'horizontal'" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :duration="500" :display-multiple-items="slides_per_view" :style="{ width: '100%', height: swiper_height + 'px' }" @change="slideChange">
<swiper-item v-for="(item, index) in data_source_content_list" :key="index">
<view :class="form.data_source_direction != 'horizontal' ? 'wh-auto ht-auto' : 'flex-row'" :style="form.data_source_direction == 'horizontal' ? 'column-gap:' + new_style.column_gap + 'px;' : ''">
<view v-for="(item1, index1) in item.split_list" :key="index1" class="wh-auto ht-auto" :style="style_chunk_container + swiper_width + (form.data_source_direction == 'horizontal' ? gap_width : 'margin-bottom:' + content_outer_spacing_magin)">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataGroupRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propFieldList="propFieldList" :propDataHeight="propDataHeight" :propScale="custom_scale" :propIsCustom="propIsCustom" :propIsCustomGroup="true" :propShowData="propShowData" :propConfigLoop="propConfigLoop !== '1' ? form.is_use_parent_data : '1'" :propDataIndex="index" :propDataSplitIndex="index1" @url_event="url_event"></dataGroupRendering>
</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="new_style.is_show == '1' && data_source_content_list.length > 1" :class="['left', 'right'].includes(new_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="indicator_location_style">
<block v-if="new_style.indicator_style == 'num'">
<view :style="indicator_style" class="dot-item">
<text :style="{ color: new_style.actived_color }">{{ actived_index + 1 }}</text>
<text>/{{ data_source_content_list.length }}</text>
</view>
</block>
<block v-else>
<view v-for="(item, index) in data_source_content_list" :key="index" :style="indicator_style + (actived_index == index ? 'background:' + new_style.actived_color : '')" class="dot-item" />
</block>
</view>
</view>
<template v-else>
<view :style="style_chunk_container">
<view class="wh-auto ht-auto oh" :style="style_chunk_img_container">
<dataGroupRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propFieldList="propFieldList" :propDataHeight="propDataHeight" :propScale="custom_scale" :propConfigLoop="propConfigLoop !== '1' ? form.is_use_parent_data : '1'" @url_event="url_event"></dataGroupRendering>
</view>
</view>
</template>
</view>
</view>
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer, percentage_count, isEmpty, get_indicator_style, get_indicator_location_style, border_width, get_is_eligible } from '@/common/js/common/common.js';
// a组件调用b组件 b组件调用a组件为了避免循环引用在uniapp中出问题复制一个相同的data-group-rendering组件
import dataGroupRendering from '@/components/diy/modules/custom/data-group-rendering.vue';
const app = getApp();
export default {
components: {
dataGroupRendering
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1
},
propDataWidth: {
type: Number,
default: 0,
},
propDataHeight: {
type: Number,
default: 0,
},
propIsCustom: {
type: Boolean,
default: false
},
propFieldList: {
type: Array,
default: []
},
propGroupSourceList: {
type: Array,
default: () => {
return [];
}
},
propShowData: {
type: Object,
default: () => ({
data_key: 'id',
data_name: 'name',
})
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
new_style: {},
custom_scale: 1,
style_container: '',
style_img_container: '',
custom_list_length: 0,
custom_group_field_id: '',
source_list: {
// 存放手动输入的id
data_ids: [],
// 手动输入
data_list: [],
// 自动
data_auto_list: [],
},
data_source_content_list: [],
data_source: '',
// 内容样式
style_content_container: '',
style_content_img_container: '',
// 数据样式
style_chunk_container: '',
style_chunk_img_container: '',
// 指示器选中的下标
actived_index: 0,
// 轮播高度
swiper_height: 0,
swiper_width: 'width: 100%;',
// 指示器样式
indicator_location_style: '',
indicator_style: '',
slides_per_view: 1,
show_data: { data_key: 'id', data_name: 'name' },
old_data_style: {
color_list: [{ color: 'rgb(244, 252, 255)', color_percentage: undefined }],
direction: '180deg',
background_img_style: '2',
background_img: [],
radius: 0,
radius_top_left: 0,
radius_top_right: 0,
radius_bottom_left: 0,
radius_bottom_right: 0,
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
},
content_outer_spacing_magin: '0rpx',
gap_width: '',
is_show: true
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
methods: {
percentage_count,
init() {
const new_form = this.propValue;
const new_style = this.propValue.data_style;
const data_source_id = new_form?.data_source_field?.id || '';
// 自定义组的数据源内容切换, 判断是取自定义组数据源内容还是取自定义数据源内容
const is_data_source_id = this.propFieldList.filter((item) => item.field == data_source_id);
let new_list = [];
// 判断是否是循环内容
if (this.propConfigLoop == '1') {
// 如果自定义组选择了数据源,就按照自定义组的数据源的方式走,否则的话就按照自定义的数据走
if (is_data_source_id.length > 0) {
const list = this.get_data_source_content_list(this.propSourceList, new_form);
// 数据来源的内容
new_list = list.length > 0 ? this.get_list(list, new_form, new_style) : [];
} else {
if (!isEmpty(this.propSourceList)) {
const new_source_list = [ this.propSourceList ];
new_list = [{ split_list: new_source_list }];
} else {
new_list = [];
}
}
} else {
// 如果使用父级数据,就直接使用父级的全部数据,否则的话就没有任何数据
if (new_form.is_use_parent_data == '1') {
new_list = this.propGroupSourceList;
} else {
new_list = [];
}
}
// 初始化数据
const { common_style, data_content_style, data_style } = new_style;
const old_width = this.propDataWidth * this.propScale;
// 外层左右间距
const outer_spacing = (common_style?.margin_left || 0) + (common_style?.margin_right || 0) + (common_style?.padding_left || 0) + (common_style?.padding_right || 0) + border_width(common_style);
// 内容左右间距
const content_spacing = (data_content_style?.margin_left || 0) + (data_content_style?.margin_right || 0) + (data_content_style?.padding_left || 0) + (data_content_style?.padding_right || 0) + border_width(data_content_style);
// 数据左右间距
const internal_spacing = (data_style?.margin_left || 0) + (data_style?.margin_right || 0) + (data_style?.padding_left || 0) + (data_style?.padding_right || 0) + border_width(data_style);
// 一行显示的数量
const carousel_col = Number(new_form.data_source_carousel_col);
// 数据间距
const data_spacing = ['vertical', 'horizontal'].includes(new_form.data_source_direction) ? new_style.column_gap * (carousel_col - 1) : 0;
// 自定义组件宽度
const width = old_width - outer_spacing - content_spacing - internal_spacing - data_spacing;
// 自定义组的比例
const scale_number = width / this.propDataWidth;
const custom_scale = scale_number > 0 ? scale_number : 0;
const new_data_style = !isEmpty(new_style.data_style) ? new_style.data_style : this.old_data_style;
const new_data_content_style = !isEmpty(new_style.data_content_style)? new_style.data_content_style : this.old_data_style;
// 判断是平移还是整屏滚动
const { padding_top = 0, padding_bottom = 0, margin_bottom = 0, margin_top = 0 } = new_data_style;
let swiper_height = 0;
let col = Number(new_form.data_source_carousel_col);
// 间距
const space_between = new_form.data_source_direction == 'horizontal' ? new_style.column_gap : new_style.row_gap;
// 轮播图高度控制
if (new_form.data_source_direction == 'horizontal') {
swiper_height = this.propDataHeight * custom_scale + padding_top + padding_bottom + margin_bottom + margin_top;
} else {
// 商品数量大于列数的时候,高度是列数,否则是当前的数量
col = new_list.length > carousel_col ? carousel_col : new_list.length;
swiper_height = (this.propDataHeight * custom_scale + padding_top + padding_bottom + margin_bottom + margin_top) * col + ((Number(new_form.data_source_carousel_col) - 1) * space_between);
}
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = (new_style.column_gap * (carousel_col - 1)) / carousel_col;
// 横向的时候,根据选择的行数和每行显示的个数来区分具体是显示多少个
const swiper_width = (new_form.data_source_direction == 'horizontal' && new_style.rolling_fashion != 'translation') ? `width: ${ 100 / carousel_col }%;`: 'width: 100%;';
this.setData({
form: new_form,
new_style: new_style,
custom_scale: custom_scale,
custom_list_length: new_form.custom_list.length - 1,
style_container: common_styles_computer(new_style.common_style) + (new_form.is_scroll_bar == '1' ? 'overflow: auto;' : '') + 'box-sizing: border-box;', // 用于样式显示
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
style_content_container: common_styles_computer(new_data_content_style) + 'box-sizing: border-box;', // 用于样式显示
style_content_img_container: common_img_computer(new_data_content_style),
style_chunk_container: common_styles_computer(new_data_style) + 'box-sizing: border-box;', // 用于样式显示
style_chunk_img_container: common_img_computer(new_data_style),
style_chunk_width: width,
data_source_content_list: new_list,
data_source: !isEmpty(new_form.data_source)? new_form.data_source : '',
indicator_style: get_indicator_style(new_style), // 指示器的样式
indicator_location_style: get_indicator_location_style(new_style),
swiper_height: swiper_height,
swiper_width: swiper_width,
content_outer_spacing_magin: space_between + 'px',
gap_width: `width: calc(${100 / carousel_col}% - ${gap}px);`,
slides_per_view: new_style.rolling_fashion == 'translation' ? (new_form.data_source_direction != 'horizontal' ? col : new_form.data_source_carousel_col ) : 1,
is_show: this.get_is_show(new_form),
custom_group_field_id: new_form.data_source_field.id,
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, false, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_data_source_content_list(sourceList, form) {
if (!isEmpty(sourceList)) {
const data_source_id = form?.data_source_field.id || '';
let list = this.get_nested_property(sourceList, data_source_id);
// 如果是自定义标题,进一步处理嵌套对象中的数据
if (sourceList.data) {
list = this.get_nested_property(sourceList.data, data_source_id);
}
return list == '' ? [] : list;
} else {
return [];
}
},
get_nested_property(obj, path) {
// 检查路径参数是否为字符串且非空,若不满足条件则返回空字符串
if (typeof path !== 'string' || !path) return [];
// 将属性路径字符串拆分为属性键数组
const keys = path.split('.');
// 使用reduce方法遍历属性键数组逐层访问对象属性
// 如果当前对象存在且拥有下一个属性键,则继续访问;否则返回空字符串
return keys.reduce((o, key) => (o != null && o[key] != null ? o[key] : []), obj) ?? [];
},
get_list(list, form, new_style) {
// 深拷贝一下,确保不会出现问题
const cloneList = JSON.parse(JSON.stringify(list));
if (new_style.rolling_fashion != 'translation' && form.data_source_direction != 'vertical') {
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (cloneList.length > 0) {
// 每页显示的数量
const num = form.data_source_carousel_col;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: cloneList.slice(i * num, (i + 1) * num),
});
}
return nav_list;
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: cloneList,
},
];
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
}
},
slideChange(e) {
this.setData({
actived_index: e.detail.current,
});
},
url_event(e, index, split_index) {
if (this.data_source == 'goods' && this.data_source_content_list.length > 0) {
const list = this.data_source_content_list[index];
if (!isEmpty(list) && !isEmpty(list.split_list[split_index])) {
const new_list = list.split_list[split_index];
if (!isEmpty(new_list)) {
// 缓存商品数据
app.globalData.goods_data_cache_handle(new_list.data.id, new_list.data);
}
}
}
app.globalData.url_open(e);
},
},
};
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,161 @@
<template>
<view v-if="is_show" class="img-outer pr oh flex-row align-c wh-auto ht-auto" :style="com_style" @tap="url_event">
<iconfont :name="'icon-' + icon_class" :color="form.icon_color" :size="form.icon_size * scale + 'px'" propContainerDisplay="flex"></iconfont>
</view>
</template>
<script>
import { radius_computer, padding_computer, gradient_handle, isEmpty, get_nested_property, get_custom_link, get_is_eligible } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1,
},
propIsCustom: {
type: Boolean,
default: false
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: []
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
com_style: '',
scale: 1,
icon_class: '',
icon_url: '',
is_show: true,
};
},
watch: {
propKey(val) {
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
const new_form = this.propValue;
let icon_class_value = '';
if (!isEmpty(new_form.icon_class)) {
icon_class_value = new_form.icon_class;
} else {
if (!isEmpty(this.propSourceList)) {
let icon = '';
// 获取数据源ID
const data_source_id = !isEmpty(new_form?.data_source_field?.id || '') && this.propConfigLoop == '1' ? new_form.data_source_field.id : '';
// 数据源内容
const option = new_form?.data_source_field?.option || {};
if (data_source_id.includes(';')) {
const ids = data_source_id.split(';');
let url = '';
ids.forEach((item, index) => {
url += this.data_handling(item) + (index != ids.length - 1 ? (option?.join || '') : '');
});
icon = url;
} else {
// 不输入商品, 文章和品牌时,从外层处理数据
icon = this.data_handling(data_source_id);
}
icon_class_value = (option?.first || '') + icon + (option?.last || '');
} else {
icon_class_value = '';
}
}
this.setData({
form: new_form,
scale: this.propScale,
com_style: this.get_com_style(new_form, this.propScale),
icon_class: icon_class_value,
icon_url: this.get_icon_link(new_form),
is_show: this.get_is_show(new_form),
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, this.propIsCustomGroup, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_icon_link(new_form) {
let url = '';
if (!isEmpty(new_form.icon_link)) {
url = new_form.icon_link?.page || '';
} else {
// 获取数据源ID
const data_source_link_id = !isEmpty(new_form?.data_source_link_field?.id || '') && this.propConfigLoop == '1' ? new_form?.data_source_link_field?.id : '';
// 数据源内容
const source_link_option = new_form?.data_source_link_field?.option || {};
url = get_custom_link(data_source_link_id, this.propSourceList, source_link_option)
}
return url;
},
data_handling(data_source_id) {
// 不输入商品, 文章和品牌时,从外层处理数据
let icon = get_nested_property(this.propSourceList, data_source_id);
// 如果是商品,品牌,文章的图片, 其他的切换为从data中取数据
if (this.propIsCustom && !isEmpty(this.propSourceList.data)) {
icon = get_nested_property(this.propSourceList.data, data_source_id);
}
return icon;
},
get_com_style(form, scale) {
let style = `${ gradient_handle(form.color_list, form.direction) } ${ radius_computer(form.bg_radius, scale, true) };transform: rotate(${form.icon_rotate}deg);${ padding_computer(form.icon_padding, scale, true) };`;
if (form.border_show == '1') {
style += `border: ${form.border_size * scale }px ${form.border_style} ${form.border_color};box-sizing: border-box;`;
}
if (form.icon_location == 'center') {
style += `justify-content: center;`;
} else if (form.icon_location == 'left') {
style += `justify-content: flex-start;`;
} else if (form.icon_location == 'right') {
style += `justify-content: flex-end;`;
}
return style;
},
url_event(e) {
this.$emit('url_event', this.icon_url);
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,178 @@
<template>
<view v-if="is_show" class="img-outer pr wh-auto ht-auto" :style="border_style" @tap="url_event">
<imageEmpty :propImageSrc="img" :propStyle="image_style" propErrorStyle="width: 60rpx;height: 60rpx;"></imageEmpty>
</view>
</template>
<script>
import { percentage_count, radius_computer, isEmpty, get_nested_property, get_custom_link, get_is_eligible } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
export default {
components: {
imageEmpty,
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1
},
propIsCustom: {
type: Boolean,
default: false
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propImgParams: {
type: String,
default: ''
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: []
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
img: '',
img_url: '',
image_style: '',
border_style: '',
keyMap: {
goods: 'images',
article: 'cover',
brand: 'logo'
},
is_show: true,
};
},
watch: {
propKey(val) {
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
const new_form = this.propValue;
this.setData({
form: new_form,
img: this.get_img_url(new_form),
image_style: this.get_image_style(new_form, this.propScale),
border_style: this.get_border_style(new_form, this.propScale),
img_url: this.get_img_link(new_form),
is_show: this.get_is_show(new_form),
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, this.propIsCustomGroup, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_img_link(form) {
let url = '';
if (!isEmpty(form.link)) {
url = form.link?.page || '';
} else {
// 获取数据源ID
const data_source_link_id = !isEmpty(form?.data_source_link_field?.id || '') && this.propConfigLoop == '1' ? form.data_source_link_field.id : '';
// 数据源内容
const source_link_option = form?.data_source_link_field?.option || {};
url = get_custom_link(data_source_link_id, this.propSourceList, source_link_option)
}
return url;
},
get_img_url(form) {
if (!isEmpty(form.img[0])) {
return form.img[0];
} else {
if (!isEmpty(this.propSourceList)) {
let image_url = '';
// 获取数据源ID
const data_source_id = !isEmpty(form?.data_source_field?.id || '') && this.propConfigLoop == '1' ? form.data_source_field.id : '';
// 数据源内容
const option = form?.data_source_field?.option || {};
if (data_source_id.includes(';')) {
const ids = data_source_id.split(';');
let url = '';
ids.forEach((item, index) => {
url += this.data_handling(item) + (index != ids.length - 1 ? (option?.join || '') : '');
});
image_url = url;
} else {
image_url = this.data_handling(data_source_id);
}
return (option?.first || '') + image_url + (option?.last || '');
} else {
return '';
}
}
},
data_handling(data_source_id) {
// 不输入商品, 文章和品牌时,从外层处理数据
let image_url = get_nested_property(this.propSourceList, data_source_id);
// 如果是商品,品牌,文章的图片, 其他的切换为从data中取数据
if (this.propIsCustom && !isEmpty(this.propSourceList.data)) {
// 判断是否是同一标志
if (data_source_id == this.propImgParams) {
// 如果是符合条件的标志,先判断新的图片是否存在,存在就取新的图片,否则的话取原来的图片
image_url = !isEmpty(this.propSourceList.new_cover)? this.propSourceList.new_cover[0]?.url || '' : get_nested_property(this.propSourceList.data, data_source_id);
} else {
image_url = get_nested_property(this.propSourceList.data, data_source_id);
}
}
return image_url;
},
get_image_style(form, scale) {
return `width: ${percentage_count(form.img_width, form.com_width)}; height: ${percentage_count(form.img_height, form.com_height)};transform: rotate(${form.img_rotate}deg); ${radius_computer(form.img_radius, scale, true)};`;
},
get_border_style(form, scale) {
let style = ``;
if (form.border_show == '1') {
style += `border: ${form.border_size * scale }px ${form.border_style} ${form.border_color}; ${radius_computer(form.border_radius, scale, true)};box-sizing: border-box;`;
}
return style;
},
url_event(e) {
this.$emit('url_event', this.img_url);
},
},
};
</script>
<style lang="scss" scoped>
.img-outer {
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<view v-if="is_show" :style="border_style"></view>
</template>
<script>
import { get_is_eligible } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1,
},
propIsCustom: {
type: Boolean,
default: false
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: []
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
border_style: '',
is_show: true,
};
},
watch: {
propKey(val) {
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
const new_form = this.propValue;
this.setData({
form: new_form,
border_style: this.get_border_style(new_form, this.propScale),
is_show: this.get_is_show(new_form),
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, this.propIsCustomGroup, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_border_style(form, scale) {
if (form.line_settings === 'horizontal') {
return `margin: 10rpx 0;border-bottom: ${form.line_size * scale }px ${form.line_style} ${form.line_color};`;
} else {
return `margin: 0 10rpx;height:100%;border-right: ${form.line_size * scale }px ${form.line_style} ${form.line_color};`;
}
}
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,133 @@
<template>
<view v-if="is_show" class="wh-auto ht-auto re oh" :style="com_style" @tap="url_event">
<view class="wh-auto ht-auto" :style="com_img_style"></view>
</view>
</template>
<script>
import { radius_computer, background_computer, gradient_handle, isEmpty, get_custom_link, get_is_eligible } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1
},
propIsCustom: {
type: Boolean,
default: false
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propFieldList: {
type: Array,
default: []
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
text_title: '',
text_style: '',
com_style: '',
panel_url: '',
is_show: true,
};
},
watch: {
propKey(val) {
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
const new_form = this.propValue;
let url = '';
if (!isEmpty(new_form.link)) {
url = new_form.link?.page || '';
} else {
// 获取数据源ID
const data_source_link_id = !isEmpty(new_form?.data_source_link_field?.id || '') && this.propConfigLoop == '1' ? new_form.data_source_link_field.id : '';
// 数据源内容
const source_link_option = new_form?.data_source_link_field?.option || {};
url = get_custom_link(data_source_link_id, this.propSourceList, source_link_option);
}
this.setData({
form: new_form,
com_style: this.get_com_style(new_form, this.propScale),
com_img_style: this.get_com_img_style(new_form),
panel_url: url,
is_show: this.get_is_show(new_form),
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, this.propIsCustomGroup, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_com_style(form, scale) {
let style = `${ gradient_handle(form.color_list, form.direction) } ${radius_computer(form.bg_radius, scale, true)}; transform: rotate(${form.panel_rotate}deg);`;
if (form.border_show == '1') {
style += `border: ${form.border_size * scale }px ${form.border_style} ${form.border_color};box-sizing: border-box;`;
}
return style;
},
get_com_img_style(form) {
const data = {
background_img: form?.background_img || [],
background_img_style: form?.background_img_style || '2'
}
return background_computer(data);
},
url_event(e) {
this.$emit('url_event', this.panel_url);
},
},
};
</script>
<style lang="scss" scoped>
.break {
word-wrap: break-word;
word-break: break-all;
}
.rich-text-content {
white-space: normal;
word-break: break-all;
* {
max-width: 100%;
}
}
</style>

View File

@@ -0,0 +1,236 @@
<template>
<view v-if="is_show" class="img-outer wh-auto ht-auto re oh" :style="com_style" @tap="url_event">
<view :style="text_style" :class="'break ' + text_line_class">
<template v-if="form.is_rich_text == '1'">
<mp-html :content="text_title" />
</template>
<template v-else>
{{ text_title }}
</template>
</view>
</view>
</template>
<script>
import { radius_computer, padding_computer, isEmpty, gradient_handle, get_nested_property, get_custom_link, get_is_eligible, custom_condition_data } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
required: true,
},
propSourceList: {
type: [ Object, Array ],
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
propScale: {
type: Number,
default: 1
},
propIsCustom: {
type: Boolean,
default: false
},
propIsCustomGroup: {
type: Boolean,
default: false
},
propCustomGroupFieldId: {
type: String,
default: ''
},
propTitleParams: {
type: String,
default: 'name'
},
propFieldList: {
type: Array,
default: []
},
propConfigLoop: {
type: String,
default: "1"
}
},
data() {
return {
form: {},
text_title: '',
text_style: '',
com_style: '',
text_url: '',
keyMap: {
goods: 'title',
article: 'title',
brand: 'name'
},
is_show: true,
text_line_class: ''
};
},
watch: {
propKey(val) {
this.init();
}
},
created() {
this.init();
},
methods: {
init() {
const new_form = this.propValue;
let url = '';
if (!isEmpty(new_form.text_link)) {
url = new_form.text_link?.page || '';
} else {
// 获取数据源ID
const data_source_link_id = !isEmpty(new_form?.data_source_link_field?.id || '') && this.propConfigLoop == '1' ? new_form.data_source_link_field.id : '';
// 数据源内容
const source_link_option = new_form?.data_source_link_field?.option || {};
// 调用方法处理数据显示
url = get_custom_link(data_source_link_id, this.propSourceList, source_link_option);
}
this.setData({
form: new_form,
text_title: (new_form?.text_captions || '') + this.get_text_title(new_form),
text_style: this.get_text_style(new_form, this.propScale),
com_style: this.get_com_style(new_form, this.propScale),
text_url: url,
is_show: this.get_is_show(new_form),
text_line_class: new_form.width_omit_num == '0' || new_form.is_rich_text == '1' ? '' : `text-line-${ new_form?.width_omit_num || '' }`
});
},
get_is_show(form) {
if (this.propConfigLoop == '1') {
// 取出条件判断的内容
const condition = form?.condition || { field: '', type: '', value: '' };
return get_is_eligible(this.propFieldList, condition, this.propSourceList, this.propIsCustom, this.propIsCustomGroup, this.propCustomGroupFieldId);
} else {
return true;
}
},
get_text_title(form) {
let text = '';
if (!isEmpty(form.text_title)) {
// 存储待处理的文本标题
let new_title = JSON.parse(JSON.stringify((form.text_title)));
let new_field_list = this.propFieldList;
// 判断是否是自定义组
if (this.propIsCustomGroup && !isEmpty(this.propCustomGroupFieldId)) {
// 取出对应自定义组的内容
const group_option_list = new_field_list.find((item) => item.field === this.propCustomGroupFieldId);
// 取出自定义组内部数据源参数的详细数据
new_field_list = group_option_list?.data || [];
}
// 遍历字段列表,替换文本标题中的占位符
if (!isEmpty(new_field_list)) {
new_field_list.forEach((item) => {
const new_field = '${' + item.field + '}';
if (form.text_title.includes(new_field)) {
// 获取到字段的真实数据
const field_value = custom_condition_data(item.field, item, this.propSourceList, this.propIsCustom);
// 使用正则表达式替换文本标题
const regular = new RegExp(`\\$\\{\\s*${item.field}\\s*\\}`, 'g');
// 替换后的内容赋值给原内容, 确保后续可以继续替换
new_title = new_title.replace(regular, field_value);
}
});
}
// 将内容替换为处理后的标题
text = new_title;
} else {
let text_title = '';
// 获取数据源ID
const data_source_id = !isEmpty(form?.data_source_field?.id || []) && this.propConfigLoop == '1' ? form?.data_source_field?.id : [];
// 数据源内容
const option = form?.data_source_field?.option || [];
// 多选判断
if (data_source_id.length > 0) {
text_title += form?.data_split?.left || '';
// 遍历取出所有的值
data_source_id.forEach((source_id, index) => {
const sourceList = option.find((item) => item.field == source_id);
// 根据数据源ID是否包含点号来区分处理方式
if (source_id.includes(';')) {
const ids = source_id.split(';');
let source_text = '';
ids.forEach((item, index) => {
source_text += this.data_handling(item) + (index != ids.length - 1 ? (sourceList?.join || '') : '');
});
text_title += (sourceList?.first || '') + source_text + (sourceList?.last || '');
} else {
text_title += (sourceList?.first || '') + this.data_handling(source_id) + (sourceList?.last || '');
}
if (index < data_source_id.length - 1) {
text_title += form?.data_split?.middle || '';
}
});
text_title += form?.data_split?.right || '';
}
// 如果是商品的标题或者是品牌的名称,需要判断是否有新的标题,没有的话就取原来的标题
text = text_title;
}
return text;
},
data_handling(data_source_id) {
let text = get_nested_property(this.propSourceList, data_source_id);
// 如果是商品的标题或者是品牌的名称,需要判断是否有新的标题,没有的话就取原来的标题
if (this.propIsCustom && !isEmpty(this.propSourceList.data)) {
// 其他的切换为从data中取数据
if (data_source_id == this.propTitleParams) {
// 如果是符合条件的标志,先判断新的标题是否存在,存在就取新的标题,否则的话取原来的标题
text = !isEmpty(this.propSourceList.new_title) ? this.propSourceList.new_title : get_nested_property(this.propSourceList.data, data_source_id);
} else {
text = get_nested_property(this.propSourceList.data, data_source_id);
}
}
return text;
},
get_text_style(form, scale) {
let style = `font-size: ${form.text_size * scale }px;line-height: ${ (typeof form.line_text_size === "number" ? form.line_text_size : form.text_size) * scale }px;color: ${form.text_color}; text-align: ${form.text_location}; transform: rotate(${form.text_rotate}deg);text-decoration: ${form.text_option};${padding_computer(form.text_padding, scale, true)};box-sizing: border-box;`;
if (form.text_weight == 'italic') {
style += `font-style: italic`;
} else if (['bold', '500'].includes(form.text_weight)) {
style += `font-weight: bold;`;
}
return style;
},
get_com_style(form, scale) {
let style = `${ gradient_handle(form.color_list, form.direction) } ${radius_computer(form.bg_radius, scale, true)}`;
if (form.border_show == '1') {
style += `border: ${form.border_size * scale }px ${form.border_style} ${form.border_color};box-sizing: border-box;`;
}
// 是富文本并且开启了上下滚动的开关
if (form.is_rich_text == '1' && form.is_up_down == '1') {
style += `overflow-y: auto;`;
}
return style;
},
url_event(e) {
this.$emit('url_event', this.text_url)
},
},
};
</script>
<style lang="scss" scoped>
.break {
word-wrap: break-word;
word-break: break-all;
}
.rich-text-content {
white-space: normal;
word-break: break-all;
* {
max-width: 100%;
}
}
</style>

View File

@@ -0,0 +1,304 @@
<template>
<view :style="style_content_container">
<view :style="style_content_img_container">
<template v-if="!isEmpty(form.data_source) && form.data_source_is_loop !== '0'">
<view v-if="data_source_content_list.length > 0 && form.data_source_direction == 'vertical'">
<view class="flex-row flex-wrap" :style="'row-gap:' + new_style.row_gap + 'px;column-gap:' + new_style.column_gap + 'px;'">
<view v-for="(item, index) in data_source_content_list" :key="index" class="ht-auto" :style="gap_width">
<view v-for="(item1, index1) in item.split_list" :key="index1">
<view :style="style_container">
<view class="wh-auto ht-auto oh" :style="style_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propSourceType="form.data_source" :propDataHeight="form.height" :propScale="scale" :propDataIndex="index" :propDataSplitIndex="index1" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-else-if="data_source_content_list.length > 0 && ['vertical-scroll', 'horizontal'].includes(form.data_source_direction)" class="oh pr">
<swiper class="w flex" circular="true" :vertical="form.data_source_direction != 'horizontal'" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :duration="500" :display-multiple-items="slides_per_view" :style="{ width: '100%', height: swiper_height + 'px' }" @change="slideChange">
<swiper-item v-for="(item, index) in data_source_content_list" :key="index">
<view :class="form.data_source_direction != 'horizontal' ? '' : 'flex-row'" :style="form.data_source_direction == 'horizontal' ? 'column-gap:' + new_style.column_gap + 'px;' : ''">
<view v-for="(item1, index1) in item.split_list" :key="index1" :style="style_container + swiper_width + (form.data_source_direction == 'horizontal' ? gap_width : 'margin-bottom:' + content_outer_spacing_magin)">
<view class="wh-auto ht-auto oh" :style="style_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="index1" :propSourceList="item1" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propSourceType="form.data_source" :propDataHeight="form.height" :propScale="scale" :propDataIndex="index" :propDataSplitIndex="index1" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
<view v-else>
<view :style="style_container">
<view class="wh-auto ht-auto oh" :style="style_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propConfigLoop="form.data_source_is_loop || '1'" :propDataHeight="form.height" :propScale="scale" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</template>
<view v-else-if="!isEmpty(form.data_source) && form.data_source_is_loop == '0'">
<view :style="style_container">
<view class="w h oh" :style="style_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propSourceList="{}" :propConfigLoop="form.data_source_is_loop || '1'" :propGroupSourceList="data_source_content_list" :propSourceType="form.data_source" :propDataHeight="form.height" :propScale="scale" :propIsCustom="form.is_custom_data == '1'" :propShowData="show_data" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
<view v-else>
<view :style="style_container">
<view class="wh-auto ht-auto oh" :style="style_img_container">
<dataRendering :propKey="propKey" :propCustomList="form.custom_list" :propIndex="0" :propConfigLoop="form.data_source_is_loop || '1'" :propDataHeight="form.height" :propScale="scale" @url_event="url_event"></dataRendering>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { padding_computer, isEmpty, margin_computer, gradient_computer, radius_computer, background_computer, common_styles_computer, common_img_computer, border_width, box_shadow_computer, border_computer, old_border_and_box_shadow, old_margin, old_padding, old_radius } from '@/common/js/common/common.js';
import dataRendering from '@/components/diy/modules/custom/data-rendering.vue';
const app = getApp();
export default {
components: {
dataRendering
},
props: {
propKey: {
type: String,
default: '',
},
propValue: {
type: Object,
default: () => {
return {};
},
},
propMagicScale: {
type: Number,
default: 1,
},
propDataSpacing: {
type: Number,
default: 0,
},
propDataIndex: {
type: Number,
default: 0,
},
},
data() {
return {
form: {},
new_style: {},
scale: 1,
style_container: '',
style_img_container: '',
div_width: 0,
div_height: 0,
custom_list_length: 0,
data_source_content_list: [],
data_source: '',
// 一屏显示的数量
slides_per_view: 1,
// 轮播高度
swiper_height: 0,
swiper_width: 'width: 100%;',
show_data: { data_key: 'id', data_name: 'name' },
gap_width: '',
content_outer_spacing_magin: '0rpx',
defalt_style: {
color_list: [{ color: '', color_percentage: undefined }],
direction: '180deg',
background_img_style: '2',
background_img: [],
radius: 0,
radius_top_left: 0,
radius_top_right: 0,
radius_bottom_left: 0,
radius_bottom_right: 0,
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
},
style_content_container: '',
style_content_img_container: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
computed: {
get_percentage_count() {
return (num) => {
return num * this.scale + 'px';
};
},
},
mounted() {
this.init();
},
methods: {
isEmpty,
init() {
if (!isEmpty(this.propValue)) {
const new_form = this.propValue.data_content;
const new_style = this.propValue.data_style;
const { data_common_style = {}, data_color_list = [], data_direction = '180deg', data_radius = old_radius, data_background_img = [], data_background_img_style = '2', data_chunk_padding = old_padding, data_chunk_margin = old_margin, data_content_style = {}, data_pattern = old_border_and_box_shadow } = new_style;
const style_data = {
color_list: data_color_list,
direction: data_direction,
}
const style_img_data = {
background_img: data_background_img,
background_img_style: data_background_img_style,
}
// 数据来源的内容
let list = [];
if (new_form.is_custom_data == '1') {
if (Number(new_form.data_source_content.data_type) === 0) {
list = new_form.data_source_content.data_list;
} else {
list = new_form.data_source_content.data_auto_list.map(item => ({
id: Math.random(),
new_cover: [],
new_title: '',
data: item,
}));
}
} else {
list = new_form.data_source_content.data_list;
}
const new_list = list.length > 0 ? this.get_list(list, new_form, new_style) : [];
// 计算宽度
const { padding_left, padding_right, padding_top, padding_bottom } = data_chunk_padding;
const { margin_left, margin_right, margin_bottom, margin_top } = data_chunk_margin;
const old_width = new_form.width * this.propMagicScale;
// 数据宽度
const data_style = padding_left + padding_right + margin_left + margin_right + border_width(data_pattern);
// 通用样式
const chunk_padding = new_style?.chunk_padding || old_padding;
const chunk_margin = new_style?.chunk_margin || old_margin;
const common_styles = (chunk_margin?.margin_left || 0) + (chunk_margin?.margin_right || 0) + (chunk_padding?.padding_left || 0) + (chunk_padding?.padding_right || 0) + border_width(data_common_style);
// 内容左右间距
const content_spacing = (data_content_style?.margin_left || 0) + (data_content_style?.margin_right || 0) + (data_content_style?.padding_left || 0) + (data_content_style?.padding_right || 0) + border_width(data_content_style);
const carousel_col = Number(new_form.data_source_carousel_col);
// 数据间距
const data_spacing = ['vertical', 'horizontal'].includes(new_form.data_source_direction) ? new_style.column_gap * (carousel_col - 1) : 0;
// 当前容器的宽度 减去 左右两边的padding值 再减去 数据间距的一半 再除以 容器的宽度 得到比例 再乘以数据魔方的比例
const width = old_width - data_style - content_spacing - common_styles - data_spacing - (this.propDataSpacing / 2);
// 计算缩放比例
// 比例增加最小值判断
const scale_number = width / new_form.width;
const new_scale = scale_number > 0 ? scale_number : 0;
// 间距
const space_between = new_form.data_source_direction == 'horizontal' ? new_style.column_gap : new_style.row_gap;
// 判断是平移还是整屏滚动
let swiper_height = 0;
// 商品数量大于列数的时候,高度是列数,否则是当前的数量
const col = new_list.length > carousel_col ? carousel_col : new_list.length;
// 轮播图高度控制
if (new_form.data_source_direction == 'horizontal') {
swiper_height = new_form.height * new_scale + padding_top + padding_bottom + margin_bottom + margin_top;
} else {
swiper_height = (new_form.height * new_scale + padding_top + padding_bottom + margin_bottom + margin_top) * col + ((carousel_col - 1) * space_between);
}
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = (new_style.column_gap * (carousel_col - 1)) / carousel_col;
// 横向的时候,根据选择的行数和每行显示的个数来区分具体是显示多少个
const swiper_width = (new_form.data_source_direction == 'horizontal' && new_style.rolling_fashion != 'translation') ? `width: ${ 100 / carousel_col }%;`: 'width: 100%;';
const content_style = !isEmpty(new_style.data_content_style)? new_style.data_content_style : this.defalt_style;
this.setData({
form: new_form,
new_style: new_style,
div_width: width,
scale: new_scale,
custom_list_length: new_form.custom_list.length - 1,
style_content_container: common_styles_computer(content_style),
style_content_img_container: common_img_computer(content_style),
style_container: gradient_computer(style_data) + radius_computer(data_radius) + margin_computer(data_chunk_margin) + box_shadow_computer(data_pattern) + border_computer(data_pattern), // 用于样式显示
style_img_container: padding_computer(data_chunk_padding) + background_computer(style_img_data) + 'box-sizing: border-box;',
data_source_content_list: new_list,
data_source: !isEmpty(new_form.data_source)? new_form.data_source : '',
slides_per_view: new_style.rolling_fashion == 'translation' ? (new_form.data_source_direction != 'horizontal' ? col : carousel_col) : 1,
swiper_height: swiper_height,
swiper_width: swiper_width,
show_data: new_form?.show_data || { data_key: 'id', data_name: 'name' },
content_outer_spacing_magin: space_between + 'px',
gap_width: `width: calc(${100 / carousel_col}% - ${gap}px);`,
});
}
},
get_list(list, form, new_style) {
// 深拷贝一下,确保不会出现问题
const cloneList = JSON.parse(JSON.stringify(list));
if (new_style.rolling_fashion != 'translation' && form.data_source_direction != 'vertical') {
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (cloneList.length > 0) {
// 每页显示的数量
const num = form.data_source_carousel_col;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: cloneList.slice(i * num, (i + 1) * num),
});
}
return nav_list;
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: cloneList,
},
];
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
}
},
slideChange(e) {
this.$emit('onCarouselChange', e.detail.current, this.propDataIndex);
},
url_event(e, index, split_index) {
if (this.form.data_source == 'goods' && this.data_source_content_list.length > 0) {
const list = this.data_source_content_list[index];
if (!isEmpty(list) && !isEmpty(list.split_list[split_index])) {
const new_list = list.split_list[split_index];
if (!isEmpty(new_list)) {
// 缓存商品数据
app.globalData.goods_data_cache_handle(new_list.data.id, new_list.data);
}
}
}
app.globalData.url_open(e);
},
},
};
</script>
<style scoped lang="scss">
.main-content {
position: absolute;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<view class="wh-auto ht-auto oh" :style="style_container">
<view class="pr oh wh-auto ht-auto" :style="style_img_container">
<swiper circular="true" :autoplay="propValue.data_style.is_roll == '1'" :interval="propValue.data_style.interval_time * 1000" :duration="500" :vertical="propValue.data_style.rotation_direction == 'vertical'" :next-margin="next_margin" :display-multiple-items="slides_per_view" class="swiper" style="height: 100%" @change="carousel_change">
<swiper-item v-for="(item1, index1) in propValue.data_content.list" :key="index1">
<template v-if="propType === 'img'">
<view class="wh-auto ht-auto" :data-value="item1.carousel_link.page" @tap="url_event">
<imageEmpty :propImageSrc="item1.carousel_img[0]" :propStyle="propValue.data_style.get_img_radius" :propImgFit="propValue.data_content.fit" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<template v-else>
<view class="wh-auto ht-auto" :style="shop_spacing">
<product-list-show :propKey="propKey" :propOuterflex="propValue.data_content.goods_outerflex" :propFlex="propValue.data_content.goods_flex" :propNum="show_num" :propActived="propActived" :propIsShow="propValue.data_content.is_show" :propChunkPadding="propValue.data_style.chunk_padding" :propValue="item1.split_list" :propGoodStyle="propGoodStyle" :propContentImgRadius="propValue.data_style.get_img_radius" @url_event="url_event"></product-list-show>
</view>
</template>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script>
import { gradient_computer, radius_computer, padding_computer, background_computer, isEmpty, border_computer, box_shadow_computer, old_border_and_box_shadow, old_margin, old_radius, old_padding, margin_computer } from "@/common/js/common/common.js";
const app = getApp();
import imageEmpty from '@/components/diy/modules/image-empty.vue';
import productListShow from '@/components/diy/modules/data-magic/product-list-show.vue';
export default {
components: {
imageEmpty,
productListShow,
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propContentImgRadius: {
type: String,
default: () => '',
},
propType: {
type: String,
default: () => '',
},
propActived: {
type: Number,
default: () => 0,
},
propGoodStyle: {
type: Object,
default: () => {},
},
propDataIndex: {
type: Number,
default: () => 0,
},
propKey: {
type: [String, Number],
default: '',
},
},
data() {
return {
style_container: '',
style_img_container: '',
slides_per_view: 1,
show_num: 1,
shop_spacing: '',
next_margin: '0rpx',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
methods: {
init() {
if (!isEmpty(this.propValue)) {
const { data_color_list = [], data_direction = '180deg', data_chunk_margin = old_margin, data_radius = old_radius, data_pattern = old_border_and_box_shadow, data_background_img = [], data_background_img_style = '2', data_chunk_padding = old_padding } = this.propValue.data_style;
const style_data = {
color_list: data_color_list,
direction: data_direction,
}
const style_img_data = {
background_img: data_background_img,
background_img_style: data_background_img_style,
}
let slides_per_view = 1;
let show_num = 1;
// 商品时的处理逻辑
const { goods_outerflex, goods_num } = this.propValue.data_content;
const { rotation_direction, rolling_fashion, data_goods_gap } = this.propValue.data_style;
// 图片时的处理
if (this.propType === 'img') {
slides_per_view = 1; // 能够同时显示的slides数量
} else {
// 判断是平移还是整屏滚动, 平移的时候是一个为1组如果是整屏滚动就为一屏为一组
if (rolling_fashion == 'translation') {
// 如果是商品是横排的,轮播也是横排的,就不对商品进行拆分/如果商品是竖排的,轮播也是竖排的,不对商品进行拆分
if ((goods_outerflex == 'row' && rotation_direction == 'horizontal') || (goods_outerflex == 'col' && rotation_direction == 'vertical')) {
slides_per_view = goods_num; // 能够同时显示的slides数量
show_num = 1; // 一屏显示的数量 用于商品内部处理显示
} else {
slides_per_view = 1; // 能够同时显示的slides数量
show_num = goods_num; // 一屏显示的数量 用于商品内部处理显示
}
} else {
// 切屏的时候为多个为一组
show_num = goods_num; // 一屏显示的数量 用于商品内部处理显示
slides_per_view = 1; // 能够同时显示的slides数量
}
}
this.setData({
style_container: gradient_computer(style_data) + radius_computer(data_radius) + margin_computer(data_chunk_margin) + box_shadow_computer(data_pattern) + border_computer(data_pattern), // 用于样式显示
style_img_container: padding_computer(data_chunk_padding) + background_computer(style_img_data) + 'box-sizing: border-box;',
next_margin: rolling_fashion == 'translation' && rotation_direction == 'horizontal' ? `-${ data_goods_gap * 2 }rpx` : '0rpx',
shop_spacing: this.propType === 'img' ? 'margin-right: 0px;' : `margin-right: ${ data_goods_gap * 2 }rpx;`,
slides_per_view: slides_per_view,
show_num: show_num,
});
} else {
return '';
}
},
carousel_change(e) {
this.$emit('onCarouselChange', e.detail.current, this.propDataIndex);
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style></style>

View File

@@ -0,0 +1,233 @@
<template>
<view :class="['align-c flex-1 w h', (propOuterflex == 'row' ? 'flex-row' : 'flex-col')]" :style="'gap:' + propGoodStyle.data_goods_gap + 'px;'">
<template v-if="propFlex === 'row'">
<view v-for="(item, index) in propValue" :key="index" :style="block_size" class="w h">
<view class="w h oh" :style="style_container">
<view class="w h flex-row gap-10" :style="style_img_container" :data-index="index" :data-value="item.goods_url" @tap="url_event">
<template v-if="!isEmpty(item.new_cover)">
<view class="w h">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<template v-else>
<view class="w h">
<imageEmpty :propImageSrc="item.images" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<view v-if="!isEmpty(propIsShow)" class="flex-col w h tl jc-sb">
<view v-if="propIsShow.includes('title')" class="text-line-2" :style="propGoodStyle.goods_title_style + 'height:'+ ((propGoodStyle.goods_title_size + 3) * 4) + 'rpx;'">{{ item.title || '' }}</view>
<view v-if="propIsShow.includes('price')" :style="propGoodStyle.goods_price_style">
<text :style="propGoodStyle.goods_price_symbol_style">{{ item.show_price_symbol || '' }}</text>
{{ item.min_price || '' }}
<template v-if="propIsShow.includes('price_unit')">
<text :style="propGoodStyle.goods_price_unit_style">{{ item.show_price_unit || '' }}</text>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<template v-else-if="propFlex === 'col_price_float'">
<view v-for="(item, index) in propValue" :key="index" :style="block_size" class="w h">
<view class="w h oh" :style="style_container">
<view class="w h flex-col gap-10" :style="style_img_container" :data-index="index" :data-value="item.goods_url" @tap="url_event">
<view class="w h flex-1 pr oh">
<template v-if="!isEmpty(item.new_cover)">
<view class="w h">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<template v-else>
<view class="w h">
<imageEmpty :propImageSrc="item.images" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<view v-if="propIsShow.includes('price')" class="pa" :style="propGoodStyle.goods_price_style + float_pirce_style">
<text :style="propGoodStyle.goods_price_symbol_style">{{ item.show_price_symbol || ''}}</text>{{ item.min_price || ''}}
<template v-if="propIsShow.includes('price_unit')">
<text :style="propGoodStyle.goods_price_unit_style">{{ item.show_price_unit || ''}}</text>
</template>
</view>
</view>
<view v-if="propIsShow.includes('title')" class="text-line-1 tl w" :style="propGoodStyle.goods_title_style + 'height:'+ ((propGoodStyle.goods_title_size + 3) * 2) + 'rpx;'">{{ item.title || '' }}</view>
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(item, index) in propValue" :key="index" :style="block_size" class="w h">
<view class="w h oh" :style="style_container">
<view class="w h flex-col" :style="style_img_container" :data-index="index" :data-value="item.goods_url" @tap="url_event">
<template v-if="!isEmpty(item.new_cover)">
<view class="w h">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<template v-else>
<view class="w h">
<imageEmpty :propImageSrc="item.images" :propStyle="propContentImgRadius" propErrorStyle="width: 80rpx;height: 80rpx;"></imageEmpty>
</view>
</template>
<view v-if="!isEmpty(propIsShow)" class="flex-col w h tl jc-sb">
<view v-if="propIsShow.includes('title')" class="text-line-2" :style="propGoodStyle.goods_title_style + 'height:'+ ((propGoodStyle.goods_title_size + 3) * 4) + 'rpx;'">{{ item.title || '' }}</view>
<view v-if="propIsShow.includes('price')" :style="propGoodStyle.goods_price_style">
<text :style="propGoodStyle.goods_price_symbol_style">{{ item.show_price_symbol || ''}}</text>{{ item.min_price || '' }}
<template v-if="propIsShow.includes('price_unit')">
<text :style="propGoodStyle.goods_price_unit_style">{{ item.show_price_unit || ''}}</text>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
</view>
</template>
<script>
const app = getApp();
import { gradient_computer, radius_computer, padding_computer, background_computer, isEmpty, margin_computer, box_shadow_computer, border_computer, old_margin, old_border_and_box_shadow, old_padding } from "@/common/js/common/common.js";
import imageEmpty from '@/components/diy/modules/image-empty.vue';
export default {
components: {
imageEmpty,
},
props: {
propValue: {
type: Array,
default: () => [],
},
propOuterflex: {
type: String,
default: () => '',
},
propFlex: {
type: String,
default: () => '',
},
propContentImgRadius: {
type: String,
default: () => '',
},
propNum: {
type: Number,
default: () => 0,
},
propActived: {
type: Number,
default: () => 0,
},
propIsShow: {
type: Array,
default: () => [],
},
propChunkPadding: {
type: Object,
default: () => {},
},
propGoodStyle: {
type: Object,
default: () => {},
},
propKey: {
type: [String, Number],
default: '',
},
},
data() {
return {
style_container: '',
style_img_container: '',
block_size: '',
float_pirce_style: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
computed: {
img_padding_computer() {
if (!isEmpty(this.propChunkPadding)) {
return padding_computer(this.propChunkPadding) + ';box-sizing: border-box;';
} else {
return '';
}
},
},
methods: {
isEmpty,
init() {
if (!isEmpty(this.propGoodStyle)) {
const { goods_color_list = [], goods_direction = '180deg', goods_radius = old_radius, goods_background_img = [], goods_background_img_style = '2', goods_chunk_padding = old_padding, goods_price_color_list = [], goods_price_direction = '180deg', goods_price_radius = old_radius, goods_price_padding = old_padding, goods_price_margin = old_margin, goods_price_location = 'center', goods_chunk_margin = old_margin} = this.propGoodStyle;
const style_data = {
color_list: goods_color_list,
direction: goods_direction,
}
const style_img_data = {
background_img: goods_background_img,
background_img_style: goods_background_img_style,
}
const data = {
color_list: goods_price_color_list,
direction: goods_price_direction,
}
let location = 'left:50%;transform:translateX(-50%);bottom:0;'
if (goods_price_location == 'left') {
location = 'left:0;bottom:0;';
} else if (goods_price_location == 'right') {
location = 'right:0;bottom:0;';
}
// 左右间距
const shop_left_right_width_margin = goods_chunk_margin?.margin_left || 0 + goods_chunk_margin?.margin_right || 0;
// 上下间距
const shop_top_bottom_width_margin = goods_chunk_margin?.margin_top || 0 + goods_chunk_margin?.margin_bottom || 0;
// 内容间距
const total_gap = this.propGoodStyle.data_goods_gap * (this.propValue.length - 1);
// 总的宽度
const all_width = total_gap + (shop_left_right_width_margin.value * this.propNum);
// 总的高度
const all_height = total_gap + (shop_top_bottom_width_margin.value * this.propNum);
this.setData({
float_pirce_style: gradient_computer(data) + radius_computer(goods_price_radius) + padding_computer(goods_price_padding) + margin_computer(goods_price_margin) + location,
style_container: gradient_computer(style_data) + radius_computer(goods_radius) + margin_computer(goods_chunk_margin) + border_computer(this.propGoodStyle) + box_shadow_computer(this.propGoodStyle), // 用于样式显示
style_img_container: this.propFlex == 'col' ? background_computer(style_img_data) : padding_computer(goods_chunk_padding) + background_computer(style_img_data) + 'box-sizing: border-box;',
block_size: this.propOuterflex == 'row' ? 'height:calc(100% - ' + shop_top_bottom_width_margin.value + 'px);width:calc((100% - ' + all_width + 'px) / ' + this.propNum + ');' : 'width:calc(100% - ' + shop_left_right_width_margin.value + 'px);height:calc((100% - ' + all_height + 'px) / ' + this.propNum + ');',
});
} else {
return '';
}
},
url_event(e) {
// 存储数据显示缓存
let index = e.currentTarget.dataset.index || 0;
let goods = this.propValue[index];
app.globalData.goods_data_cache_handle(goods.id, goods);
this.$emit('url_event', e);
},
},
};
</script>
<style scoped lang="scss">
.w {
width: 100%;
}
.h {
height: 100%;
}
.half-width {
width: 50%;
}
.gap-20 {
gap: 40rpx;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<!-- 视频 -->
<view class="video pr wh-auto ht-auto">
<video :src="video" class="wh-auto ht-auto" :poster="video_img" objectFit="cover" :style="'object-fit: cover;' + video_style"></video>
</view>
</template>
<script>
import { padding_computer, radius_computer } from '@/common/js/common/common.js';
export default {
props: {
propKey: {
type: [String, Number],
default: '',
},
propValue: {
type: Object,
default: () => ({}),
},
propDataStyle: {
type: Object,
default: () => ({}),
}
},
data() {
return {
style_container: '',
video_img: '',
video: '',
video_style: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue || {};
// 视频比例
this.setData({
video_img: new_content.video_img.length > 0 ? new_content.video_img[0].url : '',
video: new_content.video.length > 0 ? new_content.video[0].url : '',
video_style: radius_computer(this.propDataStyle.img_radius),
});
}
},
};
</script>
<style></style>

View File

@@ -0,0 +1,79 @@
<template>
<view :class="['oh img_wh', propClass]" :style="empty_outer_style + propStyle">
<image :src="img_url" :mode="propImgFit" :style="empty_style + 'display: block;'" />
</view>
</template>
<script>
import { is_obj, isEmpty } from '@/common/js/common/common.js';
export default {
props: {
propImageSrc: {
type: [Object, String],
default: () => {},
},
propErrorStyle: {
type: String,
default: () => '',
},
propImgFit: {
type: String,
default: () => 'aspectFill',
},
propStyle: {
type: String,
default: () => '',
},
propClass: {
type: String,
default: () => '',
},
},
data() {
return {
empty_outer_style: '',
empty_style: 'width: 100%; height: 100%;', // 有图片的时候显示为100%
img_url: '',
default_image: '/static/images/common/image-empty.png',
};
},
watch: {
propImageSrc(val) {
this.init();
}
},
mounted() {
this.init();
},
methods: {
init() {
let img_url = this.propImageSrc;
if (is_obj(this.propImageSrc)) {
img_url = !isEmpty(this.propImageSrc) ? this.propImageSrc.url : '';
}
// 没有图片的时候根据默认值来算
if (img_url == undefined || img_url == null || img_url == '') {
this.setData({
empty_outer_style: 'background: #f4fcff;display:flex;align-items: center;justify-content: center;',
empty_style: `${this.propErrorStyle}`,
});
img_url = this.default_image;
} else {
this.setData({
empty_outer_style: '',
empty_style: 'width: 100%; height: 100%;', // 有图片的时候显示为100%
});
}
this.setData({
img_url: img_url,
});
},
},
};
</script>
<style lang="scss" scoped>
.img_wh {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<view v-if="form.seckill_subscript_show == '1'" class="corner-marker" :style="corner_marker">
<view class="flex-row nowrap" :style="corner_img_marker">
<template v-if="form.subscript_type == 'img-icon'">
<template v-if="!isEmpty(form.subscript_img_src)">
<image :src="form.subscript_img_src[0].url" mode="aspectFill" :style="img_style" />
</template>
<template v-else>
<iconfont :name="'icon-' + form.subscript_icon_class" propContainerDisplay="flex" :size="new_style.subscript_style.text_or_icon_size * 2 + 'rpx'" :color="new_style.subscript_style.text_or_icon_color"></iconfont>
</template>
</template>
<template v-else>
<span class="text-line-1" :style="text_size">{{ form.subscript_text }}</span>
</template>
</view>
</view>
</template>
<script>
import { common_img_computer, common_styles_computer, isEmpty } from '@/common/js/common/common.js';
import iconfont from '@/components/iconfont/iconfont';
export default {
components: {
iconfont,
},
props: {
propValue: {
type: Object,
default: () => ({}),
},
propType: {
type: String,
default: 'outer',
},
},
data() {
return {
form: {},
new_style: {},
corner_img_marker: '',
img_style: '',
text_size: '',
};
},
created() {
this.init();
},
methods: {
isEmpty,
// 初始化数据
init() {
if (!isEmpty(this.propValue)) {
const new_content = this.propValue.content || {};
const new_style = this.propType == 'outer' ? this.propValue.style || {} : { subscript_style: this.propValue.style || {} };
if (!isEmpty(new_style.subscript_style)) {
// 视频比例
this.setData({
form: new_content,
new_style: new_style,
corner_marker: this.get_corner_marker(new_style),
text_size: `font-size: ${ new_style.subscript_style.text_or_icon_size * 2 }rpx;color: ${ new_style.subscript_style.text_or_icon_color };`,
corner_img_marker: common_img_computer(new_style.subscript_style),
img_style: `height: ${new_style.subscript_style.img_height * 2}rpx; width: ${new_style.subscript_style.img_width * 2}rpx`,
});
}
}
},
get_corner_marker(new_style) {
const { subscript_style } = new_style;
let location = common_styles_computer(subscript_style);
// 获取内部的显示数据
const { seckill_subscript_location, top_or_bottom_spacing, left_or_right_spacing } = subscript_style;
// 圆角根据图片的圆角来计算 对角线是同样的圆角
if (seckill_subscript_location == 'top-left') {
location += `top: ${ top_or_bottom_spacing * 2 }rpx;left: ${ left_or_right_spacing * 2 }rpx;`;
} else if (seckill_subscript_location == 'top-center') {
location += 'top: 0;left: 50%;transform: translateX(-50%);';
} else if (seckill_subscript_location == 'top-right') {
location += `top: ${ top_or_bottom_spacing * 2 }rpx;right:${ left_or_right_spacing * 2 }rpx;`;
} else if (seckill_subscript_location == 'bottom-left') {
location += `bottom: ${ top_or_bottom_spacing * 2 }rpx;left: ${ left_or_right_spacing * 2 }rpx;`;
} else if (seckill_subscript_location == 'bottom-center') {
location += 'bottom: 0;left: 50%;transform: translateX(-50%);';
} else if (seckill_subscript_location == 'bottom-right') {
location += `bottom: ${ top_or_bottom_spacing * 2 }rpx;right: ${ left_or_right_spacing * 2 }rpx;`;
}
return location;
},
},
};
</script>
<style>
.corner-marker {
position: absolute;
max-width: 100%;
}
</style>

View File

@@ -0,0 +1,592 @@
<template>
<!-- 66rpx是自定义顶部导航栏的高度-->
<view class="tabs-view" :style="tabs_sticky">
<view class="tabs-view" :style="propStyle + propTabsBackground">
<view class="pr" :style="propsTabsContainer">
<view v-if="propIsRotatingBackground" class="pa top-0 wh-auto ht-auto" :style="propBgImgStyle"></view>
<view class="flex-row gap-10 jc-sb align-c" :style="propsTabsImgContainer">
<view class="tabs flex-1 flex-width">
<scroll-view :scroll-x="true" :show-scrollbar="false" :scroll-with-animation="tabs_list_is_sliding_fixed" :scroll-left="scroll_left" :class="'wh-auto interior-area-' + propKey">
<view :class="'flex-row ' + flex_class" :style="'height:' + tabs_height + ';width:' + tabs_scroll_width + 'px;'">
<view v-for="(item, index) in tabs_list" :key="index" :class="'item nowrap flex-col jc-c align-c gap-4 scroll-item-' + propKey + ' ' + tabs_theme + (index == active_index ? ' active' : '') + ((tabs_theme_index == '0' && tabs_theme_1_style) || tabs_theme_index == '1' || tabs_theme_index == '2' ? ' pb-0' : '')" :style="'flex:0 0 auto;padding-left:' + (index == 0 ? '0' : tabs_spacing) + 'rpx;padding-right:' + (index + 1 == tabs_list.length ? '0' : tabs_spacing) + 'rpx;' + get_item_style(item.is_sliding_fixed)" :data-index="index" @tap="handle_event">
<view class="nowrap ma-auto">
<view v-if="tabs_theme_index == '4'" :class="'img oh pr z-i-deep ' + (!isEmpty(item.img) ? 'img-no-empty' : '')" :style="tabs_theme_style.tabs_top_img">
<imageEmpty :propImageSrc="item.img[0]" propImgFit="aspectFit" propErrorStyle="width: 20rpx;height: 20rpx;"></imageEmpty>
<!-- <image :src="item.img[0].url" class="img" mode="aspectFit" /> -->
</view>
<template v-if="item.tabs_type == '1'">
<template v-if="!isEmpty(item.tabs_icon)">
<view :class="tabs_theme_index == '4' ? 'title pr z-i' : 'title pr z-i-deep'" :style="(tabs_theme_index == '4' ? tabs_sign_spacing : '') + (index == active_index ? (['2', '4'].includes(tabs_theme_index) ? tabs_check : '') : '' + tabs_padding_bottom)">
<iconfont :name="'icon-' + item.tabs_icon" :color="index == active_index ? tabs_icon_checked_color : tabs_icon_color" propContainerDisplay="flex" :size="index == active_index ? tabs_icon_checked_size : tabs_icon_size"></iconfont>
</view>
</template>
<template v-else>
<view :class="tabs_theme_index == '4' ? 'title pr z-i' : 'title pr z-i-deep'" :style="(tabs_theme_index == '4' ? tabs_sign_spacing : '') + index == active_index ? new_style.is_tabs_img_background == '1' && ['2', '4'].includes(tabs_theme_index) ? tabs_check : '' : tabs_padding_bottom">
<imageEmpty :propImageSrc="item.tabs_img[0]" :propStyle="index == active_index ? tabs_theme_style.tabs_img_checked : tabs_theme_style.tabs_img" propImgFit="heightFix" propErrorStyle="width: 40rpx;height: 40rpx;"></imageEmpty>
</view>
</template>
</template>
<template v-else>
<view :class="tabs_theme_index == '4' ? 'title pr z-i' : 'title pr z-i-deep'" :style="(tabs_theme_index == '4' ? tabs_sign_spacing : '') + (index == active_index ? ['2', '4'].includes(tabs_theme_index) ? tabs_theme_style.tabs_title_checked + tabs_check : tabs_theme_style.tabs_title_checked : tabs_theme_style.tabs_title + tabs_padding_bottom)">{{ item.title }}</view>
</template>
<view class="desc pr z-i wh-auto" :style="tabs_sign_spacing + (tabs_theme_index == '1' && index == active_index ? tabs_check : '')">{{ item.desc }}</view>
<template v-if="tabs_theme_index == '3' && index == active_index">
<template v-if="!isEmpty(form.tabs_adorn_icon)">
<view class="icon pr z-i wh-auto flex-row jc-c align-c" :style="tabs_sign_spacing">
<iconfont :name="'icon-' + form.tabs_adorn_icon" :style="icon_tabs_check" propContainerDisplay="flex" :size="tabs_adorn_icon_size"></iconfont>
</view>
</template>
<template v-else>
<view class="pr z-i wh-auto flex-row jc-c align-c ma-auto" :style="tabs_sign_spacing">
<imageEmpty :propImageSrc="form.tabs_adorn_img[0]" :propStyle="tabs_adorn_img_style" propImgFit="aspectFit" propErrorStyle="width: 20rpx;height: 20rpx;"></imageEmpty>
</view>
</template>
</template>
<view class="bottom_line z-i" :class="tabs_bottom_line_theme" :style="tabs_check + tabs_sign_spacing"></view>
</view>
</view>
</view>
</scroll-view>
</view>
<view v-if="propIsTabsIcon && form.show_more !== '0'" :style="tabs_padding_bottom">
<iconfont :name="'icon-' + icon.more_icon_class || 'category-more'" :size="icon.more_icon_size + '' || '14'" :color="icon.more_icon_color || '#000'" propContainerDisplay="flex" @click="category_check_event"></iconfont>
</view>
</view>
</view>
</view>
<!-- 选项卡更多弹窗 -->
<componentPopup :propShow="popup_status" :propIsBar="propIsBar" propPosition="top" :propMask="true" :propTop="newPropTop" :propStyle="newPropStyle" @onclose="quick_close_event">
<view :class="'padding-bottom-lg ' + (['toutiao', 'app', 'h5'].includes(platform) ? 'padding-top-lg' : 'padding-top')">
<view class="padding-left-main padding-bottom-main">全部选项卡</view>
<view class="divider-b">
<view class="nav-list-more">
<view class="flex-row flex-wrap align-c">
<view v-for="(item, index) in tabs_list" :key="index" class="item tc cr-base cp text-size-xs" :data-index="index" @tap="handle_event">
<view class="dis-inline-block padding-vertical-xs padding-horizontal round" :class="active_index == index ? 'bg-main border-color-main cr-white' : ''">
{{ item.title }}
</view>
</view>
</view>
</view>
</view>
<view class="tc padding-top-lg flex-row jc-c align-c" @tap="quick_close_event">
<text class="padding-right-sm">{{ $t('nav-more.nav-more.h9g4b1') }}</text>
<iconfont name="icon-arrow-top" color="#ccc" propContainerDisplay="flex"></iconfont>
</view>
</view>
</componentPopup>
</view>
</template>
<script>
const app = getApp();
import { gradient_computer, isEmpty, radius_computer } from '@/common/js/common/common.js';
import componentPopup from '@/components/popup/popup';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
components: {
componentPopup,
imageEmpty,
},
props: {
propValue: {
type: Object,
default: () => {},
},
// 是否需要icon
propIsTabsIcon: {
type: Boolean,
default: false,
},
// 是否需要粘性定位置顶
propIsTop: {
type: Boolean,
default: false,
},
// 指定样式
propStyle: {
type: String,
default: '',
},
// 层级
propZIndex: {
type: Number,
default: 11,
},
// 自定义导航栏高度
propCustomNavHeight: {
type: String,
default: '66rpx',
},
// tabs背景色
propTabsBackground: {
type: String,
default: 'transparent',
},
// 置顶距离顶部高度
propTop: {
type: [String, Number],
default: '0',
},
propsTabsContainer: {
type: String,
default: ''
},
propsTabsImgContainer: {
type: String,
default: '',
},
propIsRotatingBackground: {
type: Boolean,
default: false,
},
propBgImgStyle: {
type: String,
default: '',
},
propKey: {
type: [String, Number],
default: '',
},
propTabsSlidingFixedBg: {
type: String,
default: ''
}
},
data() {
return {
form: {},
new_style: {},
tabs_theme_index: '',
tabs_theme: '',
tabs_check: '',
icon_tabs_check: '',
tabs_spacing: '',
tabs_sign_spacing: '',
tabs_padding_bottom: '',
tabs_list: [],
active_index: 0,
tabs_theme_style: {
tabs_title_checked: '',
tabs_title: '',
tabs_img_checked: '',
tabs_img: '',
tabs_top_img: ''
},
tabs_icon_checked_size: '',
tabs_icon_size: '',
tabs_icon_checked_color: '',
tabs_icon_color: '',
icon: {
more_icon_class: '',
more_icon_size: '',
more_icon_color: '',
},
// 过滤弹窗
popup_status: false,
propIsBar: false,
tabs_bottom_line_theme: '',
tabs_sticky: '',
tabs_height: '100%',
tabs_adorn_img_style: '',
tabs_width: 0,
tabs_scroll_width: 0,
// #ifdef MP
sticky_top: bar_height,
// #endif
// #ifdef H5 || MP-TOUTIAO
sticky_top: bar_height,
// #endif
// #ifdef APP
sticky_top: bar_height,
// #endif
newPropTop: '',
newPropStyle: '',
platform: app.globalData.application_client_type(),
is_out_of_range: false,
tabs_list_is_sliding_fixed: true,
scroll_left: 0,
tabs_adorn_icon_size: '0rpx',
// 默认数据
old_radius: { radius: 0, radius_top_left: 0, radius_top_right: 0, radius_bottom_left: 0, radius_bottom_right: 0 },
old_padding: { padding: 0, padding_top: 0, padding_bottom: 0, padding_left: 0, padding_right: 0 },
old_margin: { margin: 0, margin_top: 10, margin_bottom: 0, margin_left: 0, margin_right: 0 },
};
},
computed: {
flex_class() {
if (this.is_out_of_range) {
return this.form.justification == 'center' ? 'jc-c' : this.form.justification == 'right' ? 'jc-e': '';
} else {
return '';
}
},
get_item_style() {
return (val) => {
return val == '1' ? `${ this.propTabsSlidingFixedBg };position: sticky;left: 0;z-index: 11;` : ''
}
}
},
watch: {
propValue() {
this.init();
},
propIsTop: {
deep: true,
immediate: true,
handler(new_val) {
if (new_val) {
this.tabs_sticky = 'position: sticky;top: calc(' + parseInt(this.propTop) + 'px + ' + this.propCustomNavHeight + ');z-index: ' + this.propZIndex + ';';
} else {
this.tabs_sticky = '';
}
},
},
propTop(new_val, old_val) {
if (this.propIsTop) {
this.tabs_sticky = 'position: sticky;top: calc(' + parseInt(this.propTop) + 'px + ' + this.propCustomNavHeight + ');z-index: ' + this.propZIndex + ';';
} else {
this.tabs_sticky = '';
}
},
},
mounted() {
this.init();
},
methods: {
// 判断是否为空
isEmpty,
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
const new_tabs_check = this.tabs_check_computer(new_style);
const new_icon = {
more_icon_class: new_style.more_icon_class,
more_icon_size: new_style.more_icon_size,
more_icon_color: new_style.more_icon_color,
};
const tabs_top_img_height = new_style?.tabs_top_img_height || 39;
const tabs_top_img_radius = new_style?.tabs_top_img_radius || { radius: 100, radius_top_left: 100, radius_top_right: 100, radius_bottom_left: 100, radius_bottom_right: 100}
// 标题样式
const new_tabs_theme_style = {
tabs_title_checked: `font-weight: ${new_style.tabs_weight_checked};font-size: ${new_style.tabs_size_checked * 2}rpx;line-height: ${new_style.tabs_size_checked * 2}rpx;color:${new_style.tabs_color_checked};`,
tabs_title: `font-weight: ${new_style.tabs_weight};font-size: ${new_style.tabs_size * 2}rpx;line-height: ${new_style.tabs_size * 2}rpx;color:${new_style.tabs_color};`,
tabs_img_checked: `height: ${(new_style?.tabs_img_height || 0) * 2}rpx;` + radius_computer(new_style?.tabs_img_radius || this.old_radius),
tabs_img: `height: ${(new_style?.tabs_img_height || 0) * 2}rpx;` + radius_computer(new_style?.tabs_img_radius || this.old_radius),
tabs_top_img: `height: ${tabs_top_img_height * 2 }rpx;width: ${tabs_top_img_height * 2 }rpx;box-sizing: border-box;` + radius_computer(tabs_top_img_radius),
};
const { tabs_size_checked, tabs_size, tabs_icon_size_checked = 0, tabs_icon_size = 0, tabs_img_height = 0, tabs_sign_spacing = 0 } = new_style || {};
let default_height = 0;
if (new_content.tabs_theme == '2') {
default_height = 12; // 选中的时候,风格二的内间距
} else if (new_content.tabs_theme == '4') {
// const top_index = new_content?.tabs_list?.findIndex((item) => !isEmpty(item.img)) ?? -1;
// default_height = 4 + (top_index > -1 ? tabs_top_img_height + tabs_sign_spacing : 0); // 选中的时候,风格二的内间距 加上上边图片的大小和上边图片之间的间距
default_height = 4 + tabs_top_img_height + tabs_sign_spacing;
}
// 筛选出所有的icon
const is_icon = new_content?.tabs_list?.findIndex((item) => item.tabs_type === '1' && !isEmpty(item.tabs_icon)) ?? -1;
// 如果有icon则取选中的icon大小和未选中的icon大小取最大值作为图标的高度
let icon_height = 0;
if (is_icon > -1) {
icon_height = Math.max(tabs_icon_size_checked + default_height, tabs_icon_size);
}
// 筛选出所有的图片, 没有选择图标的时候默认是图片
const is_img = new_content?.tabs_list?.findIndex((item) => item.tabs_type === '1' && isEmpty(item.tabs_icon)) ?? -1;
// 选项卡高度 五个值,作为判断依据,因为图片没有未选中的大小设置,所以高度判断的时候只取选中的高度, 其余的icon和标题都分别取选中和未选中的大小对比取出最大的值作为选项卡的高度避免选项卡切换时会出现抖动问题
const height = Math.max(tabs_size_checked + default_height, tabs_size, icon_height, is_img > -1 ? (tabs_img_height + default_height) : '');
const findIndex = new_content.tabs_list.findIndex(item => item.is_sliding_fixed == '1');
// 参数设置
this.setData({
form: new_content,
tabs_list_is_sliding_fixed: findIndex == -1,
newPropTop: `calc(${ this.sticky_top * 2}rpx);`,
newPropStyle: `padding-top: ${ this.sticky_top * 2 }rpx;margin-top: -${ this.sticky_top * 2 }rpx;`,
new_style: new_style,
tabs_spacing: Number(new_style.tabs_spacing),
tabs_sign_spacing: !isEmpty(new_style.tabs_sign_spacing) ? `margin-top: ${new_style.tabs_sign_spacing * 2}rpx;` : 'margin-top: 8rpx;',
tabs_list: new_content.tabs_list,
tabs_padding_bottom: this.get_padding_bottom(new_content, new_style) + 'z-index: 11;',
// 选项卡主题
tabs_theme: this.get_tabs_theme(new_content),
tabs_theme_index: new_content.tabs_theme,
// 选项卡样式
tabs_check: new_tabs_check,
icon_tabs_check: `${new_tabs_check};line-height: 1;background-clip: text;-webkit-background-clip: text;-webkit-text-fill-color: transparent;`,
icon: new_icon,
tabs_icon_checked_size: (new_style?.tabs_icon_size_checked || 0) * 2 + 'rpx',
tabs_icon_size: (new_style?.tabs_icon_size || 0) * 2 + 'rpx',
tabs_icon_checked_color: new_style?.tabs_icon_color_checked || '',
tabs_icon_color: new_style?.tabs_icon_color || '',
tabs_theme_style: new_tabs_theme_style,
tabs_bottom_line_theme: new_style.tabs_one_theme == '1' ? 'tabs-bottom-line-theme' : '',
tabs_theme_1_style: new_style.tabs_one_theme == '1',
tabs_height: ['2', '4'].includes(new_content.tabs_theme) ? height * 2 + 'rpx' : '100%;',
tabs_adorn_img_style: this.get_tabs_adorn_img_style(new_style),
tabs_adorn_icon_size: (new_style?.tabs_adorn_icon_size || 0) * 2 + 'rpx',
});
// 只有居中居右的才重新获取dom判断
// if (['center', 'right'].includes(this.form.justification)) {
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.interior-area-' + this.propKey)
.fields({ size: true, scrollOffset: true},(res) => {
if ((res || null) != null) {
const { scrollWidth, width } = res;
this.setData({
is_out_of_range: scrollWidth <= width,
tabs_scroll_width: scrollWidth,
tabs_width: width
});
}
})
.exec();
}, 0)
// }
},
get_tabs_adorn_img_style(new_style) {
return radius_computer(new_style?.tabs_adorn_img_radius || this.old_radius) + `height: ${(new_style?.tabs_adorn_img_height || 10) * 2}rpx;${ new_style.is_tabs_adorn_img_background == '1' ? tabs_check.value : ''}`;
},
// 获取选项卡主题
get_tabs_theme(data) {
let arr = {
1: 'tabs-style-2',
2: 'tabs-style-3',
3: 'tabs-style-4',
4: 'tabs-style-5',
};
let value = arr[data.tabs_theme];
return value === undefined ? 'tabs-style-1' : value;
},
get_padding_bottom(form, new_style) {
let bottom = 0;
if (form.tabs_theme == '0') {
if (new_style.tabs_one_theme == '1') {
bottom = 6;
} else {
bottom = 3;
}
} else if (form.tabs_theme == '3') {
bottom = !isEmpty(form.tabs_adorn_icon) ? new_style.tabs_adorn_icon_size : new_style.tabs_adorn_img_height;
}
const tabs_sign_spacing = !isEmpty(new_style.tabs_sign_spacing) ? new_style.tabs_sign_spacing : 4;
return ['1', '2', '4'].includes(form.tabs_theme) ? '' : `padding-bottom: ${(tabs_sign_spacing + bottom) * 2 }rpx;`;
},
// 选中的背景渐变色样式
tabs_check_computer(data) {
const new_gradient_params = {
color_list: data.tabs_checked,
direction: data.tabs_direction,
};
return gradient_computer(new_gradient_params);
},
// 事件
// tabs切换事件
handle_event(e) {
const index = e.currentTarget.dataset.index;
const tabs_list_item = this.tabs_list[index];
this.setData({
active_index: index,
popup_status: false,
});
this.set_scoll_left(index);
this.$emit('onTabsTap', index, tabs_list_item);
setTimeout(() => {
this.$emit('tabsZindex', 11)
}, 200)
},
// 将选中的内容定位到中间
set_scoll_left(index) {
const query = uni.createSelectorQuery().in(this);
query.selectAll(`.scroll-item-` + this.propKey)
.boundingClientRect((rect) => {
const tabs_index = this.form.tabs_list.findIndex(item => item.is_sliding_fixed == '1');
// 如果第一个悬浮了就取第二个的left加上 第一个的宽度和left
let new_width = tabs_index == 0 && index != 0 ? rect[1].left - rect[0].width - rect[0].left : rect[0].left;
// 如果悬浮的不是第一个并且选中的是悬浮的内容
if (index > 0 && tabs_index == index) {
new_width = rect[0].left - rect[index + 1].left + rect[index].width;
}
const scrollLeft =
rect[index].left +
rect[index].width / 2 -
this.tabs_width / 2 -
new_width
this.setData({
scroll_left: scrollLeft,
});
})
.exec();
},
// 分类选择事件
category_check_event() {
this.setData({
popup_status: true,
});
setTimeout(() => {
this.$emit('tabsZindex', 13)
}, 0)
},
// 关闭分类选择事件
quick_close_event(e) {
this.setData({
popup_status: false,
});
setTimeout(() => {
this.$emit('tabsZindex', 11)
}, 200)
},
},
};
</script>
<style lang="scss" scoped>
.tabs {
.item {
// padding: 0 0 10rpx 0;
position: relative;
&:first-of-type {
margin-left: 0;
}
&:last-of-type {
margin-right: 0;
}
.title {
font-size: 28rpx;
text-align: center;
}
.desc {
font-size: 22rpx;
color: #999;
text-align: center;
display: none;
}
.bottom_line {
width: 100%;
height: 6rpx;
border-radius: 20rpx;
background-color: red;
// position: absolute;
left: 0;
right: 0;
bottom: 0;
display: none;
}
.icon {
// position: absolute;
bottom: 0;
text-align: center;
font-size: 40rpx;
line-height: 20rpx !important;
display: none;
}
.img {
// width: 78rpx;
// height: 78rpx;
border-radius: 100%;
border: 2rpx solid transparent;
display: none;
margin: 0 auto;
}
&.tabs-style-1 {
&.active {
.bottom_line {
display: block;
}
}
.tabs-bottom-line-theme {
opacity: 0.6;
bottom: 0;
z-index: 0;
height: 14rpx;
border-radius: 0;
// left: 12%;
}
}
&.tabs-style-2 {
&.active {
.desc {
background: #ff5e5e;
color: #fff;
}
}
.desc {
border-radius: 40rpx;
padding: 4rpx 12rpx;
box-sizing: border-box;
display: block;
}
}
&.tabs-style-3 {
&.active {
.title {
// background: #ff2222;
border-radius: 40rpx;
padding: 6px 12px;
box-sizing: border-box;
color: #fff;
}
}
}
&.tabs-style-4 {
// padding-bottom: 28rpx;
&.active {
.title {
color: #ff2222;
}
.icon {
color: #ff2222;
display: flex;
}
}
}
&.tabs-style-5 {
align-items: center;
&.active {
.title {
// font-size: 22rpx;
// background: #ff5e5e;
border-radius: 40rpx;
padding: 2px 7px;
box-sizing: border-box;
// color: #fff;
}
.img {
border-color: #ff5e5e;
}
}
.img-no-empty {
width: 100% !important;
}
.img {
display: block;
}
}
}
}
.nav-list-more {
width: 100%;
max-height: 550rpx;
padding-bottom: 20rpx;
overflow-y: auto;
}
.nav-list-more .item {
width: 20%;
padding: 20rpx 0;
}
.pb-14 {
padding-bottom: 28rpx;
}
.pb-0 {
padding-bottom: 0 !important;
}
.ma-auto {
margin: auto;
}
</style>

View File

@@ -0,0 +1,248 @@
<template>
<view :style="style_container">
<view class="pr" :style="style_img_container">
<swiper class="swiper" circular="true" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :duration="500" :display-multiple-items="slides_per_group" :style="{ height: new_height }" @change="slideChange">
<swiper-item v-for="(item, index) in nav_content_list" :key="index" class="swiper-item">
<view class="banner-img flex-row flex-wrap wh-auto" :class="'banner-img-' + propKey" :style="space">
<view v-for="(item1, index1) in item.split_list" :key="index1" class="flex-col gap-10 align-c" :style="group_width + nav_title_space" :data-value="item1.link.page" @tap="url_open_event">
<view v-if="['image_with_text', 'image'].includes(nav_style)" class="flex-row align-c jc-c pr">
<view class="top-img" :style="img_size">
<imageEmpty :propImageSrc="item1.img[0]" :propStyle="img_style" propErrorStyle="width: 60rpx;height: 60rpx;"></imageEmpty>
</view>
<!-- 角标 -->
<subscriptIndex :propValue="item1.subscript" propType="nav-group"></subscriptIndex>
</view>
<view v-if="['image_with_text', 'text'].includes(nav_style)" class="wh-auto size-12 ma-0 nowrap oh tc" :style="text_style">{{ item1.title }}</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="form.display_style == 'slide' && new_style.is_show == '1'" :class="['left', 'right'].includes(new_style.indicator_new_location) ? 'indicator_up_down_location' : 'indicator_about_location'" :style="indicator_location_style">
<block v-if="new_style.indicator_style == 'num'">
<view :style="indicator_style" class="dot-item">
<text :style="{ color: new_style.actived_color }">{{ actived_index + 1 }}</text>
<text>/{{ nav_content_list.length }}</text>
</view>
</block>
<block v-else>
<view v-for="(item, index) in nav_content_list" :key="index" :style="indicator_style + (actived_index == index ? 'background:' + new_style.actived_color : '')" class="dot-item" />
</block>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { isEmpty, common_styles_computer, common_img_computer, radius_computer, padding_computer, get_indicator_style, get_indicator_location_style } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
import subscriptIndex from '@/components/diy/modules/subscript/index.vue';
export default {
components: {
imageEmpty,
subscriptIndex,
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
form: {},
new_style: {},
style_container: '',
style_img_container: '',
img_style: '',
text_style: '',
indicator_style: '',
new_height: '300rpx',
actived_index: 0,
group_width: '',
nav_content_list: [],
nav_title_space: 'row-gap:20rpx', // 导航标题间距
space: 'row-gap:20rpx', // 导航间距
img_size: '72rpx',
slides_per_group: 1,
indicator_location_style: '',
subscriptStyle: {
seckill_subscript_location: 'top-left',
text_or_icon_color: '#fff',
text_or_icon_size: 12,
img_width: 20,
img_height: 20,
direction: '90deg',
top_or_bottom_spacing: 0,
left_or_right_spacing: 0,
color_list: [{ color: '#FF7607', color_percentage: undefined }],
background_img_style: '2',
background_img: [],
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
radius: 4,
radius_top_left: 4,
radius_top_right: 4,
radius_bottom_left: 4,
radius_bottom_right: 4,
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
box_shadow_color: '',
box_shadow_x: 0,
box_shadow_y: 0,
box_shadow_blur: 0,
box_shadow_spread: 0,
}
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
mounted() {
this.init();
},
methods: {
init() {
const new_content = this.propValue.content;
const new_style = this.propValue.style;
let group = 1;
let group_width = `width: ${100 / (new_content.single_line || 4)}%;`;
// 判断是否是轮播图
if (new_content?.display_style == 'slide') {
if (new_content.row == 1 && new_style.rolling_fashion == 'translation') {
group = new_content.single_line || 4;
group_width = 'width: 100%;';
} else {
group = 1;
group_width = `width: ${100 / (new_content.single_line || 4)}%;`;
}
}
this.setData({
form: new_content,
new_style: new_style,
style_container: common_styles_computer(new_style.common_style), // 用于样式显示
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
img_style: radius_computer(new_style), // 图片的设置
text_style: `font-size: ${new_style.title_size * 2 || 24}rpx; color: ${new_style.title_color || '#000'};`, // 标题的样式
indicator_style: get_indicator_style(new_style), // 指示器的样式
indicator_location_style: get_indicator_location_style(new_style), // 指示器位置处理
actived_color: new_style.actived_color || '#2A94FF', // 轮播图显示样式
slides_per_group: group, // 每个轮播图显示的个数
group_width: group_width, // 每个导航所占位置
nav_title_space: 'row-gap:' + (new_style.title_space || 0) * 2 + 'rpx', // 导航标题间距
space: 'row-gap:' + (new_style.space || 0) * 2 + 'rpx;' + padding_computer(new_style.data_padding), // 导航间距
img_size: 'width:' + (new_style.img_size || 0) * 2 + 'rpx;height:' + (new_style.img_size || 0) * 2 + 'rpx;', // 图片大小
nav_style: new_content.nav_style || 'image_with_text', // 是否显示文字和图片
nav_content_list: this.get_nav_content_list(new_content, new_style),
});
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
// 选择我们想要的元素
query
.select('.banner-img-' + this.propKey)
.boundingClientRect((res) => {
if ((res || null) != null) {
// data包含元素的宽度、高度等信息
this.setData({
new_height: res.height + 'px',
});
}
})
.exec(); // 执行查询
}, 50);
},
get_nav_content_list(data, new_style) {
// 深拷贝一下,确保不会出现问题
const list = JSON.parse(JSON.stringify(data.nav_content_list || Array(4))).map(item => ({
...item,
// 角标配置
subscript: isEmpty(item.subscript) ?
{
content: {
seckill_subscript_show: '0',
subscript_type: 'text',
subscript_img_src: [],
subscript_icon_class: '',
subscript_text: '',
},
style: this.subscriptStyle,
} : item.subscript,
}));
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (list.length > 0 && data.display_style == 'slide') {
// 存储数据显示
let nav_list = [];
if (data.row == 1 && new_style.rolling_fashion == 'translation') {
// 拆分的数量
list.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
} else {
// 每页显示的数量
const num = (data.single_line || 4) * (data.row || 1);
// 拆分的数量
const split_num = Math.ceil(list.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: list.slice(i * num, (i + 1) * num),
});
}
return nav_list;
}
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: list,
},
];
}
},
slideChange(e) {
this.setData({
actived_index: e.detail.current,
});
},
url_open_event(link) {
app.globalData.url_event(link);
},
},
};
</script>
<style scoped lang="scss">
.top-img {
height: 72rpx;
width: 72rpx;
border-radius: 8rpx;
}
.gap-x-10 {
row-gap: 20rpx;
}
</style>

211
components/diy/notice.vue Normal file
View File

@@ -0,0 +1,211 @@
<template>
<!-- 公告 -->
<view :style="style_container">
<view :style="style_img_container">
<template v-if="form_content.notice_style == 'inherit'">
<view class="news-box" :style="container_background_style + container_height">
<view class="flex-row align-c gap-8" :style="container_background_img_style">
<template v-if="form_content.title_type == 'img-icon'">
<view v-if="form_content.icon_class">
<iconfont :name="'icon-' + form_content.icon_class" :size="form_style.icon_size * 2 + 'rpx'" :color="form_style.icon_color" propContainerDisplay="flex"></iconfont>
</view>
<view v-else>
<image v-if="form_content.img_src.length > 0" :src="form_content.img_src[0].url" class="border-radius-sm dis-block" mode="aspectFill" :style="img_style"></image>
</view>
</template>
<template v-else>
<view :style="title_style" class="padding-horizontal-sm border-radius-sm">{{ form_content.title || '公告' }}</view>
</template>
<swiper class="swiper flex-1" circular :indicator-dots="false" :autoplay="true" :interval="interval_time" :vertical="direction_type == 'vertical'" :style="container_height">
<swiper-item v-for="(item, index) in notice_list" :key="index">
<view class="swiper-item flex-row align-c ht-auto" :style="content_title_style + 'color:' + form_style.news_color" :data-value="item.notice_link.page" @tap="url_event">
<view class="text-line-1">{{ item.notice_title }}</view>
</view>
</swiper-item>
</swiper>
<view v-if="form_content.is_right_button == '1'" class="flex-row align-c" :style="'color: ' + form_style.right_button_color + ';font-size:' + form_style.right_button_size * 2 + 'rpx;'" :data-value="form_content.more_link.page" @tap="url_event">
{{ form_content.right_title }}
<view class="pr">
<iconfont name="icon-arrow-right" :color="form_style.right_button_color || '#999'" :size="form_style.right_button_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
</view>
</template>
<template v-else>
<view class="news-card" :style="container_background_style">
<view class="flex-col gap-10" :style="container_background_img_style">
<view class="flex-row wh-auto jc-sb align-c">
<template v-if="form_content.title_type == 'img-icon'">
<template v-if="form_content.icon_class">
<iconfont :name="'icon-' + form_content.icon_class" :size="form_style.icon_size * 2 + 'rpx'" :color="form_style.icon_color" propContainerDisplay="flex"></iconfont>
</template>
<template v-else>
<image v-if="form_content.img_src.length > 0" :src="form_content.img_src[0].url" class="border-radius-sm dis-block" mode="aspectFill" :style="img_style"></image>
</template>
</template>
<template v-else>
<view :style="title_style" class="padding-horizontal-sm border-radius-sm">{{ form_content.title }}</view>
</template>
<view v-if="form_content.is_right_button == '1'" class="flex-row align-c" :style="'color: ' + form_style.right_button_color + ';font-size:' + form_style.right_button_size * 2 + 'rpx;'" :data-value="form_content.more_link.page" @tap="url_event">
{{ form_content.right_title }}
<view class="pr">
<iconfont name="icon-arrow-right" :color="form_style.right_button_color || '#999'" :size="form_style.right_button_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
<view v-for="(item, index) in notice_list" :key="index" class="flex-row" :style="content_title_style" :data-value="item.notice_link.page" @tap="url_event">
<view class="num" :class="'one' + (index + 1)">{{ index + 1 }}</view>
<view class="break" :style="'color:' + form_style.news_color">{{ item.notice_title }}</view>
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script>
const app = getApp();
import { background_computer, common_styles_computer, common_img_computer, gradient_computer, gradient_handle, radius_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
form_content: {},
form_style: {},
style_container: '',
style_img_container: '',
// 容器高度
container_height: '',
// 容器背景
container_background_style: '',
// 图片设置
img_style: '',
// 标题的设置
title_style: '',
// 内容标题设置
content_title_style: '',
// 指示器的样式
// 轮播图定时显示
interval_time: 2000,
// 轮播图滚动方向 // vertical' | 'horizontal
direction_type: 'vertical',
// 记录当前显示的轮播图的数据
interval_list: {
time: 2000,
direction: 'vertical',
notice_length: 1,
},
// 公告数据
notice_list: [],
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
// 容器背景
const { container_color_list, container_direction, container_background_img_style, container_background_img } = new_style;
const temp_obj = {
color_list: container_color_list,
direction: container_direction,
background_img: container_background_img,
background_img_style: container_background_img_style,
};
const temp_container_background_style = gradient_computer(temp_obj) + radius_computer(new_style.container_radius) + `overflow:hidden;`;
const temp_container_background_img_style = background_computer(temp_obj);
// 标题渐变色处理
const gradient = gradient_handle(new_style.title_color_list, '90deg');
const time = (new_style.interval_time || 2) * 1000;
const direction = new_content.direction;
const new_notice_list = new_content.notice_list.filter((item) => item.is_show == '1');
// 判断长度是否相等
const notice_length = new_notice_list.length;
const new_interval_list = {
time: time,
direction: direction,
notice_length: notice_length,
};
this.setData({
form_content: new_content,
form_style: new_style,
container_height: 'height:' + new_style.container_height * 2 + 'rpx',
container_background_style: temp_container_background_style,
container_background_img_style: temp_container_background_img_style,
img_style: `height: ${new_style.title_height * 2}rpx; width: ${new_style.title_width * 2}rpx`,
title_style: `color:${new_style.title_color}; font-size: ${new_style.title_size * 2}rpx; font-weight: ${new_style.title_typeface}; ${gradient}`,
content_title_style: `font-size: ${new_style.news_size * 2}rpx; font-weight: ${new_style.news_typeface};`,
notice_list: new_notice_list,
interval_time: time,
direction_type: direction,
interval_list: new_interval_list,
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style lang="scss" scoped>
.news-box {
overflow: hidden;
padding: 0 20rpx;
background: #fff;
}
.news-card {
padding: 30rpx;
background: #fff;
}
.num {
padding-right: 14rpx;
color: #999;
}
.one1 {
color: #ea3323;
}
.one2 {
color: #ff7303;
}
.one3 {
color: #ffc300;
}
.two-style {
width: 48rpx;
height: 48rpx;
}
.break {
word-break: break-word;
overflow-wrap: break-word;
word-wrap: break-word;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<!-- 富文本 -->
<view class="diy-rich-text" :style="style_container">
<view :style="style_img_container">
<mp-html :content="content" />
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
content: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
this.setData({
content: new_content.html,
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
},
};
</script>
<style>
.diy-rich-text {
* {
max-width: 100%;
}
/* #ifdef H5 */
[id^=v] {
width: 100%;
}
/* #endif */
}
.diy-rich-text video {
width: 100%;
}
</style>

327
components/diy/search.vue Normal file
View File

@@ -0,0 +1,327 @@
<template>
<view :style="style_container">
<view :style="style_img_container">
<view class="search wh-auto pr">
<view class="box oh flex-row align-c" :style="box_style">
<view v-if="form.positioning_name_float == '1' && propSearchType == 'header'" :style="propLocationMargin">
<component-choice-location propType="search" :propLocationContainerStyle="propLocationContainerStyle" :propLocationImgContainerStyle="propLocationImgContainerStyle" :propBaseColor="propBaseColor" :propTextDefaultName="form.positioning_name" :propIsLeftIconArrow="form.is_location_left_icon_show == '1'" :propLeftImgValue="form.location_left_img" :propLeftIconValue="'icon-' + form.location_left_icon" :propIconLocationSize="propIconLocationSize" :propIconArrowSize="propIconArrowSize" :propIsRightIconArrow="form.is_location_right_icon_show == '1'" :propRightImgValue="form.location_right_img" :propRightIconValue="'icon-' + form.location_right_icon" :propTextMaxWidth="['4'].includes(form.theme) ? '300rpx' : '200rpx'" propContainerDisplay="flex" @onBack.stop="choice_location_back"></component-choice-location>
</view>
<view :class="'oh flex-1 ht-auto flex-row align-c gap-10' + (form.is_center == '1' ? ' tips-float' : '')" @tap.stop="search_tap">
<view v-if="form.is_icon_show == '1'" class="pr">
<view class="search-icon-before" :style="(form.positioning_name_float == '1' && propSearchType == 'header') || form.is_center == '1' ? '' : 'left: -' + (new_style.search_padding_left ? new_style.search_padding_left * 2 : 30) + 'rpx;'" @tap.stop="search_icon_tap"></view>
<view class="wh-auto ht-auto">
<template v-if="form.icon_img.length > 0">
<view class="img-box">
<image :src="form.icon_img[0].url" class="img" mode="heightFix"></image>
</view>
</template>
<template v-else>
<view>
<iconfont :name="!isEmpty(form.icon_class) ? 'icon-' + form.icon_class : 'icon-search-max'" size="28rpx" :color="new_style.icon_color" propContainerDisplay="flex"></iconfont>
</view>
</template>
</view>
</view>
<view v-if="!isEmpty(form.hot_word_list) && form.is_hot_word_show == '1'" :style="form.is_center == '1' ? `min-width:100px;` : 'width:100%;'">
<swiper circular="true" :autoplay="new_style.is_roll == '1'" :interval="new_style.interval_time * 1000" :vertical="true" :duration="500" class="swiper_style" @change="slideChange">
<swiper-item v-for="(item, index) in form.hot_word_list" :key="index">
<view class="flex-row align-c wh-auto ht-auto" :style="{ color: !isEmpty(item.color) ? item.color : !isEmpty(new_style.hot_words_color) ? new_style.hot_words_color : '#999' }" :data-value="item.value" @tap.stop="serch_event">
{{ item.value }}
</view>
</swiper-item>
</swiper>
</view>
<template v-else>
<text v-if="form.is_tips_show == '1'" :class="[propIsPageSettings ? 'text-size-xs text-line-1' : 'text-size-md text-line-1']" :style="'color:' + new_style.tips_color">{{ form.tips }}</text>
</template>
</view>
</view>
<view class="flex-row align-c pa search-outer-botton oh" :style="right_icon_style">
<view v-if="form.is_right_icon_show == '1' && (form.right_icon_img.length > 0 || !isEmpty(form.right_icon_class))" class="pr margin-right-main">
<view class="search-icon-before" @tap.stop="search_right_icon_tap"></view>
<view class="wh-auto ht-auto">
<template v-if="form.right_icon_img.length > 0">
<view class="img-box right_icon_height flex-row align-c">
<image :src="form.right_icon_img[0].url" class="img" mode="heightFix"></image>
</view>
</template>
<template v-else>
<view class="right_icon_height flex-row align-c">
<iconfont :name="!isEmpty(form.right_icon_class) ? 'icon-' + form.right_icon_class : 'icon-search-max'" size="28rpx" :color="new_style.right_icon_color" propContainerDisplay="flex"></iconfont>
</view>
</template>
</view>
</view>
<view v-if="form.is_search_show == '1'" class="flex-row align-c jc-c">
<view class="search-botton flex-row align-c jc-c z-i" :style="search_button_style" @tap.stop="serch_button_event">
<view class="oh" :style="search_button_img_style">
<template v-if="form.search_type === 'text'">
<view class="text-size-xs">{{ form.search_tips }}</view>
</template>
<template v-else-if="!isEmpty(form.search_botton_img) && form.search_botton_img.length > 0">
<image :src="form.search_botton_img[0].url" class="img" :style="search_button_height" mode="heightFix"></image>
</template>
<template v-else>
<view class="text-size-xs">
<iconfont :name="!isEmpty(form.search_botton_icon) ? 'icon-' + form.search_botton_icon : ''" size="28rpx" propContainerDisplay="flex"></iconfont>
</view>
</template>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import componentChoiceLocation from '@/components/choice-location/choice-location';
const app = getApp();
import { background_computer, common_styles_computer, common_img_computer, gradient_computer, radius_computer, isEmpty, padding_computer, old_padding, old_radius, margin_computer } from '@/common/js/common/common.js';
export default {
components: {
componentChoiceLocation,
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propIsPageSettings: {
type: Boolean,
default: false,
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propSearchType: {
type: String,
default: 'search',
},
propBaseColor: {
type: String,
default: '',
},
propLocationMargin: {
type: String,
default: '',
},
propLocationContainerStyle: {
type: String,
default: '',
},
propLocationImgContainerStyle: {
type: String,
default: '',
},
propIconLocationSize: {
type: String,
default: '',
},
propIconArrowSize: {
type: String,
default: '',
},
},
data() {
return {
search_content: '',
form: {},
new_style: {},
style: '',
style_container: '',
style_img_container: '',
search_button_radius: '',
box_style: '',
keywords: '',
right_icon_style: '',
search_button_style: '',
search_button_img_style: '',
search_button_height: '',
button_padding: { padding: 0, padding_bottom: 3, padding_left: 12, padding_right: 12, padding_top: 3 },
button_margin: { margin: 0, margin_bottom: 2, margin_left: 0, margin_right: 2, margin_top: 2 },
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
isEmpty,
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
const { search_button_radius, common_style } = new_style;
this.setData({
form: new_form,
new_style: new_style,
search_button_img_style: this.get_search_button_img_style(new_style),
search_button_height: this.get_search_button_height(new_style),
// style: this.get_style(new_style), // 内部样式
style_container: this.propIsPageSettings ? '' : common_styles_computer(common_style), // 全局样式
style_img_container: this.propIsPageSettings ? '' : common_img_computer(common_style, this.propIndex),
search_button_radius: radius_computer(search_button_radius), // 按钮圆角
box_style: this.get_box_style(new_style, new_form), // 搜索框设置
search_box_style: `border: 2rpx solid ${new_style.search_border == '#fff' ? '#eee' : new_style.search_border};`,
search_button_style: this.get_search_button_style(new_form, new_style), // 搜索按钮显示
right_icon_style: `border-radius: 0px ${ new_style.search_border_radius?.radius_top_right * 2 || 0 }rpx ${ new_style.search_border_radius?.radius_bottom_right * 2 || 0 }rpx 0px;`,
});
},
get_search_button_style(form, new_style) {
const { search_botton_color_list = [], search_botton_direction, search_botton_background_img_style, search_button_radius = old_radius, search_botton_background_img, search_botton_margin = this.button_margin, search_botton_border_show = '0', search_botton_border_size = old_padding, search_botton_border_style = 'solid', search_botton_border_color = '' } = new_style;
let style = radius_computer(search_button_radius);
if (form.search_type != 'img') {
const data = {
color_list: search_botton_color_list,
direction: search_botton_direction,
background_img: search_botton_background_img,
background_img_style: search_botton_background_img_style,
}
style += gradient_computer(data) + margin_computer(search_botton_margin) + `color: ${ new_style.button_inner_color };`;
}
let border = ``;
if (search_botton_border_show == '1') {
border += `border-width: ${search_botton_border_size.padding_top * 2}rpx ${search_botton_border_size.padding_right * 2}rpx ${search_botton_border_size.padding_bottom * 2}rpx ${search_botton_border_size.padding_left * 2}rpx;border-style: ${ search_botton_border_style };border-color: ${search_botton_border_color};`
}
const height = 32 - search_botton_margin.margin_top - search_botton_margin.margin_bottom - search_botton_border_size.padding_top - search_botton_border_size.padding_bottom;
return style + border + `height: ${height > 0 ? height * 2 : 0}rpx;line-height: ${height > 0 ? height * 2 : 0}rpx;`;
},
get_search_button_height(new_style) {
const { search_botton_border_size = old_padding, search_botton_padding = this.button_padding } = new_style;
const height = 32 - search_botton_border_size.padding_top - search_botton_border_size.padding_bottom - search_botton_padding.padding_top - search_botton_padding.padding_bottom;
return `height: ${height > 0 ? height * 2 : 0}rpx !important;line-height: ${height > 0 ? height * 2 : 0}rpx;`;
},
// 搜索按钮圆角
get_search_button_img_style(new_style) {
const { search_botton_background_img_style, search_botton_background_img } = new_style;
const data = {
background_img: search_botton_background_img,
background_img_style: search_botton_background_img_style,
}
return background_computer(data) + padding_computer(new_style?.search_botton_padding || this.button_padding) + 'box-sizing: border-box;';
},
// get_style(new_style) {
// let common_styles = '';
// if (new_style.text_style == 'italic') {
// common_styles += `font-style: italic`;
// } else if (new_style.text_style == '500') {
// common_styles += `font-weight: 500`;
// }
// return common_styles;
// },
get_box_style(new_style, form) {
let style = `background: ${ new_style?.search_bg_color || '' };border: 2rpx solid ${new_style.search_border}; ${radius_computer(new_style.search_border_radius)};box-sizing: border-box;`;
if (form.positioning_name_float == '1' && this.propSearchType == 'header') {
style += `padding-left: ${ (new_style.search_padding_left? new_style.search_padding_left : 15) * 2 }rpx;`;
} else if (form.is_center != '1') {
style += `padding-left: ${ (new_style.search_padding_left ? new_style.search_padding_left : 15) * 2 }rpx;`;
}
return style;
},
search_tap() {
app.globalData.url_open('/pages/goods-search-start/goods-search-start?keywords=' + this.keywords);
},
serch_event() {
app.globalData.url_open('/pages/goods-search-start/goods-search-start?keywords=' + this.keywords);
},
serch_button_event() {
if (!isEmpty(this.keywords)) {
app.globalData.url_open('pages/goods-search/goods-search?keywords=' + this.keywords);
}
app.globalData.url_open('/pages/goods-search-start/goods-search-start?keywords=' + this.keywords);
},
search_icon_tap() {
if (isEmpty(this.form.icon_link)) {
this.search_tap();
return;
}
app.globalData.url_open(this.form.icon_link.page);
},
search_right_icon_tap() {
if (isEmpty(this.form.right_icon_link)) {
this.search_tap();
return;
}
app.globalData.url_open(this.form.right_icon_link.page);
},
slideChange(e) {
let actived_index = e.detail.current;
this.setData({
keywords: this.form.hot_word_list[actived_index].value,
})
},
// 位置回调
onBack(e) {
this.$emit('onBack', e);
},
},
};
</script>
<style lang="scss" scoped>
.search {
.box {
height: 64rpx;
// padding: 12rpx 30rpx;
}
.swiper_style {
height: 64rpx;
width: 100%;
}
.img-box {
height: 100%;
.img {
height: 36rpx;
display: block;
}
}
.search-outer-botton {
height: 64rpx;
top: 0;
right: 0;
.search-botton {
height: 64rpx;
.img {
height: 64rpx;
min-width: 6rpx;
max-width: 20rpx;
}
}
}
.right_icon_height {
position: relative;
height: 56rpx !important;
}
.search-icon {
position: relative;
}
.search-icon-before {
position: absolute;
z-index: 10; // 确保悬浮在内容上层
top: -20rpx;
right: -20rpx;
bottom: -20rpx;
left: -20rpx
}
.tips-float {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
}
</style>

643
components/diy/seckill.vue Normal file
View File

@@ -0,0 +1,643 @@
<template>
<view v-if="!isEmpty(list) || !isEmpty(sckill_list)" :style="style_container">
<view :style="style_img_container">
<view class="flex-col gap-10">
<view v-if="form.head_state == '1'" class="oh" :style="seckill_head_style">
<view class="seckill-head flex-row align-c jc-sb" :style="seckill_head_img_style">
<view :class="['flex-row align-c', { 'gap-10': form.theme != '1', 'jc-sb wh-auto': form.theme == '2' }]">
<view class="seckill-title">
<imageEmpty v-if="form.title_type == 'image'" :propImageSrc="form.title_src[0]" propImgFit="heightFix" propErrorStyle="width:42rpx; height: 20rpx;"></imageEmpty>
<text v-else :style="{ color: new_style.title_color, 'font-size': new_style.title_size * 2 + 'rpx', 'line-height': '42rpx', 'font-weight': 600 }">{{ form.title_text }}</text>
</view>
<view v-if="form.theme == '1'" class="padding-horizontal-sm cr-white">|</view>
<view v-if="intervalId != undefined" class="flex-row align-c gap-4">
<text class="text-size-xss" :style="{ color: new_style.end_text_color }">{{ seckill_time.time_first_text }}</text>
<view class="flex-row gap-3 jc-c align-c" :style="form.theme == '4' ? time_bg + 'padding: 6rpx 8rpx;border-radius: 22rpx;' : ''">
<image v-if="form.theme == '4' && form.theme_4_static_img.length > 0" class="seckill-head-icon radius-xs" :src="form.theme_4_static_img[0].url" />
<view v-for="(item, index) in time_config" :key="item.key" class="flex-row gap-3 jc-c align-c">
<template v-if="form.theme == '4'">
<view class="text-size-xs flex-row jc-c align-c" :style="'min-width:50rpx;color:' + new_style.countdown_color">{{ item.value }}</view>
<text v-if="[0, 1].includes(index)" class="colon" :style="{ color: new_style.countdown_color }">:</text>
</template>
<template v-else>
<view class="time-config text-size-xs flex-row jc-c align-c" :style="time_bg + 'min-width:50rpx;color:' + new_style.countdown_color">{{ item.value }}</view>
<text v-if="[0, 1].includes(index)" class="colon" :style="icon_time_check">:</text>
</template>
</view>
</view>
</view>
<view v-else class="flex-row align-c gap-4">
<text class="text-size-xss" :style="{ color: new_style.end_text_color }">已结束</text>
</view>
</view>
<view v-if="form.button_status == '1'" class="flex-row align-c" :style="{ color: new_style.head_button_color }" :data-value="'/pages/plugins/seckill/index/index'" @tap="url_event">
<text :style="{ 'font-size': new_style.head_button_size * 2 + 'rpx' }">{{ form.button_text }}</text>
<iconfont name="icon-arrow-right" :color="new_style.head_button_color" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
<view class="oh" :style="shop_container">
<view class="oh" :style="shop_img_container">
<template v-if="form.shop_style_type != '3'">
<view class="flex-row flex-wrap wh-auto ht-auto" :style="{ gap: content_outer_spacing }">
<view v-for="(item, index) in sckill_list" :key="index" :style="layout_style + layout_type_style + content_radius" :data-value="item.goods_url" @tap="url_event">
<view :class="layout_type" :style="layout_img_style">
<template v-if="!isEmpty(item)">
<view class="oh pr">
<view v-if="!isEmpty(item.new_cover)" :style="img_size">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="content_img_radius" propErrorStyle="width:100rpx; height: 100rpx;"></imageEmpty>
</view>
<view v-else :style="img_size">
<imageEmpty :propImageSrc="item.images" :propStyle="content_img_radius" propErrorStyle="width:100rpx; height: 100rpx;"></imageEmpty>
</view>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
</template>
<view v-if="is_show('title') || is_show('simple_desc') || is_show('price') || is_show('original_price') || form.is_shop_show == '1'" class="flex-col gap-10 wh-auto flex-1 jc-sb oh" :style="content_style">
<view class="flex-col gap-10 wh-auto">
<!-- 标题 -->
<view v-if="is_show('title') || is_show('simple_desc')" class="flex-col" :style="{'gap': new_style.title_simple_desc_spacing * 2 + 'rpx' }">
<view v-if="is_show('title')" :style="title_style" class="text-line-2">{{ item.title }}</view>
<view v-if="is_show('simple_desc')" class="text-line-1" :style="simple_desc">{{ item.simple_desc }}</view>
</view>
<!-- 进度条 -->
<!-- <view v-if="form.shop_style_type == '1'" class="flex-row align-c gap-6">
<view class="re flex-1">
<view class="slide-bottom" :style="`background: ${new_style.progress_bg_color}`"></view>
<view class="slide-top" :style="` width: 51%; ${slide_active_color}`">
<view class="slide-top-icon round" :style="`background: ${new_style.progress_button_color}`"><icon name="a-miaosha" :color="new_style.progress_button_icon_color" size="9"></icon></view>
</view>
</view>
<text class="text-size-xss" :style="`color: ${new_style.progress_text_color}`">已抢51%</text>
</view> -->
</view>
<view class="flex-row align-e gap-10 jc-sb">
<view class="flex-col gap-5">
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num" :style="{ color: new_style.shop_price_color }">
<text v-if="form.shop_style_type == '1'" class="text-size-xss pr-4">{{ form.seckill_pirce_title ? form.seckill_pirce_title : ''}}</text>
<text :style="price_symbol">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" :style="price_unit">{{ item.show_price_unit }}</text>
</view>
<view v-if="is_show('original_price') && !isEmpty(item.min_original_price)" class="size-11 flex" :style="original_price">
<text class="original-price text-line-1 flex-1">
{{ item.show_original_price_symbol }}{{ item.min_original_price }}
<template v-if="is_show('original_price_unit')">
{{ item.show_original_price_unit }}
</template>
</text>
</view>
</view>
<view v-if="form.is_shop_show == '1'">
<template v-if="form.shop_type == 'text'">
<view class="plr-11 padding-vertical-xs round" :style="button_style + 'color:' + new_style.shop_button_text_color">{{ form.shop_button_text }}</view>
</template>
<view v-else class="round padding-horizontal-sm ptb-5" :styles="button_gradient">
<iconfont :name="'icon-' + (!isEmpty(form.shop_button_icon_class) ? form.shop_button_icon_class : 'cart')" :color="new_style.shop_icon_color" :size="new_style.shop_icon_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<template v-else>
<swiper circular="false" :autoplay="new_style.is_roll == '1'" :next-margin="new_style.rolling_fashion == 'translation' ? '-' + content_outer_spacing_magin : '0rpx'" :interval="new_style.interval_time * 1000" :duration="500" :display-multiple-items="slides_per_group" :style="{ height: new_style.content_outer_height * 2 + 'rpx' }">
<swiper-item v-for="(item1, index1) in list" :key="index1">
<view class="wh-auto ht-auto" :class="{ 'flex-row': new_style.rolling_fashion != 'translation' }" :style="new_style.rolling_fashion != 'translation' ? 'gap:' + content_outer_spacing_magin : ''">
<view v-for="(item, index) in item1.split_list" :key="index" :style="layout_style + content_radius + (new_style.rolling_fashion != 'translation' ? layout_type_style : 'margin-right:' + content_outer_spacing_magin + ';height: 100%;whith: 100%')" :data-value="item.goods_url" @tap="url_event">
<view :class="layout_type" :style="layout_img_style">
<template v-if="!isEmpty(item)">
<view class="oh pr wh-auto ht-auto">
<view v-if="!isEmpty(item.new_cover)" class="wh-auto ht-auto">
<imageEmpty :propImageSrc="item.new_cover[0]" :propStyle="content_img_radius" propErrorStyle="width:100rpx; height: 100rpx;"></imageEmpty>
</view>
<view v-else class="wh-auto ht-auto">
<imageEmpty :propImageSrc="item.images" :propStyle="content_img_radius" propErrorStyle="width:100rpx; height: 100rpx;"></imageEmpty>
</view>
<!-- 角标 -->
<subscriptIndex :propValue="propValue"></subscriptIndex>
</view>
</template>
<view v-if="is_show('title') || is_show('simple_desc') || is_show('price') || is_show('original_price') || form.is_shop_show == '1'" class="flex-col gap-10 wh-auto flex-1 jc-sb" :style="content_style">
<view class="flex-col gap-10 wh-auto">
<!-- 标题 -->
<view v-if="is_show('title') || is_show('simple_desc')" class="flex-col" :style="{'gap': new_style.title_simple_desc_spacing * 2 + 'rpx' }">
<view v-if="is_show('title')" :style="title_style" class="text-line-2">{{ item.title }}</view>
<view v-if="is_show('simple_desc')" class="text-line-1" :style="simple_desc">{{ item.simple_desc }}</view>
</view>
<!-- 进度条 -->
<!-- <view v-if="form.shop_style_type == '1'" class="flex-row align-c gap-6">
<view class="re flex-1">
<view class="slide-bottom" :style="{ 'background': new_style.progress_bg_color }"></view>
<view class="slide-top" :style="'width: 51%;' + slide_active_color ">
<view class="slide-top-icon round" :style="{ 'background': new_style.progress_button_color}"><icon name="a-miaosha" :color="new_style.progress_button_icon_color" size="9"></icon></view>
</view>
</view>
<text class="text-size-xss" :style="{ 'color': new_style.progress_text_color }">已抢51%</text>
</view> -->
</view>
<view class="flex-row align-e gap-10 jc-sb">
<view class="flex-col gap-5">
<view v-if="is_show('price') && !isEmpty(item.min_price)" class="num" :style="{ color: new_style.shop_price_color }">
<text v-if="form.shop_style_type == '1'" class="text-size-xss pr-4">{{ form.seckill_pirce_title ? form.seckill_pirce_title : ''}}</text>
<text :style="price_symbol">{{ item.show_price_symbol }}</text>
<text :style="price_style">{{ item.min_price }}</text>
<text v-if="is_show('price_unit')" :style="price_unit">{{ item.show_price_unit }}</text>
</view>
<view v-if="is_show('original_price') && !isEmpty(item.min_original_price)" class="size-11 flex" :style="original_price">
<text class="original-price text-line-1 flex-1">
{{ item.show_original_price_symbol }}{{ item.min_original_price }}
<template v-if="is_show('original_price_unit')">
{{ item.show_original_price_unit }}
</template>
</text>
</view>
</view>
<view v-if="form.is_shop_show == '1'">
<template v-if="form.shop_type == 'text'">
<view class="plr-11 padding-vertical-xs round" :style="button_style + 'color:' + new_style.shop_button_text_color">{{ form.shop_button_text }}</view>
</template>
<view v-else class="round padding-horizontal-sm ptb-5" :styles="button_gradient">
<iconfont :name="'icon-' + (!isEmpty(form.shop_button_icon_class) ? form.shop_button_icon_class : 'cart')" :color="new_style.shop_icon_color" :size="new_style.shop_icon_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { background_computer, common_styles_computer, common_img_computer, gradient_computer, gradient_handle, padding_computer, radius_computer, isEmpty, margin_computer, box_shadow_computer, border_computer, old_margin, old_border_and_box_shadow } from '@/common/js/common/common.js';
import imageEmpty from '@/components/diy/modules/image-empty.vue';
import subscriptIndex from '@/components/diy/modules/subscript/index.vue';
var system = app.globalData.get_system_info(null, null, true);
var sys_width = app.globalData.window_width_handle(system.windowWidth);
export default {
components: {
imageEmpty,
subscriptIndex
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
form: {},
new_style: {},
time_bg: '',
slide_active_color: '',
seckill_head_style: '',
style_container: '',
style_img_container: '',
time_config: [
{ key: 'hour', value: '00' },
{ key: 'minute', value: '00' },
{ key: 'second', value: '00' },
],
seckill_time: {},
// 商品间距
content_outer_spacing: '',
content_outer_spacing_magin: '',
// 圆角设置
content_radius: '',
// 内边距设置
content_padding: '',
// 内容区域的样式
content_style: '',
// 不同风格下的样式
img_size: '',
layout_type: '',
layout_type_style: '',
layout_img_style: '',
layout_style: '',
//图片圆角设置
content_img_radius: '',
//角标设置
corner_marker: '',
// 定时器
intervalId: null,
// 数据存储
list: [],
// 一屏显示的数量
slides_per_group: '',
// 内容样式
title_style: '',
price_style: '',
button_style: '',
simple_desc: '',
price_symbol: '',
price_unit: '',
original_price: '',
shop_container: '',
shop_img_container: '',
// 商品列表
sckill_list: [],
};
},
computed: {
icon_time_check() {
return `${this.time_bg};line-height: 1;background-clip: text;-webkit-background-clip: text;-webkit-text-fill-color: transparent;`;
},
// 按钮渐变色处理
button_gradient() {
return gradient_handle(this.new_style.shop_button_color, '180deg');
},
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
beforeDestroy() {
// 如果有定时任务执行,在离开的时候清空掉定时任务
if (!isEmpty(this.intervalId)) {
clearInterval(this.intervalId);
}
},
methods: {
isEmpty,
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
const data = new_form.data;
let new_list = [];
if (data && !isEmpty(data.current)) {
if (!isEmpty(data.current.goods)) {
new_list = data.current.goods;
}
const { status, time_first_text } = data.current.time;
this.setData({
seckill_time: {
time_end_number: Number(data.current.time_end_number + '000'),
time_start_number: Number(data.current.time_start_number + '000'),
status: status,
time_first_text: time_first_text,
},
});
// 先执行一次倒计时,后续的等待倒计时执行
setTimeout(() => {
this.updateCountdown();
}, 0);
this.setData({
intervalId: setInterval(this.updateCountdown, 1000),
});
}
// 默认数据
const product_style_list = [
{ name: '单列', value: '1', width: 110, height: 120 },
{ name: '双列', value: '2', width: 180, height: 180 },
{ name: '横向滑动', value: '3', width: 0, height: 0 },
];
const scale = sys_width / 390;
let img_style = ``;
if (['1'].includes(new_form.shop_style_type)) {
// 图片宽度
if (typeof new_style.content_img_width == 'number') {
img_style += `width: ${ new_style.content_img_width * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_form.shop_style_type);
if (list.length > 0) {
img_style += `width: ${ list[0].width * scale }px;`;
} else {
img_style += 'width: auto;';
}
}
}
if (!['3'].includes(new_form.shop_style_type)) {
// 图片宽度
if (typeof new_style.content_img_height == 'number') {
img_style += `height: ${ new_style.content_img_height * scale }px;`;
} else {
const list = product_style_list.filter(item => item.value == new_form.shop_style_type);
if (list.length > 0) {
img_style += `height: ${ list[0].height * scale }px;`;
} else {
img_style += 'height: auto;';
}
}
}
const shop_style = new_style?.shop_style || old_border_and_box_shadow;
this.setData({
form: this.propValue.content,
new_style: this.propValue.style,
time_bg: this.get_time_bg(new_style),
slide_active_color: this.get_slide_active_color(new_style),
seckill_head_style: this.get_seckill_head_style(new_style, '1'),
seckill_head_img_style: this.get_seckill_head_style(new_style, '2'),
style_container: common_styles_computer(new_style.common_style) + 'box-sizing: border-box;',
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
content_outer_spacing: new_style.content_outer_spacing + 'px',
content_outer_spacing_magin: new_style.content_outer_spacing * 2 + 'rpx',
content_radius: radius_computer(new_style.shop_radius),
content_padding: padding_computer(new_style.shop_padding) + 'box-sizing: border-box;',
content_style: this.get_content_style(new_style, new_form),
layout_type: new_form.shop_style_type == '1' ? 'flex-row wh-auto ht-auto oh' : 'flex-col wh-auto ht-auto oh',
layout_img_style: this.get_layout_img_style(new_style, new_form),
layout_type_style: this.get_layout_type_style(new_style, new_form),
layout_style: gradient_handle(new_style.shop_color_list, new_style.shop_direction) + margin_computer(new_style?.shop_margin || old_margin) + border_computer(shop_style) + box_shadow_computer(shop_style),
content_img_radius: radius_computer(new_style.shop_img_radius),
corner_marker: this.get_corner_marker(new_style),
slides_per_group: new_style.rolling_fashion == 'translation' ? new_form.carousel_col : 1,
// 内容样式设置
title_style: this.trends_config(new_style, 'title', 'title'),
price_style: this.trends_config(new_style, 'price'),
button_style: this.trends_config(new_style, 'button', 'gradient'),
simple_desc: this.trends_config(new_style, 'simple_desc', 'desc'),
price_symbol: !isEmpty(new_style.shop_price_symbol_color) ? this.trends_config(new_style, 'price_symbol') : 'font-size: 18rpx;color: #EA3323;' ,
price_unit: !isEmpty(new_style.shop_price_unit_color) ? this.trends_config(new_style, 'price_unit') : 'font-size: 18rpx;color: #EA3323;',
original_price: this.trends_config(new_style, 'original_price'),
list: this.get_shop_content_list(new_list, new_form, new_style),
shop_container: this.get_shop_container(new_style),
shop_img_container: this.get_shop_img_container(new_style),
sckill_list: new_list,
img_size: img_style,
});
},
// 商品内容区域显示
get_shop_container(new_style){
const { shop_content_color_list = [], shop_content_direction = '', shop_content_radius = old_radius, shop_content_margin = old_margin, shop_content = old_border_and_box_shadow } = new_style;
// 渐变
const gradient = { color_list: shop_content_color_list, direction: shop_content_direction };
return gradient_computer(gradient) + radius_computer(shop_content_radius) + margin_computer(shop_content_margin) + border_computer(shop_content) + box_shadow_computer(shop_content);
},
get_shop_img_container (new_style) {
const { shop_content_background_img_style = '2', shop_content_background_img = [], shop_content_padding = old_padding } = new_style;
// 背景图
const back = { background_img: shop_content_background_img, background_img_style: shop_content_background_img_style };
return padding_computer(shop_content_padding) + background_computer(back);
},
get_shop_content_list(list, form, new_style) {
// 深拷贝一下,确保不会出现问题
const cloneList = JSON.parse(JSON.stringify(list));
if (new_style.rolling_fashion != 'translation') {
// 如果是分页滑动情况下,根据选择的行数和每行显示的个数来区分具体是显示多少个
if (cloneList.length > 0) {
// 每页显示的数量
const num = form.carousel_col;
// 存储数据显示
let nav_list = [];
// 拆分的数量
const split_num = Math.ceil(cloneList.length / num);
for (let i = 0; i < split_num; i++) {
nav_list.push({
split_list: cloneList.slice(i * num, (i + 1) * num),
});
}
return nav_list;
} else {
// 否则的话,就返回全部的信息
return [
{
split_list: cloneList,
},
];
}
} else {
// 存储数据显示
let nav_list = [];
cloneList.forEach((item) => {
nav_list.push({
split_list: [item],
});
});
return nav_list;
}
},
get_time_bg(new_style) {
const { countdown_bg_color_list, countdown_direction } = new_style;
// 渐变
const gradient = { color_list: countdown_bg_color_list, direction: countdown_direction };
return gradient_computer(gradient);
},
get_slide_active_color(new_style) {
const { progress_actived_color_list, progress_actived_direction } = new_style;
// 渐变
const gradient = { color_list: progress_actived_color_list, direction: progress_actived_direction };
return gradient_computer(gradient);
},
get_seckill_head_style(new_style, num) {
const { header_background_img, header_background_img_style, header_background_color_list, header_background_direction, seckill_head_padding, seckill_head_radius, seckill_head_margin = old_margin, seckill_head_style = old_border_and_box_shadow } = new_style;
// 渐变
const gradient = { color_list: header_background_color_list, direction: header_background_direction };
// 背景图
const back = { background_img: header_background_img, background_img_style: header_background_img_style };
if (num == '1') {
return gradient_computer(gradient) + radius_computer(seckill_head_radius) + margin_computer(seckill_head_margin) + border_computer(seckill_head_style) + box_shadow_computer(seckill_head_style);
} else {
// 秒杀头部内间距设置, 没有的时候默认15px
const padding = !isEmpty(seckill_head_padding) ? seckill_head_padding : { padding: 0, padding_top: 15, padding_bottom: 15, padding_left: 13, padding_right: 13};
return background_computer(back) + padding_computer(padding) + 'box-sizing: border-box;';
}
},
updateCountdown() {
let time_end_number = this.seckill_time.time_end_number;
if (this.seckill_time.status === 0) {
time_end_number = this.seckill_time.time_start_number;
}
// 先获取秒杀结束时间和当前时间的差值
const distance = time_end_number - new Date().getTime();
// 如果倒计时结束,显示结束信息
if (distance <= 1000) {
clearInterval(this.intervalId);
// 如果是待开始状态,则显示开始倒计时,并且在结束的时候根据结束时候再执行一个定时器
if (this.seckill_time.status === 0) {
this.setData({
seckill_time: {
time_end_number: this.seckill_time.time_end_number,
time_start_number: this.seckill_time.time_start_number,
status: 1,
time_first_text: '距结束',
},
});
// 先执行一次倒计时,后续的等待倒计时执行
setTimeout(() => {
this.updateCountdown();
}, 0);
this.setData({
intervalId: setInterval(this.updateCountdown, 1000),
});
}
return;
}
// 计算时间
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
this.time_config.forEach((item) => {
if (item.key == 'hour') {
item.value = hours < 10 ? '0' + hours : hours.toString();
} else if (item.key == 'minute') {
item.value = minutes < 10 ? '0' + minutes : minutes.toString();
} else if (item.key == 'second') {
item.value = seconds < 10 ? '0' + seconds : seconds.toString();
}
});
},
get_content_style(new_style, form) {
const spacing_value = new_style.content_spacing;
let spacing = '';
if (form.shop_style_type == '1') {
spacing = `margin-left: ${spacing_value * 2}rpx;`;
} else {
spacing = padding_computer(new_style.shop_padding) + 'box-sizing: border-box;';
}
return `${spacing}`;
},
get_layout_type_style(new_style, form) {
return ['1', '2'].includes(form.shop_style_type) ? `height: 100%;width: ${this.get_multicolumn_columns_width(new_style, form)};min-width: ${this.get_multicolumn_columns_width(new_style, form)};` : `height: ${new_style.content_outer_height * 2}rpx;width: ${this.get_multicolumn_columns_width(new_style, form)};min-width: ${this.get_multicolumn_columns_width(new_style, form)};`;
},
get_layout_img_style(new_style, form) {
const padding = ['1'].includes(form.shop_style_type) ? padding_computer(new_style.shop_padding) + 'box-sizing: border-box;' : '';
const data = {
background_img_style: new_style.shop_background_img_style,
background_img: new_style.shop_background_img,
}
return `${padding} ${ background_computer(data) }`;
},
// 根据传递的参数,从对象中取值
trends_config(new_style, key, type) {
return this.style_config(new_style[`shop_${key}_typeface`], new_style[`shop_${key}_size`], new_style[`shop_${key}_color`], type, new_style);
},
// 根据传递的值,显示不同的内容
style_config(typeface, size, color, type, new_style) {
let style = `font-weight:${typeface}; font-size: ${size * 2}rpx;`;
if (type == 'gradient') {
style += gradient_handle(new_style.shop_button_color, '180deg');
} else if (type == 'title') {
style += `line-height: ${size > 0 ? (size + 3) * 2 : 0}rpx;height: ${size > 0 ? (size + 3) * 4 : 0}rpx;color: ${color};`;
} else if (type == 'desc') {
style += `line-height: ${size * 2}rpx;height: ${size * 2}rpx;color: ${color};`;
} else {
style += `color: ${color};`;
}
return style;
},
get_multicolumn_columns_width(new_style, form) {
let model_number = form.carousel_col;
if (form.shop_style_type == '1') {
model_number = 1;
} else if (form.shop_style_type == '2') {
model_number = 2;
}
// 计算间隔的空间。(gap * gap数量) / 模块数量
let gap = (new_style.content_outer_spacing * (model_number - 1)) / model_number;
return `calc(${100 / model_number}% - ${gap}px)`;
},
is_show(index) {
return this.form.is_show.includes(index);
},
get_corner_marker(new_style) {
const { seckill_subscript_location, shop_img_radius, seckill_subscript_bg_color, seckill_subscript_text_color } = new_style;
let location = `background: ${seckill_subscript_bg_color};color: ${seckill_subscript_text_color};`;
// 圆角根据图片的圆角来计算 对角线是同样的圆角
if (seckill_subscript_location == 'top-left') {
location += `top: 0;left: 0;border-radius: ${shop_img_radius.radius_top_left * 2}rpx 0 ${shop_img_radius.radius_top_left * 2}rpx 0;`;
} else if (seckill_subscript_location == 'top-right') {
location += `top: 0;right: 0;border-radius: 0 ${shop_img_radius.radius_top_right * 2}rpx 0 ${shop_img_radius.radius_top_right * 2}rpx;`;
} else if (seckill_subscript_location == 'bottom-left') {
location += `bottom: 0;left: 0;border-radius: 0 ${shop_img_radius.radius_bottom_left * 2}rpx 0 ${shop_img_radius.radius_bottom_left * 2}rpx;`;
} else if (seckill_subscript_location == 'bottom-right') {
location += `bottom: 0;right: 0;border-radius: ${shop_img_radius.radius_bottom_right * 2}rpx 0 ${shop_img_radius.radius_bottom_right * 2}rpx 0;`;
}
return location;
},
// 跳转链接
url_event(link) {
app.globalData.url_event(link);
},
},
};
</script>
<style scoped lang="scss">
.seckill-head {
padding: 30rpx 26rpx;
width: 100%;
height: 102rpx;
border-radius: 16rpx 16rpx 0 0;
box-sizing: border-box;
.seckill-title {
width: auto;
height: 42rpx;
}
.time-config {
padding: 2rpx 10rpx;
box-sizing: border-box;
line-height: 34rpx;
border-radius: 8rpx;
}
}
.seckill-head-icon {
width: 32rpx;
height: 32rpx;
}
.colon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 33rpx;
top: -2rpx;
}
.slide-bottom {
height: 20rpx;
border-radius: 10rpx;
background: red;
}
.slide-top {
position: absolute;
height: 20rpx;
top: 0;
left: 0;
border-radius: 10rpx;
.slide-top-icon {
position: absolute;
top: -6rpx;
right: 0;
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.original-price {
text-decoration-line: line-through;
}
.size-11 {
font-size: 22rpx;
}
</style>

View File

@@ -0,0 +1,231 @@
<template>
<view class="ou pr" :style="style_container + swiper_bg_style">
<view class="pa top-0 wh-auto ht-auto" :style="swiper_bg_img_style"></view>
<view class="ou wh-auto" :style="style_img_container + (!isEmpty(swiper_bg_img_style) ? swiper_bg_img_style_null : '')">
<componentDiyTabs :propKey="propKey" :propContentPadding="propContentPadding" :propValue="propValue" :propTop="propTop" :propIsRotatingBackground="is_rotating_background" :propBgStyle="swiper_bg_style" :propBgImgStyle="swiper_bg_img_style" :propStickyTop="propStickyTop" :propIsImmersionModel="propIsImmersionModel" :propNewIsTabsSafeDistance="new_is_tabs_safe_distance" :propNavIsTop="propNavIsTop" :propTabsIsTop="propTabsIsTop" :propIsCommon="false" :propsTabsContainer="tabs_container" :propsTabsImgContainer="tabs_img_container" :propSpacingCommonStyle="spacing_common_style" :propTabsSlidingFixedBg="tabs_sliding_fixed_bg" @onComputerHeight="tabs_height_event" @onTabsTap="tabs_click_event"></componentDiyTabs>
<view :style="carousel_margin_top">
<view :style="carousel_container">
<view :style="carousel_img_container">
<componentDiycarousel :propValue="propValue" :propIsCommon="false" @onVideoPlay="video_play" @slideChange="slideChange"></componentDiycarousel>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, padding_computer, isEmpty, gradient_computer, margin_computer, background_computer, radius_computer, old_margin, old_padding, old_radius, old_border_and_box_shadow, border_computer, box_shadow_computer, } from '@/common/js/common/common.js';
import componentDiyTabs from '@/components/diy/tabs';
import componentDiycarousel from '@/components/diy/carousel';
export default {
components: {
componentDiyTabs,
componentDiycarousel,
},
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propTop: {
type: [String, Number],
default: 0,
},
propStickyTop: {
type: Number,
default: 0,
},
// 是否导航栏置顶
propNavIsTop: {
type: Boolean,
default: true,
},
// 是否选项卡置顶
propTabsIsTop: {
type: Boolean,
default: false,
},
propKey: {
type: [String, Number],
default: '',
},
propContentPadding: {
type: String,
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
// 选项卡是否使用安全距离
propIsImmersionModel: {
type: Boolean,
default: false
}
},
data() {
return {
style_container: '',
style_img_container: '',
style_margin_container: '',
spacing_common_style: {
padding: 0,
padding_top: 0,
padding_bottom: 0,
padding_left: 0,
padding_right: 0,
margin: 0,
margin_top: 0,
margin_bottom: 0,
margin_left: 0,
margin_right: 0,
},
tabs_padding_style: '',
// 选项卡内容
tabs_container: '',
tabs_img_container: '',
tabs_sliding_fixed_bg: '',
// 轮播图内容
carousel_margin_top: '',
carousel_container: '',
carousel_img_container: '',
// top_up: '0',
actived_index: 0,
// 轮播图背景
swiper_bg_style: '',
swiper_bg_img_style: '',
swiper_bg_img_style_null: `background-image: url('')`,
is_rotating_background: false,
new_is_tabs_safe_distance: false,
};
},
created() {
this.init();
},
// 属性值改变监听
watch: {
// 数据
propTabsIsTop(value, old_value) {
this.init();
},
propKey(val) {
// 初始化
this.init();
},
},
methods: {
isEmpty,
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
const { tabs_bg_color_list = [], tabs_bg_direction = '', tabs_bg_background_img_style = '', tabs_bg_background_img = [], tabs_radius = old_radius, tabs_padding = old_padding, tabs_margin = old_margin, tabs_content = old_border_and_box_shadow, carousel_content_color_list = [], carousel_content_direction = '', carousel_content_background_img_style = '', carousel_content_background_img = [], carousel_content_margin = old_margin, carousel_content_padding = old_padding, carousel_content_radius = old_radius, carousel_content = old_border_and_box_shadow } = new_style;
// 选项卡背景设置
const tabs_data = {
color_list: tabs_bg_color_list,
direction: tabs_bg_direction,
background_img_style: tabs_bg_background_img_style,
background_img: tabs_bg_background_img,
}
// 商品区域背景设置
const carousel_content_data = {
color_list: carousel_content_color_list,
direction: carousel_content_direction,
background_img_style: carousel_content_background_img_style,
background_img: carousel_content_background_img,
}
// 头部的高度
const newPropTop = app.globalData.rpx_to_px(this.propTop);
// 选项卡的外边距
const new_tabs_top = app.globalData.rpx_to_px(tabs_margin?.margin_top || 0);
// 选项卡的实际外边距
const tabs_margin_top = new_content.is_tabs_safe_distance == '1' ? newPropTop + this.propStickyTop : 0;
// 选项卡的内边距处理
const new_padding_top = new_style.common_style.padding_top - newPropTop;
this.setData({
// style_container: `${common_styles_computer(common_style)};gap:${new_style.data_spacing * 2}rpx`,
style_container: `${common_styles_computer(new_style.common_style)};`,
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
carousel_margin_top: 'margin-top:' + new_style.data_spacing * 2 + 'rpx',
new_is_tabs_safe_distance: new_content.is_tabs_safe_distance == '1',
// 是否开启轮播图背景设置
is_rotating_background: new_content.rotating_background == '1',
tabs_sliding_fixed_bg: gradient_computer(tabs_data),
tabs_container: gradient_computer(tabs_data) + radius_computer(tabs_radius) + margin_computer(tabs_margin) + border_computer(tabs_content) + box_shadow_computer(tabs_content) + `overflow: hidden;margin-top: ${ new_tabs_top - tabs_margin_top }px;`,
tabs_img_container: background_computer(tabs_data) + padding_computer(tabs_padding) + `box-sizing: border-box;overflow: hidden;padding-top: ${ (tabs_padding?.padding_top || 0) + tabs_margin_top }px;`,
carousel_container: gradient_computer(carousel_content_data) + margin_computer(carousel_content_margin) + radius_computer(carousel_content_radius) + border_computer(carousel_content) + box_shadow_computer(carousel_content) + 'overflow: hidden;',
carousel_img_container: background_computer(carousel_content_data) + padding_computer(carousel_content_padding) + 'box-sizing: border-box;overflow: hidden;',
spacing_common_style: {
padding: 0,
padding_top: (this.propIsImmersionModel && new_content.is_tabs_safe_distance == '1' ? new_style.common_style.padding_top + this.propStickyTop : new_padding_top > 0 ? new_padding_top : 0),
padding_bottom: 0,
padding_left: new_style.common_style.padding_left,
padding_right: new_style.common_style.padding_right,
margin: 0,
margin_top: new_style.common_style.margin_top,
margin_left: new_style.common_style.margin_left,
margin_right: new_style.common_style.margin_right,
},
swiper_bg_style: this.get_swiper_bg_style(new_content, 0),
swiper_bg_img_style: this.get_swiper_bg_img_style(new_content, 0),
});
},
// tab点击
tabs_click_event(tabs_id, is_micro_page) {
this.$emit('onTabsTap', tabs_id, is_micro_page);
},
// tab高度
tabs_height_event(height) {
this.$emit('onComputerHeight', height);
},
// 视频播放
video_play(url, popup_width, popup_height) {
this.$emit('onVideoPlay', url, popup_width, popup_height);
},
get_swiper_bg_style(form, actived_index) {
const style = form?.carousel_list?.[actived_index]?.style;
if (style && !isEmpty(style.color_list)) {
const color_list = style.color_list;
const list = color_list.filter((item) => !isEmpty(item.color));
if (list.length > 0) {
try {
return gradient_computer(style);
} catch (error) {
return '';
}
}
return '';
}
return '';
},
get_swiper_bg_img_style(form, actived_index) {
const { carousel_img, style = {} } = form?.carousel_list[actived_index] || {};
// 如果是自定义的图片 判断图片是否存在
if (!isEmpty(carousel_img) && style?.background_type == 'carousel') {
// 如果是使用轮播图,判断轮播图是否存在
const data = {
background_img: carousel_img,
background_img_style: style?.background_img_style || '2',
}
return background_computer(data) + (style.is_background_img_blur == '1' ? `filter: blur(14px);opacity: 0.6;` : '');
} else if (!isEmpty(style?.background_img)) {
return background_computer(style) + (style.is_background_img_blur == '1' ? `filter: blur(14px);opacity: 0.6;` : '');
}
return '';
},
slideChange(index) {
this.setData({
actived_index: index,
swiper_bg_style: this.get_swiper_bg_style(this.propValue.content || {}, index),
swiper_bg_img_style: this.get_swiper_bg_img_style(this.propValue.content || {}, index),
});
},
},
};
</script>
<style scoped lang="scss"></style>

270
components/diy/tabs.vue Normal file
View File

@@ -0,0 +1,270 @@
<template>
<!-- 选项卡 -->
<view class="tabs-container pr" :style="tabs_z_index">
<view :class="top_up == '1' ? 'tabs-top' : ''" :style="tabs_top_style + (top_up == '1' ? propContentPadding : '')">
<view :style="style_margin_container">
<view class="tabs-contents bs-bb pr" :style="style_container">
<view :class="top_up == '1' ? 'bs-bb' : 'wh-auto bs-bb'" :style="style_img_container">
<componentDiyModulesTabsView :propKey="propKey" :propValue="tabs_data" :propIsTabsIcon="true" :propTop="propTop" :propsTabsContainer="propsTabsContainer + (propIsRotatingBackground ? propBgStyle : '')" :propIsRotatingBackground="propIsRotatingBackground" :propBgImgStyle="propBgImgStyle" :propsTabsImgContainer="propsTabsImgContainer" :propStyle="propStyle" :propTabsSlidingFixedBg="tabs_sliding_fixed_bg" @onTabsTap="tabs_click_event" @tabsZindex="tabsZindex"></componentDiyModulesTabsView>
</view>
</view>
</view>
</view>
<view v-if="top_up == '1'" class="tabs-seat" :style="'height:' + (propIsCommon ? tabs_seat_height : tabs_carousel_seat_height) + 'px;'"></view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, padding_computer, margin_computer, gradient_computer, background_computer } from '@/common/js/common/common.js';
import componentDiyModulesTabsView from '@/components/diy/modules/tabs-view';
// 状态栏高度
var bar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0));
// #ifdef MP-TOUTIAO
bar_height = 0;
// #endif
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
// 置顶距离顶部高度
propTop: {
type: [String, Number],
default: '0',
},
propStickyTop: {
type: Number,
default: 0,
},
// 是否导航栏置顶
propNavIsTop: {
type: Boolean,
default: true,
},
// 是否选项卡置顶
propTabsIsTop: {
type: Boolean,
default: false,
},
propIsCommon: {
type: Boolean,
default: true,
},
propSpacingCommonStyle: {
type: Object,
default: () => ({}),
},
// 样式
propStyle: {
type: String,
default: '',
},
propKey: {
type: [String, Number],
default: '',
},
propsTabsContainer: {
type: String,
default: ''
},
propsTabsImgContainer: {
type: String,
default: '',
},
propContentPadding: {
type: String,
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
propIsImmersionModel: {
type: Boolean,
default: false
},
propNewIsTabsSafeDistance: {
type: Boolean,
default: true
},
propTabsSlidingFixedBg: {
type: String,
default: ''
},
propIsRotatingBackground: {
type: Boolean,
default: false
},
propBgStyle: {
type: String,
default: ''
},
propBgImgStyle: {
type: String,
default: ''
}
},
components: {
componentDiyModulesTabsView,
},
data() {
return {
style_container: '',
style_img_container: '',
tabs_sliding_fixed_bg: '',
content: '',
tabs_data: {},
// 是否滑动置顶
top_up: '0',
// 置顶时,选项卡高度
tabs_seat_height: 0,
// 置顶时,轮播选项卡高度
tabs_carousel_seat_height: 0,
// 置顶时,选项卡样式
tabs_top_style: '',
style_margin_container: '',
tabs_z_index: ''
};
},
created() {
this.init();
},
mounted() {
setTimeout(() => {
this.get_tabs_height();
}, 100);
},
// 属性值改变监听
watch: {
// 数据
propTabsIsTop(value, old_value) {
this.init();
setTimeout(() => {
this.get_tabs_height();
}, 100);
},
propTop(val) {
this.init();
},
propStickyTop(val) {
this.init();
},
propKey(val) {
// 初始化
this.init();
},
propSpacingCommonStyle(val) {
// 初始化
this.init();
},
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
let new_tabs_data = JSON.parse(JSON.stringify(this.propValue));
new_tabs_data.content.tabs_list.unshift(new_tabs_data.content.home_data);
// 判断选项卡是否置顶
let other_style = this.propTop;
let new_tabs_top_style = this.propNavIsTop || this.propTabsIsTop || new_content.tabs_top_up == '1' ? (new_content.tabs_top_up == '1' ? 'top:calc(' + (this.propStickyTop > 0 ? this.propStickyTop + 'px + ' : '') + other_style * 2 + 'rpx);z-index:11;' : '') : '';
let new_top_up = new_content.tabs_top_up;
// #ifdef H5 || MP-TOUTIAO
// if (this.propTabsIsTop) {
// other_style = '0';
// }
new_tabs_top_style = 'top:calc(' + (this.propStickyTop > 0 ? this.propStickyTop + 'px + ' : '') + other_style * 2 + 'rpx);z-index:11;';
new_top_up = this.propNavIsTop || this.propTabsIsTop ? new_content.tabs_top_up : '0';
// #endif
let tabs_bg = new_style.common_style.color_list;
let new_tabs_background = '';
if (!Array.isArray(tabs_bg) || tabs_bg.length === 0 || !tabs_bg[0] || !tabs_bg[0].color) {
new_tabs_background = 'background:#fff;';
}
const newPropTop = this.propIsCommon ? (app.globalData.rpx_to_px(this.propTop) + this.propStickyTop) : (app.globalData.rpx_to_px(this.propTop + this.propStickyTop));
this.setData({
tabs_data: new_tabs_data,
tabs_sliding_fixed_bg: this.propIsCommon ? gradient_computer(new_style.common_style) : this.propTabsSlidingFixedBg,
style_container: this.propIsCommon ? new_tabs_background + common_styles_computer(new_style.common_style) : new_content.tabs_top_up == '1' ? new_tabs_background + gradient_computer(new_style.common_style) + margin_computer(this.propSpacingCommonStyle) : '', // 如果是选项卡轮播,不需要走默认样式
// 如果开了滑动置顶并且开了沉浸式不需要走传递过来的index否则的话就用传递过来的index
style_img_container: this.propIsCommon ? common_img_computer(new_style.common_style, this.propIndex) : new_content.tabs_top_up == '1' ? background_computer(new_style.common_style) + padding_computer(this.propSpacingCommonStyle, 1, false) + 'box-sizing: border-box;' : '', // 如果是选项卡轮播,不需要走默认样式
tabs_top_style: new_tabs_top_style,
// 沉浸模式下并且开通了安全距离 会显示-的margin
style_margin_container: this.propIsImmersionModel && this.propNewIsTabsSafeDistance ? `margin-top: -${ newPropTop }px;` : '',
// 判断是否置顶
top_up: new_top_up,
tabs_z_index: 'z-index: 11;'
});
},
// 获取选项卡高度
get_tabs_height() {
if (this.top_up == '1') {
// 选择我们想要的元素
const query = uni.createSelectorQuery();
query
.in(this)
.select('.tabs-top')
.boundingClientRect((res) => {
if ((res || null) != null) {
const newPropTop = app.globalData.rpx_to_px(this.propTop);
// data包含元素的宽度、高度等信息
this.setData({
tabs_seat_height: res.height + (this.propIsImmersionModel ? newPropTop + this.propStickyTop : 0),
tabs_carousel_seat_height: (res.height + (this.propIsImmersionModel ? newPropTop + this.propStickyTop : 0)) - this.propSpacingCommonStyle.padding_top - this.propSpacingCommonStyle.margin_top, // 轮播选项卡置顶时去掉顶部间距
});
this.$emit('onComputerHeight', this.tabs_seat_height);
}
})
.exec();
} else {
this.$emit('onComputerHeight', 0);
}
},
tabsZindex(val) {
this.setData({
tabs_z_index: `z-index: ${ val };`
});
},
// 选项卡回调
tabs_click_event(index, item) {
let tabs_id = '';
// 抽象出获取 tabs_id 的逻辑
tabs_id = this.get_tabs_id(item, index);
// 是否是商品分类页面
const is_micro_page = item.data_type == '0';
this.$emit('onTabsTap', tabs_id, is_micro_page);
},
// 获取 tabs_id
get_tabs_id(item, index) {
if (item.data_type === '0') {
return index !== 0 ? item.micro_page_list?.id : '';
} else {
return index !== 0 ? item.classify?.id : '';
}
},
},
};
</script>
<style lang="scss" scoped>
.tabs-container {
z-index: 11;
.tabs-top {
position: fixed;
left: 0;
right: 0;
max-width: 100%;
}
}
.tabs-contents {
max-width: 1600rpx !important;
}
@media only screen and (min-width: 1600rpx) {
.tabs-container .tabs-top {
left: calc(50% - 400px) !important;
}
}
</style>

143
components/diy/title.vue Normal file
View File

@@ -0,0 +1,143 @@
<template>
<view :style="style_container">
<view :style="style_img_container">
<view class="flex-col gap-10">
<view class="pr flex-row" :class="title_center">
<view class="z-i flex-row align-c gap-10">
<template v-if="!isEmpty(form.img_src) && !isEmpty(form.img_src[0].url)">
<view class="wh-auto" :style="{'height': new_style.img_height * 2 + 'rpx' }">
<image :src="form.img_src[0].url" class="ht-auto" mode="heightFix"></image>
</view>
</template>
<template v-else-if="!isEmpty(form.icon_class)">
<iconfont :name="'icon-' + form.icon_class" :size="new_style.icon_size * 2 + 'rpx'" :color="new_style.icon_color" propContainerDisplay="flex"></iconfont>
</template>
<view v-if="!isEmpty(form.title)" class="nowrap" :style="title_style" :data-value="!isEmpty(form.title_link) ? form.title_link.page : ''" @tap="url_event">{{ form.title }}</view>
<view v-if="!isEmpty(form.subtitle) && new_style.title_line == '1'" class="text-word-break nowrap" :style="subtitle_style">{{ form.subtitle }}</view>
</view>
<view class="flex-row gap-10 align-c right-0 pa">
<template v-if="form.keyword_show == '1'">
<view class="flex-row align-c" :style="keyword_gap">
<view v-for="item in keyword_list" :key="item.id" :style="keyword_style" :data-value="!isEmpty(item.link) ? item.link.page : ''" @tap="url_event">
{{ item.title }}
</view>
</view>
</template>
<view v-if="form.right_show == '1'" class="nowrap flex-row align-c" :style="right_style" :data-value="!isEmpty(form.right_link) ? form.right_link.page : ''" @tap="url_event"
>{{ form.right_title }}
<iconfont name="icon-arrow-right" :color="new_style.right_color" :size="new_style.right_size * 2 + 'rpx'" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
<view v-if="!isEmpty(form.subtitle) && new_style.title_line != '1'" class="text-word-break" :style="subtitle_style">{{ form.subtitle }}</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, isEmpty } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => {
return {};
},
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
form: {},
new_style: {},
style_container: '',
style_img_container: '',
title_center: '',
subtitle_style: '',
keyword_list: [],
keyword_style: '',
keyword_gap: '',
right_style: '',
right_size: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 判断是否为空
isEmpty,
// 初始化数据
init() {
const new_form = this.propValue.content;
const new_style = this.propValue.style;
const { keyword_color, keyword_size, right_color, right_size, common_style, title_weight, title_color, title_size, keyword_spacing = 10 } = new_style;
// 标题样式设置
let common_styles = '';
if (title_weight == 'italic') {
common_styles += `font-style: italic`;
} else if (['bold', '500'].includes(title_weight)) {
common_styles += `font-weight: bold`;
}
// 是否居中
this.setData({
form: new_form,
new_style: new_style,
title_center: new_form.is_title_center == '1' ? 'jc-c' : '',
keyword_list: new_form.keyword_list.filter((item) => item.is_show == '1'), // 关键字
keyword_style: `color:${keyword_color}; font-size: ${keyword_size * 2}rpx;`, // 关键字设置
keyword_gap: !isEmpty(keyword_spacing) ? `gap: ${ keyword_spacing * 2}rpx` : 'gap: 20rpx;', // 关键字间距设置
right_size: right_size * 2 + 'rpx', // 右边按钮设置
right_style: `color:${right_color}; font-size: ${right_size * 2}rpx;`, //右侧按钮样式
title_style: `color:${title_color}; font-size: ${title_size * 2}rpx; ${common_styles}`, // 标题样式设置
subtitle_style: this.get_subtitle_style(new_style), // 副标题样式设置
style_container: common_styles_computer(common_style), // 通用样式区
style_img_container: common_img_computer(common_style, this.propIndex), // 通用图片样式区
});
},
// 副标题样式设置
get_subtitle_style(new_style) {
let common_styles = '';
if (new_style.subtitle_weight == 'italic') {
common_styles += `font-style: italic`;
} else if (['bold', '500'].includes(new_style.subtitle_weight)) {
common_styles += `font-weight: bold;`;
}
return `color:${new_style.subtitle_color}; font-size: ${new_style.subtitle_size * 2}rpx; ${common_styles}`;
},
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style scoped lang="scss">
.right-0 {
top: 50%;
transform: translateY(-50%);
}
.break {
word-break: break-word;
overflow-wrap: break-word;
word-wrap: break-word;
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<!-- 用户信息 -->
<view :style="style_container">
<view :style="style_img_container">
<view class="pr padding-xxl" :style="style">
<view class="flex-row jc-sb align-c margin-bottom-xxl">
<view class="flex-1 flex-row align-c gap-12">
<image :src="(user_info.user || null) !== null ? user_info.user.avatar : user.avatar" class="circle" mode="widthFix" :style="'width:' + base_data.user_avatar_size * 2 + 'rpx;height:' + base_data.user_avatar_size * 2 + 'rpx;'" data-value="/pages/personal/personal" @tap="url_event" />
<view class="flex-col gap-8" data-value="/pages/personal/personal" @tap="url_event">
<view class="text-size fw-b" :style="user_name_style">{{ (user_info.user || null) !== null ? user_info.user.user_name_view : user.user_name_view }}</view>
<view v-if="id_bool && (user_info.user || null) !== null" class="padding-horizontal-sm padding-vertical-xsss border-radius-sm" :style="number_code_style">ID:{{ user_info.user.number_code }}</view>
</view>
</view>
<view class="flex-row align-c" :style="'gap:' + base_data.img_space * 2 + 'rpx;'">
<view v-for="(item, index) in icon_setting" :key="index" :style="{ width: base_data.img_size + 'px', height: base_data.img_size + 'px' }" :data-value="item.link.page || ''" @tap="url_event">
<image v-if="item.img.length > 0" :src="item.img[0].url" class="border-radius-sm" mode="scaleToFill" :style="{ width: base_data.img_size + 'px', height: base_data.img_size + 'px' }" />
<iconfont v-else :name="'icon-' + item.icon" :size="base_data.img_size * 2 + 'rpx'" color="#666" propContainerDisplay="flex"></iconfont>
</view>
</view>
</view>
<view class="flex-row jc-sa align-c">
<template v-for="(item, index) in stats_list">
<view v-if="config.includes(item.id)" :key="index" class="tc" :data-value="'/pages/' + item.url + '/' + item.url" @tap="url_event">
<view class="text-size fw-b margin-bottom-sm" :style="stats_number_style">{{ item.value }}</view>
<view class="text-size-xs" :style="stats_name_style">{{ item.name }}</view>
</view>
</template>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import { common_styles_computer, common_img_computer, gradient_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String,Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
id_bool: true,
stats_list: [
{ id: 'order_count', name: '订单总数', value: '100', url: 'user-order' },
{ id: 'goods_favor_count', name: '商品收藏', value: '10', url: 'user-favor' },
{ id: 'goods_browse_count', name: '我的足迹', value: '1000', url: 'user-goods-browse' },
{ id: 'integral_number', name: '我的积分', value: '10000', url: 'user-integral' },
],
config: ['order_count', 'goods_favor_count', 'goods_browse_count', 'integral_number'],
icon_setting: [
{ id: '1', img: [], icon: '', link: {} },
{ id: '2', img: [], icon: '', link: {} },
],
base_data: {},
// 样式
user_name_style: '',
number_code_style: '',
stats_name_style: '',
stats_number_style: '',
// 用户信息
user_info: {
user: null,
order_count: '0',
goods_favor_count: '0',
goods_browse_count: '0',
message_unread_count: '0',
integral_number: '0',
},
user: {
avatar: app.globalData.data.default_user_head_src,
user_name_view: '用户名',
number_code: '',
},
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
const temp_base_data = {
// 头像
user_avatar_size: new_style.user_avatar_size,
// 人物
user_name_color: new_style.user_name_color,
user_name_weight: new_style.user_name_weight,
user_name_size: new_style.user_name_size,
// id
number_code_color_list: new_style.number_code_color_list,
number_code_color: new_style.number_code_color,
number_code_direction: new_style.number_code_direction,
number_code_weight: new_style.number_code_weight,
number_code_size: new_style.number_code_size,
// 图标设置
img_size: new_style.img_size,
img_space: new_style.img_space,
stats_name_color: new_style.stats_name_color,
stats_name_weight: new_style.stats_name_weight,
stats_name_size: new_style.stats_name_size,
stats_number_color: new_style.stats_number_color,
stats_number_weight: new_style.stats_number_weight,
stats_number_size: new_style.stats_number_size,
};
// id样式
const new_gradient_obj = {
color_list: temp_base_data.number_code_color_list,
direction: temp_base_data.number_code_direction,
};
let temp_stats_list = this.stats_list;
temp_stats_list.map((item) => {
if (new_content.config.includes(item.id)) {
item.value = new_content.data[item.id];
}
});
this.setData({
user_info: new_content.data,
config: new_content.config,
icon_setting: new_content.icon_setting,
base_data: temp_base_data,
id_bool: new_content.config ? new_content.config.includes('number_code') : true,
stats_list: temp_stats_list,
// 人物名称样式
user_name_style: 'color:' + temp_base_data.user_name_color + ';' + 'font-size:' + temp_base_data.user_name_size * 2 + 'rpx;' + 'font-weight:' + temp_base_data.user_name_weight + ';',
number_code_style: gradient_computer(new_gradient_obj) + 'color:' + temp_base_data.number_code_color + ';' + 'font-size:' + temp_base_data.number_code_size * 2 + 'rpx;' + 'font-weight:' + temp_base_data.number_code_weight + ';',
// 统计名称样式
stats_name_style: 'color:' + temp_base_data.stats_name_color + ';' + 'font-size:' + temp_base_data.stats_name_size * 2 + 'rpx;' + 'font-weight:' + temp_base_data.stats_name_weight + ';',
stats_number_style: 'color:' + temp_base_data.stats_number_color + ';' + 'font-size:' + temp_base_data.stats_number_size * 2 + 'rpx;' + 'font-weight:' + temp_base_data.stats_number_weight + ';',
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
// 跳转链接
url_event(e) {
app.globalData.url_event(e);
},
},
};
</script>
<style></style>

86
components/diy/video.vue Normal file
View File

@@ -0,0 +1,86 @@
<template>
<!-- 视频 -->
<view :style="style_container">
<view :style="style_img_container">
<view class="video pr" :style="style">
<video :src="video" class="wh-auto ht-auto" :poster="video_img" objectFit="cover" style="object-fit: cover"></video>
</view>
</view>
</view>
</template>
<script>
import { common_styles_computer, common_img_computer } from '@/common/js/common/common.js';
export default {
props: {
propValue: {
type: Object,
default: () => ({}),
},
propKey: {
type: [String, Number],
default: '',
},
// 组件渲染的下标
propIndex: {
type: Number,
default: 1000000,
},
},
data() {
return {
style_container: '',
style_img_container: '',
style: '',
video_img: '',
video: '',
};
},
watch: {
propKey(val) {
// 初始化
this.init();
},
},
created() {
this.init();
},
methods: {
// 初始化数据
init() {
const new_content = this.propValue.content || {};
const new_style = this.propValue.style || {};
// 视频比例
this.get_video_height(new_content.video_ratio);
this.setData({
video_img: new_content.video_img.length > 0 ? new_content.video_img[0].url : '',
video: new_content.video.length > 0 ? new_content.video[0].url : '',
style_container: common_styles_computer(new_style.common_style),
style_img_container: common_img_computer(new_style.common_style, this.propIndex),
});
},
// 获取视频高度
get_video_height(data) {
uni.getSystemInfo({
success: (res) => {
let video_ratio = ``;
const width = res.windowWidth;
if (data == '4:3') {
video_ratio = `height: ${(((width * 3) / 4) * 2).toFixed(2)}rpx;`;
} else if (data == '1:1') {
video_ratio = `height: ${width * 2}rpx;`;
} else {
// 16:9 保留两位小数
video_ratio = `height: ${(((width * 9) / 16) * 2).toFixed(2)}rpx;`;
}
this.setData({
style: video_ratio,
});
},
});
},
},
};
</script>
<style></style>

View File

@@ -0,0 +1,95 @@
<template>
<view :class="theme_view">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event">
<view class="emoji-popup bg-white">
<view class="close fr oh">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="emoji-popup-content oh tc">
<block v-if="emoji_list.length > 0">
<block v-for="(item, index) in emoji_list" :key="index">
<view class="emoji-item fl bs-bb text-size-xxxl" @tap="choice_confirm_event" :data-value="item.emoji">{{item.emoji}}</view>
</block>
</block>
</view>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
var common_static_url = app.globalData.get_static_url('common');
import componentPopup from "@/components/popup/popup";
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: common_static_url,
popup_status: false,
emoji_list: []
};
},
components: {
componentPopup
},
created: function() {},
methods: {
// 初始配置
init(config = {}) {
if(!app.globalData.is_single_page_check()) {
return false;
}
this.setData({
popup_status: config.status == undefined ? true : config.status,
emoji_list: config.emoji_list || [],
});
},
// 弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false
});
},
// 选择确认事件
choice_confirm_event(e) {
// 关闭弹窗
this.setData({
popup_status: false
});
// 调用父级
this.$emit('choiceConfirmEvent', e.currentTarget.dataset.value);
}
}
};
</script>
<style>
.emoji-popup {
padding: 20rpx 10rpx 0 10rpx;
position: relative;
}
.emoji-popup .close {
position: absolute;
top: 20rpx;
right: 20rpx;
z-index: 2;
}
.emoji-popup-content {
max-height: 50vh;
overflow-y: scroll;
overflow-x: hidden;
margin-top: 20rpx;
}
.emoji-popup-content .emoji-item {
height: 130rpx;
line-height: 130rpx;
width: 25%;
}
</style>

View File

@@ -0,0 +1,576 @@
<template>
<view :class="theme_view">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event">
<view class="bg-white">
<view class="close oh padding-horizontal-main padding-top-main">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="plugins-batchbuy-container">
<!-- 批发规则 -->
<view v-if="(plugins_wholesale_data || null) != null" class="padding-main br-b-f5">
<component-wholesale-rules :propCurrencySymbol="propCurrencySymbol" :propData="plugins_wholesale_data"></component-wholesale-rules>
</view>
<!-- 下单规格选择 -->
<view v-if="(goods || null) != null && (batchbuy_data || null) != null && (batchbuy_data.goods_spec_data || null) != null && batchbuy_data.goods_spec_data.length > 0" :class="'spec-data-content oh '+((plugins_wholesale_data || null) != null ? 'wholesale' : '')">
<block v-if="batchbuy_data.is_only_level_one == 0">
<view class="left-nav ht-auto bg-base fl">
<scroll-view :scroll-y="true" class="ht-auto">
<block v-for="(item, index) in batchbuy_data.goods_spec_data" :key="index">
<view :class="'padding-vertical-main tc cp oh pr ' + (nav_active_index == index ? 'bg-white cr-main' : '')" :data-index="index" @tap="nav_event">
<block v-if="(item.images || null) != null">
<image class="dis-inline-block br-f5 radius dis-block spec-images" :src="item.images" mode="widthFix"></image>
<view class="icon-enlarge-submit pa padding-xs lh-sm radius" :data-value="item.images" @tap="spec_images_view_event">
<iconfont name="icon-enlarge" size="26rpx" color="#fff" propClass="lh-sm"></iconfont>
</view>
</block>
<view class="cr-base">{{ item.name }}</view>
<view v-if="(item.badge_total || 0) > 0" class="badge-icon pa">
<component-badge :propNumber="item.badge_total"></component-badge>
</view>
</view>
</block>
</scroll-view>
</view>
<view class="right-conent ht-auto bs-bb fr">
<scroll-view :scroll-y="true" class="ht-auto">
<block v-for="(item, index) in batchbuy_data.goods_spec_data[nav_active_index]['data']" :key="index">
<view class="padding-main oh">
<view class="fl item-left">
<view class="text-size-xs">{{ item.name }}</view>
<view class="sales-price text-size-xs">{{ propCurrencySymbol }}{{ item.base.price }}</view>
</view>
<text v-if="(item.base.inventory || 0) == 0" class="fr text-size-xs cr-grey">{{$t('goods-batch-buy.goods-batch-buy.dsfd98')}}</text>
<view v-else class="tc oh round fr item-right text-size-xs">
<view @tap="batchbuy_goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="0" :data-index="index">-</view>
<input @blur="batchbuy_goods_buy_number_blur" class="number-input tc cr-grey bg-white fl va-m radius-0" type="number" :value="item.buy_number || 0" :data-index="index" />
<view @tap="batchbuy_goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="1" :data-index="index">+</view>
</view>
</view>
</block>
</scroll-view>
</view>
</block>
<block v-else>
<view class="right-conent ht-auto padding-main bs-bb right-conent-only-level-one">
<scroll-view :scroll-y="true" class="ht-auto">
<block v-for="(item, index) in batchbuy_data.goods_spec_data" :key="index">
<view class="oh padding-vertical-main">
<view class="fl item-left">
<view class="pr">
<block v-if="(item.images || null) != null">
<image class="dis-inline-block va-m br-f5 radius margin-right-sm spec-images" :src="item.images" mode="widthFix"></image>
<view class="icon-enlarge-submit pa padding-xs lh-sm radius" :data-value="item.images" @tap="spec_images_view_event">
<iconfont name="icon-enlarge" size="26rpx" color="#fff" propClass="lh-sm"></iconfont>
</view>
</block>
<text class="text-size-xs va-m">{{ item.name }}</text>
</view>
<view class="sales-price text-size-xs margin-top-xs">{{ propCurrencySymbol }}{{ item.base.price }}</view>
</view>
<text v-if="(item.base.inventory || 0) == 0" class="fr text-size-xs cr-grey">{{$t('goods-batch-buy.goods-batch-buy.dsfd98')}}</text>
<view v-else :class="'tc oh round fr item-right text-size-xs margin-top'+((item.images || null) == null ? 'xs' : '')">
<view @tap="batchbuy_goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="0" :data-index="index">-</view>
<input @blur="batchbuy_goods_buy_number_blur" class="tc cr-grey bg-white fl va-m radius-0" type="number" :value="item.buy_number || 0" :data-index="index" />
<view @tap="batchbuy_goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="1" :data-index="index">+</view>
</view>
</view>
</block>
</scroll-view>
</view>
</block>
<view class="confirm-submit pa wh-auto bottom-line-exclude bg-white padding-top-main br-t-f5">
<view class="oh padding-horizontal-main padding-bottom-main cr-grey">
<text class="text-size-xs">
<text>{{$t('buy.buy.g2vt78')}}</text>
<text class="cr-red padding-left-xs padding-right-xs">{{ base_data.kind }}</text>
<text>{{$t('goods-batch-buy.goods-batch-buy.9ectyf')}}</text>
<text class="cr-red padding-left-xs padding-right-xs">{{ base_data.quantity }}</text>
<text>{{ goods.inventory_unit }}</text>
</text>
<text class="text-size-xs fr">{{$t('goods-batch-buy.goods-batch-buy.geq82x')}}<text class="fw-b sales-price">{{ propCurrencySymbol }}{{ base_data.amount_money }}</text></text>
</view>
<view v-if="(opt_button || null) != null && opt_button.length > 0" class="padding-bottom-main">
<view :class="'oh buy-nav-btn-number-' + (opt_button.length || 0)">
<block v-for="(item, index) in opt_button" :key="index">
<view v-if="(item.name || null) != null && (item.type || null) != null" class="item fl bs-bb padding-horizontal-main">
<button :class="'cr-white round text-size-sm bg-' + ((item.color || 'main') == 'main' ? 'main' : 'main-pair')" type="default" @tap="confirm_event" :data-type="item.type" hover-class="none">{{ item.name }}</button>
</view>
</block>
</view>
</view>
</view>
</view>
<block v-else>
<view class="cr-grey tc padding-top-xl padding-bottom-xxxl">{{$t('goods-batch-buy.goods-batch-buy.ypby1k')}}</view>
</block>
</view>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import base64 from "@/common/js/lib/base64.js";
import componentPopup from "@/components/popup/popup";
import componentBadge from "@/components/badge/badge";
import componentWholesaleRules from '@/components/wholesale-rules/wholesale-rules';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
popup_status: false,
nav_active_index: 0,
goods: null,
batchbuy_data: null,
back_data: null,
opt_button: [],
base_data: {
kind: 0,
quantity: 0,
amount_money: "0.00",
},
// 批发数据
plugins_wholesale_data: null,
};
},
components: {
componentPopup,
componentBadge,
componentWholesaleRules
},
props: {
// 价格符号
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
// 批发数据
propPluginsWholesaleData: {
type: [Array, Object],
default: null,
}
},
// 页面被展示
created: function () {
this.setData({
plugins_wholesale_data: this.propPluginsWholesaleData,
});
},
methods: {
// 初始化
init(params = {}) {
if (!app.globalData.is_single_page_check()) {
return false;
}
// 购买按钮处理,仅展示购买和购物车
var opt_button = [];
var buy_button = params.buy_button || null;
if(buy_button != null && (buy_button.data || null) != null && buy_button.data.length > 0) {
var arr = ['plugins-batchbuy-button-cart', 'plugins-batchbuy-button-buy'];
for(var i in buy_button.data) {
if(arr.indexOf(buy_button.data[i]['type']) != -1) {
opt_button.push(buy_button.data[i]);
}
}
}
// 设置数据
this.setData({
popup_status: true,
opt_button: opt_button,
goods: params.goods || null,
batchbuy_data: params.batchbuy_data || null,
back_data: params.back_data || null,
plugins_wholesale_data: params.plugins_wholesale_data || null
});
},
// 弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false,
});
},
// 弹层导航切换
nav_event(e) {
this.setData({
nav_active_index: e.currentTarget.dataset.index,
});
},
// 商品批量下单数量操作事件
batchbuy_goods_buy_number_event(e) {
var type = e.currentTarget.dataset.type;
var index = e.currentTarget.dataset.index;
var temp_data = this.batchbuy_data;
var temp_spec_data = parseInt(temp_data.is_only_level_one || 0) == 1 ? temp_data.goods_spec_data[index] : temp_data.goods_spec_data[this.nav_active_index]["data"][index];
var number = parseInt(temp_spec_data.buy_number || 0);
var min = parseInt(temp_spec_data.base.buy_min_number || 0);
// 首次增加使用起购数量
number = type == 0 ? number - 1 : number == 0 && min > 0 ? min : number + 1;
this.batch_goods_buy_number_handle(temp_data, temp_spec_data, index, number);
},
// 商品批量下单数量输入事件
batchbuy_goods_buy_number_blur(e) {
var number = parseInt(e.detail.value) || 0;
var index = e.currentTarget.dataset.index;
var temp_data = this.batchbuy_data;
var temp_spec_data = parseInt(temp_data.is_only_level_one || 0) == 1 ? temp_data.goods_spec_data[index] : temp_data.goods_spec_data[this.nav_active_index]["data"][index];
if (isNaN(number)) {
number = 0;
}
this.batch_goods_buy_number_handle(temp_data, temp_spec_data, index, number);
},
// 商品批量下单数量处理
batch_goods_buy_number_handle(temp_data, temp_spec_data, index, number) {
var min = parseInt(temp_spec_data.base.buy_min_number || 0);
var max = parseInt(temp_spec_data.base.buy_max_number || 0);
var inventory = parseInt(temp_spec_data.base.inventory || 0);
var inventory_unit = this.goods.inventory_unit;
// 是否负数
if (number < 0) {
number = 0;
}
// 不能小于起购数则0
if (number > 0 && min > 0 && number < min) {
number = 0;
app.globalData.showToast(this.$t('recommend-detail.recommend-detail.265vyu') + min + inventory_unit);
}
// 不能超过最大限购
if (max > 0 && number > max) {
number = max;
app.globalData.showToast(this.$t('goods-category.goods-category.z1eh3v') + max + inventory_unit);
}
// 不能超过库存
if (number > inventory) {
number = inventory;
app.globalData.showToast(this.$t('recommend-detail.recommend-detail.2sis3v') + inventory + inventory_unit);
}
temp_spec_data["buy_number"] = number;
if (parseInt(temp_data.is_only_level_one || 0) == 1) {
temp_data.goods_spec_data[index] = temp_spec_data;
} else {
temp_data.goods_spec_data[this.nav_active_index]["data"][index] = temp_spec_data;
}
// 非仅一级则处理一级总数
if (parseInt(temp_data.is_only_level_one || 0) != 1) {
var badge_total = 0;
temp_data.goods_spec_data[this.nav_active_index]["data"].forEach((item) => {
var temp_badge = parseInt(item.buy_number || 0);
if (temp_badge > 0) {
badge_total += temp_badge;
}
});
temp_data.goods_spec_data[this.nav_active_index]["badge_total"] = badge_total;
}
// 总数、汇总
var stock_total = 0;
var kind_total = 0;
var amount_money_total = 0;
temp_data.goods_spec_data.forEach((item) => {
if (parseInt(temp_data.is_only_level_one || 0) == 1) {
var temp_stock = parseInt(item.buy_number || 0);
if (temp_stock > 0) {
stock_total += temp_stock;
kind_total += 1;
amount_money_total += temp_stock * parseFloat(item.base.price);
}
} else {
item.data.forEach((item2) => {
var temp_stock = parseInt(item2.buy_number || 0);
if (temp_stock > 0) {
stock_total += temp_stock;
kind_total += 1;
amount_money_total += temp_stock * parseFloat(item2.base.price);
}
});
}
});
// 设置数据
this.setData({
batchbuy_data: temp_data,
base_data: {
kind: kind_total,
quantity: stock_total,
amount_money: app.globalData.price_two_decimal(amount_money_total),
},
});
// 下单数据
var goods_data = [];
var goods_id = this.goods.id;
temp_data.goods_spec_data.forEach((item) => {
if (parseInt(temp_data.is_only_level_one || 0) == 1) {
goods_data.push({
id: goods_id,
stock: stock_total,
spec: item.spec || "",
});
} else {
item.data.forEach((item2) => {
goods_data.push({
id: goods_id,
stock: stock_total,
spec: item2.spec || "",
});
});
}
});
// 数量更新获取最新数据
uni.request({
url: app.globalData.get_request_url("stock", "goods"),
method: "POST",
data: { goods_data: goods_data },
dataType: "json",
success: (res) => {
if (res.data.code == 0) {
var plugins_wholesale_data = null;
res.data.data.forEach((item) => {
if (item.code == 0) {
// 批发数据
if((item.data.plugins_wholesale_data || null) != null && temp_spec_data.base_id == item.data.spec_base.id) {
plugins_wholesale_data = item.data.plugins_wholesale_data;
}
// 规格数据处理
for (var i1 in temp_data.goods_spec_data) {
if (parseInt(temp_data.is_only_level_one || 0) == 1) {
if (temp_data.goods_spec_data[i1].base.id == item.data.spec_base.id) {
temp_data.goods_spec_data[i1].base.price = item.data.spec_base.price;
temp_data.goods_spec_data[i1].base.original_price = item.data.spec_base.original_price;
temp_data.goods_spec_data[i1].base.inventory = item.data.spec_base.inventory;
}
} else {
for (var i2 in temp_data.goods_spec_data[i1].data) {
if (temp_data.goods_spec_data[i1].data[i2].base.id == item.data.spec_base.id) {
temp_data.goods_spec_data[i1].data[i2].base.price = item.data.spec_base.price;
temp_data.goods_spec_data[i1].data[i2].base.original_price = item.data.spec_base.original_price;
temp_data.goods_spec_data[i1].data[i2].base.inventory = item.data.spec_base.inventory;
}
}
}
}
}
});
this.setData({
batchbuy_data: temp_data,
plugins_wholesale_data: plugins_wholesale_data,
});
// 调用父级
this.$emit("BatchStockSuccessEvent", {
current_spec: temp_spec_data,
goods_data: goods_data,
back_data: res.data.data,
plugins_wholesale_data: plugins_wholesale_data,
});
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
// 确认事件
confirm_event(e) {
var user = app.globalData.get_user_info(this, "confirm_event", e);
if (user != false) {
// 获取数据
var goods_data = [];
var goods_id = this.goods.id;
this.batchbuy_data.goods_spec_data.forEach((item) => {
if (parseInt(this.batchbuy_data.is_only_level_one || 0) == 1) {
var buy_number = parseInt(item.buy_number || 0);
if (buy_number > 0) {
goods_data.push({
goods_id: goods_id,
stock: buy_number,
spec: item.spec || "",
});
}
} else {
item.data.forEach((item2) => {
var buy_number = parseInt(item2.buy_number || 0);
if (buy_number > 0) {
goods_data.push({
goods_id: goods_id,
stock: buy_number,
spec: item2.spec || "",
});
}
});
}
});
if (goods_data.length <= 0) {
app.globalData.showToast(this.$t('goods-detail.goods-detail.6brk57'));
return false;
}
// 操作类型
switch (e.currentTarget.dataset.type) {
case "plugins-batchbuy-button-buy":
// 进入订单确认页面
var data = {
buy_type: "goods",
goods_data: encodeURIComponent(base64.encode(JSON.stringify(goods_data))),
};
app.globalData.url_open("/pages/buy/buy?data=" + encodeURIComponent(base64.encode(JSON.stringify(data))));
this.popup_close_event();
break;
// 加入购物车
case "plugins-batchbuy-button-cart":
this.goods_cart_event(goods_data);
break;
default:
app.globalData.showToast(this.$t('goods-batch-buy.goods-batch-buy.7tp1tc'));
}
}
},
// 加入购物车事件
goods_cart_event(goods_data) {
uni.showLoading({
title: this.$t('common.processing_in_text'),
});
uni.request({
url: app.globalData.get_request_url("save", "cart"),
method: "POST",
data: { goods_data: goods_data },
dataType: "json",
success: (res) => {
uni.hideLoading();
if (res.data.code == 0) {
app.globalData.showToast(res.data.msg, "success");
// 调用父级
this.$emit("BatchCartSuccessEvent", {
cart_number: res.data.data.buy_number,
back_data: this.back_data,
});
// 关闭购买弹窗窗口
this.popup_close_event();
} else {
if (app.globalData.is_login_check(res.data, this, "confirm_event")) {
app.globalData.showToast(res.data.msg);
}
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
// 规格图片预览事件
spec_images_view_event(e) {
var value = e.currentTarget.dataset.value || null;
if (value != null) {
uni.previewImage({
current: value,
urls: [value],
});
}
}
},
};
</script>
<style>
.plugins-batchbuy-container {
height: 70vh;
padding-bottom: 160rpx;
}
.plugins-batchbuy-container .spec-data-content {
height: calc(100% - 15rpx);
}
.plugins-batchbuy-container .spec-data-content.wholesale {
height: calc(100% - 160rpx);
}
.plugins-batchbuy-container .left-nav {
width: 200rpx;
}
.plugins-batchbuy-container .left-nav .badge-icon {
top: 8rpx;
right: 36rpx;
}
.plugins-batchbuy-container .left-nav .spec-images {
width: 100rpx;
height: 100rpx !important;
}
.plugins-batchbuy-container .icon-enlarge-submit {
left: 55rpx;
top: 30rpx;
background: rgb(0, 0, 0, 30%);
}
.plugins-batchbuy-container .right-conent {
width: calc(100% - 210rpx);
}
.plugins-batchbuy-container .right-conent .item-left {
width: calc(100% - 290rpx);
}
.plugins-batchbuy-container .item-right {
background: #fbfbfb;
border: 1px solid #f0f0f0;
}
.plugins-batchbuy-container .item-right .number-submit {
width: 80rpx;
font-weight: bold;
}
.plugins-batchbuy-container .item-right .number-input {
width: 50px;
}
.plugins-batchbuy-container .item-right .number-submit,
.plugins-batchbuy-container .item-right .number-input {
padding: 0;
height: 60rpx;
line-height: 60rpx;
}
.plugins-batchbuy-container .right-conent-only-level-one {
width: 100%;
}
.plugins-batchbuy-container .right-conent-only-level-one .spec-images {
width: 100rpx;
height: 100rpx !important;
}
.plugins-batchbuy-container .right-conent-only-level-one .icon-enlarge-submit {
left: 5rpx;
top: 5rpx;
}
.plugins-batchbuy-container .confirm-submit {
left: 0;
bottom: 0;
}
.plugins-batchbuy-container .buy-nav-btn-number-0 .item,
.plugins-batchbuy-container .buy-nav-btn-number-1 .item {
width: 100% !important;
}
.plugins-batchbuy-container .buy-nav-btn-number-2 .item {
width: 50% !important;
}
.plugins-batchbuy-container .buy-nav-btn-number-3 .item {
width: 33.33% !important;
}
.plugins-batchbuy-container .buy-nav-btn-number-4 .item {
width: 25% !important;
}
</style>

View File

@@ -0,0 +1,945 @@
<template>
<view :class="theme_view" class="z-i-deep">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event" :propIndex="propIndex">
<view class="goods-spec-choice-container padding-main bg-white pr">
<view class="close fr oh">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<!-- 规格基础信息 -->
<view class="goods-spec-base oh br-b pr">
<image :src="goods_spec_base_images" mode="scaleToFill" class="spec-images radius br" @tap="goods_detail_images_view_event" :data-value="goods_spec_base_images"></image>
<view class="goods-spec-base-content">
<view class="goods-price">
<view v-if="(goods.show_field_price_status || 0) == 1">
<text class="sales-price va-m">{{ goods.show_price_symbol }}{{ goods_spec_base_price }}</text>
<text class="cr-grey text-size-xs va-m">{{ goods.show_price_unit }}</text>
</view>
<view v-if="(goods.show_field_original_price_status || 0) == 1 && (goods_spec_base_original_price || null) != null && goods_spec_base_original_price != 0" class="original-price margin-top-sm">{{ goods.show_original_price_symbol }}{{ goods_spec_base_original_price }}{{ goods.show_original_price_unit }}</view>
</view>
<view v-if="(goods.show_inventory_status || 0) == 1" class="inventory text-size-xs margin-top">
<text class="cr-grey">{{ $t('goods-detail.goods-detail.1s79t4') }}</text>
<text class="cr-base">{{ goods_spec_base_inventory }}</text>
<text class="cr-grey">{{ goods.inventory_unit }}</text>
</view>
</view>
</view>
<block v-if="(goods.is_exist_many_spec || 0) == 1 && goods_spec_choose.length == 0">
<view class="padding-top-xxxl padding-bottom-xxxl tc cr-red">{{ $t('goods-buy.goods-buy.ufdm25') }}</view>
</block>
<block v-else>
<view class="goods-spec-choice-content">
<!-- 商品规格 -->
<view v-if="goods_spec_choose.length > 0" class="goods-spec-choose">
<view v-for="(item, key) in goods_spec_choose" :key="key" class="item padding-top-xxl padding-bottom-xxl">
<view class="text-size-sm">{{ item.name }}</view>
<view v-if="item.value.length > 0" class="spec margin-top-sm">
<block v-for="(items, keys) in item.value" :key="keys">
<button @tap.stop="goods_spec_choice_event" :data-key="key" :data-keys="keys" type="default" size="mini" hover-class="none" :class="'spec-btn round ' + items.is_active + ' ' + items.is_dont + ' ' + items.is_disabled">
<image v-if="(items.images || null) != null" :src="items.images" mode="scaleToFill" class="spec-images va-m dis-inline-block round margin-right-sm"></image>
<text class="va-m">{{ items.name }}</text>
</button>
</block>
</view>
</view>
</view>
<!-- 购买数量 -->
<view class="goods-buy-number oh pr margin-top-xl margin-bottom-xxl">
<view class="fl margin-top">{{ $t('goods-buy.goods-buy.737wzz') }}</view>
<view class="number-content tc oh round">
<view @tap="goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="0">-</view>
<input @blur="goods_buy_number_blur" class="number-input tc cr-grey bg-white fl va-m radius-0" type="number" :value="buy_number" />
<view @tap="goods_buy_number_event" class="number-submit tc cr-grey fl va-m" data-type="1">+</view>
</view>
</view>
</view>
<view v-if="(opt_button || null) != null && opt_button.length > 0" class="padding-bottom-main">
<view :class="'oh buy-nav-btn-number-' + (opt_button.length || 0)">
<block v-for="(item, index) in opt_button" :key="index">
<view v-if="(item.name || null) != null && (item.type || null) != null" class="item fl bs-bb padding-horizontal-main">
<button :class="'cr-white round text-size-sm bg-' + ((item.color || 'main') == 'main' ? 'main' : 'main-pair')" type="default" @tap="spec_confirm_event" :data-value="item.value" :data-type="item.type" :data-business="item.business || ''" hover-class="none">{{ item.name }}</button>
</view>
</block>
</view>
</view>
<button v-else class="bg-main br-main cr-white text-size-sm round" type="default" @tap.stop="spec_confirm_event" hover-class="none">{{ $t('index.index.7w75zb') }}</button>
</block>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import base64 from '@/common/js/lib/base64.js';
import componentPopup from '@/components/popup/popup';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
params: {},
back_data: {},
popup_status: false,
goods_spec_base_price: 0,
goods_spec_base_original_price: 0,
goods_spec_base_inventory: 0,
goods_spec_base_buy_min_number: 0,
goods_spec_base_buy_max_number: 0,
goods_spec_base_images: '',
goods: {},
goods_spec_choose: [],
buy_number: 1,
buy_event_type: 'cart',
opt_button: [],
is_direct_cart: 0,
is_success_tips: 1,
// 选中规格临时定时变量
spec_selected_timer: null,
spec_selected_timerout: null,
// 智能工具插件
plugins_intellectstools_config: app.globalData.get_config('plugins_base.intellectstools.data'),
};
},
components: {
componentPopup,
},
props: {
propParams: {
type: [String, Object],
default: () => {
return {};
},
},
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
// 弹窗层级
propIndex: {
type: Number,
default: 100,
},
},
created: function () {},
methods: {
// 初始化
init(goods = {}, params = {}, back_data = null) {
if (!app.globalData.is_single_page_check()) {
return false;
}
params = params || {};
// 状态默认开启弹窗
var status = true;
// 商品可选规格
var goods_spec_choose = (goods.specifications || null) != null ? goods.specifications.choose || [] : [];
// 无规格是否直接操作
var is_direct_cart = 0;
if ((params.is_direct_cart || 0) == 1 && parseInt(goods.is_exist_many_spec || 0) == 0 && goods_spec_choose.length == 0) {
status = false;
is_direct_cart = 1;
}
// 是否成功提示、默认提示
var is_success_tips = params.is_success_tips == undefined ? 1 : params.is_success_tips || 0;
// 直接加购、并且用户已经存在购物车则依次+1
if (is_direct_cart == 1 && parseInt(goods.user_cart_count || 0) > 0) {
var buy_number = 1;
} else {
var buy_number = goods.buy_min_number || 1;
}
// 购买按钮处理,仅展示购买和购物车
var opt_button = [];
var buy_button = params.buy_button || null;
if (buy_button != null && (buy_button.data || null) != null && buy_button.data.length > 0) {
var arr = ['buy', 'cart', 'show'];
for (var i in buy_button.data) {
if (arr.indexOf(buy_button.data[i]['type']) != -1) {
opt_button.push(buy_button.data[i]);
}
}
}
// 设置数据
this.setData({
popup_status: status,
params: params || {},
back_data: back_data,
goods: goods || {},
goods_spec_choose: goods_spec_choose,
goods_spec_base_price: goods.price,
goods_spec_base_original_price: goods.original_price || 0,
goods_spec_base_inventory: goods.inventory,
goods_spec_base_images: goods.images,
buy_number: buy_number,
buy_event_type: params.buy_event_type || 'cart',
opt_button: opt_button,
is_direct_cart: is_direct_cart,
is_success_tips: is_success_tips,
});
// 初始化不能选择规格处理
this.spec_handle_dont(0, 1);
// 获取规格详情
this.get_spec_detail();
// 规格选中处理
this.selected_spec_handle();
// 是否直接操作加入购物车
if (is_direct_cart) {
this.spec_confirm_event();
}
},
// 规格选中处理
selected_spec_handle() {
var temp_spec_choose = this.goods_spec_choose;
if (temp_spec_choose.length > 0) {
// 是否已选择
var active_count = 0;
for (var i in temp_spec_choose) {
for (var k in temp_spec_choose[i]['value']) {
if ((temp_spec_choose[i]['value'][k]['is_active'] || null) != null) {
active_count++;
}
}
}
if (active_count > 0) {
return false;
}
// 是否指定规格初始化
var spec = (this.propParams || null) != null && (this.propParams.spec || null) != null ? this.propParams.spec : null;
if (spec != null) {
this.appoint_selected_spec_handle(temp_spec_choose, spec);
} else {
// 是否默认选中第一个规格、、已存在指定规格初始化则不走默认选择第一个规格
this.plugins_intellectstools_selected_spec_handle(temp_spec_choose);
}
}
},
// 指定规格初始化
appoint_selected_spec_handle(spec_choose, spec) {
spec = decodeURIComponent(spec).split('|');
if (spec.length == spec_choose.length) {
// 选择处理
var self = this;
// 销毁之前的任务
clearInterval(self.spec_selected_timer);
clearInterval(self.spec_selected_timerout);
// 必须存在购买和加入购物车任意一个、规格必须多个
var sku_count = app.globalData.get_length(spec_choose);
// 先清除价格展示信息
self.setData({
goods_spec_base_price: '...',
goods_spec_base_original_price: '...',
});
var num = 0;
var timer = setInterval(function () {
for (var i in spec_choose) {
// 清除价格展示信息、避免获取价格类型赋值
self.setData({
goods_spec_base_price: '...',
goods_spec_base_original_price: '...',
});
// 必须不存在已选择项
var active =
spec_choose[i]['value']
.map(function (v) {
return v.is_active;
})
.join('') || null;
if (active == null) {
// 不能选择规格处理
self.spec_handle_dont(i);
// 规格选择处理
var temp_spec = spec[i];
var status = false;
for (var k in spec_choose[i]['value']) {
// 必须是可选和未选
if (!status && (spec_choose[i]['value'][k]['is_disabled'] || null) == null && (spec_choose[i]['value'][k]['is_dont'] || null) == null && temp_spec == spec_choose[i]['value'][k]['name']) {
self.goods_spec_choice_handle(i, k);
status = true;
num++;
}
}
}
}
if (num >= sku_count) {
clearInterval(self.spec_selected_timer);
}
}, 100);
var timerout = setTimeout(function () {
clearInterval(self.spec_selected_timerout);
}, 20000);
self.setData({
spec_selected_timer: timer,
spec_selected_timerout: timerout,
});
}
},
// 默认选中第一个规格 - 智能工具箱插件
plugins_intellectstools_selected_spec_handle(spec_choose) {
// 选择处理
var self = this;
// 销毁之前的任务
clearInterval(self.spec_selected_timer);
clearInterval(self.spec_selected_timerout);
// 读取智能工具插件配置、是否开启
var config = self.plugins_intellectstools_config || null;
if (config != null && (config.is_goods_detail_selected_first_spec || 0) == 1) {
// 必须存在购买和加入购物车任意一个、规格必须多个
var sku_count = app.globalData.get_length(spec_choose);
// 先清除价格展示信息
self.setData({
goods_spec_base_price: '...',
goods_spec_base_original_price: '...',
});
var num = 0;
var timer = setInterval(function () {
for (var i in spec_choose) {
// 清除价格展示信息、避免获取价格类型赋值
self.setData({
goods_spec_base_price: '...',
goods_spec_base_original_price: '...',
});
// 必须不存在已选择项
var active =
spec_choose[i]['value']
.map(function (v) {
return v.is_active;
})
.join('') || null;
if (active == null) {
// 不能选择规格处理
self.spec_handle_dont(i);
// 规格选择处理
var status = false;
for (var k in spec_choose[i]['value']) {
// 必须是可选和未选
if (!status && (spec_choose[i]['value'][k]['is_disabled'] || null) == null && (spec_choose[i]['value'][k]['is_dont'] || null) == null) {
self.goods_spec_choice_handle(i, k);
status = true;
num++;
}
}
}
}
if (num >= sku_count) {
clearInterval(self.spec_selected_timer);
}
}, 100);
var timerout = setTimeout(function () {
clearInterval(self.spec_selected_timerout);
}, 20000);
self.setData({
spec_selected_timer: timer,
spec_selected_timerout: timerout,
});
}
},
// 弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false,
});
},
// 规格事件
goods_spec_choice_event(e) {
var key = e.currentTarget.dataset.key || 0;
var keys = e.currentTarget.dataset.keys || 0;
this.goods_spec_choice_handle(key, keys);
},
// 规格选择处理
goods_spec_choice_handle(key, keys) {
var temp_spec = this.goods_spec_choose;
var temp_images = this.goods_spec_base_images;
// 不能选择和禁止选择跳过
if ((temp_spec[key]['value'][keys]['is_dont'] || null) == null && (temp_spec[key]['value'][keys]['is_disabled'] || null) == null) {
// 规格选择
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_dont'] || null) == null && (temp_spec[i]['value'][k]['is_disabled'] || null) == null) {
if (key == i) {
if (keys == k && (temp_spec[i]['value'][k]['is_active'] || null) == null) {
temp_spec[i]['value'][k]['is_active'] = 'cr-white bg-main br-main';
if ((temp_spec[i]['value'][k]['images'] || null) != null) {
temp_images = temp_spec[i]['value'][k]['images'];
}
} else {
temp_spec[i]['value'][k]['is_active'] = '';
}
}
}
}
}
this.setData({
goods_spec_choose: temp_spec,
goods_spec_base_images: temp_images,
});
// 不能选择规格处理
this.spec_handle_dont(key);
// 获取下一个规格类型
this.get_spec_type(key);
// 获取规格详情
this.get_spec_detail();
// 规格选择回调
this.$emit('SpecChoiceEvent', {
goods_id: this.goods.id,
spec: this.choice_spec_data(),
goods_spec_choose: this.goods_spec_choose,
});
}
},
// 获取下一个规格类型
get_spec_type(key) {
var temp_spec = this.goods_spec_choose;
var active_index = parseInt(key) + 1;
var sku_count = app.globalData.get_length(temp_spec);
if (active_index <= 0 || active_index >= sku_count) {
return false;
}
// 获取规格值
var spec = this.choice_spec_data();
if (spec.length <= 0) {
return false;
}
// 获取数据
var data = this.params;
data['id'] = this.goods.id;
data['spec'] = JSON.stringify(spec);
uni.request({
url: app.globalData.get_request_url('spectype', 'goods'),
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
var spec_type = res.data.data.spec_type;
var spec_count = spec.length;
var index = spec_count > 0 ? spec_count : 0;
if (index < sku_count) {
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if (index == i) {
temp_spec[i]['value'][k]['is_dont'] = '';
var temp_value = temp_spec[i]['value'][k]['name'];
var temp_status = false;
for (var t in spec_type) {
if (spec_type[t] == temp_value) {
temp_status = true;
break;
}
}
if (temp_status == true) {
temp_spec[i]['value'][k]['is_disabled'] = '';
} else {
temp_spec[i]['value'][k]['is_disabled'] = 'spec-items-disabled';
}
}
}
}
this.setData({
goods_spec_choose: temp_spec,
});
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
// 选择规格数据
choice_spec_data() {
var spec = [];
var temp_spec = this.goods_spec_choose;
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_active'] || null) != null) {
spec.push({
type: temp_spec[i]['name'],
value: temp_spec[i]['value'][k]['name'],
});
break;
}
}
}
return spec;
},
// 获取规格详情
get_spec_detail() {
// 获取规格值
var spec = this.goods_selected_spec();
// 存在规格的时候是否已完全选择规格
var sku_count = this.goods_spec_choose.length;
var active_count = spec.length;
if (spec.length <= 0 || active_count < sku_count) {
var buy_number = parseInt(this.buy_number);
var buy_min_number = parseInt(this.goods.buy_min_number || 1);
var buy_max_number = parseInt(this.goods.buy_max_number || 0);
if (buy_number < buy_min_number) {
buy_number = buy_min_number;
}
if (buy_max_number > 0 && buy_number > buy_max_number) {
buy_number = buy_max_number;
}
this.setData({
goods_spec_base_price: this.goods.price,
goods_spec_base_original_price: this.goods.original_price || 0,
goods_spec_base_inventory: this.goods.inventory,
goods_spec_base_buy_min_number: 0,
goods_spec_base_buy_max_number: 0,
buy_number: buy_number,
});
return false;
}
// 获取数据
var data = this.params;
data['id'] = this.goods.id;
data['spec'] = JSON.stringify(spec);
data['stock'] = this.buy_number;
uni.request({
url: app.globalData.get_request_url('specdetail', 'goods'),
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
this.goods_spec_detail_back_handle(res.data.data);
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
// 商品规格详情返回数据处理
goods_spec_detail_back_handle(data) {
var spec_base = data.spec_base;
var buy_number = parseInt(this.buy_number);
var spec_buy_min_number = parseInt(spec_base.buy_min_number || 1);
var spec_buy_max_number = parseInt(spec_base.buy_max_number || 0);
if (spec_buy_min_number > 0 && buy_number < spec_buy_min_number) {
buy_number = spec_buy_min_number;
}
if (spec_buy_max_number > 0 && buy_number > spec_buy_max_number) {
buy_number = spec_buy_max_number;
}
this.setData({
goods_spec_base_price: spec_base.price,
goods_spec_base_original_price: spec_base.original_price || 0,
goods_spec_base_inventory: parseInt(spec_base.inventory || 0),
goods_spec_base_buy_min_number: spec_buy_min_number,
goods_spec_base_buy_max_number: spec_buy_max_number,
buy_number: buy_number,
});
// 调用父级
this.$emit("BackSuccessEvent", {
buy_number: this.buy_number,
base_price: this.goods_spec_base_price,
base_original_price: this.goods_spec_base_original_price,
base_inventory: this.goods_spec_base_inventory,
base_buy_min_number: this.goods_spec_base_buy_min_number,
base_buy_max_number: this.goods_spec_base_buy_max_number,
back_data: data
});
},
// 已选的商品规格
goods_selected_spec() {
var spec = [];
var temp_spec = this.goods_spec_choose;
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_active'] || null) != null) {
spec.push({
type: temp_spec[i]['name'],
value: temp_spec[i]['value'][k]['name'],
});
break;
}
}
}
return spec;
},
// 不能选择规格处理
spec_handle_dont(key, is_init = 0) {
var temp_spec = this.goods_spec_choose || [];
if (temp_spec.length <= 0) {
return false;
}
// 是否不能选择
key = parseInt(key);
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if (i > key) {
temp_spec[i]['value'][k]['is_dont'] = 'spec-dont-choose';
temp_spec[i]['value'][k]['is_disabled'] = '';
temp_spec[i]['value'][k]['is_active'] = '';
} else {
if (is_init == 1) {
temp_spec[i]['value'][k]['is_active'] = '';
}
}
// 当只有一个规格的时候
if (key == 0 && temp_spec.length == 1) {
temp_spec[i]['value'][k]['is_disabled'] = (temp_spec[i]['value'][k]['is_only_level_one'] || null) != null && parseInt(temp_spec[i]['value'][k]['inventory'] || 0) <= 0 ? 'spec-items-disabled' : '';
}
}
}
this.setData({
goods_spec_choose: temp_spec,
});
},
// 数量输入事件
goods_buy_number_blur(e) {
var number = parseInt(e.detail.value) || 1;
if (isNaN(number)) {
number = this.goods.buy_min_number || 1;
}
this.goods_buy_number_func(number);
},
// 数量操作事件
goods_buy_number_event(e) {
var type = parseInt(e.currentTarget.dataset.type || 0);
var temp_number = parseInt(this.buy_number);
var number = type == 0 ? temp_number - 1 : temp_number + 1;
this.goods_buy_number_func(number);
},
// 数量处理方法
goods_buy_number_func(number) {
var buy_min_number = parseInt(this.goods.buy_min_number || 1);
var buy_max_number = parseInt(this.goods.buy_max_number || 0);
var spec_buy_min_number = parseInt(this.goods_spec_base_buy_min_number || 0);
var spec_buy_max_number = parseInt(this.goods_spec_base_buy_max_number || 0);
var inventory = parseInt(this.goods_spec_base_inventory || 0);
var inventory_unit = this.goods.inventory_unit;
// 最小起购数量
var min = spec_buy_min_number > 0 ? spec_buy_min_number : buy_min_number;
if (min > 0 && number < min) {
number = min;
app.globalData.showToast(this.$t('recommend-detail.recommend-detail.265vyu') + min + inventory_unit);
}
// 最大购买数量
var max = spec_buy_max_number > 0 ? spec_buy_max_number : buy_max_number;
if (max > 0 && number > max) {
number = max;
app.globalData.showToast(this.$t('goods-category.goods-category.z1eh3v') + max + inventory_unit);
}
// 是否超过库存数量
if (number > inventory) {
number = inventory;
app.globalData.showToast(this.$t('recommend-detail.recommend-detail.2sis3v') + inventory + inventory_unit);
}
this.setData({ buy_number: number });
// 存在规格的时候是否已完全选择规格
var spec = this.goods_selected_spec();
var sku_count = this.goods_spec_choose.length;
var active_count = spec.length;
if (sku_count > 0 && active_count < sku_count) {
return false;
}
// 获取数据
var data = this.params;
data['id'] = this.goods.id;
data['spec'] = spec;
data['stock'] = this.buy_number;
uni.request({
url: app.globalData.get_request_url('stock', 'goods'),
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
this.goods_spec_detail_back_handle(res.data.data);
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
// 详情图片查看
goods_detail_images_view_event(e) {
var value = e.currentTarget.dataset.value || null;
if (value != null) {
uni.previewImage({
current: value,
urls: [value],
});
}
},
// 规格确认事件
spec_confirm_event(e = null) {
var user = app.globalData.get_user_info(this, 'spec_confirm_event');
if (user != false) {
// 规格
var temp_data = this.goods_spec_choose;
var sku_count = temp_data.length;
var active_count = 0;
var spec = [];
if (sku_count > 0) {
for (var i in temp_data) {
for (var k in temp_data[i]['value']) {
if ((temp_data[i]['value'][k]['is_active'] || null) != null) {
active_count++;
spec.push({
type: temp_data[i]['name'],
value: temp_data[i]['value'][k]['name'],
});
}
}
}
if (active_count < sku_count) {
app.globalData.showToast(this.$t('goods-detail.goods-detail.6brk57'));
return false;
}
}
// 操作类型
var type = (e == null) ? this.buy_event_type : (e.currentTarget.dataset.type || this.buy_event_type);
var value = (e == null) ? null : (e.currentTarget.dataset.value || null);
var business = (e == null) ? null : (e.currentTarget.dataset.business || null);
switch (type) {
// 展示型、商品页面规格选择展示型 拨打电话操作
case 'show':
case 'spec-show':
app.globalData.call_tel(value || app.globalData.get_config('config.common_app_customer_service_tel'));
break;
// 购买
case 'buy':
// 进入订单确认页面
var data = {
buy_type: 'goods',
goods_data: encodeURIComponent(
base64.encode(
JSON.stringify([
{
goods_id: this.goods.id,
stock: this.buy_number,
spec: spec,
},
])
)
)
};
// 转换数据
var data_params = encodeURIComponent(base64.encode(JSON.stringify(data)));
// 是否互联网医院插件-开处方
if(business == 'plugins-hospital') {
app.globalData.url_open('/pages/plugins/hospital/prescription/prescription?data=' + data_params);
} else {
// 默认进去订单确认页面
app.globalData.url_open('/pages/buy/buy?data=' + data_params);
}
// 关闭弹窗
this.popup_close_event();
break;
// 加入购物车
case 'cart':
this.goods_cart_event(spec);
break;
default:
app.globalData.showToast(this.$t('goods-buy.goods-buy.4maexq') + type + ')');
}
}
},
// 加入购物车事件
goods_cart_event(spec) {
var data = this.params;
data['goods_id'] = this.goods.id;
data['spec'] = JSON.stringify(spec);
data['stock'] = this.buy_number;
uni.request({
url: app.globalData.get_request_url('save', 'cart'),
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
// 是否成功提示
if (this.is_success_tips == 1) {
app.globalData.showToast(res.data.msg, 'success');
}
var cart_number = res.data.data.buy_number;
// 调用父级
this.$emit('CartSuccessEvent', {
goods_id: this.goods.id,
spec: spec,
stock: this.buy_number,
cart_number: cart_number,
back_data: this.back_data,
goods_spec_choose: this.goods_spec_choose,
});
// 是否返回定义来源返回
if (parseInt(this.params.is_opt_buy_status || 0) == 1 && this.is_opt_back == 1) {
setTimeout(function () {
uni.navigateBack();
}, 1000);
} else {
// 关闭购买弹窗窗口
this.popup_close_event();
}
} else {
if (app.globalData.is_login_check(res.data, this, 'spec_confirm_event')) {
app.globalData.showToast(res.data.msg);
}
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
},
},
};
</script>
<style>
.goods-spec-choice-container .close {
position: absolute;
top: 20rpx;
right: 20rpx;
z-index: 2;
}
.goods-spec-base {
height: 230rpx;
}
.goods-spec-base .spec-images {
width: 200rpx;
height: 200rpx;
position: absolute;
left: 0;
top: 0;
}
.goods-spec-base-content {
position: absolute;
left: 220rpx;
top: 0;
}
.goods-spec-choice-content {
max-height: 50vh;
overflow-y: scroll;
overflow-x: hidden;
margin-top: 20rpx;
}
.goods-spec-choice-container .item .spec .spec-btn {
background-color: #f5f5f5;
color: #666;
border: 1px solid #ccc;
}
.goods-spec-choice-container .item .spec .spec-btn:not(:last-child) {
margin-right: 25rpx;
}
.goods-spec-choice-container .item .spec .spec-btn .spec-images {
width: 40rpx;
height: 40rpx !important;
}
.goods-spec-choice-container .spec-dont-choose {
color: #b4b3b3 !important;
background-color: #ffffff !important;
border: 1px solid #ebeaea !important;
}
.goods-spec-choice-container .spec-dont-choose .spec-images {
opacity: 0.5;
}
.goods-spec-choice-container .spec-items-disabled {
color: #d2cfcf !important;
background-color: #ffffff !important;
border: 1px dashed #d5d5d5 !important;
}
.goods-spec-choice-container .spec-items-disabled .spec-images {
opacity: 0.3;
}
.goods-spec-choice-container .goods-buy-number {
height: 70rpx;
}
.goods-spec-choice-container .number-content {
position: absolute;
right: 20rpx;
top: 0;
background: #eee;
border: 1px solid #eee;
}
.goods-spec-choice-container .number-content .number-submit {
width: 80rpx;
font-weight: bold;
}
.goods-spec-choice-container .number-content .number-input {
width: 50px;
}
.goods-spec-choice-container .number-content .number-submit,
.goods-spec-choice-container .number-content .number-input {
padding: 0;
height: 60rpx;
line-height: 60rpx;
}
.goods-spec-choice-container .buy-nav-btn-number-0 .item,
.goods-spec-choice-container .buy-nav-btn-number-1 .item {
width: 100% !important;
}
.goods-spec-choice-container .buy-nav-btn-number-2 .item {
width: 50% !important;
}
.goods-spec-choice-container .buy-nav-btn-number-3 .item {
width: 33.33% !important;
}
.goods-spec-choice-container .buy-nav-btn-number-4 .item {
width: 25% !important;
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<view :class="theme_view">
<block v-if="(propData || null) != null && propData.length > 0">
<view v-for="(item, index) in propData" :key="index" class="goods-comment-item flex-row" :class="propClass">
<image class="avatar dis-block margin-right-xs" :src="item.user.avatar || common_static_url + 'default-user.png'" mode="aspectFit"></image>
<view class="base-nav flex-1 flex-width margin-left-sm" :class="propIsReply ? 'is-reply' : ''">
<view class="oh nav padding-bottom-sm">
<view class="">
<text class="va-m">{{ item.user.user_name_view }}</text>
<view class="dis-inline-block va-m margin-left-sm">
<uni-rate :value="item.rating" :readonly="true" :is-fill="false" :size="14" />
</view>
<view class="fr">
<text class="cr-grey text-size-xs">{{ item.add_time_date }}</text>
</view>
</view>
</view>
<view class="base-content oh padding-vertical-sm">
<view class="content cr-base text-size-sm">{{ item.content }}</view>
<view v-if="(item.images || null) != null && item.images.length > 0" class="image-list oh margin-top-lg">
<block v-for="(iv, ix) in item.images" :key="ix">
<image class="image br radius" @tap="comment_images_show_event" :data-index="index" :data-ix="ix" :src="iv" mode="aspectFit"></image>
</block>
</view>
<view v-if="(item.msg || null) != null" class="spec cr-grey margin-top-lg">{{ item.msg }}</view>
<block v-if="propIsReply">
<view v-if="item.is_reply == 1 && (item.reply || null) != null" class="reply br-t-dashed margin-top-sm padding-top-sm text-size-sm">
<text class="cr-base">{{$t('goods-comments.goods-comments.s65197')}}</text>
<text class="reply-desc cr-main-pair">{{ item.reply }}</text>
</view>
</block>
</view>
</view>
</view>
</block>
<block v-else>
<view class="tc spacing-mb flex-row jc-c align-c margin-top-xxxxl">
<image :src="common_static_url + 'no-comment.png'" mode="widthFix" class="no-comment margin-right-main" />
<view class="cr-grey-d">{{$t('goods-comments.goods-comments.1p1r2e')}}</view>
</view>
</block>
</view>
</template>
<script>
const app = getApp();
var common_static_url = app.globalData.get_static_url('common');
export default {
props: {
propData: {
type: [Array,String],
default: '',
},
// 是否需要显示管理员回复
propIsReply: {
type: Boolean,
default: false,
},
// 额外样式控制
propClass: {
type: String,
default: '',
},
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: common_static_url,
};
},
created: function () {},
methods: {
// 评价图片预览
comment_images_show_event(e) {
var index = e.currentTarget.dataset.index;
var ix = e.currentTarget.dataset.ix;
uni.previewImage({
current: this.propData[index]['images'][ix],
urls: this.propData[index]['images'],
});
},
},
};
</script>
<style scoped>
/**
* 商品评价
*/
.goods-comment-item {
padding-bottom: 10rpx;
margin-bottom: 20rpx;
}
.goods-comment-item .avatar {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
border: 1px solid #e2e2e2;
}
.goods-comment-item .base-nav {
border-bottom: 2rpx solid #f5f5f5;
}
.goods-comment-item .base-nav.is-reply {
border: 0;
}
.goods-comment-item:last-of-type {
margin-bottom: 0;
}
.goods-comment-item:last-of-type .base-nav {
border: 0;
}
.goods-comment-item .base-content .content,
.goods-comment-item .base-content .reply {
line-height: 46rpx;
}
.goods-comment-item .base-content .image-list .image {
width: 100rpx;
height: 100rpx;
}
.goods-comment-item .base-content .image-list .image:not(:last-child) {
margin-right: 10rpx;
}
.no-comment {
width: 174rpx;
}
</style>

View File

@@ -0,0 +1,395 @@
<template>
<view :class="theme_view">
<view class="plugins-goods" :class="data.style_type == 2 ? propStyleTypeTowClass : ''" v-if="(data || null) != null && (data.goods_list || null) != null && data.goods_list.length > 0">
<view v-if="(data.title || null) != null || (data.vice_title || null) != null" class="spacing-nav-title flex-row align-c jc-sb text-size-xs">
<view class="title-left">
<text v-if="(data.title || null) != null" class="text-wrapper" :class="data.style_type == 2 ? '' : 'title-left-border'" :style="'color:' + (data.color || '#333') + ';'">{{ data.title }}</text>
<text v-if="(data.vice_title || null) != null" class="vice-name margin-left-sm cr-grey-9">{{ data.vice_title }}</text>
</view>
<text v-if="(data[propMoreUrlKey] || null) != null" :data-value="data[propMoreUrlKey]" @tap="url_event" class="arrow-right padding-right cr-grey cp">{{ $t('common.more') }}</text>
</view>
<view class="wh-auto oh pr goods-list">
<!-- 默认图文 -->
<block v-if="(data.style_type || 0) == 0">
<view class="goods-data-list">
<view v-for="(item, index) in data.goods_list" :key="index" class="item oh padding-main border-radius-main bg-white oh pr spacing-mb">
<!-- 商品主体内容 -->
<view class="cp" :data-index="index" :data-value="item.goods_url" @tap="goods_event">
<image class="goods-img fl radius" :src="item.images" mode="aspectFit"></image>
<view class="base fr">
<view class="multi-text">{{ item.title }}</view>
<view v-if="(item.simple_desc || null) != null" class="cr-grey single-text margin-top-sm text-size-sm">{{ item.simple_desc }}</view>
<view v-if="(item.show_field_price_status || 0) == 1"class="flex-row jc-sb align-c margin-top-main pr">
<block v-if="(propPriceField || null) != null && item[propPriceField] != undefined">
<view class="base-bottom">
<text v-if="propIsShowPriceIcon && (item.price_icon || null) != null" class="bg-red br-red cr-white text-size-xs padding-left-sm padding-right-sm round va-m margin-right-xs">{{ item.price_icon }}</text>
<text class="sales-price va-m text-size-xs va-m">{{ item.show_price_symbol }}</text>
<text class="sales-price va-m">{{ item[propPriceField] }}</text>
<text class="cr-grey text-size-xs va-m">{{ item.show_price_unit }}</text>
</view>
</block>
<block v-if="(item.is_error || 0) == 0 && is_show_cart">
<view v-if="propOpenCart" class="bg-white right-cart-icon pr" :data-index="index" @tap.stop="goods_cart_event">
<iconfont name="icon-cart-inc" size="48rpx" :color="theme_color"></iconfont>
<view class="cart-badge-icon pa">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</block>
</view>
</view>
</view>
<!-- 标签插件 -->
<view v-if="(propLabel || null) != null && propLabel.data.length > 0" :class="'plugins-label oh pa plugins-label-' + ((propLabel.base.is_user_goods_label_icon || 0) == 0 ? 'text' : 'img') + ' plugins-label-' + (propLabel.base.user_goods_show_style || 'top-left')">
<block v-for="(lv, li) in propLabel.data" :key="li">
<view v-if="(lv.goods_ids || null) != null && lv.goods_ids.indexOf(item.id) != -1" class="lv dis-inline-block va-m" :data-value="(propLabel.base.is_user_goods_label_url || 0) == 1 ? lv.url || '' : ''" @tap="url_event">
<view v-if="(propLabel.base.is_user_goods_label_icon || 0) == 0" class="round cr-white bg-main text-size-xs fl" :style="('background-color:'+(lv.bg_color || '#666'))+' !important;'+('color:'+(lv.text_color || '#fff'))+' !important;'">
{{ lv.name }}
</view>
<block v-else>
<image v-if="(lv.icon || null) != null" class="dis-block" :src="lv.icon" mode="scaleToFill"></image>
</block>
</view>
</block>
</view>
</view>
</view>
</block>
<!-- 九方格 -->
<block v-else-if="data.style_type == 1">
<view class="goods-data-grid-list flex-row flex-wrap">
<view v-for="(item, index) in data.goods_list" :key="index" class="item oh border-radius-main bg-white oh pr spacing-mb">
<!-- 商品主体内容 -->
<view class="cp" :data-index="index" :data-value="item.goods_url" @tap="goods_event">
<image class="goods-img dis-block wh-auto" :src="item.images" mode="widthFix"></image>
<view class="base padding-horizontal-main margin-top-sm">
<view class="goods-title multi-text">{{ item.title }}</view>
<view v-if="(item.show_field_price_status || 0) == 1" class="margin-top-sm flex-row jc-sb align-c pr">
<view :class="propIsOpenGridBtnSet ? 'open-grid-btn' : ''">
<block v-if="(propPriceField || null) != null && item[propPriceField] != undefined">
<text v-if="propIsShowPriceIcon && (item.price_icon || null) != null" class="bg-red br-red cr-white text-size-xs padding-left-sm padding-right-sm round va-m margin-right-xs">{{ item.price_icon }}</text>
<text class="sales-price va-m text-size-xs va-m">{{ item.show_price_symbol }}</text>
<text class="sales-price va-m">{{ item[propPriceField] }}</text>
<text class="cr-grey text-size-xs va-m">{{ item.show_price_unit }}</text>
</block>
</view>
<block v-if="propIsOpenGridBtnSet">
<view class="pa bottom-0 right-0" :disabled="grid_btn_config.disabled" @tap="url_event" :style="[{ color: grid_btn_config.color }, { 'background-color': grid_btn_config.bg_color }, { padding: grid_btn_config.padding }, { 'border-radius': grid_btn_config.border_radius }, { 'font-size': grid_btn_config.font_size }]">
{{ grid_btn_config.name }}
</view>
</block>
<block v-else>
<block v-if="(item.is_error || 0) == 0 && is_show_cart">
<view v-if="propOpenCart" class="bg-white pr" :data-index="index" @tap.stop="goods_cart_event">
<iconfont name="icon-cart-inc" size="48rpx" :color="theme_color"></iconfont>
<view class="cart-badge-icon pa">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</block>
</block>
</view>
</view>
</view>
<!-- 标签插件 -->
<view v-if="(propLabel || null) != null && propLabel.data.length > 0" :class="'plugins-label oh pa plugins-label-' + ((propLabel.base.is_user_goods_label_icon || 0) == 0 ? 'text' : 'img') + ' plugins-label-' + (propLabel.base.user_goods_show_style || 'top-left')">
<block v-for="(lv, li) in propLabel.data" :key="li">
<view v-if="(lv.goods_ids || null) != null && lv.goods_ids.indexOf(item.id) != -1" class="lv dis-inline-block va-m" :data-value="(propLabel.base.is_user_goods_label_url || 0) == 1 ? lv.url || '' : ''" @tap="url_event">
<view v-if="(propLabel.base.is_user_goods_label_icon || 0) == 0" class="round cr-white bg-main text-size-xs fl" :style="('background-color:'+(lv.bg_color || '#666'))+' !important;'+('color:'+(lv.text_color || '#fff'))+' !important;'">
{{ lv.name }}
</view>
<block v-else>
<image v-if="(lv.icon || null) != null" class="dis-block" :src="lv.icon" mode="scaleToFill"></image>
</block>
</view>
</block>
</view>
</view>
</view>
</block>
<!-- 滚动 -->
<view v-else-if="data.style_type == 2" class="rolling-horizontal border-radius-main oh">
<view class="goods-data-rolling-list scroll-view-horizontal">
<swiper class="swiper" :vertical="false" :autoplay="propIsAutoPlay" :circular="false" :display-multiple-items="(data.multiple_items || null) == null ? (data.goods_list.length < 3 ? data.goods_list.length : 3) : data.goods_list.length < data.multiple_items ? data.goods_list.length : data.multiple_items" interval="3000">
<block v-for="(item, index) in data.goods_list" :key="index">
<swiper-item>
<view class="item bg-white border-radius-main margin-right-main oh pr ht-auto pr">
<!-- 商品主体内容 -->
<view class="cp" :data-index="index" :data-value="item.goods_url" @tap="goods_event">
<image class="goods-img dis-block wh-auto" :src="item.images" mode="scaleToFill"></image>
<view class="padding-left-sm padding-right-sm margin-top-sm">
<view class="single-text text-size-xs">{{ item.title }}</view>
<view v-if="(item.show_field_price_status || 0) == 1" class="margin-top-xs flex-row align-c">
<block v-if="(item.is_error || 0) == 0 && is_show_cart">
<view v-if="propOpenCart" class="bg-white right-cart-icon pr" :data-index="index" @tap.stop="goods_cart_event">
<iconfont name="icon-cart-inc" size="28rpx" :color="theme_color" propClass="pr top-xs margin-right-xs"></iconfont>
<view class="cart-badge-icon pa">
<component-badge :propNumber="item.user_cart_count || 0"></component-badge>
</view>
</view>
</block>
<block v-if="(propPriceField || null) != null && item[propPriceField] != undefined">
<view class="flex-1 flex-width">
<text v-if="propIsShowPriceIcon && (item.price_icon || null) != null" class="bg-red br-red cr-white text-size-xs padding-left-sm padding-right-sm round va-m margin-right-xs">{{ item.price_icon }}</text>
<text class="sales-price va-m text-size-xs va-m">{{ item.show_price_symbol }}</text>
<text class="sales-price va-m">{{ item[propPriceField] }}</text>
<text class="cr-grey text-size-xs va-m">{{ item.show_price_unit }}</text>
</view>
</block>
</view>
</view>
</view>
<!-- 标签插件 -->
<view v-if="(propLabel || null) != null && propLabel.data.length > 0" :class="'plugins-label oh pa plugins-label-' + ((propLabel.base.is_user_goods_label_icon || 0) == 0 ? 'text' : 'img') + ' plugins-label-' + (propLabel.base.user_goods_show_style || 'top-left')">
<block v-for="(lv, li) in propLabel.data" :key="li">
<view v-if="(lv.goods_ids || null) != null && lv.goods_ids.indexOf(item.id) != -1" class="lv dis-inline-block va-m" :data-value="(propLabel.base.is_user_goods_label_url || 0) == 1 ? lv.url || '' : ''" @tap="url_event">
<view v-if="(propLabel.base.is_user_goods_label_icon || 0) == 0" class="round cr-white bg-main text-size-xs fl" :style="('background-color:'+(lv.bg_color || '#666'))+' !important;'+('color:'+(lv.text_color || '#fff'))+' !important;'">
{{ lv.name }}
</view>
<block v-else>
<image v-if="(lv.icon || null) != null" class="dis-block" :src="lv.icon" mode="scaleToFill"></image>
</block>
</view>
</block>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
</view>
</view>
<!-- 商品购买 -->
<component-goods-buy v-if="is_show_cart" ref="goods_buy" v-on:CartSuccessEvent="goods_cart_back_event"></component-goods-buy>
<!-- 购物车抛物线 -->
<component-cart-para-curve v-if="is_show_cart" ref="cart_para_curve"></component-cart-para-curve>
</view>
</template>
<script>
const app = getApp();
import componentBadge from '@/components/badge/badge';
import componentGoodsBuy from '@/components/goods-buy/goods-buy';
import componentCartParaCurve from '@/components/cart-para-curve/cart-para-curve';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data: null,
is_show_cart: false,
theme_color: app.globalData.get_theme_color(),
grid_btn_config: {
bg_color: '#D8D8D8',
color: '#fff',
name: this.$t('goods-list.goods-list.nr77jf'),
disabled: false,
border_radius: '24rpx',
padding: '6rpx 16rpx',
font_size: '24rpx',
},
};
},
components: {
componentBadge,
componentGoodsBuy,
componentCartParaCurve,
},
props: {
// 价格符号
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
// 列表数据
propData: {
type: [Array, Object],
default: [],
},
// 模式2默认class
propStyleTypeTowClass: {
type: String,
default: 'bg-white border-radius-main padding-main spacing-mb',
},
// 更多url地址
propMoreUrlKey: {
type: String,
default: 'url',
},
// 关键字url地址
propKeywordsUrl: {
type: String,
default: '/pages/goods-search/goods-search?keywords=',
},
// 滚动自动播放
propIsAutoPlay: {
type: Boolean,
default: true,
},
// 标签数据
propLabel: {
type: [Array, Object, String],
default: null,
},
// 展示加购抛物线
propIsCartParaCurve: {
type: Boolean,
default: false,
},
// 展示价格icon图标
propIsShowPriceIcon: {
type: Boolean,
default: false,
},
// 展示价格字段
propPriceField: {
type: String,
default: 'min_price',
},
// 来源
propSource: {
type: String,
default: '',
},
// 当开启时以前的按钮将失效
propIsOpenGridBtnSet: {
type: Boolean,
default: false,
},
// 九宫格 按钮样式, ----- 需要和propIsOpenGridBtnSet一起使用
propGridBtnConfig: {
type: Object,
default: () => {
return {};
},
},
// 是否开启购物车按钮 ------ 滚动使用
propOpenCart: {
type: Boolean,
default: true,
},
// 是否开启购物车数量同步到底部导航
propIsCartNumberTabBarBadgeSync: {
type: Boolean,
default: true,
},
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.setData({
data: value,
});
},
propIsOpenGridBtnSet(newVal, oldVal) {
if (newVal !== oldVal) {
this.setData({
grid_btn_config: Object.assign({}, this.grid_btn_config, this.propGridBtnConfig),
});
}
},
},
// 页面被展示
created: function () {
var is_app_mourning = app.globalData.is_app_mourning();
var is_show_cart = app.globalData.data.is_goods_list_show_cart_opt == 1 ? (is_app_mourning && this.propSource == 'index' ? false : true) : false;
this.setData({
data: this.propData,
is_show_cart: is_show_cart,
grid_btn_config: Object.assign({}, this.grid_btn_config, this.propGridBtnConfig),
});
},
methods: {
// 加入购物车
goods_cart_event(e) {
if ((this.$refs.goods_buy || null) != null) {
var index = e.currentTarget.dataset.index || 0;
var goods = this.propData.goods_list[index];
// 开启购物车抛物线效果则展示提示操作
var is_success_tips = this.propIsCartParaCurve ? 0 : 1;
this.$refs.goods_buy.init(
goods,
{
buy_event_type: 'cart',
is_direct_cart: 1,
is_success_tips: is_success_tips,
},
{
index: index,
pos: e,
}
);
}
},
// 加入购物车成功回调
goods_cart_back_event(e) {
// 增加数量
var back = e.back_data;
var new_data = this.data;
var goods = new_data['goods_list'][back.index];
goods['user_cart_count'] = parseInt(goods['user_cart_count'] || 0) + parseInt(e.stock);
if (goods['user_cart_count'] > 99) {
goods['user_cart_count'] = '99+';
}
new_data['goods_list'][back.index] = goods;
this.setData({
data: new_data,
});
// 抛物线
if (this.propIsCartParaCurve && (this.$refs.cart_para_curve || null) != null) {
this.$refs.cart_para_curve.init(null, back.pos, goods.images);
}
// 购物车总数
var cart_total = e.cart_number || 0;
// 购物车导航角标
if (this.propIsCartNumberTabBarBadgeSync) {
app.globalData.set_tab_bar_badge('cart', cart_total);
}
// 当前页面
var page = app.globalData.current_page().split('?');
switch (page[0]) {
// 商品详情页面
case 'pages/goods-detail/goods-detail':
// 商品搜索
case 'pages/goods-search/goods-search':
var res = app.globalData.get_page_object(page[0]);
if (res.length > 0) {
for (var i in res) {
res[i].$vm.goods_cart_count_handle(cart_total);
}
}
break;
}
this.$emit('CartSuccessEvent', { ...e, ...{ goods_list: new_data.goods_list, goods: goods } });
},
// 商品事件
goods_event(e) {
// 商品数据缓存处理
var goods = this.data.goods_list[e.currentTarget.dataset.index];
app.globalData.goods_data_cache_handle(goods.id, goods);
// 调用公共打开url地址
app.globalData.url_event(e);
},
// url事件
url_event(e) {
app.globalData.url_event(e);
},
// 购物车角标变化回调
goods_badge_change() {
this.$emit('goods_badge_change');
},
},
};
</script>
<style></style>

View File

@@ -0,0 +1,362 @@
<template>
<view :class="theme_view">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event" :propIndex="propIndex">
<view class="goods-spec-choice-container padding-main bg-white pr">
<view class="close fr oh">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="goods-spec-choice-content">
<!-- 商品规格 -->
<view v-if="spec.length > 0" class="goods-spec-choose">
<view v-for="(item, key) in spec" :key="key" class="item padding-top-xxl padding-bottom-xxl">
<view class="text-size-sm">{{item.name}}</view>
<view v-if="item.value.length > 0" class="spec margin-top-sm">
<block v-for="(items, keys) in item.value" :key="keys">
<button @tap.stop="goods_spec_choice_event" :data-key="key" :data-keys="keys" type="default" size="mini" hover-class="none" :class="'round '+items.is_active + ' ' + items.is_dont + ' ' + items.is_disabled">
<image v-if="(items.images || null) != null" :src="items.images" mode="scaleToFill" class="va-m dis-inline-block round margin-right-sm"></image>
<text class="va-m">{{items.name}}</text>
</button>
</block>
</view>
</view>
</view>
</view>
<button class="bg-main br-main cr-white text-size-sm round" type="default" @tap.stop="spec_confirm_event" hover-class="none">{{$t('index.index.7w75zb')}}</button>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import componentPopup from "@/components/popup/popup";
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
popup_status: false,
goods_id: 0,
spec: [],
buy_min_number: 1,
out_value: ''
};
},
components: {
componentPopup
},
props: {
propIndex: {
type: Number,
default: 100
}
},
created: function() {},
methods: {
// 获取数据
init(goods_id, spec, buy_min_number = 1, out_value = '') {
this.setData({
popup_status: true,
goods_id: goods_id,
spec: spec || [],
buy_min_number: buy_min_number || 1,
out_value: out_value,
});
// 不能选择规格处理
this.spec_handle_dont(0, 1);
},
// 购买弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false
});
},
// 规格事件
goods_spec_choice_event(e) {
var key = e.currentTarget.dataset.key || 0;
var keys = e.currentTarget.dataset.keys || 0;
this.goods_spec_choice_handle(key, keys);
},
// 规格选择处理
goods_spec_choice_handle(key, keys) {
var temp_spec = this.spec;
// 不能选择和禁止选择跳过
if ((temp_spec[key]['value'][keys]['is_dont'] || null) == null && (temp_spec[key]['value'][keys]['is_disabled'] || null) == null) {
// 规格选择
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_dont'] || null) == null && (temp_spec[i]['value'][k]['is_disabled'] || null) == null) {
if (key == i) {
if (keys == k && (temp_spec[i]['value'][k]['is_active'] || null) == null) {
temp_spec[i]['value'][k]['is_active'] = 'cr-white bg-main br-main';
} else {
temp_spec[i]['value'][k]['is_active'] = '';
}
}
}
}
}
this.setData({
spec: temp_spec
});
// 不能选择规格处理
this.spec_handle_dont(key);
// 获取下一个规格类型
this.get_spec_type(key);
// 获取规格详情
this.get_spec_detail();
}
},
// 获取下一个规格类型
get_spec_type(key) {
var temp_spec = this.spec;
var active_index = parseInt(key) + 1;
var sku_count = app.globalData.get_length(temp_spec);
if (active_index <= 0 || active_index >= sku_count) {
return false;
}
// 获取规格值
var spec = [];
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_active'] || null) != null) {
spec.push({
type: temp_spec[i]['name'],
value: temp_spec[i]['value'][k]['name']
});
break;
}
}
}
if (spec.length <= 0) {
return false;
}
// 获取数据
uni.request({
url: app.globalData.get_request_url('spectype', 'goods'),
method: 'POST',
data: {
id: this.goods_id,
spec: JSON.stringify(spec)
},
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
var spec_type = res.data.data.spec_type;
var spec_count = spec.length;
var index = spec_count > 0 ? spec_count : 0;
if (index < sku_count) {
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if (index == i) {
temp_spec[i]['value'][k]['is_dont'] = '';
var temp_value = temp_spec[i]['value'][k]['name'];
var temp_status = false;
for (var t in spec_type) {
if (spec_type[t] == temp_value) {
temp_status = true;
break;
}
}
if (temp_status == true) {
temp_spec[i]['value'][k]['is_disabled'] = '';
} else {
temp_spec[i]['value'][k]['is_disabled'] = 'spec-items-disabled';
}
}
}
}
this.setData({
spec: temp_spec
});
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
}
});
},
// 获取规格详情
get_spec_detail() {
// 获取规格值
var spec = this.goods_selected_spec();
// 存在规格的时候是否已完全选择规格
var sku_count = this.spec.length;
var active_count = spec.length;
if (spec.length <= 0 || active_count < sku_count) {
return false;
}
// 获取数据
uni.request({
url: app.globalData.get_request_url('specdetail', 'goods'),
method: 'POST',
data: {
id: this.goods_id,
spec: JSON.stringify(spec),
stock: this.buy_min_number
},
dataType: 'json',
success: res => {
if (res.data.code != 0) {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('common.internet_error_tips'));
}
});
},
// 已选的商品规格
goods_selected_spec() {
var spec = [];
var temp_spec = this.spec;
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_active'] || null) != null) {
spec.push({
type: temp_spec[i]['name'],
value: temp_spec[i]['value'][k]['name']
});
break;
}
}
}
return spec;
},
// 不能选择规格处理
spec_handle_dont(key, is_init = 0) {
var temp_spec = this.spec || [];
if (temp_spec.length <= 0) {
return false;
}
// 是否不能选择
key = parseInt(key);
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if (i > key) {
temp_spec[i]['value'][k]['is_dont'] = 'spec-dont-choose';
temp_spec[i]['value'][k]['is_disabled'] = '';
temp_spec[i]['value'][k]['is_active'] = '';
} else {
if(is_init == 1) {
temp_spec[i]['value'][k]['is_active'] = '';
}
}
// 当只有一个规格的时候
if (key == 0 && temp_spec.length == 1) {
temp_spec[i]['value'][k]['is_disabled'] = (temp_spec[i]['value'][k]['is_only_level_one'] || null) != null && (temp_spec[i]['value'][k]['inventory'] || 0) <= 0 ? 'spec-items-disabled' : '';
}
}
}
this.setData({
spec: temp_spec
});
},
// 规格确认事件
spec_confirm_event(e) {
var temp_spec = this.spec;
var sku_count = temp_spec.length;
var active_count = 0;
var spec = [];
if (sku_count > 0) {
for (var i in temp_spec) {
for (var k in temp_spec[i]['value']) {
if ((temp_spec[i]['value'][k]['is_active'] || null) != null) {
active_count++;
spec.push({
type: temp_spec[i]['name'],
value: temp_spec[i]['value'][k]['name']
});
}
}
}
if (active_count < sku_count) {
app.globalData.showToast(this.$t('goods-detail.goods-detail.6brk57'));
return false;
}
}
// 关闭弹窗
this.setData({
popup_status: false
});
// 调用父级
this.$emit('specConfirmEvent', {
goods_id: this.goods_id,
spec: spec,
stock: this.buy_min_number,
out_value: this.out_value,
});
}
}
};
</script>
<style>
.goods-spec-choice-container .close {
position: absolute;
top: 20rpx;
right: 20rpx;
z-index: 2;
}
.goods-spec-choice-content {
max-height: 50vh;
overflow-y: scroll;
overflow-x: hidden;
margin-top: 20rpx;
}
.goods-spec-choice-container .item .spec button {
background-color: #f5f5f5;
color: #666;
border: 1px solid #ccc;
}
.goods-spec-choice-container .item .spec button:not(:last-child) {
margin-right: 25rpx;
}
.goods-spec-choice-container .item .spec button image {
width: 40rpx;
height: 40rpx !important;
}
.goods-spec-choice-container .spec-dont-choose {
color: #b4b3b3 !important;
background-color: #ffffff !important;
border: 1px solid #ebeaea !important;
}
.goods-spec-choice-container .spec-dont-choose image {
opacity: 0.5;
}
.goods-spec-choice-container .spec-items-disabled {
color: #d2cfcf !important;
background-color: #ffffff !important;
border: 1px dashed #d5d5d5 !important;
}
.goods-spec-choice-container .spec-items-disabled image {
opacity: 0.3;
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<view :class="theme_view">
<view v-if="(propData || null) != null && (propData.data || null) != null && propData.data.length > 0 && swiper_data.length > 0" class="icon-nav-list" :class="propData.data.length > 5 ? 'swiper-height-max' : 'swiper-height-min'">
<uni-swiper-dot class="uni-swiper-dot-box" mode="default" :dots-styles="dots_styles" @clickItem="click_item" :info="swiper_data" :current="current">
<swiper class="swiper-box" :autoplay="autoplay" :duration="duration" @change="swiper_change" :current="swiper_dot_index">
<swiper-item v-for="(swiper_item_data, i) in swiper_data" :key="i">
<view class="swiper-item flex-row flex-wrap" :class="'swiper-item' + i">
<view v-for="(item, j) in swiper_item_data" :key="j" class="swiper-item item">
<view :class="'item-content ' + ((item.bg_color || null) == null ? 'item-exposed' : '')" :data-value="item.event_value" :data-type="item.event_type" @tap="navigation_event" :style="(item.bg_color || null) == null ? '' : 'background-color:' + item.bg_color + ';'">
<image class="image" :src="item.images_url" mode="aspectFit"></image>
</view>
<view class="title">{{ item.name }}</view>
</view>
</view>
</swiper-item>
</swiper>
</uni-swiper-dot>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
swiper_data: [],
autoplay: false,
duration: 500,
styleIndex: -1,
current: 0,
swiper_dot_index: 0,
dots_styles: {
backgroundColor: '#eee',
bottom: '0',
border: '0',
color: '#fff',
selectedBackgroundColor: app.globalData.get_theme_color(),
selectedBorder: '0',
},
};
},
components: {},
props: {
propData: {
type: [Array, Object],
default: [],
},
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.handle_data();
}
},
beforeMount() {
this.handle_data();
},
methods: {
navigation_event(e) {
app.globalData.operation_event(e);
},
// 数据处理
handle_data() {
if((this.propData || null) != null && (this.propData.data || null) != null && this.propData.data.length > 0) {
this.swiper_data = app.globalData.group_arry(this.propData.data, 10);
}
},
swiper_change(e) {
this.current = e.detail.current;
},
click_item(e) {
this.swiper_dot_index = e;
},
},
};
</script>
<style scoped>
.icon-nav-list {
overflow: hidden;
padding: 20rpx;
}
.icon-nav-list .item {
width: 20%;
float: left;
padding: 16rpx 0;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.icon-nav-list .item .item-content {
border-radius: 50%;
padding: 20rpx;
text-align: center;
margin: 0 auto;
-webkit-box-shadow: 0 2px 12px rgb(226 226 226 / 95%);
box-shadow: 0 2px 12px rgb(226 226 226 / 95%);
}
.icon-nav-list .item .item-content,
.icon-nav-list .item .image {
width: 50rpx !important;
height: 50rpx !important;
}
.icon-nav-list .item .item-exposed {
padding: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.icon-nav-list .item .item-exposed,
.icon-nav-list .item .item-exposed .image {
width: 90rpx !important;
height: 90rpx !important;
}
.icon-nav-list .item .title {
margin-top: 12rpx;
font-size: 28rpx;
text-align: center;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
color: #666;
}
.swiper-height-min /deep/ .swiper-box {
height: 180rpx;
}
.swiper-height-max /deep/ .swiper-box {
height: 360rpx;
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<view class="iconfont-container" :class="propClass" :style="'display:'+propContainerDisplay+';'">
<text class="iconfont" :class="name" :style="[{ color: color }, { 'font-size': size }]" @tap="$emit('click', $event)"></text>
</view>
</template>
<script>
export default {
props: {
name: {
type: String,
default: '',
},
color: {
type: String,
default: '',
},
size: {
type: String,
default: '28rpx',
},
propClass: {
type: String,
default: '',
},
propContainerDisplay: {
type: String,
default: 'inline-block',
}
},
};
</script>
<style scoped>
/* iconfont.css全局注册需要将src切换成绝对路径 */
/* @/static/icon/ */
@import url('@/static/icon/iconfont.css');
/* @import url('https://at.alicdn.com/t/c/font_4227145_kbr2f9jt68b.css'); */
.iconfont {
display: flex;
font-size: inherit;
overflow: hidden;
/* 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果 */
vertical-align: -0.15em;
outline: none;
/* 定义元素的颜色currentColor是一个变量这个变量的值就表示当前元素的color值如果当前元素未设置color值则从父元素继承 */
fill: currentcolor;
}
</style>

1359
components/layout/layout.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,246 @@
<template>
<view :class="theme_view">
<view v-if="(data_goods_list || null) != null && data_goods_list.length > 0">
<view v-for="(item, index) in data_goods_list" :key="index" class="plugins-magic-content border-radius-main oh spacing-mb">
<view v-if="(item.data || null) != null && item.data.length > 0" :style="(item.bg_images || null) !== null ? 'background-image: url(' + item.bg_images + ');background-size: auto 100%;' : (((item.is_text_white || 0) == 1) ? 'background: linear-gradient(180deg, '+theme_color+' 0%, '+theme_color_light+' 80%);' : 'background: #fff;')">
<!-- 上下滚动 -->
<view v-if="item.show_style == 0" :class="'hot-list flex-row flex-wrap padding-vertical-main '+(((item.is_text_white || 0) == 1) ? 'is-text-white' : '')">
<block v-for="(items, indexs) in item.data" :key="indexs">
<block v-if="(items.data || null) != null && items.data.length > 0">
<view :class="'group-item '+(item.data.length%2 != 0 && item.data.length-1 === indexs ? 'wh-auto' : 'flex-width-half')">
<view class="padding-horizontal-main">
<view class="flex-row align-c" :data-value="items.url" @tap="url_event">
<text :class="'text-size fw-b single-text cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'black')">{{ items.title }}</text>
<view class="hot-go margin-left-sm">
<block v-if="(items.icon || null) !== null">
<image :src="items.icon" mode="heightFix" class="ht-auto"></image>
</block>
</view>
</view>
<view :class="'single-text text-size-xs margin-top-xs cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'grey-9')">{{ items.describe }}</view>
<swiper class="swiper-list border-radius-main oh" circular :autoplay="(items.rolling_time || null) !== null ? true : false" :vertical="propVertical" :interval="(items.rolling_time || null) !== null ? Number(items.rolling_time) * 1000 : '6000'" :duration="propDuration">
<swiper-item v-for="(itemss, indexss) in items.data" :key="indexss">
<view class="swiper-item">
<view :class="'flex-row gap-10 goods-item-number-'+itemss.length">
<view v-for="(gv, gi) in itemss" :key="gi" :class="'bg-white border-radius-main oh flex-width-half '+(itemss.length % 2 != 0 && itemss.length-1 == gi ? 'wh-auto' : '')">
<view class="tc" :data-index="index" :data-indexs="indexs" :data-indexss="indexss" :data-gi="gi" :data-value="(gv.goods_url || null) !== null ? gv.goods_url : ''" @tap="goods_event">
<image :src="(gv.images || null) !== null ? gv.images : ''" :mode="itemss.length % 2 != 0 && itemss.length-1 == gi ? 'aspectFit' : 'scaleToFill'" :class="'swiper-img wh-auto dis-block '+(((item.is_text_white || 0) == 1) ? '' : 'border-radius-main')"> </image>
<view v-if="(gv.show_field_price_status || 0) == 1" :class="'price tc single-text text-line-1 '+(((item.is_text_white || 0) == 1) ? 'padding-horizontal-xs padding-bottom-xs' : '')">
<text class="sales-price va-m text-size-xss va-b">{{ gv.show_price_symbol }}</text>
<text class="sales-price va-m text-size-xs">{{ gv.min_price }}</text>
<text class="va-m text-size-xss cr-grey">{{ gv.show_price_unit }}</text>
</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</block>
</block>
</view>
<!-- 1切换滚动2切换九宫格3切换滚动 -->
<view v-else-if="item.show_style == 1 || item.show_style == 2 || item.show_style == 3" class="switch-tabs-item-list pr padding-top-main">
<view class="scroll-view-horizontal padding-left-main">
<scroll-view :scroll-x="true" :show-scrollbar="false" :scroll-with-animation="true" :scroll-into-view="'switch-tabs-item-' + (show_style1_active_index[index] || 0)">
<block v-for="(items, indexs) in item.data" :key="indexs">
<view :class="'tc cp dis-inline-block '+(indexs > 0 ? 'margin-left-xxl' : '')" :id="'switch-tabs-item-' + indexs" :data-index="index" :data-indexs="indexs" @tap="switch_tabs_event">
<image v-if="(items.icon || null) != null" :src="items.icon" class="switch-tabs-item-icon va-m margin-right-xs" mode="aspectFit"></image>
<text :class="'text-size va-m cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'black')+' '+((show_style1_active_index[index] || 0) == indexs ? 'fw-b cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'main') : '')">{{ items.title }} </text>
<view class="lh-xs">
<iconfont name="icon-down-mark" size="36rpx" :color="(show_style1_active_index[index] || 0) == indexs ? (((item.is_text_white || 0) == 1) ? '#fff' : theme_color) : 'transparent'" propClass="lh-xs"></iconfont>
</view>
</view>
</block>
</scroll-view>
</view>
<block v-for="(items, indexs) in item.data" :key="indexs">
<view v-if="(show_style1_active_index[index] || 0) == indexs">
<view v-if="(items.url || null) != null" :data-value="items.url" @tap="url_event" class="padding-right cp pa top-xxxxxl right-0">
<text :class="'text-size-xs va-m cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'grey')">{{ $t('common.more') }}</text>
<view class="dis-inline-block va-m lh-xs">
<iconfont name="icon-arrow-right" size="24rpx" :color="((item.is_text_white || 0) == 1) ? '#f5f5f5' : '#999'"></iconfont>
</view>
</view>
<view v-if="(items.describe || null) != null" :class="'text-size-xs single-text padding-vertical-sm padding-horizontal-main cr-'+(((item.is_text_white || 0) == 1) ? 'white' : 'grey')">{{items.describe}}</view>
<view :class="(item.show_style == 1 || item.show_style == 2) ? 'padding-horizontal-main padding-top-sm' : (item.show_style == 3 ? 'padding-top-xs padding-bottom-main' : '')">
<component-goods-list :propData="{ style_type: parseInt(item.show_style)-1, goods_list: items.goods_list }" :propLabel="propLabel" :propCurrencySymbol="propCurrencySymbol" :propIsAutoPlay="(items.rolling_time || null) != null" :propIsCartParaCurve="true" propSource="index" propStyleTypeTowClass="border-radius-main padding-horizontal-main padding-top-xs"></component-goods-list>
</view>
</view>
</block>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import componentGoodsList from '@/components/goods-list/goods-list';
export default {
name: 'recommend-hot',
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
theme_color: app.globalData.get_theme_color(),
theme_color_light: app.globalData.get_theme_color(null, true),
data_goods_list: [],
show_style1_active_index: {},
};
},
props: {
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
propData: {
type: Object,
default: () => {
return {};
},
},
propVertical: {
type: Boolean,
default: true,
},
propDuration: {
type: Number,
default: 1000,
},
// 标签数据
propLabel: {
type: [Array, Object, String],
default: null,
}
},
components: {
componentGoodsList
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.set_data(value);
}
},
mounted() {
this.set_data(this.propData);
},
methods: {
// 商品事件
goods_event(e) {
// 商品数据缓存处理
var index = e.currentTarget.dataset.index;
var indexs = e.currentTarget.dataset.indexs;
var indexss = e.currentTarget.dataset.indexss;
var gi = e.currentTarget.dataset.gi;
var goods = this.data_goods_list[index]['data'][indexs]['data'][indexss][gi];
app.globalData.goods_data_cache_handle(goods.id, goods);
// 调用公共打开url地址
app.globalData.url_event(e);
},
// url事件
url_event(e) {
app.globalData.url_event(e);
},
// tabs切换
switch_tabs_event(e) {
var temp = this.show_style1_active_index || {};
temp[e.currentTarget.dataset.index] = e.currentTarget.dataset.indexs;
this.setData({
show_style1_active_index: temp
});
},
// 轮播数据处理
set_data(data) {
let goods = data.goods;
goods.forEach((item) => {
switch(parseInt(item.show_style || 0)) {
// 上下滚动数据处理
case 0 :
item.data.forEach((items, indexs) => {
let swiper_data = [];
if (item.data.length % 2 == 0) {
// 偶数
swiper_data = app.globalData.group_arry(items.goods_list, 2);
} else {
// 奇数
if (item.data.length === indexs + 1) {
swiper_data = app.globalData.group_arry(items.goods_list, 4);
} else {
swiper_data = app.globalData.group_arry(items.goods_list, 2);
}
}
items.data = swiper_data;
});
break;
}
});
this.setData({
data_goods_list: goods,
});
},
},
};
</script>
<style scoped>
.plugins-magic-content .hot-list {
gap: 10rpx 0;
}
.plugins-magic-content .hot-list > .flex-width-half {
margin-bottom: 0;
}
.plugins-magic-content .hot-list > .flex-width-half:nth-last-of-type(1),
.plugins-magic-content .hot-list > .flex-width-half:nth-last-of-type(2) {
margin-bottom: 0;
}
.plugins-magic-content .hot-list > .flex-width-half:nth-child(even) {
position: relative;
}
.plugins-magic-content .hot-list > .flex-width-half:nth-child(even)::before {
content: '';
height: 80%;
width: 2rpx;
border-left: 2rpx solid rgb(232 232 232 / 28%);
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
.plugins-magic-content .hot-list.is-text-white > .flex-width-half:nth-child(even)::before {
border-left: 2rpx solid rgb(255 245 245 / 9%);
}
.plugins-magic-content .hot-list .swiper-list {
height: 216rpx;
}
.plugins-magic-content .hot-list .swiper-list .swiper-item {
margin-top: 16rpx;
}
.plugins-magic-content .hot-list .swiper-list .swiper-img {
height: 140rpx !important;
}
.plugins-magic-content .hot-go {
height: 34rpx;
line-height: 34rpx;
}
/**
* 切换滚动、切换九宫格、切换滚动
*/
.plugins-magic-content .switch-tabs-item-list > .scroll-view-horizontal {
padding-right: 140rpx;
}
.plugins-magic-content .switch-tabs-item-list .switch-tabs-item-icon {
width: 32rpx !important;
height: 32rpx !important;
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<view :class="theme_view">
<view class="pa-w" :class="(propFixed ? 'pf z-i left-0 top-0 right-0' : '') + ' ' + propClass" :style="'padding-top:' + (status_bar_height > 0 ? status_bar_height + 5 : 0) + 'px;background-color:rgba(255,255,255,' + opacity + ');' + propStyle">
<view v-if="(propName || null) != null || propIsRightSlot || is_show_back" class="nav-back padding-horizontal-main round va-m flex-row align-c" :class="(opacity > 0.3 ? 'cr-black ' : 'cr-white ') + (status_bar_height > 0 ? '' : 'padding-vertical-main')">
<view v-if="(propName || null) != null" :class="'text-size-md tc pa left-0 right-0 padding-top-xs ' + propNameClass" :style="propNameOpacity ? (opacity ? 'color:rgba(51,51,51,' + opacity + ')' : '') : ''">{{ propName }}</view>
<view v-if="is_show_back" @tap="top_nav_left_back_event" class="dis-inline-block">
<iconfont name="icon-arrow-left" size="40rpx" propClass="pr top-xs z-i" :color="(client_value == 'alipay' || client_value == 'baidu') ? 'transparent' : propColor"></iconfont>
</view>
<slot v-if="propIsRightSlot" name="right"></slot>
</view>
<slot name="content"></slot>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
name: 'back',
props: {
// 是否显示返回按钮
propIsShowBack: {
type: Boolean,
default: true,
},
// 最外层class
propClass: {
type: String,
default: '',
},
// 最外层style
propStyle: {
type: String,
default: '',
},
// 是否需要定位
propFixed: {
type: Boolean,
default: true,
},
// 箭头颜色
propColor: {
type: String,
default: '',
},
// 标题名称
propName: {
type: String,
default: '',
},
// 标题名称class
propNameClass: {
type: String,
default: '',
},
// 标题是否需要透明
propNameOpacity: {
type: Boolean,
default: false,
},
// 是否有左侧卡槽内容
propIsRightSlot: {
type: Boolean,
default: true,
},
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
client_value: app.globalData.application_client_type(),
status_bar_height: 0,
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-KUAISHOU || MP-ALIPAY || H5 || APP
status_bar_height: parseInt(app.globalData.get_system_info('statusBarHeight', 0, true)),
// #endif
// 顶部返回导航背景透明度
opacity: 0,
};
},
// 页面被展示
created: function () {
this.setData({
is_show_back: this.propIsShowBack && !app.globalData.is_tabbar_pages()
});
},
mounted() {
var self = this;
uni.$on('onPageScroll', function (e) {
var top = e.scrollTop > 47 ? 1 : e.scrollTop / 47;
self.setData({
opacity: top,
});
});
},
methods: {
top_nav_left_back_event() {
app.globalData.page_back_prev_event();
},
},
};
</script>
<style scoped>
.nav-back {
height: 30px;
padding-bottom: 10px !important;
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<view :class="theme_view">
<view class="more cr-black padding-top-main padding-bottom-sm flex-row flex-wrap align-c" :class="propClass" @tap="open_popup">
<text v-if="isMoreText">{{ $t('common.more_null') }}</text>
<iconfont name="icon-category-more"></iconfont>
</view>
<!-- 弹窗 -->
<component-popup :propShow="popup_status" :propIsBar="propIsBar" propPosition="top" :propMask="true" :propTop="propTop" @onclose="quick_close_event">
<view class="padding-top-lg">
<view class="padding-left-main padding-bottom-main">{{ $t('recommend-form.recommend-form.7gc30l') }}</view>
<view class="divider-b">
<slot></slot>
</view>
<view class="tc padding-vertical-lg" @tap="quick_close_event">
<text class="padding-right-sm">{{ $t('nav-more.nav-more.h9g4b1') }}</text>
<iconfont name="icon-arrow-top" color="#ccc"></iconfont>
</view>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import componentPopup from '@/components/popup/popup';
export default {
name: 'more',
components: {
componentPopup,
},
props: {
propData: {
type: Array,
default: () => {
return [];
},
},
// 顶部定位的距离
propTop: {
type: String,
default: '',
},
propStatus: {
type: Boolean,
default: false,
},
propClass: {
type: String,
default: '',
},
themeBtn: {
type: String,
default: '1',
},
isMoreText: {
type: Boolean,
default: true,
},
},
watch: {
propStatus(newVal, oldVal) {
if (newVal !== oldVal) {
this.setData({
popup_status: newVal,
});
}
},
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
popup_status: false,
propIsBar: false,
};
},
methods: {
// 打开弹窗
open_popup() {
this.$emit('open-popup', true);
},
// 关闭弹窗
quick_close_event(e) {
this.$emit('open-popup', false);
},
},
};
</script>
<style scoped>
.more {
width: 30rpx;
padding: 15rpx 20rpx;
position: absolute;
top: 0;
bottom: 0;
right: 0;
z-index: 101;
white-space: normal;
word-break: break-all;
}
</style>

View File

@@ -0,0 +1,329 @@
<template>
<view :class="theme_view">
<!-- 是否有网络 -->
<view v-if="network_type_value == 'none' && not_network_await_status == 0" class="network-type-tips wh-auto tc bs-bb padding-horizontal-main">
<view class="cr-base text-size">{{$t('no-data.no-data.1u202v')}}</view>
<view class="cr-grey margin-top-sm">{{$t('no-data.no-data.imw8f1')}}{{title}}{{$t('no-data.no-data.q87572')}}</view>
<view class="margin-top-lg tc">
<button type="default" class="br-main bg-main cr-white round padding-horizontal-xxl" size="mini" @tap="open_setting_event">{{ $t('setup.setup.377uwg') }}</button>
</view>
</view>
<view v-else>
<!-- 1 加载中(0loog, 1名称) -->
<view v-if="propStatus == 1 && network_type_value != 'none'" :class="'no-data-box tc no-data-loading '+(is_loading_use_skeleton == 1 && (propPage || null) != null ? 'skeleton' : '')">
<block v-if="is_loading_use_skeleton == 1 && (propPage || null) != null">
<!-- 是否展示头站位 -->
<view v-if="propIsHeader" class="skeleton-header"></view>
<!-- 首页 -->
<block v-if="propPage == 'home'">
<x-skeleton propType="banner"></x-skeleton>
<x-skeleton propType="menu"></x-skeleton>
<x-skeleton propType="waterfall"></x-skeleton>
<x-skeleton propType="list" propRowNumber="4"></x-skeleton>
</block>
<!-- 商品分类-整体内容 -->
<block v-else-if="propPage == 'goods-category'">
<x-skeleton propType="menu" propRowNumber="1"></x-skeleton>
<view class="goods-category-content flex-row jc-sb">
<view class="left">
<x-skeleton :propConfig="skeleton_goods_category_left_config"></x-skeleton>
</view>
<view class="right">
<x-skeleton propType="list" propRowNumber="7"></x-skeleton>
</view>
</view>
</block>
<!-- 商品分类-内容项 -->
<block v-else-if="propPage == 'goods-category-item'">
<x-skeleton propType="list" propRowNumber="6"></x-skeleton>
</block>
<!-- 购物车 -->
<block v-else-if="propPage == 'cart'">
<x-skeleton propType="list" propRowNumber="3"></x-skeleton>
<x-skeleton propType="waterfall" propRowNumber="4"></x-skeleton>
</block>
<!-- 商品详情 -->
<block v-else-if="propPage == 'goods'">
<x-skeleton propType="banner" propHeightNumber="600"></x-skeleton>
<x-skeleton propType="text"></x-skeleton>
<x-skeleton propType="info"></x-skeleton>
<x-skeleton propType="waterfall" propRowNumber="4"></x-skeleton>
</block>
</block>
<block v-else>
<view v-if="loading_content_type == 1" class="loading-title-animation">
<text class="title">{{title}}</text>
</view>
<view v-else class="loading-logo-content" :style="'margin-top: '+propLoadingLogoTop+';'">
<view class="loading-logo" :style="'background-image: url('+loading_logo+')'"></view>
<view class="loading-border" :style="'background-image: url('+loading_logo_border+')'"></view>
</view>
</block>
</view>
<!-- 2 处理错误 -->
<view v-else-if="propStatus == 2" class="no-data-box tc">
<image class="image" :src="static_dir + 'error.png'" mode="widthFix"></image>
<view class="no-data-tips">{{propMsg || $t('form.form.bniyyt')}}</view>
<view v-if="propBackBtn" class="margin-top-xxxl tc">
<button type="default" size="mini" class="bg-grey-e br-grey cr-base round" @tap="back_event">{{$t('common.return')}}</button>
</view>
</view>
<!-- 0 默认没有数据 -->
<view v-else-if="propStatus == 0" class="no-data-box tc">
<image class="image" :src="propUrl ? propUrl : static_dir + 'empty.png'" mode="widthFix"></image>
<view class="no-data-tips">{{propMsg || $t('common.no_relevant_data_tips')}}</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
static_dir: '/static/images/common/',
is_loading_use_skeleton: app.globalData.data.is_loading_use_skeleton,
loading_logo_border: app.globalData.data.static_url+'static/common/svg/loading-border.svg',
loading_logo: app.globalData.get_application_logo_square() || app.globalData.data.static_url+'favicon.ico',
loading_content_type: app.globalData.data.loading_content_type,
title: app.globalData.get_application_title(),
network_type_value: '',
not_network_await_status: 0,
// 骨架屏配置
// 商品分类内容-左侧
skeleton_goods_category_left_config: {
padding: '30rpx',
gridRows: 19,
gridColumns: 1,
gridRowsGap: '30rpx',
headShow: true,
headWidth: '200rpx',
headHeight: '60rpx',
headBorderRadius: '16rpx',
textShow: false,
},
};
},
components: {},
props: {
propStatus: {
type: [Number, String],
default: 0,
},
propMsg: {
type: String,
default: '',
},
propUrl: {
type: String,
default: '',
},
propBackBtn: {
type: Boolean,
default: true,
},
propNetworkTimeNum: {
type: Number,
default: 40,
},
propPage: {
type: String,
default: '',
},
propIsHeader: {
type: Boolean,
default: false,
},
propLoadingLogoTop: {
type: String,
default: '50%',
}
},
// 页面被展示
created: function () {
self = this;
uni.getNetworkType({
success: function (res) {
// 当前网络
self.network_type_value = res.networkType;
// 无网络进入等待网络中
if(self.network_type_value == 'none') {
self.not_network_await_status = 1;
}
// 定时处理
self.countdown(self);
}
});
},
// #ifndef VUE2
destroyed() {
clearInterval(this.timer);
},
// #endif
// #ifdef VUE3
unmounted() {
clearInterval(this.timer);
},
// #endif
methods: {
// 定时任务
countdown(self) {
// 销毁之前的任务
clearInterval(self.timer);
// 没有网络则启动定时任务
if(self.network_type_value == 'none') {
var temp_num = self.propNetworkTimeNum;
self.timer = setInterval(function () {
// 读取网络状态
uni.getNetworkType({
success: function (res) {
self.network_type_value = res.networkType;
// 已经有网络了则结束定时任务、并正常继续等待走加载过程
if(self.network_type_value != 'none') {
clearInterval(self.timer);
}
}
});
// 每次减1
temp_num--;
// 0则结束
if(temp_num <= 0) {
// 销毁任务
clearInterval(self.timer);
// 无需等待网络
self.not_network_await_status = 0;
}
}, 500);
}
},
// 返回事件
back_event(e) {
app.globalData.page_back_prev_event();
},
// 打开权限管理中心
open_setting_event() {
app.globalData.open_setting_event();
},
},
};
</script>
<style scoped>
.no-data-box {
padding: 15% 0;
}
.no-data-box .image {
width: 160rpx;
margin-bottom: 30rpx;
}
.no-data-box .no-data-tips {
font-size: 24rpx;
color: #999;
}
.no-data-loading .title {
color: #999;
}
/**
* 名称加载
*/
.loading-title-animation,
.network-type-tips {
padding-top: 25%;
}
.loading-title-animation {
background: #e7e7e7 -webkit-linear-gradient(left, #c6c6c6 0%, #c6c6c6 90%) no-repeat 0 0;
background-size: 20% 100%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 60rpx;
font-weight: bold;
-webkit-animation: loading-text-animation 2s linear infinite;
animation: loading-text-animation 2s linear infinite;
}
@-webkit-keyframes loading-text-animation {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 100%;
}
}
@keyframes loading-text-animation {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 100%;
}
}
/**
* logo加载
*/
.loading-logo-content {
position: absolute;
width: 140rpx;
height: 140rpx;
left: calc(50% - 70rpx);
top: 0;
border-radius: 50%;
overflow: hidden;
background: #fff;
margin-top: 50%;
}
.loading-logo-content .loading-logo {
content: '';
display: block;
position: absolute;
left: 30rpx;
top: 30rpx;
width: 80rpx;
height: 80rpx;
opacity: 0.8;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
}
.loading-logo-content .loading-border {
content: '';
display: block;
position: absolute;
width: 168rpx;
height: 168rpx;
left: -14rpx;
top: -14rpx;
opacity: 0.8;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
}
/**
* 骨架屏
*/
.skeleton-header {
/* #ifndef H5 || APP */
padding-top: var(--status-bar-height);
padding-bottom: 55px;
/* #endif */
}
.no-data-box.skeleton {
padding: 0;
}
.no-data-loading .goods-category-content > .left {
width: 30%;
}
.no-data-loading .goods-category-content > .right {
width: 70%;
}
</style>

View File

@@ -0,0 +1,444 @@
<template>
<view :class="theme_view">
<block v-if="online_service_status == 1">
<!-- 是否商品页样式 -->
<view v-if="propIsGoods == true" class="goods-chat-container fl cp">
<block v-if="is_chat == 1">
<view @tap="chat_event">
<image class="icon" :src="chat_icon" mode="scaleToFill"></image>
<text class="text dis-block text-size-xs cr-grey">{{$t('online-service.online-service.4l6k22')}}</text>
</view>
</block>
<block v-else>
<!-- #ifdef MP-WEIXIN || MP-TOUTIAO || MP-BAIDU || MP-KUAISHOU -->
<button class="chat-btn" open-type="contact" :show-message-card="propCard" :send-message-title="propTitle" :send-message-path="propPath" :send-message-img="propImg">
<image class="icon" :src="chat_icon" mode="scaleToFill"></image>
<text class="text dis-block text-size-xs cr-grey">{{$t('online-service.online-service.4l6k22')}}</text>
</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button class="chat-btn alipay-contact" open-type="contact">
<contact-button class="alipay-chat-btn" :tnt-inst-id="mini_alipay_tnt_inst_id" :scene="mini_alipay_scene" :alipay-card-no="mini_alipay_openid || ''" :icon="chat_icon" size="40rpx*40rpx" />
<text class="text dis-block text-size-xs cr-grey">{{$t('online-service.online-service.4l6k22')}}</text>
</button>
<!-- #endif -->
<!-- #ifdef H5 || APP -->
<button class="chat-btn" type="default" @tap="call_event">
<image class="icon" :src="chat_icon" mode="scaleToFill"></image>
<text class="text dis-block text-size-xs cr-grey">{{$t('online-service.online-service.4l6k22')}}</text>
</button>
<!-- #endif -->
</block>
</view>
<!-- 默认浮动展示-可拖拽位置 -->
<view v-else>
<block v-if="is_online_service_fixed == 1">
<block v-if="propIsMovable">
<movable-area class="online-service-movable-container" :style="'height: calc(100% - '+height_dec+'rpx);top:'+top+'rpx;'">
<movable-view direction="all" :x="x" :y="y" :animation="false" :class="'online-service-event-submit '+(propIsSpread ? ' spread' : '')">
<block v-if="propIsSpread">
<view class="ring"></view>
<view class="ring"></view>
</block>
<block v-if="is_chat == 1">
<button class="chat-btn" type="default" :class="common_ent" @tap="chat_event">
<image class="icon dis-block" :src="chat_image"></image>
</button>
</block>
<block v-else>
<!-- #ifdef MP-WEIXIN || MP-TOUTIAO || MP-BAIDU -->
<button class="chat-btn" open-type="contact" :class="common_ent" :show-message-card="propCard" :send-message-title="propTitle" :send-message-path="propPath" :send-message-img="propImg">
<image class="icon dis-block" :src="chat_image"></image>
</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button class="chat-btn" open-type="contact" :class="'alipay-contact '+common_ent">
<contact-button class="alipay-chat-btn" :tnt-inst-id="mini_alipay_tnt_inst_id" :scene="mini_alipay_scene" :alipay-card-no="mini_alipay_openid || ''" :icon="chat_image" size="40rpx*40rpx" />
</button>
<!-- #endif -->
<!-- #ifdef H5 || APP -->
<button class="chat-btn" type="default" :class="common_ent" @tap="call_event">
<image class="icon dis-block" :src="chat_image"></image>
</button>
<!-- #endif -->
</block>
</movable-view>
</movable-area>
</block>
<block v-else>
<view class="online-service-movable-container" :style="'height: calc(100% - '+height_dec+'rpx);top:'+top+'rpx;'">
<view :class="'online-service-event-submit '+(propIsSpread ? ' spread' : '')">
<block v-if="propIsSpread">
<view class="ring"></view>
<view class="ring"></view>
</block>
<block v-if="is_chat == 1">
<button class="chat-btn" type="default" :class="common_ent" @tap="chat_event">
<image class="icon dis-block" :src="chat_image"></image>
</button>
</block>
<block v-else>
<!-- #ifdef MP-WEIXIN || MP-TOUTIAO || MP-BAIDU -->
<button class="chat-btn" open-type="contact" :class="common_ent" :show-message-card="propCard" :send-message-title="propTitle" :send-message-path="propPath" :send-message-img="propImg">
<image class="icon dis-block" :src="chat_image"></image>
</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button class="chat-btn alipay-contact" :class="common_ent" open-type="contact">
<contact-button class="alipay-chat-btn" :tnt-inst-id="mini_alipay_tnt_inst_id" :scene="mini_alipay_scene" :alipay-card-no="mini_alipay_openid || ''" :icon="chat_image" size="40rpx*40rpx" />
</button>
<!-- #endif -->
<!-- #ifdef H5 || APP -->
<button class="chat-btn" type="default" :class="common_ent" @tap="call_event">
<image class="icon dis-block" :src="chat_image"></image>
</button>
<!-- #endif -->
</block>
</view>
</view>
</block>
</block>
</view>
</block>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: app.globalData.get_static_url('common'),
client_value: app.globalData.application_client_type(),
is_chat: 0,
chat_url: null,
chat_icon: '',
chat_image: '',
common_app_customer_service_tel: null,
common_app_customer_service_custom: null,
common_app_customer_service_company_weixin_corpid: null,
common_app_customer_service_company_weixin_url: null,
online_service_status: 0,
is_online_service_fixed: app.globalData.data.is_online_service_fixed,
mini_alipay_tnt_inst_id: null,
mini_alipay_scene: null,
mini_alipay_openid: null,
system: null,
x: 0,
y: 0,
top: 0,
height_dec: 0,
is_first: 1,
common_ent: ''
};
},
components: {},
props: {
propIsGoods: {
type: Boolean,
default: false
},
propIsBar: {
type: Boolean,
default: false
},
propIsNav: {
type: Boolean,
default: false
},
propCard: {
type: Boolean,
default: false
},
propTitle: {
type: String,
default: ''
},
propImg: {
type: String,
default: ''
},
propPath: {
type: String,
default: ''
},
propIsGrayscale: {
type: Boolean,
default: false
},
propIsChat: {
type: Number,
default: 0
},
propChatUrl: {
type: String,
default: ''
},
propChatIcon: {
type: String,
default: ''
},
propChatImage: {
type: String,
default: ''
},
propIsSpread: {
type: Boolean,
default: true
},
propIsMovable: {
type: Boolean,
default: true
},
},
// 属性值改变监听
watch: {
// 是否灰度
propIsGrayscale(value, old_value) {
this.common_ent = value ? 'grayscale' : '';
}
},
// 页面被展示
created: function(e) {
this.init_config();
// 非首次进入则重新初始化配置接口
if (this.is_first == 0) {
app.globalData.init_config();
}
// 数据设置
var system = app.globalData.get_system_info(null, null, true);
var width = app.globalData.window_width_handle(system.windowWidth);
var height = app.globalData.window_height_handle(system);
// 页面是否定义导航
var top_h = this.propIsNav ? 130 : 0;
this.setData({
is_first: 0,
system: system,
chat_icon: this.propChatIcon || this.common_static_url+'chat-icon.png',
chat_image: this.propChatImage || this.common_static_url+'online-service-icon.png',
// 位置坐标
x: width - 65,
y: height - 380,
// 展示位置处理
top: top_h,
height_dec: top_h,
// #ifdef H5 || APP
top: 210,
height_dec: this.propIsBar ? 310 : 210,
// #endif
// 是否灰度
common_ent: this.propIsGrayscale ? 'grayscale' : ''
});
},
methods: {
// 初始化配置
init_config(status) {
// 客服优先级顺序( 客服系统 -> 自定义客服 -> 企业微信客服(仅app+h5+微信小程序生效) -> 各端平台客服 -> 电话客服 )
if ((status || false) == true) {
// 是否使用客服系统
var is_chat = app.globalData.get_config('plugins_base.chat.data.is_mobile_chat', 0);
var chat_url = app.globalData.get_config('plugins_base.chat.data.chat_url');
var is_online_service = app.globalData.get_config('config.common_app_is_online_service', 0);
if(is_chat == 1 && (chat_url != null || (this.propChatUrl || null) != null)) {
this.setData({
is_chat: is_chat,
chat_url: this.propChatUrl || chat_url,
online_service_status: is_online_service,
});
} else {
var online_service_url = app.globalData.get_config('config.common_app_customer_service_custom', null);
this.setData({
common_app_customer_service_tel: app.globalData.get_config('config.common_app_customer_service_tel', null),
common_app_customer_service_custom: (online_service_url == null || (online_service_url[this.client_value] || null) == null) ? null : online_service_url[this.client_value],
common_app_customer_service_company_weixin_corpid: app.globalData.get_config('config.common_app_customer_service_company_weixin_corpid', null),
common_app_customer_service_company_weixin_url: app.globalData.get_config('config.common_app_customer_service_company_weixin_url', null),
online_service_status: is_online_service,
});
// 存在自定义客服和微信企业客服则走客服模式
if((this.common_app_customer_service_custom || null) != null || ((this.common_app_customer_service_company_weixin_corpid || null) != null && (this.common_app_customer_service_company_weixin_url || null) != null)) {
this.setData({
is_chat: 1
});
}
// 对应平台没有提供客服的、[电话,自定义客服,企业微信客服]必须存在一个,则关闭在线客服
if(['qq', 'h5', 'ios', 'android'].indexOf(this.client_value) != -1 && (this.common_app_customer_service_tel || null) == null && (this.common_app_customer_service_custom || null) == null) {
var temp_service_status = this.online_service_status;
if(this.client_value == 'qq') {
temp_service_status = 0;
} else {
// h5,app是否配置企业微信客服
if((this.common_app_customer_service_company_weixin_corpid || null) == null && (this.common_app_customer_service_company_weixin_url || null) == null) {
temp_service_status = 0;
}
}
this.setData({
online_service_status: temp_service_status
});
}
// #ifdef MP-ALIPAY
// 在线客服开启获取用户openid
if(this.online_service_status == 1)
{
this.setData({
mini_alipay_tnt_inst_id: app.globalData.get_config('config.common_app_mini_alipay_tnt_inst_id'),
mini_alipay_scene: app.globalData.get_config('config.common_app_mini_alipay_scene'),
mini_alipay_openid: app.globalData.get_user_cache_info('alipay_openid')
});
}
// #endif
}
} else {
app.globalData.is_config(this, 'init_config');
}
},
// 客服事件
chat_event() {
// 在线客服系统
if((this.chat_url || null) != null) {
app.globalData.chat_entry_handle(this.chat_url);
} else {
// 自定义客服
if((this.common_app_customer_service_custom || null) != null) {
app.globalData.url_open(this.common_app_customer_service_custom);
} else {
// 企业微信客服
if((this.common_app_customer_service_company_weixin_corpid || null) != null && (this.common_app_customer_service_company_weixin_url || null) != null) {
// #ifdef APP
// app打开企业微信客服
plus.share.getServices(res => {
var wechat = res.find(i => i.id === 'weixin')
if(wechat) {
wechat.openCustomerServiceChat({
corpid: this.common_app_customer_service_company_weixin_corpid,
url: this.common_app_customer_service_company_weixin_url,
});
}
});
// #endif
// #ifdef MP-WEIXIN
// 微信小程序打开企业微信客服
uni.openCustomerServiceChat({
extInfo: {url: this.common_app_customer_service_company_weixin_url},
corpId: this.common_app_customer_service_company_weixin_corpid,
showMessageCard: this.propCard,
sendMessageTitle: this.propTitle,
sendMessagePath: this.propPath,
sendMessageImg: this.propImg,
});
// #endif
// #ifdef H5
app.globalData.url_open(this.common_app_customer_service_company_weixin_url);
// #endif
} else {
// 电话客服
this.call_event();
}
}
}
},
// 客服电话
call_event() {
app.globalData.call_tel(this.common_app_customer_service_tel);
}
}
};
</script>
<style scoped>
.online-service-movable-container {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: transparent;
pointer-events: none;
z-index: 2;
}
.online-service-event-submit {
pointer-events: auto;
}
.online-service-event-submit,
.online-service-event-submit .chat-btn {
width: 50px;
height: 50px;
border-radius: 50%;
}
.online-service-event-submit .chat-btn {
border: 0;
padding: 0;
}
.online-service-event-submit .icon {
width: 50px !important;
height: 50px !important;
position: relative;
}
.goods-chat-container .chat-btn {
padding: 0;
border: 0;
line-height: initial;
font-size: 24rpx;
background: transparent;
}
.goods-chat-container .icon {
width: 40rpx;
height: 40rpx;
margin: 10rpx 0 5rpx 0;
}
.goods-chat-container .text {
margin-top: -10rpx;
}
/* #ifdef MP-ALIPAY */
.goods-chat-container .alipay-contact {
margin-top: 10rpx;
}
.goods-chat-container .alipay-contact .text {
margin-top: -5rpx;
}
.online-service-event-submit .alipay-chat-btn {
line-height: initial;
display: block;
}
/* #endif */
/**
* 呼吸灯
*/
.spread {
background-color: rgba(238, 73, 70,0.4);
border-radius: 100%;
width: 50px;
height: 50px;
position: relative;
z-index: 1;
}
.spread .ring {
/* 速度为1.5 * 层数 = 实际运行速度,速度修改则 animation-delay 属性也修改相同速度 */
animation: pulsing 1.5s ease-out infinite;
}
/* 速度为1*层数 */
.spread .ring:nth-of-type(1) {
-webkit-animation-delay: -1.5s;
animation-delay: -1.5s;
}
/* 速度为1*层数 */
.spread .ring:nth-of-type(2) {
-webkit-animation-delay: -2s;
animation-delay: -2s;
}
@keyframes pulsing {
100% {
transform: scale(1.35);
opacity: 0
}
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<view :class="theme_view">
<!-- 简洁的数据一般列表展示使用 -->
<view v-if="propIsTerse" class="content margin-top cp">
<block v-if="data != null && data_field.length > 0">
<block v-for="(item, index) in data_field" :key="index">
<view v-if="(item.is_hide || 0) == 0" class="single-text margin-top-xs">
<text class="cr-grey margin-right-xl">{{ item.name }}</text>
<text class="cr-base">
<block v-if="item.type == 'images'">
<image :src="data[item.field]" mode="aspectFit" class="radius panel-item-images"></image>
</block>
<text v-else>{{ data[item.field] }}</text>
</text>
<view v-if="(item.is_copy || 0) == 1" class="dis-inline-block margin-left" data-event="copy" :data-value="data[item.field]" @tap.stop="text_event_handle">
<iconfont name="icon-copy" size="28rpx" class="cr-grey"></iconfont>
</view>
</view>
</block>
<view v-if="propIsItemShowMax > 0 && propIsItemShowMax < data_field.length" @tap.stop="item_more_event" class="margin-top-sm tc">
<text class="cr-grey-c margin-right-sm">{{ $t('common.view_more') }}</text>
<iconfont :name="'icon-arrow-' + (more_status ? 'top' : 'bottom')" size="28rpx" color="#ccc"></iconfont>
</view>
</block>
<slot></slot>
</view>
<!-- 详情面板数据 -->
<view v-else class="padding-horizontal-main padding-top-main">
<view class="panel-item padding-main border-radius-main bg-white spacing-mb">
<view v-if="(propTitle || null) != null" class="br-b padding-bottom-main fw-b text-size">{{ propTitle }}</view>
<view class="panel-content oh">
<block v-if="data != null && data_field.length > 0">
<block v-for="(item, index) in data_field" :key="index">
<view v-if="(item.is_hide || 0) == 0" class="item br-b-f5 oh padding-vertical-main">
<view class="title fl padding-right-main cr-grey">{{ item.name }}</view>
<view class="content fl br-l padding-left-main">
<block v-if="item.type == 'images'">
<image :src="data[item.field]" mode="aspectFit" class="panel-item-images"></image>
</block>
<text v-else>{{ data[item.field] }}</text>
<view v-if="(item.is_copy || 0) == 1" class="dis-inline-block margin-left" data-event="copy" :data-value="data[item.field]" @tap.stop="text_event_handle">
<iconfont name="icon-copy" size="28rpx" class="cr-grey lh-il"></iconfont>
</view>
</view>
</view>
</block>
<view v-if="propIsItemShowMax > 0 && propIsItemShowMax < data_field.length" @tap="item_more_event" class="margin-top-sm tc">
<text class="cr-grey-c margin-right-sm">{{ $t('common.view_more') }}</text>
<iconfont :name="'icon-arrow-' + (more_status ? 'top' : 'bottom')" size="28rpx" color="#ccc"></iconfont>
</view>
</block>
<slot></slot>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import componentNoData from '@/components/no-data/no-data';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data: null,
data_field: [],
more_status: false,
};
},
components: {
componentNoData,
},
props: {
// 标题
propTitle: {
type: String,
default: '',
},
// 数据
propData: {
type: [Object, String],
default: '',
},
// 数据字段
propDataField: {
type: Array,
default: [],
},
// 数据项最大展示数量0则显示全部
propIsItemShowMax: {
type: Number,
default: 0,
},
// 指定字段
propAppointField: {
type: String,
default: '',
},
// 排除字段
propExcludeField: {
type: String,
default: '',
},
// 无数据提示状态
propNoDataStatus: {
type: [Number, String],
default: 3,
},
// 无数据提示内容
propNoDataMsg: {
type: [String],
default: '',
},
// 是否简洁的模式展示
propIsTerse: {
type: Boolean,
default: false,
},
},
// 属性值改变监听
watch: {
// 数据改变
propData(value, old_value) {
this.data = value;
},
},
// 页面被展示
created: function (e) {
this.setData({
data: this.propData,
});
this.data_field_handle(this.propDataField);
},
methods: {
// 数据字段处理
data_field_handle(data) {
var appoint = (this.propAppointField || null) == null ? [] : this.propAppointField.split(',');
var exclude = (this.propExcludeField || null) == null ? [] : this.propExcludeField.split(',');
var temp_data = [];
var index = 0;
for (var i in data) {
if ((exclude.length == 0 && appoint.length > 0 && appoint.indexOf(data[i]['field']) != -1) || (appoint.length == 0 && (exclude.length == 0 || exclude.indexOf(data[i]['field']) == -1))) {
data[i]['is_hide'] = (data[i]['is_hide'] || 0) == 0 ? (index >= this.propIsItemShowMax && this.propIsItemShowMax > 0 ? 1 : 0) : 0;
temp_data.push(data[i]);
index++;
}
}
this.setData({
data_field: temp_data,
});
},
// 文本事件
text_event_handle(e) {
app.globalData.text_event_handle(e);
},
// 数据项更多事件
item_more_event(e) {
this.data_field_handle(this.data_field);
this.setData({
more_status: !this.more_status,
});
},
},
};
</script>
<style scoped>
.panel-item-images {
width: 40rpx;
height: 40rpx;
}
</style>

View File

@@ -0,0 +1,922 @@
<template>
<view :class="theme_view">
<!-- 支付二维码展示 -->
<component-popup :propShow="popup_view_pay_qrcode_is_show" propPosition="bottom" @onclose="popup_view_pay_qrcode_event_close">
<view class="padding-top-xxxl padding-bottom-xxxl padding-left-xxxl padding-right-xxxl tc">
<block v-if="(popup_view_pay_data || null) == null || (popup_view_pay_data.qrcode_url || null) == null || (popup_view_pay_data.name || null) == null || (popup_view_pay_data.order_no || null) == null">
<text class="cr-grey">{{$t('payment.payment.973g2e')}}</text>
</block>
<block v-else>
<view class="fw-b text-size cr-base margin-bottom-sm">{{ popup_view_pay_data.name }}</view>
<image :src="popup_view_pay_data.qrcode_url" mode="aspectFit" class="dis-block auto max-w"></image>
<view v-if="(popup_view_pay_data.msg || null) != null" class="cr-yellow margin-top-sm">{{ popup_view_pay_data.msg }}</view>
<!-- #ifdef H5 -->
<view v-if="popup_view_pay_data.pay_url != null" class="margin-top-xl">
<a :href="popup_view_pay_data.pay_url" target="_blank" class="dis-inline-block cr-green">{{$t('payment.payment.z3y296')}}</a>
</view>
<!-- #endif -->
</block>
</view>
</component-popup>
<!-- 支付方式 popup -->
<component-popup :propShow="is_show_payment_popup" propPosition="bottom" @onclose="payment_popup_event_close">
<view class="poupon-title padding-main tc text-size-md pr">{{$t('payment.payment.iu792d')}}<iconfont name="icon-close-o" propClass="pa right-0 margin-right-main margin-top-xs" size="30rpx" color="#999" @tap="payment_popup_event_close"></iconfont>
</view>
<view class="payment-price tc padding-top-sm padding-bottom-sm br-b">
<text class="text-size-md">{{ propCurrencySymbol }}</text>
{{ propPayPrice }}
</view>
<view v-if="payment_list.length > 0" class="oh">
<view class="payment-list">
<scroll-view scroll-y="true" class="scroll-y wh-auto">
<view v-for="(item, index) in payment_list" :key="index" class="item br-b flex-row jc-sb align-c" :data-value="item.id" @tap="checked_payment">
<view class="flex-1">
<image v-if="(item.logo || null) != null" class="icon va-m margin-right-sm" :src="item.logo" mode="widthFix"></image>
<text class="va-m">{{ item.name }}</text>
<text v-if="(item.tips || null) !== null" class="va-m cr-red">{{ item.tips }}</text>
</view>
<iconfont :name="payment_id == item.id ? 'icon-zhifu-yixuan' : 'icon-zhifu-weixuan'" size="44rpx" :color="payment_id == item.id ? '#E22C08' : '#ccc'"></iconfont>
</view>
</scroll-view>
</view>
<view class="payment-submit">
<view class="bottom-line-exclude">
<button class="bg-main br-main cr-white round text-size" type="default" hover-class="none" @tap="popup_payment_event" :disabled="submit_disabled_status">{{$t('payment.payment.25r53g')}}</button>
</view>
</view>
</view>
<view v-else class="padding-top-xxxl padding-bottom-xxxl oh bg-white tc cr-grey">{{$t('payment.payment.058a46')}}</view>
</component-popup>
<!-- 支付html展示 -->
<component-popup :propShow="popup_view_pay_html_is_show" propPosition="bottom" @onclose="popup_view_pay_html_event_close">
<view class="popup-pay-html-content padding-top-xxxl padding-bottom-xxxl padding-left-xxxl padding-right-xxxl tc">
<block v-if="(popup_view_pay_data || null) == null">
<text class="cr-grey">{{$t('payment.payment.973g2e')}}</text>
</block>
<block v-else>
<mp-html :content="popup_view_pay_data" />
</block>
</view>
</component-popup>
<!-- 支付中提示弹窗 -->
<view v-if="payment_confirm_modal_status" class="payment-confirm-modal">
<view class="content padding-xl margin-xxl tc bg-white border-radius-main">
<view class="padding-vertical-xxxxl">{{$t('common.payment_in_text')}}</view>
<view class="margin-top-lg">
<button type="default" size="mini" class="bg-white br-black cr-black text-size-sm round margin-right-xxxxl" data-type="0" @tap="payment_confirm_event">{{$t('common.not_have_name')}}</button>
<button type="default" size="mini" class="bg-main br-main cr-white text-size-sm round margin-left-xxxxl" data-type="1" @tap="payment_confirm_event">{{$t('order.order.s8g966')}}</button>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
import base64 from '@/common/js/lib/base64.js';
import componentPopup from '@/components/popup/popup';
export default {
name: 'pay',
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
// 支付方式列表
payment_list: [],
// 弹窗开关
is_show_payment_popup: false,
popup_view_pay_qrcode_is_show: false,
// 支付弹窗展示数据
popup_view_pay_data: null,
// 定时器
pay_statuc_check_timer: null,
// 支付id
payment_id: 0,
submit_disabled_status: true,
order_id: 0,
popup_view_pay_html_is_show: false,
// 打开url地址定时任务和状态
open_pay_url_timer: null,
open_pay_url_status: true,
// 支付返回数据
pay_response_data: {},
// 支付确认弹窗
payment_confirm_modal_status: false,
};
},
props: {
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
propPayUrl: {
type: String,
default: '',
},
propQrcodeUrl: {
type: String,
default: '',
},
propPaymentList: {
type: Array,
default: () => {
return [];
},
},
propIsShowPayment: {
type: Boolean,
default: false,
},
// 订单id
propTempPayValue: {
type: [String, Number],
default: '',
},
// 订单id参数名默认为id
propPayDataKey: {
type: String,
default: 'id',
},
// 订单下标 ---- 用于处理支付成功后前端修改成功状态
propTempPayIndex: {
type: [Number, String],
default: 0,
},
// 支付id 值为0表示没有配置支付方式
propPaymentId: {
type: [Number, String],
default: 0,
},
// 默认支付id 值为0表示没有默认
propDefaultPaymentId: {
type: [Number, String],
default: 0,
},
// 付款金额
propPayPrice: {
type: [Number, String],
default: 0,
},
// 支付跳转页面------跳转成功页面---后返回的页面
propToPageBack: {
type: Object,
default: () => {
return {};
},
},
// 指定所有页面跳转到指定页面------除现金支付外
propToPage: {
type: String,
default: '',
},
// 现金支付-指定跳转页面----不进入充值成功页面:不配置则表示不跳转
propToAppointPage: {
type: String,
default: '',
},
// 支付失败跳转页面------不传则停留在当前页面
propToFailPage: {
type: String,
default: '',
},
//是否需要关闭页面进行跳转
propIsRedirectTo: {
type: Boolean,
default: false,
},
// 判断错误时是否需要弹窗提示
propIsFailAlert: {
type: Boolean,
default: true,
},
},
components: {
componentPopup,
},
watch: {
// 支付方式是否改变
propPaymentList(value, old_value) {
this.setData({
payment_list: value,
});
},
// 是否显示支付方式
propIsShowPayment(new_val, old_val) {
if (new_val !== old_val) {
let bool = true;
if (this.payment_list.length == 1) {
bool = false;
this.setData({
payment_id: this.payment_list[0].id,
});
} else {
let self = this;
self.payment_list.forEach((item) => {
let new_payment_id = Number(self.propPaymentId) == 0 ? self.propDefaultPaymentId : Number(self.propPaymentId);
if (item.id == new_payment_id) {
bool = false;
}
});
this.setData({
payment_id: Number(this.propPaymentId) == 0 ? this.propDefaultPaymentId : Number(this.propPaymentId),
});
}
this.setData({
is_show_payment_popup: new_val,
submit_disabled_status: bool,
});
}
}
},
// 页面被展示
created: function () {
this.setData({
payment_list: this.propPaymentList,
payment_id: Number(this.propPaymentId) == 0 ? this.propDefaultPaymentId : Number(this.propPaymentId),
});
},
methods: {
// 支付弹窗关闭
payment_popup_event_close(e) {
this.setData({
is_show_payment_popup: false,
});
this.$emit('close-payment-popup', false);
},
// 支付二维码展示窗口事件
popup_view_pay_qrcode_event_close(e) {
// 关闭弹窗
this.setData({
popup_view_pay_qrcode_is_show: false,
});
// 清除定时和支付数据
clearInterval(this.pay_statuc_check_timer);
},
// 选择支付方式
checked_payment(e) {
this.setData({
payment_id: e.currentTarget.dataset.value,
submit_disabled_status: false,
});
},
// 支付弹窗发起支付
popup_payment_event() {
if (this.submit_disabled_status) {
app.globalData.showToast(this.$t('payment.payment.x6d585'));
return false;
}
this.setData({
is_show_payment_popup: false,
});
this.pay_handle(this.propTempPayValue, this.payment_id);
this.$emit('close-payment-popup', false);
},
// 支付方法
pay_handle(order_id, payment_id = 0, payment_list = []) {
// 没有指定支付方式则使用属性传过来的值
if((payment_list || null) != null && payment_list.length > 0) {
this.setData({
payment_list: payment_list
});
}
// 没有支付方式
if((payment_id || 0) == 0) {
this.pay_handle_event(order_id, payment_id);
} else {
// 循环匹配支付方式
this.payment_list.forEach((item) => {
if (item.id == payment_id) {
if (item.payment == 'WalletPay') {
var self = this;
uni.showModal({
title: self.$t('common.warm_tips'),
content: self.$t('payment.payment.011cj4'),
confirmText: self.$t('common.confirm'),
cancelText: self.$t('common.not_yet'),
success(res) {
if (res.confirm) {
self.pay_handle_event(order_id, payment_id);
} else {
self.order_item_pay_fail_handle(null, order_id, self.$t('paytips.paytips.6mpsl7'));
}
},
});
} else {
this.pay_handle_event(order_id, payment_id);
}
}
});
}
},
// 支付处理
pay_handle_event(order_id, payment_id = 0) {
// 没有指定支付方式则不匹配支付标识
var payment = null;
if((payment_id || 0) != 0) {
// #ifdef H5
// 微信环境判断是否已有web_openid、不存在则不继续执行跳转到插件进行授权
if (!app.globalData.is_user_weixin_web_openid(order_id, payment_id || this.payment_id, this.propToAppointPage)) {
return false;
}
// #endif
// 支付方式
for (var i in this.payment_list) {
if (this.payment_list[i]['id'] == (payment_id || this.payment_id)) {
payment = this.payment_list[i];
}
}
if (payment == null) {
app.globalData.showToast(this.$t('payment.payment.7ihx9u'));
return false;
}
}
// 请求数据
var post_data = {
[this.propPayDataKey]: order_id,
payment_id: payment_id || this.payment_id,
};
// h5自定义重定向地址
// #ifdef H5
var redirect_url = app.globalData.page_url_protocol(this.propToAppointPage || app.globalData.get_page_url(false));
post_data['redirect_url'] = encodeURIComponent(base64.encode(redirect_url));
// 存在支付标识、指定支付方式使用respond_url返回地址、移除重定向地址
if(payment != null) {
var respond_arr = ['PayPal', 'UniPayment'];
if (respond_arr.indexOf(payment.payment) != -1) {
post_data['respond_url'] = post_data['redirect_url'];
delete post_data['redirect_url'];
}
}
// #endif
// 请求支付接口
uni.showLoading({
title: this.$t('payment.payment.e1f54e'),
mask: true
});
if (this.propPayUrl) {
uni.request({
url: this.propPayUrl,
method: 'POST',
data: post_data,
dataType: 'json',
success: (res) => {
uni.hideLoading();
var data = res.data.data;
this.setData({
pay_response_data: data || {}
});
if (res.data.code == 0) {
// 是否直接支付成功
if ((data.is_success || 0) == 1) {
// 数据设置
this.order_item_pay_success_handle(data, order_id, false);
app.globalData.showToast(this.$t('paytips.paytips.679rxu'), 'success');
setTimeout(() => {
this.to_success_page_event();
}, 2000);
} else {
// 支付方式类型
let payment_type = Number(data.is_payment_type || 0);
switch (payment_type) {
// 正常线上支付
case 0:
// #ifdef APP
this.app_pay_handle(this, data, order_id);
// #endif
// #ifdef MP-TOUTIAO
// 头条是否非普通版本支持
if(parseInt(data.data.pay_type || 0) == 1) {
this.toutiao_transaction_pay_handle(this, data, order_id);
} else {
this.mp_pay_handle(this, data, order_id);
}
// #endif
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU
this.mp_pay_handle(this, data, order_id);
// #endif
// #ifdef MP-KUAISHOU
this.kuaishou_pay_handle(this, data, order_id);
// #endif
// #ifdef MP-QQ
this.qq_pay_handle(this, data, order_id);
// #endif
// #ifdef H5
this.h5_pay_handle(this, data, order_id);
// #endif
break;
// 线下支付
case 1:
// 现金支付
let self = this;
uni.showModal({
content: res.data.msg,
showCancel: false,
confirmText: self.$t('common.confirm'),
success(res) {
if (res.confirm) {
self.to_other(order_id);
} else {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6mpsl7'));
}
},
});
break;
// 钱包支付
case 2:
this.order_item_pay_success_handle(data, order_id);
break;
// 默认
default:
app.globalData.showToast(this.$t('payment.payment.vhx5dv'));
}
}
} else {
// 是否返回html代码展示、则提示错误
if (res.data.code == -6666 && (data || null) != null) {
this.setData({
popup_view_pay_data: data,
popup_view_pay_html_is_show: true,
});
} else {
this.order_item_pay_fail_handle(data, order_id, res.data.msg);
}
}
},
fail: (res) => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
} else {
app.globalData.showToast(this.$t('payment.payment.597s8b'));
}
},
// APP支付
app_pay_handle(self, data, order_id) {
var arr = {
Alipay: 'alipay',
Weixin: 'wxpay',
PayPal: 'paypal'
}
var pay_value = arr[data.payment.payment] || null;
if(pay_value != null) {
uni.getProvider({
service: 'payment',
success: function (res) {
if(~res.provider.indexOf(pay_value)) {
var pay_data = ((data.data.pay_data || null) == null) ? data.data : data.data.pay_data;
uni.requestPayment({
provider: pay_value,
orderInfo: pay_data,
success: function (res) {
// 是否需要回调捕获
var call_back_url = data.data.call_back_url || null;
if(call_back_url != null) {
uni.request({url: call_back_url, method: 'GET'});
}
// 成功处理数据
self.order_item_pay_success_handle(data, order_id);
},
fail: function (err) {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
}
});
} else {
app.globalData.showToast(data.payment.payment+self.$t('payment.payment.bv637f'));
}
}
});
} else {
// 先清除定时任务
if(self.open_pay_url_timer != null) {
clearTimeout(self.open_pay_url_timer);
}
// 显示加载层
uni.showLoading({
title: self.$t('common.loading_in_text'),
mask: true
});
// 设置打开url状态
self.setData({
open_pay_url_status: true
});
// 打开url
plus.runtime.openURL(data.data, function(error) {
uni.hideLoading();
// 打开url失败、并进入提示失败环节
self.setData({
open_pay_url_status: false
});
self.order_item_pay_fail_handle(data, order_id, error.message+'('+error.code+')');
});
// 定时3秒后提示用户确认支付状态
self.open_pay_url_timer = setTimeout(function() {
if(self.open_pay_url_status) {
uni.hideLoading();
uni.showModal({
content: self.$t('payment.payment.sdfs31'),
showCancel: true,
cancelText: self.$t('common.not_have_name'),
confirmText: self.$t('order.order.s8g966'),
success(res) {
if (res.confirm) {
self.order_item_pay_success_handle(data, order_id);
} else {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
}
},
});
}
}, 3000);
}
},
// 快手小程序
kuaishou_pay_handle(self, data, order_id) {
uni.pay({
orderInfo: data.data,
serviceId: '1',
success: (res) => {
// 数据设置
self.order_item_pay_success_handle(data, order_id);
},
fail: (res) => {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
},
});
},
// 头条小程序非普通交易支付处理
toutiao_transaction_pay_handle(self, data, order_id) {
if(!uni.canIUse('requestOrder') || !uni.canIUse('getOrderPayment')) {
app.globalData.showToast(self.$t('payment.payment.4dszme'));
return false;
}
uni.requestOrder({
data: data.data.data,
byteAuthorization: data.data.auth,
success: (res) => {
uni.getOrderPayment({
orderId: res.orderId,
success: (res) => {
// 数据设置
self.order_item_pay_success_handle(data, order_id);
},
fail: (res) => {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
}
});
},
fail: (res) => {
app.globalData.showToast(res.errMsg+'('+res.errNo+')');
}
});
},
// 小程序: 微信、支付宝、百度、头条、QQ
mp_pay_handle(self, data, order_id) {
// 是否打开另一个小程序
if (typeof data.data != 'string' && (data.data.appid || null) != null && (data.data.path || null) != null && (data.data.order_no || null) != null) {
uni.navigateToMiniProgram({
appId: data.data.appid,
path: data.data.path,
extraData: data.data.extra_data || {},
success(res) {
// 支付状态验证
self.pay_status_check_handle(self, data, order_id);
// 提示弹窗
self.setData({
payment_confirm_modal_status: true,
});
},
fail(res) {
app.globalData.showToast(self.$t('paytips.paytips.6y488i'));
}
});
} else {
uni.requestPayment({
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
orderInfo: data.data,
// #endif
// #ifdef MP-QQ
package: data.data,
// #endif
// #ifdef MP-WEIXIN
timeStamp: data.data.timeStamp,
nonceStr: data.data.nonceStr,
package: data.data.package,
signType: data.data.signType,
paySign: data.data.paySign,
// #endif
// #ifdef MP-TOUTIAO
service: 5,
// #endif
success: (res) => {
// #ifdef MP-ALIPAY
if (res.resultCode != 9000) {
self.order_item_pay_fail_handle(data, order_id, res.memo || self.$t('paytips.paytips.6y488i'));
return false;
}
// #endif
// #ifdef MP-TOUTIAO
if (res.code != 0) {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
return false;
}
// #endif
// 数据设置
self.order_item_pay_success_handle(data, order_id);
},
fail: (res) => {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
},
});
}
},
// QQ支付处理
qq_pay_handle(self, data, order_id) {
// 是否微信支付
if (data.payment.payment == 'Weixin') {
uni.requestWxPayment({
url: data.data,
referer: app.globalData.data.request_url,
success: function (res) {
app.globalData.alert({
msg: self.$t('payment.payment.k2i010'),
is_show_cancel: 0,
});
// 支付接口调用成功,但是不知道是否支付成功,所以需要重新获取列表数据
self.$emit('reset-event');
},
fail: function (res) {
self.order_item_pay_fail_handle(data, order_id, self.$t('paytips.paytips.6y488i'));
},
});
} else {
self.mp_pay_handle(self, data, order_id);
}
},
// h5支付处理
h5_pay_handle(self, data, order_id) {
// 字符串则为跳转地址直接进入
if (typeof data.data == 'string') {
window.location.href = data.data;
} else {
var status = false;
// 微信jsapi
if (data.payment.payment == 'Weixin' && (data.data.appId || null) != null && (data.data.timeStamp || null) != null && (data.data.nonceStr || null) != null && (data.data.package || null) != null && (data.data.signType || null) != null && (data.data.paySign || null) != null) {
status = true;
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: data.data.appId,
timeStamp: data.data.timeStamp,
nonceStr: data.data.nonceStr,
package: data.data.package,
signType: data.data.signType,
paySign: data.data.paySign,
},
function (res) {
if (res.err_msg == 'get_brand_wcpay_request:ok') {
// 数据设置
self.order_item_pay_success_handle(data, order_id);
} else {
self.order_item_pay_fail_handle(data, order_id, res.err_msg);
}
}
);
}
if (typeof WeixinJSBridge == 'undefined') {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
}
// 二维码展示
if ((data.data.qrcode_url || null) != null && (data.data.name || null) != null && (data.data.order_no || null) != null) {
status = true;
// 显示支付窗口
self.setData({
popup_view_pay_data: data.data,
popup_view_pay_qrcode_is_show: true,
});
// 状态验证
self.pay_status_check_handle(self, data, order_id);
}
// 返回html表单
if ((data.data.html || null) != null) {
status = true;
var div = document.createElement('paydivform');
div.innerHTML = data.data.html;
document.body.appendChild(div);
var fm = document.forms;
var fm_len = fm.length;
if (fm_len > 0) {
fm[fm_len - 1].submit();
}
}
// 未匹配到的支付处理方式
if (!status) {
app.globalData.showToast(data.payment.name + self.$t('payment.payment.2rw3qh'));
}
}
},
// 支付状态验证处理
pay_status_check_handle(self, data, order_id) {
// 先清除已存在的定时
clearInterval(self.pay_statuc_check_timer);
// 定时校验支付状态
var timer = setInterval(function () {
uni.request({
url: self.propQrcodeUrl,
method: 'POST',
data: {
order_no: data.data.order_no,
},
dataType: 'json',
success: (res) => {
if (res.data.code == 0) {
// 清除定时、支付数据、弹窗
clearInterval(self.pay_statuc_check_timer);
self.setData({
popup_view_pay_data: null,
popup_view_pay_qrcode_is_show: false,
payment_confirm_modal_status: false,
});
// 数据设置
self.order_item_pay_success_handle(data, order_id);
} else {
// -333支付中、其它状态则提示错误
if (res.data.code != -333) {
clearInterval(self.pay_statuc_check_timer);
app.globalData.showToast(res.data.msg);
}
}
},
fail: () => {
clearInterval(self.pay_statuc_check_timer);
self.order_item_pay_fail_handle(data, order_id, self.$t('common.internet_error_tips'));
},
});
}, 3000);
self.setData({
pay_statuc_check_timer: timer,
});
},
// 支付确认弹窗事件
payment_confirm_event(e) {
// 关闭弹窗清除定时任务
this.setData({
payment_confirm_modal_status: false,
});
clearInterval(this.pay_statuc_check_timer);
// 回调处理
if(parseInt(e.currentTarget.dataset.type || 0) == 1) {
this.order_item_pay_success_handle(this.pay_response_data, this.order_id);
} else {
this.order_item_pay_fail_handle(this.pay_response_data, this.order_id, this.$t('paytips.paytips.6y488i'));
}
},
// 支付成功数据设置 data:后台返回的参数, order_id: 订单idis_to_page是否需要跳转页面的参数控制
order_item_pay_success_handle(data, order_id, is_to_page = true) {
let back_data = {
data: data,
order_id: order_id,
temp_pay_index: this.propTempPayIndex,
payment_id: this.payment_id,
is_to_page: is_to_page,
};
this.$emit('pay-success', back_data);
if (is_to_page) {
this.to_success_page_event();
}
},
// 支付失败数据设置 data:后台返回的参数, order_id: 订单id, msg: 错误提示信息
order_item_pay_fail_handle(data, order_id, msg) {
let back_data = {
data: data,
order_id: order_id,
temp_pay_index: this.propTempPayIndex,
payment_id: this.payment_id,
};
this.$emit('pay-fail', back_data);
this.to_fail_page_event(msg);
},
// 成功跳转
to_success_page_event() {
if (this.propToPage) {
// 跳转支付页面
app.globalData.url_open(this.propToPage, true);
} else {
let url_data = {
code: '9000',
};
url_data = Object.assign({}, url_data, this.propToPageBack);
// 跳转支付页面
app.globalData.url_open('/pages/paytips/paytips?params=' + encodeURIComponent(base64.encode(JSON.stringify(url_data))), this.propIsRedirectTo);
}
},
// 失败跳转
to_fail_page_event(msg) {
let to_fail_page = this.propToFailPage || null;
if (to_fail_page != null) {
let join = (to_fail_page.indexOf('?') == -1) ? '?' : '&';
to_fail_page += join+'msg='+msg;
if (this.propIsFailAlert) {
// 现金支付
uni.showModal({
content: msg,
showCancel: false,
confirmText: this.$t('common.confirm'),
success(res) {
if (res.confirm) {
// 跳转支付页面
app.globalData.url_open(to_fail_page, true);
}
},
});
} else {
// 跳转支付页面
app.globalData.url_open(to_fail_page, true);
}
} else {
if (msg) {
app.globalData.showToast(msg);
}
}
},
to_other(order_id) {
if (this.propToAppointPage) {
// 跳转订单列表页
app.globalData.url_open(this.propToAppointPage, true);
}
},
// 页面卸载
onUnload(e) {
clearInterval(this.pay_statuc_check_timer);
},
// 支付html展示窗口事件
popup_view_pay_html_event_close(e) {
this.setData({
popup_view_pay_html_is_show: false,
});
this.to_other();
},
},
};
</script>
<style scoped>
/**
* 支付方式
*/
.payment-price {
font-size: 80rpx;
}
.payment-list .scroll-y {
max-height: 430rpx;
}
.payment-list .item {
padding: 28rpx 28rpx 28rpx 32rpx;
}
.payment-list .icon {
width: 50rpx;
height: 50rpx !important;
}
.payment-submit {
padding: 40rpx;
}
/**
* 支付确认弹窗
*/
.payment-confirm-modal {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 100;
width: 100%;
height: 100%;
}
.payment-confirm-modal .content {
margin-top: 60%;
z-index: 101;
}
</style>

214
components/popup/popup.vue Normal file
View File

@@ -0,0 +1,214 @@
<template>
<view :class="theme_view + ' ' + propMostClass">
<view :class="'popup ' + (propClassname || '') + ' ' + (propShow ? 'popup-show' : 'popup-hide') + ' ' + (propAnimation ? 'animation' : '')" :disable-scroll="propDisablescroll">
<view class="popup-mask" :style="'z-index: ' + propIndex + ';'" v-if="propMask" @tap="on_mask_tap"></view>
<view :class="'popup-content popup-' + (propPosition || 'bottom') + ' ' + (propIsRadius ? '' : 'popup-radius-0') + ' ' + (propIsBar ? 'popup-bar' : '') + ' ' + (propPosition === 'bottom' ? 'bottom-line-exclude' : '')" :style="popup_content_style + propStyle">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
popup_content_style: '',
};
},
components: {},
props: {
// 最外层的class
propMostClass: {
type: String,
default: '',
},
// 内层class
propClassname: {
type: String,
default: '',
},
propShow: {
type: Boolean,
default: false,
},
propPosition: {
type: String,
default: 'bottom',
},
propMask: {
type: Boolean,
default: true,
},
propAnimation: {
type: Boolean,
default: true,
},
propDisablescroll: {
type: Boolean,
default: false,
},
propIsBar: {
type: Boolean,
default: false,
},
// 弹窗是否需要圆角 默认需要
propIsRadius: {
type: Boolean,
default: true,
},
propIndex: {
type: Number,
default: 100,
},
// 需要携带单位后缀
propTop: {
type: [String, Number],
default: '',
},
propBottom: {
type: String,
default: '',
},
propStyle: {
type: String,
default: '',
},
},
// 属性值改变监听
watch: {
// 监听状态
propShow(value, old_value) {
this.init_handle();
},
},
// 组建创建
created: function () {
this.init_handle();
},
methods: {
// 事件处理
on_mask_tap: function on_mask_tap() {
this.$emit(
'onclose',
{
detail: {},
},
{}
);
},
// 初初始化处理
init_handle() {
var tabbar_height = 0;
if(this.propPosition == 'bottom') {
// 弹窗从底部弹出,获取底部菜单高度、如果当前为底部菜单页面则增加底部间距
if(app.globalData.data.is_use_native_tabbar != 1 && app.globalData.is_tabbar_pages()) {
tabbar_height = (app.globalData.app_system_tabbar_height_value()*2)+20;
} else {
var height = (app.globalData.current_page(false) == 'pages/diy/diy') ? app.globalData.app_diy_tabbar_height_value() : 0;
tabbar_height = (height > 0) ? (height*2)+20 : 0;
}
}
// 左边距位置处理
var left = 0;
// #ifdef H5
// 处理内容左边距、避免父级设置内边距影响
var width = uni.getSystemInfoSync().windowWidth;
if (width > 960) {
left = (width - 800) / 2;
}
// #endif
this.setData({
popup_content_style: 'left:' + left + 'px;' + (this.propTop ? 'top:' + this.propTop : '') + ';' + (this.propBottom ? 'bottom:' + this.propBottom : '') + ';padding-bottom:' + tabbar_height + 'rpx;',
});
},
},
};
</script>
<style>
.popup {
opacity: 0;
}
.popup-content {
position: fixed;
background: #fff;
z-index: 101;
overflow: hidden;
}
.popup-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.6);
opacity: 0;
pointer-events: none;
z-index: 100;
}
.popup-left {
transform: translateX(-100%);
left: 0;
top: 0;
bottom: 0;
}
.popup-right {
transform: translateX(100%);
right: 0;
top: 0;
bottom: 0;
}
.popup-top {
top: 0;
width: 100vw;
transform: translateY(-100%);
}
.popup-bottom {
bottom: var(--window-bottom);
width: 100vw;
transform: translateY(100%);
}
.popup-show {
opacity: 1;
}
.popup-hide {
transition: all 1s linear;
}
.popup-show .popup-content {
transform: none;
}
.popup-show .popup-mask {
opacity: 1;
pointer-events: auto;
}
.popup.animation .popup-mask,
.popup.animation .popup-content {
transition: all 0.35s linear;
}
.popup-top {
border-bottom-right-radius: 20rpx;
border-bottom-left-radius: 20rpx;
}
.popup-bottom {
border-top-right-radius: 20rpx;
border-top-left-radius: 20rpx;
}
.popup-left {
border-top-right-radius: 20rpx;
border-bottom-right-radius: 20rpx;
}
.popup-right {
border-top-left-radius: 20rpx;
border-bottom-left-radius: 20rpx;
}
.popup-radius-0 {
border-radius: 0 !important;
}
.popup-bar {
/* #ifdef H5 || APP */
bottom: var(--window-bottom) !important;
/* #endif */
}
</style>

View File

@@ -0,0 +1,131 @@
<template>
<view :class="theme_view">
<view v-if="(data || null) != null && status == 1" class="plugins-popupscreen wh-auto ht-auto">
<view class="content pr">
<image class="dis-block auto" :src="data.images" mode="widthFix" :data-value="data.images_url || ''" @tap="url_event"></image>
<view class="tc margin-top-xl">
<view class="close cp round padding-sm auto" @tap.stop="close_event">
<iconfont name="icon-close-o" size="28rpx" color="#cacaca"></iconfont>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data: null,
status: 0,
cache_key: 'plugins_popupscreen_cache_key',
timer: null,
};
},
props: {},
// 页面被展示
created: function () {
this.init_config();
},
methods: {
// 初始化配置
init_config(status) {
if ((status || false) == true) {
this.setData({
data: app.globalData.get_config('plugins_base.popupscreen.data') || null,
});
this.init();
} else {
app.globalData.is_config(this, 'init_config');
}
},
// 初始化
init() {
var data = this.data || null;
if (data != null && parseInt(data.is_app_enable || 0) == 1 && parseInt(data.is_valid || 0) == 1) {
// 是否全局
var temp_status = true;
if(parseFloat(data.is_overall || 0) == 0) {
// 非首页则不展示
if(!app.globalData.is_tabbar_home()) {
temp_status = false;
}
}
if(temp_status) {
// 不存在关闭缓存或者超过间隔时间则显示
var key = this.cache_key;
var cv = parseInt(uni.getStorageSync(key)) || 0;
var pv = parseInt(data.interval_time) || 86400;
if (cv == 0 || cv + pv < app.globalData.get_timestamp()) {
// 是否开启自动关闭
var timer = null;
var ct = parseInt(data.close_time) || 0;
if (ct > 0) {
var self = this;
timer = setTimeout(function () {
self.setData({
status: 0,
});
uni.setStorage({
key: key,
data: app.globalData.get_timestamp(),
});
}, ct * 1000);
}
this.setData({
status: 1,
timer: timer,
});
}
}
}
},
// 关闭事件
close_event(e) {
this.setData({
status: 0,
});
uni.setStorage({
key: this.cache_key,
data: app.globalData.get_timestamp(),
});
clearInterval(this.timer);
},
// url事件
url_event(e) {
app.globalData.url_event(e);
}
}
};
</script>
<style scoped>
.plugins-popupscreen {
position: fixed;
left: 0;
top: 0;
z-index: 20;
background-color: rgb(0 0 0 / 0.3);
}
.plugins-popupscreen .close {
right: 10%;
top: 0;
z-index: 1;
width: 46rpx;
height: 46rpx;
line-height: 46rpx;
background-color: rgb(4 4 4 / 0.3);
border: solid 1px #a9a9a9;
}
.plugins-popupscreen .content {
margin-top: calc(50vh - 200rpx) !important;
}
.plugins-popupscreen .content image {
width: 600rpx;
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<view :class="theme_view">
<!-- 开启事件 -->
<movable-area v-if="propIsBtn && quick_status == 1" :class="'quick-movable-container ' + common_ent" :style="'height: calc(100% - ' + height_dec + 'rpx);top:' + top + 'rpx;'">
<movable-view direction="all" :x="x" :y="y" :animation="false" class="quick-event-submit" @tap="quick_open_event">
<image class="image" :src="common_static_url + 'quick-icon.png'" mode="widthFix"></image>
</movable-view>
</movable-area>
<!-- 弹窗 -->
<component-popup :propShow="popup_status" :propIsBar="propIsBar" propPosition="bottom" @onclose="quick_close_event">
<view :class="'nav-popup-container ' + common_ent">
<view class="close oh">
<view class="fr" @tap.stop="quick_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="nav-popup-content">
<view v-if="data_list.length > 0" class="nav-data-list">
<view v-for="(item, index) in data_list" :key="index" class="item cp">
<view :class="'item-content ' + ((item.bg_color || null) == null ? 'item-exposed' : '')" :data-value="item.event_value" :data-type="item.event_type" @tap="navigation_event" :style="(item.bg_color || null) == null ? '' : 'background-color:' + item.bg_color + ';'">
<image class="image" :src="item.images_url" mode="aspectFit"></image>
</view>
<view class="title">{{ item.name }}</view>
</view>
</view>
<view v-else>
<!-- 提示信息 -->
<component-no-data :propStatus="0"></component-no-data>
</view>
</view>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import componentPopup from '@/components/popup/popup';
import componentNoData from '@/components/no-data/no-data';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: app.globalData.get_static_url('common'),
popup_status: false,
quick_status: 0,
data_list: [],
system: null,
x: 0,
y: 0,
top: 0,
height_dec: 0,
is_first: 1,
common_ent: '',
};
},
components: {
componentPopup,
componentNoData,
},
props: {
propIsBtn: {
type: Boolean,
default: true,
},
propIsBar: {
type: Boolean,
default: false,
},
propIsNav: {
type: Boolean,
default: false,
},
propIsGrayscale: {
type: Boolean,
default: false,
},
},
// 属性值改变监听
watch: {
// 是否灰度
propIsGrayscale(value, old_value) {
this.common_ent = value ? 'grayscale' : '';
},
},
// 页面被展示
created: function () {
this.init_config();
// 页面是否定义导航
var value = this.propIsNav ? 100 : 0;
this.top = value;
this.height_dec = value;
// #ifdef H5 || APP
this.top = 140;
this.height_dec = this.propIsBar ? 280 : 140;
// #endif
// 非首次进入则重新初始化配置接口
if (this.is_first == 0) {
app.globalData.init_config();
}
// 数据设置
var system = app.globalData.get_system_info(null, null, true);
var height = app.globalData.window_height_handle(system);
var width = app.globalData.window_width_handle(system.windowWidth);
this.setData({
is_first: 0,
system: system,
x: width - 65,
y: height - 280,
// 是否灰度
common_ent: this.propIsGrayscale ? 'grayscale' : '',
});
},
methods: {
// 初始化配置
init_config(status) {
if ((status || false) == true) {
var data_list = app.globalData.get_config('quick_nav') || [];
this.setData({
data_list: data_list,
quick_status: (data_list.length > 0) ? (app.globalData.get_config('config.home_navigation_main_quick_status') || 0) : 0,
});
} else {
app.globalData.is_config(this, 'init_config');
}
},
// 弹层开启
quick_open_event(e) {
this.setData({
popup_status: true,
data_list: app.globalData.get_config('quick_nav') || [],
});
},
// 弹层关闭
quick_close_event(e) {
this.setData({
popup_status: false,
});
},
// 操作事件
navigation_event(e) {
this.setData({
popup_status: false,
});
app.globalData.operation_event(e);
},
},
};
</script>
<style>
/**
* 按钮
*/
.quick-movable-container {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: transparent;
pointer-events: none;
z-index: 2;
}
.quick-event-submit {
pointer-events: auto;
width: 50px;
height: 50px;
border-radius: 50%;
}
.quick-event-submit .image {
width: 50px !important;
height: 50px !important;
display: block;
position: relative;
z-index: 10;
border-radius: 100%;
box-shadow: 0 0 6rpx 10rpx rgba(89, 181, 255, 15%);
background: #59b5ff;
}
/**
* 弹窗
*/
.nav-popup-container {
padding: 20rpx 10rpx 0 10rpx;
background: #fff;
}
.nav-popup-container .close {
position: absolute;
top: 20rpx;
right: 20rpx;
z-index: 2;
}
.nav-popup-content {
max-height: 80vh;
overflow-y: scroll;
overflow-x: hidden;
padding-bottom: 20rpx;
}
/**
* 内容
*/
.nav-data-list {
overflow: hidden;
background: #fff;
}
.nav-data-list .item {
width: calc(25% - 60rpx);
float: left;
padding: 30rpx;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.nav-data-list .item-content {
border-radius: 50%;
padding: 20rpx;
text-align: center;
margin: 0 auto;
-webkit-box-shadow: 0 2px 12px rgb(226 226 226 / 95%);
box-shadow: 0 2px 12px rgb(226 226 226 / 95%);
}
.nav-data-list .item-content,
.nav-data-list .item .image {
width: 70rpx !important;
height: 70rpx !important;
}
.nav-data-list .item .item-exposed {
padding: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.nav-data-list .item .item-exposed,
.nav-data-list .item .item-exposed .image {
width: 110rpx !important;
height: 110rpx !important;
}
.nav-data-list .item .title {
margin-top: 10rpx;
font-size: 28rpx !important;
text-align: center;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
<template>
<view :class="theme_view">
<view v-if="(data_list || null) != null && data_list.length > 0" class="plugins-realstore-data-list oh">
<block v-for="(item, index) in data_list" :key="index">
<view class="item bg-white padding-top-xl padding-bottom-sm padding-horizontal-main border-radius-main pr spacing-mb" :class="item.status_info.type === 2 ? 'opacity' : ''" :data-index="index" :data-value="item.url+propRealstoreDetailQuery" @tap="realstore_item_event">
<view class="base oh flex-row">
<!-- 基础内容 -->
<image :src="item.logo" mode="widthFix" class="logo circle br"></image>
<view class="base-right flex-1 flex-width">
<view class="title fw-b text-size single-text tl">
<text v-if="(item.alias || null) != null" class="va-m title-icon border-radius-sm br-main cr-main text-size-xs padding-horizontal-xs margin-right-xs">{{ item.alias }}</text>
<text class="va-m">{{ item.name }}</text>
</view>
<view class="margin-top-sm padding-top-xs text-size-xs cr-grey">
<view v-if="(item.status_info.time || null) != null" class="flex-row align-c">
<iconfont name="icon-time pr top-xs cr-grey-9"></iconfont>
<view :class="'status-icon text-size-xs divider-r padding-left-xs padding-right-sm margin-right-sm ' + (item.status_info.status == 1 ? 'cr-green' : item.status_info.type == 1 ? 'cr-red' : 'cr-grey-c')">
{{ item.status_info.msg }}
</view>
{{ item.status_info.time }}
</view>
</view>
</view>
</view>
<view class="flex-row jc-sb align-c br-t-dashed margin-top-main padding-top-sm">
<!-- 地址 -->
<view class="address-content single-text cr-base margin-left-xs dis-inline-block text-size-xs oh cp tl" :data-value="item.province_name + item.city_name + item.county_name + item.address" @tap.stop="text_copy_event">
<view class="dis-inline-block va-m cr-grey-9 margin-top-sm">
<iconfont name="icon-map-address"></iconfont>
</view>
<text class="va-m margin-left-xs">{{ item.province_name }}{{ item.city_name }}{{ item.county_name }}{{ item.address }}</text>
</view>
<view v-if="(item.distance || null) != null" class="text-size-xs cr-grey-c pa address-distance">{{$t('extraction-address.extraction-address.42v8tv')}}{{ item.distance }}</view>
</view>
<!-- 右侧操作 -->
<view class="icon-list pa">
<view v-if="(item.service_data || null) != null && (item.service_data.service_tel || null) != null" class="icon-item dis-inline-block tc cp" :data-value="item.service_data.service_tel" @tap.stop="tel_event">
<iconfont name="icon-tel" size="30rpx"></iconfont>
</view>
<!-- #ifndef MP-KUAISHOU -->
<view v-if="item.lat != 0 && item.lng != 0" class="icon-item dis-inline-block tc cp" :data-index="index" @tap.stop="address_map_event">
<iconfont name="icon-send" size="30rpx"></iconfont>
</view>
<!-- #endif -->
</view>
</view>
</block>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data_list: [],
favor_user: [],
};
},
components: {},
props: {
propIsFavor: {
type: Boolean,
default: true,
},
propIsChoice: {
type: Boolean,
default: false,
},
propIsChoiceBackType: {
type: String,
default: '',
},
propIsOpenRealstoreRedirect: {
type: Boolean,
default: true,
},
propData: {
type: Object,
default: () => {
return {};
},
},
propFavorUser: {
type: Array,
default: () => [],
},
propRealstoreDetailQuery: {
type: String,
default: '',
},
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.init();
}
},
// 页面被展示
created: function (e) {
this.init();
},
methods: {
// 初始化
init() {
var data_list = ((this.propData || null) == null || (this.propData.data || null) == null || this.propData.data.length == 0) ? [] : this.propData.data;
this.setData({
data_list: data_list,
favor_user: this.propFavorUser,
});
this.data_list_handle();
},
// 数据列表处理
data_list_handle() {
var temp_data_list = this.data_list;
for (var i in temp_data_list) {
temp_data_list[i]["is_favor"] = this.favor_user.indexOf(temp_data_list[i]["id"]) == -1 ? 0 : 1;
}
this.setData({
data_list: temp_data_list,
});
},
// 收藏事件
favor_event(e) {
if (!app.globalData.is_single_page_check()) {
return false;
}
var user = app.globalData.get_user_info(this, "favor_event");
if (user != false) {
var index = e.currentTarget.dataset.index;
var info = this.data_list[index];
uni.showLoading({
title: this.$t('common.processing_in_text'),
});
uni.request({
url: app.globalData.get_request_url("reversal", "favor", "realstore"),
method: "POST",
data: {
id: info.id,
},
dataType: "json",
success: (res) => {
uni.hideLoading();
if (res.data.code == 0) {
var temp_data = this.data_list;
var temp_favor = this.favor_user;
temp_data[index]["is_favor"] = res.data.data.status;
if (res.data.data.status == 1) {
if (temp_favor.indexOf(info.id) == -1) {
temp_favor.push(info.id);
}
} else {
if (temp_favor.indexOf(info.id) != -1) {
temp_favor.splice(index, 1);
}
}
this.setData({
data_list: temp_data,
favor_user: temp_favor,
});
app.globalData.showToast(res.data.msg, "success");
} else {
if (app.globalData.is_login_check(res.data, this, "favor_event")) {
app.globalData.showToast(res.data.msg);
}
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
}
},
// 电话
tel_event(e) {
app.globalData.call_tel(e);
},
// 剪切板
text_copy_event(e) {
app.globalData.text_copy_event(e);
},
// 地图查看
address_map_event(e) {
var info = this.data_list[e.currentTarget.dataset.index] || {};
if (info.lat == 0 || info.lng == 0) {
app.globalData.showToast(this.$t('user-order-detail.user-order-detail.i876o3'));
return false;
}
var address = (info.province_name || "") + (info.city_name || "") + (info.county_name || "") + (info.address || "");
app.globalData.open_location(info.lng, info.lat, info.name, address);
},
// 门店事件
realstore_item_event(e) {
// 是否选择模式
if(this.propIsChoice) {
// 存储门店缓存
var data = this.data_list[e.currentTarget.dataset.index];
uni.setStorageSync(app.globalData.data.cache_realstore_detail_choice_key, {
data: data,
status: 1
});
// 回调事件
this.$emit('onChoiceEvent', data);
// 选择回调类型
switch(this.propIsChoiceBackType) {
// 返回上一个页面
case 'back' :
app.globalData.page_back_prev_event();
break;
// 进入门店详情页面
case 'realstore-detail' :
app.globalData.url_open(data.url, this.propIsOpenRealstoreRedirect);
break;
}
} else {
app.globalData.url_event(e);
}
}
}
};
</script>
<style></style>

View File

@@ -0,0 +1,234 @@
<template>
<view :class="theme_view">
<component-popup :propShow="propShow" propPosition="bottom" @onclose="popup_close_event">
<view class="flex-row jc-sb align-c padding-main">
<text class="cr-grey" @tap="popup_close_event">{{$t('common.cancel')}}</text>
<text class="cr-blue" @tap="sub_ragion_event">{{$t('common.confirm')}}</text>
</view>
<view class="g-dp-ctt-wrapper">
<picker-view class="picker-view" :indicator-style="indicatorStyle" :value="columns_index[0]" data-column="0" @change="changeHandler">
<picker-view-column>
<view class="g-dp-ctt-wp-item" v-for="(item, a) in columns[0]" :key="item.id">{{ item.name }}</view>
</picker-view-column>
</picker-view>
<picker-view class="picker-view" :indicator-style="indicatorStyle" :value="columns_index[1]" data-column="1" @change="changeHandler">
<picker-view-column>
<view class="g-dp-ctt-wp-item" v-for="(item, b) in columns[1]" :key="item.id">{{ item.name }}</view>
</picker-view-column>
</picker-view>
<picker-view class="picker-view" :indicator-style="indicatorStyle" :value="columns_index[2]" data-column="2" @change="changeHandler">
<picker-view-column>
<view class="g-dp-ctt-wp-item" v-for="(item, c) in columns[2]" :key="item.id">{{ item.name }}</view>
</picker-view-column>
</picker-view>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import componentPopup from "@/components/popup/popup";
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
columns: [[0], [0], [0]],
// 下标
columns_index: [[0], [0], [0]],
indicatorStyle: `height: ${uni.upx2px(88)}px;`,
};
},
components: {
componentPopup,
},
props: {
propShow: {
type: Boolean,
default: false,
},
propProvinceId: {
type: [String,Number],
default: "",
},
propCityId: {
type: [String,Number],
default: "",
},
propCountyId: {
type: [String,Number],
default: "",
},
},
watch: {
propShow(new_val, old_val) {
if (new_val) {
this.getProvince();
}
},
},
methods: {
// 地区初始化匹配索引
get_region_value(index, id) {
var data = this.columns[index];
var data_id = id;
var list_index = [];
data.forEach((d, i) => {
if (d.id == data_id) {
list_index = [i];
return false;
}
});
this.$set(this.columns_index, index, list_index);
},
// picker 滚动change事件
changeHandler(e) {
const { dataset, value } = e.target;
if (dataset.column == 0) {
if (this.columns[0][value[0]].id) {
this.$set(this.columns_index, dataset.column, value);
this.$set(this.columns_index, 1, [0]);
this.$set(this.columns_index, 2, [0]);
this.getCity(this.columns[0][value[0]].id, true);
}
} else if (dataset.column == 1) {
if (this.columns[1][value[0]].id) {
this.$set(this.columns_index, dataset.column, value);
this.$set(this.columns_index, 2, [0]);
this.getArea(this.columns[1][value[0]].id, true);
}
} else if (dataset.column == 2) {
this.$set(this.columns_index, dataset.column, value);
}
},
// 获取省
getProvince() {
uni.request({
url: app.globalData.get_request_url("index", "region"),
method: "POST",
data: {},
dataType: "json",
success: (res) => {
if (res.data.code == 0) {
var data = res.data.data;
this.$set(this.columns, 0, data);
this.getCity(this.propProvinceId ? this.propProvinceId : data[0].id);
if (this.propProvinceId) {
this.get_region_value(0, this.propProvinceId);
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('extraction-apply.extraction-apply.fo7y6c'));
},
});
},
// 获取市
getCity(province_id, init = false) {
if (province_id) {
uni.request({
url: app.globalData.get_request_url("index", "region"),
method: "POST",
data: {
pid: province_id,
},
dataType: "json",
success: (res) => {
if (res.data.code == 0) {
var data = res.data.data;
this.$set(this.columns, 1, data);
if (init) {
this.getArea(data[0].id);
} else {
this.getArea(this.propCityId ? this.propCityId : data[0].id);
if (this.propCityId) {
this.get_region_value(1, this.propCityId);
}
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('extraction-apply.extraction-apply.b6qg7b'));
},
});
}
},
// 获取区
getArea(city_id, init = false) {
if (city_id) {
// 加载loding
uni.request({
url: app.globalData.get_request_url("index", "region"),
method: "POST",
data: {
pid: city_id,
},
dataType: "json",
success: (res) => {
if (res.data.code == 0) {
var data = res.data.data;
this.$set(this.columns, 2, data);
if (!init) {
if (this.propCountyId) {
this.get_region_value(2, this.propCountyId);
}
}
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
app.globalData.showToast(this.$t('extraction-apply.extraction-apply.5s5734'));
},
});
}
},
// 关闭按钮
popup_close_event(e) {
this.$emit("onclose", false);
},
//提交按钮
sub_ragion_event(e) {
let province = this.columns[0][this.columns_index[0]];
let city = this.columns[1][this.columns_index[1]];
let areal = this.columns[2][this.columns_index[2]];
this.popup_close_event();
this.$emit("call-back", province, city, areal);
},
},
};
</script>
<style scoped>
::v-deep .popup-bottom {
border-radius: 0;
}
.picker-view {
width: 32%; height: 480rpx
}
.picker-view-column {
height: 480rpx !important;
}
.g-dp-ctt-wrapper {
height: 480upx;
width: 100%;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
.g-dp-ctt-wp-item {
width: 100%;
height: 88upx;
line-height: 88upx;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30upx;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<view :class="theme_view">
<view :class="'search-content pr round '+propSize" :style="'background:' + propBgColor + ';' + ((propBrColor || null) != null ? 'border:1px solid ' + propBrColor + ';' : '')">
<view class="search-icon dis-inline-block pa" :style="'padding:' + propPadding" @tap="search_icon_event">
<iconfont :name="propIcon" :color="propIconColor" size="24rpx"></iconfont>
</view>
<input
type="text"
confirm-type="search"
:class="'input round wh-auto dis-block '+propClass"
:placeholder="(propPlaceholder || propPlaceholderValue || this.$t('search.search.660us5'))"
:placeholder-class="propPlaceholderClass"
:value="propDefaultValue"
:focus="propFocus"
@input="search_input_value_event"
@confirm="search_submit_confirm_event"
@focus="search_input_focus_event"
@blur="search_input_blur_event"
:style="'color:' + propTextColor + ';'"
/>
<button v-if="propIsBtn" class="search-btn pa bg-main" size="mini" type="default" @tap="search_submit_confirm_event">{{$t('common.search')}}</button>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
input_value: '',
};
},
components: {},
props: {
propUrl: {
type: String,
default: '/pages/goods-search/goods-search',
},
propFormName: {
type: String,
default: 'keywords',
},
propDefaultValue: {
type: String,
default: '',
},
propPlaceholder: {
type: String,
default: '',
},
propPlaceholderValue: {
type: String,
default: '',
},
propPlaceholderClass: {
type: String,
default: 'cr-grey-c',
},
propClass: {
type: String,
default: '',
},
propTextColor: {
type: String,
default: '#666',
},
propBgColor: {
type: String,
default: '#fff',
},
propBrColor: {
type: String,
default: '',
},
propIsRequired: {
type: Boolean,
default: true,
},
propIsOnEvent: {
type: Boolean,
default: false,
},
propIsOnFocusEvent: {
type: Boolean,
default: false,
},
propIsOnBlurEvent: {
type: Boolean,
default: false,
},
propIsOnInputEvent: {
type: Boolean,
default: false,
},
propIcon: {
type: String,
default: 'icon-search-max',
},
propIconColor: {
type: String,
default: '#ccc',
},
propIsIconOnEvent: {
type: Boolean,
default: false,
},
propIsBtn: {
type: Boolean,
default: false,
},
propSize: {
type: String,
default: '',
},
propFocus: {
type: Boolean,
default: false,
},
propPadding: {
type: String,
default: '16rpx 20rpx 0 20rpx;',
},
},
// 属性值改变监听
watch: {
// 默认值
propDefaultValue(value, old_value) {
this.setData({
input_value: value,
});
}
},
// 页面被展示
created: function () {
this.setData({
input_value: this.propDefaultValue
});
},
methods: {
// 搜索输入事件
search_input_value_event(e) {
this.setData({
input_value: e.detail.value,
});
// 是否回调事件
if (this.propIsOnInputEvent) {
this.$emit('oninput', e.detail.value);
}
},
// 搜索失去焦点事件
search_input_blur_event(e) {
this.setData({
input_value: e.detail.value,
});
// 是否回调事件
if (this.propIsOnBlurEvent) {
this.$emit('onblur', e.detail.value);
}
},
// 搜索获取焦点事件
search_input_focus_event(e) {
this.setData({
input_value: e.detail.value,
});
// 是否回调事件
if (this.propIsOnFocusEvent) {
this.$emit('onfocus', e.detail.value);
}
},
// 搜索确认事件
search_submit_confirm_event() {
// 是否验证必须要传值
var value = this.input_value || this.propPlaceholderValue;
if (this.propIsRequired && value === '') {
app.globalData.showToast(this.$t('search.search.ic9b89'));
return false;
}
// 是否回调事件
if (this.propIsOnEvent) {
this.$emit('onsearch', value);
} else {
// 进入搜索页面
app.globalData.url_open(this.propUrl + '?' + this.propFormName + '=' + value);
}
},
// 搜索确认(外部调用直接跳转搜索)
search_submit_confirm(value = '') {
app.globalData.url_open(this.propUrl + '?' + this.propFormName + '=' + value);
},
// icon事件
search_icon_event() {
// 是否回调事件
if (this.propIsIconOnEvent) {
this.$emit('onicon', {});
}
},
},
};
</script>
<style scoped>
.search-content .search-icon {
z-index: 1;
left: 0;
top: 0;
line-height: 28rpx;
height: 42rpx;
}
.search-content .input {
box-sizing: border-box;
font-size: 24rpx;
padding: 0 32rpx 0 64rpx;
height: 56rpx;
line-height: 56rpx;
background: transparent;
}
.search-content .search-btn {
width: 106rpx;
height: 46rpx;
line-height: 46rpx;
font-size: 28rpx;
border-radius: 30rpx;
padding: 0;
color: #fff;
right: 6rpx;
top: 50%;
transform: translateY(-50%);
z-index: 2;
}
.search-content.sm .search-icon {
padding-top: 18rpx;
}
.search-content.sm .input {
height: 60rpx;
line-height: 60rpx;
font-size: 26rpx;
}
.search-content.sm .search-btn {
height: 50rpx;
line-height: 50rpx;
}
.search-content.md .search-icon {
padding-top: 22rpx;
}
.search-content.md .input {
height: 66rpx;
line-height: 66rpx;
font-size: 28rpx;
}
.search-content.md .search-btn {
height: 56rpx;
line-height: 56rpx;
}
</style>

View File

@@ -0,0 +1,299 @@
<template>
<view :class="theme_view">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event">
<view class="share-popup bg-white">
<view class="close fr oh">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="share-popup-content">
<!-- #ifdef MP-ALIPAY -->
<view class="share-items oh cp" @tap="share_base_event">
<image class="image" :src="common_static_url + 'share-user-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.h04xiy') }}</text>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO || MP-KUAISHOU -->
<view class="share-items oh cp">
<button class="btn dis-block br-0 ht-auto" type="default" size="mini" open-type="share" hover-class="none" @tap="popup_close_event">
<image class="image" :src="common_static_url + 'share-user-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.h04xiy') }}</text>
</button>
</view>
<!-- #endif -->
<!-- #ifdef APP -->
<block v-if="is_app_weixin">
<view class="share-items oh cp" data-scene="WXSceneSession" data-provider="weixin" @tap="share_app_event">
<image class="image" :src="common_static_url + 'share-user-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.rhs2c5') }}</text>
</view>
<view class="share-items oh cp" data-scene="WXSceneTimeline" data-provider="weixin" @tap="share_app_event">
<image class="image" :src="common_static_url + 'share-friend-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.mv9l10') }}</text>
</view>
<view class="share-items oh cp" data-scene="WXSceneFavorite" data-provider="weixin" @tap="share_app_event">
<image class="image" :src="common_static_url + 'share-favor-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.f08y38') }}</text>
</view>
</block>
<block v-if="is_app_qq">
<view class="share-items oh cp" data-provider="qq" @tap="share_app_event">
<image class="image":src="common_static_url + 'share-qq-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.1242w9') }}</text>
</view>
</block>
<!-- #endif -->
<!-- #ifdef H5 || APP -->
<view class="share-items oh cp" @tap="share_url_copy_event">
<image class="image" :src="common_static_url + 'share-url-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.1oh013') }}</text>
</view>
<!-- #endif -->
<view v-if="is_goods_poster == 1 && (goods_id || 0) != 0" class="share-items oh cp" @tap="poster_event">
<image class="image" :src="common_static_url + 'share-poster-icon.png'" mode="scaleToFill"></image>
<text class="cr-grey text-size-xs single-text">{{ $t('share-popup.share-popup.dcp2qu') }}</text>
</view>
</view>
</view>
</component-popup>
<!-- 用户基础 -->
<component-user-base ref="user_base"></component-user-base>
</view>
</template>
<script>
const app = getApp();
var common_static_url = app.globalData.get_static_url('common');
import componentPopup from '@/components/popup/popup';
import componentUserBase from '@/components/user-base/user-base';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: common_static_url,
popup_status: false,
type: null,
is_goods_poster: 0,
goods_id: 0,
url: null,
images: null,
title: null,
summary: null,
is_app_weixin: true,
is_app_qq: true,
share_info: {},
};
},
components: {
componentPopup,
componentUserBase,
},
created: function () {},
methods: {
// 初始配置
init(config = {}) {
if (!app.globalData.is_single_page_check()) {
return false;
}
this.setData({
popup_status: config.status == undefined ? true : config.status,
type: config.type == undefined ? null : config.type,
is_goods_poster: config.is_goods_poster || 0,
goods_id: config.goods_id || 0,
goods_id: config.goods_id || 0,
url: config.url || null,
images: config.images || null,
title: config.title || null,
summary: config.summary || null,
share_info: config.share_info || {},
});
// 用户头像和昵称设置提示
if ((this.$refs.user_base || null) != null) {
this.$refs.user_base.init('share');
}
// #ifdef APP
// app分享通道隔离
uni.getProvider({
service: 'share',
success: (result) => {
var provider = result.provider || [];
this.setData({
is_app_weixin: provider.indexOf('weixin') != -1,
is_app_qq: provider.indexOf('qq') != -1,
});
},
fail: (error) => {},
});
// #endif
},
// 弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false,
});
},
// url链接地址复制分享
share_url_copy_event() {
var url = app.globalData.get_page_url();
// 增加分享标识
if(url.indexOf('referrer') == -1) {
var uid = app.globalData.get_user_cache_info('id') || null;
if(uid != null) {
var join = url.indexOf('?') == -1 ? '?' : '&';
url += join+'referrer='+uid;
}
}
app.globalData.text_copy_event(url);
},
// 基础分享事件
share_base_event() {
this.setData({
popup_status: false,
});
uni.pageScrollTo({
scrollTop: 0,
duration: 300,
complete: (res) => {
setTimeout(function () {
uni.showShareMenu();
}, 500);
},
});
},
// 商品海报分享
poster_event() {
var user = app.globalData.get_user_info(this, 'poster_event');
if (user != false) {
uni.showLoading({
title: this.$t('detail.detail.6xvl35'),
});
uni.request({
url: app.globalData.get_request_url('goodsposter', 'distribution', 'distribution'),
method: 'POST',
data: { goods_id: this.goods_id },
dataType: 'json',
success: (res) => {
uni.hideLoading();
if (res.data.code == 0) {
uni.previewImage({
current: res.data.data,
urls: [res.data.data],
});
} else {
if (app.globalData.is_login_check(res.data, this, 'poster_event')) {
app.globalData.showToast(res.data.msg);
}
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
}
},
// app分享
share_app_event(e) {
// 分享参数
var provider = e.currentTarget.dataset.provider;
var scene = e.currentTarget.dataset.scene || null;
// 分享基础数据
var share = app.globalData.share_content_handle(this.share_info || {});
var img = this.images || share.img;
var url = this.url || share.url;
var title = this.title || share.title;
var summary = this.summary || share.desc;
var type = this.type === null ? ((img || null) == null ? 1 : 0) : this.type;
var miniProgram = {};
// #ifdef APP
// 分享到好友是否走微信小程序则获取微信小程序原始id
if (scene == 'WXSceneSession') {
var weixin_original_id = app.globalData.get_config('config.common_app_mini_weixin_share_original_id') || null;
if (weixin_original_id != null) {
type = 5;
miniProgram = {
id: weixin_original_id,
path: url.split('#')[1],
type: 0,
webUrl: url,
};
}
}
// #endif
// 关闭分享弹窗
this.setData({
popup_status: false,
});
// 调用分享组件
uni.share({
provider: provider,
scene: scene,
type: type,
href: url,
title: title,
summary: summary,
imageUrl: img,
miniProgram: miniProgram,
success: function (res) {},
fail: function (err) {},
});
},
},
};
</script>
<style>
.share-popup {
padding: 20rpx 10rpx 0 10rpx;
position: relative;
}
.share-popup .close {
position: absolute;
top: 0;
right: 0;
z-index: 2;
padding: 20rpx;
}
.share-popup-content {
padding: 0 20rpx;
text-align: left;
}
.share-popup-content .share-items {
padding: 30rpx 0;
min-height: 85rpx;
display: flex;
}
.share-popup-content .share-items:not(:first-child) {
border-top: 1px solid #f0f0f0;
}
.share-popup-content .share-items .btn {
background: transparent;
padding: 0;
width: 100%;
text-align: left;
margin: 0;
}
.share-popup-content .share-items .image {
width: 80rpx;
height: 80rpx;
vertical-align: middle;
margin-right: 20rpx;
}
.share-popup-content .share-items .single-text {
width: calc(100% - 100rpx);
line-height: 85rpx;
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<view>
<view v-if="(data_list || null) != null && data_list.length > 0" class="plugins-shop-data-list oh">
<block v-for="(item, index) in data_list" :key="index">
<view class="item oh border-radius-main padding-vertical-xl padding-horizontal-main bg-white spacing-mb pr">
<view class="item-right-icon">
<iconfont name="icon-arrow-right" color="#999"></iconfont>
</view>
<view class="flex-row align-c" :data-value="item.url" @tap="url_event">
<image :src="item.logo" mode="aspectFit" class="logo border-radius-xs fl br"></image>
<view class="right-content fr flex-1 flex-width">
<view class="title single-text">
<!-- 认证信息 -->
<view
v-if="(config.is_enable_auth || 0) == 1 && ((item.auth_type != -1 && (item.auth_type_msg || null) != null) || ((item.bond_status || 0) == 1 && (item.bond_status_msg || null) != null))"
class="auth-icon dis-inline-block">
<!-- 实名认证 -->
<block v-if="item.auth_type != -1 && (item.auth_type_msg || null) != null">
<block v-if="item.auth_type == 0">
<image :src="config.shop_auth_personal_icon" class="icon va-m" mode="aspectFill"></image>
</block>
<block v-if="item.auth_type == 1">
<image :src="config.shop_auth_company_icon" class="icon va-m" mode="aspectFill"></image>
</block>
</block>
<!-- 保证金认证 -->
<block v-if="(item.bond_status || 0) == 1 && (item.bond_status_msg || null) != null">
<image :src="config.shop_auth_bond_icon" class="icon va-m" mode="aspectFill"></image>
</block>
</view>
<!-- 标题 -->
<text class="fw-b text-size va-m">{{item.name}}</text>
</view>
<view class="desc multi-text cr-base text-size-xs margin-top-sm padding-top-xs">{{item.describe}}</view>
</view>
</view>
</view>
</block>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
config: {},
data_list: []
};
},
components: {},
props: {
propConfig: {
type: [String, Object],
default: null
},
propData: {
type: Object,
default: () => {
return {};
},
}
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.init();
}
},
// 页面被展示
created: function(e) {
this.init();
},
methods: {
// 初始化
init() {
var config = ((this.propConfig || null) == null ? app.globalData.get_config('plugins_base.shop.data') : this.propConfig) || {};
var data_list = ((this.propData || null) == null || (this.propData.data || null) == null || this.propData.data.length == 0) ? [] : this.propData.data;
this.setData({
config: config,
data_list: data_list
});
},
// url事件
url_event(e) {
app.globalData.url_event(e);
}
}
};
</script>
<style>
</style>

View File

@@ -0,0 +1,158 @@
<template>
<view :class="theme_view">
<view v-if="propData.length > 0" class="spacing-mb" :class="(propLeft ? 'swiper-left ' : '') + (propRight ? 'swiper-right ' : '')">
<uni-swiper-dot class="uni-swiper-dot-box" :mode="propMode" :dots-styles="dotsStyles" @clickItem="click_item" :info="propData" :current="current">
<swiper class="banner oh" :class="' banner-' + (propSize || 'default') + ' ' + propRadius" :autoplay="propData.length > 0" :duration="duration" :circular="circular" @change="swiper_change" :current="swiperDotIndex">
<swiper-item v-for="(item, i) in propData" :key="i">
<image class="image" :src="item.images_url" mode="widthFix" :data-value="item.event_value || item.url" :data-type="item.event_type == undefined ? 1 : item.event_type" @tap="banner_event"> </image>
</swiper-item>
</swiper>
</uni-swiper-dot>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
circular: true,
duration: 500,
styleIndex: -1,
current: 0,
swiperDotIndex: 0,
dotsStyles: {},
};
},
components: {},
props: {
propData: {
type: Array,
default: [],
},
propSize: {
type: String,
default: 'default',
},
propRadius: {
type: String,
default: 'border-radius-main',
},
// 指示点 class 调整靠左
propLeft: {
type: Boolean,
default: false,
},
// 指示点 class 调整靠右
propRight: {
type: Boolean,
default: false,
},
// 轮播指示点分类 default/dot/round/nav/indexes
propMode: {
type: String,
default: 'dot',
},
// 未选择指示点背景色
propBackgroundColor: {
type: String,
default: '#fff',
},
// 指示点宽度 在 mode = nav、mode = indexes 时不生效
propWidth: {
type: Number,
default: 6,
},
// 指示点距 swiper 底部的高度
propBottom: {
type: Number,
default: 10,
},
// 未选择指示点边框样式
propBorder: {
type: String,
default: '0',
},
// 指示点前景色,只在 mode = nav mode = indexes 时生效
propColor: {
type: String,
default: '#fff',
},
// 已选择指示点背景色,在 mode = nav 时不生效
propSelectedBackgroundColor: {
type: String,
default: '' + app.globalData.hex_rgba(app.globalData.get_theme_color(), 0.5),
},
// 已选择指示点边框样式,在 mode = nav 时不生效
propSelectedBorder: {
type: String,
default: '0',
},
},
beforeMount() {
this.dotsStyles = {
backgroundColor: this.propBackgroundColor,
width: this.propWidth,
bottom: this.propBottom,
border: this.propBorder,
color: this.propColor,
selectedBackgroundColor: this.propSelectedBackgroundColor,
selectedBorder: this.propSelectedBorder,
};
},
methods: {
swiper_change(e) {
// 原始index
this.current = e.detail.current;
// 当前滑index
// this.currentIndex = tmpCurrent == this.propData.length - 1 ? 0 : tmpCurrent + 1;
this.$emit('changeBanner', this.propData[this.current].bg_color);
},
click_item(e) {
this.swiperDotIndex = e;
},
banner_event(e) {
app.globalData.operation_event(e);
},
},
};
</script>
<style>
.banner {
transform: translateY(0);
}
.banner .image {
min-width: 100%;
}
.banner-mini,
.banner-mini .image {
height: 200rpx !important;
}
.banner-default,
.banner-default .image {
height: 280rpx !important;
}
.banner-max,
.banner-max .image {
height: 420rpx !important;
}
/**
* 指示点 左右定位
*/
.swiper-left /deep/ .uni-swiper__dots-box {
justify-content: start;
padding-left: 24rpx;
}
.swiper-right /deep/ .uni-swiper__dots-box {
justify-content: end;
padding-right: 24rpx;
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<view :class="theme_view">
<view :style="{ height: statusBarHeight }" class="status-bar-height"></view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
statusBarHeight: 20
};
},
components: {},
props: {},
mounted() {
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight + 'px';
},
methods: {}
};
</script>
<style>
.status-bar-height {
height: 20px;
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<view :class="'switch-container switch-container-size-' + propSize" :style="'background:' + propBgColor + ';border:1px solid ' + propBrColor + ';'">
<view class="switch_view">
<view :class="'switch-item ' + propCheckedClass + ' ' + (isSwitch ? 'checked_switch' : '')" :style="isSwitch ? `color:${propCheckedColor}` : ''" @click.prevent.stop="changeSwitch(true)" :animation="animationData2">
{{ propSwitchList[0] || this.$t('switch.switch.924s7v') }}
</view>
<view class="switch-item" :class="{ checked_switch: !isSwitch }" :style="!isSwitch ? `color:${propCheckedColor}` : ''" @click.prevent.stop="changeSwitch(false)" :animation="animationData3">
{{ propSwitchList[1] || this.$t('switch.switch.g142o6') }}
</view>
</view>
<view class="disabled" v-if="propDisabled"></view>
<view :class="'position_view ' + propCheckedBgClass" :animation="animationData1" :style="[{ background: propCheckedBgColor }]"></view>
</view>
</template>
<script>
export default {
props: {
propSwitchList: {
type: Array,
default: ['', ''],
},
propSize: {
type: String,
default: 'sm',
},
propDefault: {
type: Boolean,
default: true,
},
propIsShowModal: {
type: Boolean,
default: false,
},
propDisabled: {
type: Boolean,
default: false,
},
propBgColor: {
type: String,
default: '#fff',
},
propBrColor: {
type: String,
default: '#ccc',
},
propCheckedBgColor: {
type: String,
default: '#4caf50',
},
propCheckedBgClass: {
type: String,
default: '',
},
propCheckedColor: {
type: String,
default: '#fff',
},
propCheckedClass: {
type: String,
default: '',
},
propId: {
type: null,
default: null,
},
},
data() {
return {
isSwitch: true,
initAnimation: {},
animationData1: {},
animationData2: {},
animationData3: {},
};
},
created() {
this.initAnimation = uni.createAnimation({
duration: 500,
timingFunction: 'ease',
});
this.isSwitch = this.propDefault;
this.changeAnimation();
},
methods: {
changeSwitch(isSwitch) {
if (isSwitch == this.isSwitch || this.propDisabled) {
return;
}
if (this.propIsShowModal) {
let index = isSwitch ? 0 : 1;
let text = this.propSwitchList[index];
let self = this;
uni.showModal({
title: self.$t('switch.switch.447u86'),
content: self.$t('switch.switch.8w5ok6', [text]),
success: (res) => {
if (res.confirm) {
self.isSwitch = isSwitch;
self.changeAnimation();
self.callParentEvent(isSwitch);
}
},
});
} else {
this.isSwitch = isSwitch;
this.changeAnimation();
this.callParentEvent(isSwitch);
}
},
changeAnimation() {
if (this.isSwitch) {
this.animationData1 = this.initAnimation.left(0).width('60%').step().export();
this.animationData2 = this.initAnimation.width('60%').step().export();
this.animationData3 = this.initAnimation.width('40%').step().export();
} else {
this.animationData1 = this.initAnimation.left('40%').width('60%').step().export();
this.animationData2 = this.initAnimation.width('40%').step().export();
this.animationData3 = this.initAnimation.width('60%').step().export();
}
},
callParentEvent() {
this.$emit('change', this.isSwitch, this.propId, () => {
// 回调方法应用场景父级组件请求api接口失败调用
this.isSwitch = !this.isSwitch;
this.changeAnimation();
});
},
},
};
</script>
<style>
.switch-container {
display: flex;
flex-direction: row;
width: 180rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 1000px;
position: relative;
}
.switch-container .switch_view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
display: flex;
border-radius: 1000px;
}
.switch-container .switch_view .switch-item {
color: #666;
font-size: 24rpx;
height: 100%;
width: 40%;
border-radius: 1000px;
display: flex;
justify-content: center;
align-items: center;
}
.switch-container .position_view {
position: absolute;
top: 0;
left: 0;
width: 60%;
height: 100%;
border-radius: 1000px;
background: #1aad19;
}
.switch-container .disabled {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99;
background: #fff;
opacity: 0.6;
border-radius: 1000px;
}
.switch-container-size-xs {
width: 150rpx;
height: 45rpx;
line-height: 45rpx;
}
</style>

View File

@@ -0,0 +1,554 @@
<template>
<view :class="theme_view">
<view class="time-select-content" @tap="_dataOpen">
<slot></slot>
</view>
<view :class="'time-select-popup-mask ' + (propIsShow ? 'time-select-popup-show' : '')" @tap="_maskClose">
<view class="time-select-popup-content" @click.stop="_stopFunc">
<view class="time-select-close-btn" v-if="propCloseBtn" @tap="_closeBtnClose">×</view>
<view class="time-select-title padding-bottom-sm">
<view v-if="(propTitle || null) != null">{{ propTitle || this.$t('buy.buy.q8u066') }}</view>
<view v-if="(propSubhead || null) != null">{{ propSubhead }}</view>
</view>
<view class="time-select-time-box">
<view class="left_box">
<block v-if="item.timeArr.length > 0" v-for="(item, index) in timeList" :key="item.dateStr">
<view @tap="_changeDay(index)" :class="{ active: item.checked }">
{{ item.name }}
</view>
</block>
</view>
<view class="right_box">
<view v-if="day_active_index == 0 && (propPlaceholder || null) != null" @tap="_changeTime('')" :class="time_active_index === '' ? 'active' : ''">{{ propPlaceholder }}</view>
<block v-for="(item, index) in activeTimeArr" :key="item.time">
<view @tap="_changeTime(index)" :class="{ active: item.checked }"> {{ item.time }}{{ propRangeType ? '-' + item.endtime : '' }} </view>
</block>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
props: {
propTitle: {
type: String,
default: ',',
},
propPlaceholder: {
type: String,
default: '',
},
propSubhead: {
type: String,
default: '',
},
propRangeType: {
type: Boolean,
default: true,
},
propIsShow: {
type: Boolean,
default: false,
},
propMaskHide: {
type: Boolean,
default: true,
},
propCloseBtn: {
type: Boolean,
default: true,
},
propRangeDay: {
type: [Number, String],
default: 2,
},
propRangeStartTime: {
type: String,
default: '00:00:00',
},
propRangeEndTime: {
type: String,
default: '00:00:00',
},
propDefaultTime: {
type: String,
default: '',
},
propIsRoundingTime: {
type: Boolean,
default: true,
},
propIntervalTime: {
//间隔时间
type: [Number, String],
default: 30,
},
propIsNow: {
type: Boolean,
default: false,
},
propDayStartIntTime: {
//每天开始间隔时间
type: [Number, String],
default: 0,
},
propDisabled: {
type: [String, Array],
default: () => [],
},
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
timeList: [],
selectDateStr: '',
select_dateStr: '',
selectTime: '',
selectEndime: '',
activeTimeArr: [],
day_active_index: 0,
time_active_index: '',
};
},
beforeMount() {
this._initDay();
},
watch: {
propIsShow: function (val, oldVal) {
val && this.timeList.length <= 0 && this._initDay();
},
},
methods: {
_stopFunc() {},
_dataOpen() {
this._selectEvent('open');
},
_closeBtnClose() {
if (this.propCloseBtn) {
this._selectEvent('close');
}
},
_maskClose() {
if (this.propMaskHide) {
this._selectEvent('close');
}
},
_selectEvent(data = '') {
this.$emit('selectEvent', data);
},
_changeDay(e) {
let _ind = e - 0;
let { timeList } = this;
timeList.forEach((ele) => {
ele.checked = false;
});
timeList[_ind].checked = true;
this.timeList = timeList;
this.selectDateStr = timeList[_ind].dateStr;
this.select_dateStr = timeList[_ind]._dateStr;
this.activeTimeArr = timeList[_ind].timeArr;
this.day_active_index = e;
},
_changeTime(e) {
let { activeTimeArr } = this;
let timeArr = JSON.parse(JSON.stringify(activeTimeArr));
timeArr.forEach((ele) => {
ele.checked = false;
});
let _data = '';
if (e !== '') {
let _ind = e - 0;
timeArr[_ind].checked = true;
this.selectTime = timeArr[_ind].time;
this.selectEndime = timeArr[_ind].endtime;
_data = this._handleData();
}
this.time_active_index = e;
this.activeTimeArr = timeArr;
_data['value'] = this.propRangeType ? _data._dateRange : _data._date;
this._selectEvent(_data);
},
_handleData() {
let _data = {};
let { selectDateStr, select_dateStr, selectTime, selectEndime } = this;
_data.date = selectDateStr + ' ' + selectTime;
_data._date = select_dateStr + ' ' + selectTime;
_data.dateRange = selectDateStr + ' ' + selectTime + '-' + selectEndime;
_data._dateRange = select_dateStr + ' ' + selectTime + '-' + selectEndime;
_data.timeStamp = new Date(selectDateStr + ' ' + selectTime).getTime();
return _data;
},
_initDay() {
let _timeList = [];
for (let index = 0; index < this.propRangeDay; index++) {
let _item = {
...this._getDate(index),
};
_item.timeArr = this._initTime(index);
_timeList.push(_item);
}
if (this.propDefaultTime) {
//存在默认时间
let _day = this.propDefaultTime.split(' ')[0].replace(/-/g, '/');
let _time = this.propDefaultTime.split(' ')[1];
let _flag = true;
for (let index = 0; index < _timeList.length; index++) {
const element = _timeList[index];
element.checked = false;
if (element.timeArr.length > 0 && element.dateStr === _day) {
element.checked = true;
_flag = false;
element.timeArr.forEach((item) => {
if (this._timeRange(item.time + ':00', item.endtime + ':00', _time)) {
item.checked = true;
this.time = item.time;
this.endtime = item.endtime;
}
});
this.selectDateStr = element.dateStr;
this.select_dateStr = element._dateStr;
this.activeTimeArr = element.timeArr;
}
}
if (_flag) {
this._setDefaultTime(_timeList);
}
} else {
this._setDefaultTime(_timeList);
}
this.timeList = _timeList;
this.time_active_index = this.propDefaultTime || '';
},
_setDefaultTime(list) {
for (let index = 0; index < list.length; index++) {
const element = list[index];
if (element.timeArr.length > 0) {
element.checked = true;
//是否默认选择时间
// element.timeArr[0].checked = true;
this.selectDateStr = element.dateStr;
this.select_dateStr = element._dateStr;
this.activeTimeArr = element.timeArr;
break;
}
}
},
_initTime(num) {
let _item = [];
let TempDisabled = this.propDisabled;
let tempIntervalTime = this.propIntervalTime;
if (tempIntervalTime <= 0) {
tempIntervalTime = 1;
}
let TempRangeStartTime = this._timeHandle(this.propRangeStartTime);
let TempRangeEndTime = this._timeHandle(this.propRangeEndTime);
if (TempRangeEndTime == '00:00:00') {
TempRangeEndTime = '23:59:59';
}
if (typeof TempDisabled === 'string') {
TempDisabled = TempDisabled ? TempDisabled.split(',') : [];
} else if (Array.isArray(TempDisabled)) {
TempDisabled = TempDisabled.map((ele) => {
return ele + '';
});
}
if (num === 0 && !TempDisabled.includes('0')) {
//今天
let _nowTime = this._roundingTime();
if (this._timeRange(TempRangeStartTime, TempRangeEndTime, _nowTime)) {
//当前时间在营业时间内
return this._forTime(_nowTime, TempRangeEndTime, tempIntervalTime, _nowTime);
} else if (this._toTimeStr(_nowTime) < this._toTimeStr(TempRangeStartTime)) {
//早于今天开始时间
return this._forTime(TempRangeStartTime, TempRangeEndTime, tempIntervalTime, _nowTime);
} else {
return [];
}
} else {
//其他日期
if (TempDisabled.includes(num + '')) {
//禁止当前日期了
return [];
}
return this._forTime(TempRangeStartTime, TempRangeEndTime, tempIntervalTime);
}
},
_getDate(num) {
let date = new Date();
let date1 = new Date(date);
let weekday = [this.$t('time-select.time-select.8k5rr7'), this.$t('time-select.time-select.17353c'), this.$t('time-select.time-select.40rmq4'), this.$t('time-select.time-select.q35g41'), this.$t('time-select.time-select.v213l8'), this.$t('time-select.time-select.8q3q2x'), this.$t('time-select.time-select.9605m5')];
date1.setDate(date.getDate() + num);
let name = '',
dateStr = '';
name = date1.getMonth() - 0 + 1 + this.$t('detail.detail.zill36') + date1.getDate() + this.$t('time-select.time-select.h7l2xj') + weekday[date1.getDay()] + ')';
dateStr = date1.getFullYear() + '/' + (date1.getMonth() - 0 + 1) + '/' + date1.getDate();
if (num == 0) {
name = this.$t('time-select.time-select.cq522p') + weekday[date1.getDay()] + ')';
} else if (num == 1) {
name = this.$t('time-select.time-select.ub264m') + weekday[date1.getDay()] + ')';
}
return {
name: name,
dateStr: dateStr,
_dateStr: dateStr.replace(/\//g, '-'),
};
},
_addZero(num) {
num = num + '';
if (num.length === 1) {
return '0' + num;
}
return num;
},
_roundingTime() {
let _now = new Date();
let _h = _now.getHours() - 0;
let _m = _now.getMinutes() - 0;
if (_m > 30) {
_m = '00';
_h += 1;
} else if (_m - 0 > 0 && _m - 0 < 30) {
_m = 30;
}
if (this.propIsRoundingTime && !this.propIsNow) {
return _h + ':' + _m + ':00';
}
return new Date().getHours() + ':' + new Date().getMinutes() + ':00';
},
_forTime(st, et, it, dt = 0) {
let TempDayStartIntTime = this.propDayStartIntTime * 1000 * 60;
let _st = this._toTimeStr(st);
let _et = this._toTimeStr(et);
let _it = it * 1000 * 60;
let _list = [];
if (_st < _et) {
for (let i = _st + TempDayStartIntTime; i < _et; i += _it) {
_list.push({
time: this._toLocalTime(i),
endtime: this._toLocalTime(i + _it > _et ? _et : i + _it),
checked: false,
});
}
} else {
//跨天了
for (let i = dt; i < _et; i += _it) {
_list.push({
time: this._toLocalTime(i),
endtime: this._toLocalTime(i + _it > _et ? _et : i + _it),
checked: false,
});
}
for (let i = _st + TempDayStartIntTime; i < this._toTimeStr('23:59:59'); i += _it) {
_list.push({
time: this._toLocalTime(i),
endtime: this._toLocalTime(i + _it),
checked: false,
});
}
}
return _list;
},
_toTimeStr(time = '') {
let newTime = time;
let itemStr = 0;
let timeArr = newTime.split(':');
let h = timeArr[0] * 1000 * 60 * 60;
let m = timeArr[1] * 1000 * 60;
let s = timeArr[2] * 1000;
itemStr = h + m + s;
return itemStr;
},
_toLocalTime(v, type = 0) {
let h = parseInt((v / 1000 / 60 / 60) % 24);
let m = parseInt((v / 1000 / 60) % 60);
let s = parseInt((v / 1000) % 60);
v = type === 0 ? `${this._addZero(h)}:${this._addZero(m)}` : `${this._addZero(h)}:${this._addZero(m)}:${this._addZero(s)}`;
return v;
},
_timeRange(beginTime, endTime, nowTime) {
let strb = beginTime.split(':');
let stre = endTime.split(':');
let strn = nowTime.split(':');
let b = new Date();
let e = new Date();
let n = new Date();
b.setHours(strb[0]);
b.setMinutes(strb[1]);
e.setHours(stre[0]);
e.setMinutes(stre[1]);
n.setHours(strn[0]);
n.setMinutes(strn[1]);
let _b = b.getTime();
let _e = e.getTime();
let _n = n.getTime();
if (_b > _e) {
if (_n - _e >= 0 && _n - _b < 0) {
return false;
} else {
return true;
}
} else {
if (_n - _b >= 0 && _n - _e < 0) {
return true;
} else {
return false;
}
}
},
_timeHandle(value) {
let arr = (value || null) == null ? [] : value.split(':');
let len = arr.length;
if (len < 3) {
while (len < 3) {
arr.push('00');
len++;
}
}
return arr.join(':');
},
},
};
</script>
<style>
.time-select-popup-mask {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
z-index: 100;
pointer-events: none;
}
.time-select-popup-mask view {
box-sizing: border-box;
}
.time-select-popup-content {
height: 0rpx;
background-color: #fff;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 20rpx 10rpx 0 10rpx;
transition: height 0.5s;
border-top-right-radius: 20rpx;
border-top-left-radius: 20rpx;
height: auto;
z-index: 101;
transform: translateY(100%);
}
.time-select-popup-show {
opacity: 1;
pointer-events: auto;
}
.time-select-popup-show .time-select-popup-content {
transform: none;
}
.time-select-popup-mask,
.time-select-popup-content {
transition: all 0.25s linear;
}
.time-select-title {
text-align: center;
font-size: 32rpx;
color: #222222;
font-weight: 600;
position: relative;
}
.time-select-close-btn {
position: absolute;
width: 80rpx;
line-height: 80rpx;
text-align: center;
right: 0rpx;
padding-right: 15rpx;
top: 0;
color: #999999;
z-index: 99;
font-size: 44rpx;
}
.time-select-title > view:nth-child(2) {
font-size: 22rpx;
color: #919191;
margin-top: 10rpx;
}
.time-select-time-box {
border-top: 1px solid #fbf8fb;
background-color: #fbf8fb;
height: 660rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.time-select-time-box > view {
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
font-size: 28rpx;
}
.time-select-time-box .left_box {
color: #919191;
width: 45%;
}
.time-select-time-box .left_box view,
.time-select-time-box .right_box view {
text-align: center;
line-height: 90rpx;
position: relative;
color: #666;
}
.time-select-time-box .right_box view::before {
content: ' ';
width: 80%;
position: absolute;
left: 10%;
bottom: 0;
border: 1px dashed #f4f4f4;
}
.time-select-time-box .right_box view:last-child::before {
border: none;
}
.time-select-time-box .right_box {
width: 55%;
flex: 1;
background-color: #fff;
text-align: center;
}
.time-select-time-box .right_box .active {
position: relative;
color: #000;
font-weight: bold;
}
.time-select-time-box .left_box .active {
background-color: #fff;
color: #000;
}
.time-select-time-box .right_box .active::after {
content: ' ';
position: absolute;
top: 50%;
margin-top: -16rpx;
right: 50rpx;
width: 10rpx;
height: 20rpx;
border-color: #000;
border-style: solid;
border-width: 0 4rpx 4rpx 0;
transform: rotate(45deg);
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<view :class="theme_view">
<view class="spacing-nav-title flex-row align-c jc-sb text-size-xs">
<view class="title-left">
<text class="text-wrapper" :class="propTitleLeftBorder ? 'title-left-border' : ''" :style="'color:' + propTitleColor + ';'">{{propTitle}}</text>
<text class="vice-name margin-left-sm" :style="'color:' + propViceTitleColor + ';'">{{propViceTitle}}</text>
</view>
<text :data-value="propMoreUrl" @tap="url_event" class="arrow-right padding-right cr-grey cp">{{propMore || this.$t('common.more')}}</text>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
};
},
props: {
propTitle: {
type: String,
default: ''
},
propTitleColor: {
type: String,
default: '#333'
},
propViceTitle: {
type: String,
default: ''
},
propViceTitleColor: {
type: String,
default: '#999'
},
propMore: {
type: String,
default: '',
},
propMoreUrl: {
type: String,
default: ''
},
propTitleLeftBorder: {
type: Boolean,
default: true
},
},
methods: {
// url事件
url_event(e) {
app.globalData.url_event(e);
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,90 @@
<template>
<view>
<view :style="'background:'+(propBackgroundColor || '#fff')+';'+nav_style" :class="'trn-nav-top '+(propEnt || '')">
<view v-if="(propTitle || null) != null" class="trn-nav-top-title single-text">{{propTitle}}</view>
<slot></slot>
</view>
</view>
</template>
<script>
const app = getApp();
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
statusbar_height: 0,
nav_style: ''
}
},
props: {
propTitle: String,
propBackgroundColor: String,
propEnt: String,
propScroll: Number,
propHeight: Number,
},
watch: {
// 属性值改变监听
propScroll(value, old_value) {
// opacity 0 就是完全透明 1是不透明
var opacity = 0;
if(value >= 70 && value <= 100) {
opacity = 0.2;
}
// 越往下滑动 透明度越低
if(value >= 101 && value <= 120) {
opacity = 0.3;
}
if(value >= 120 && value <= 150) {
opacity = 0.5;
}
if(value >= 150 && value <= 170) {
opacity = 0.7;
}
if(value >= 170 && value <= 190) {
opacity = 1;
}
if(value > 190) {
opacity = 1;
}
// 距离小于多少就不显示了
if(value <= 70) {
opacity = 0;
}
// 设置样式
this.nav_style = 'opacity:'+opacity+';';
// #ifdef MP
this.nav_style += 'height:'+(this.propHeight+this.statusbar_height)+'px;';
// #endif
// #ifdef H5 || APP
this.nav_style += 'height:44px;';
// #endif
}
},
methods: {
},
mounted() {
// 获取系统状态栏高度
this.statusbar_height = parseInt(app.globalData.get_system_info('statusBarHeight', 0, true));
}
}
</script>
<style>
.trn-nav-top {
padding-top: var(--status-bar-height);
opacity: 0;
width: 100%;
z-index: 10;
position: fixed;
top: 0;
left: 0;
}
.trn-nav-top .trn-nav-top-title {
font-size: 36rpx;
text-align: left;
padding: 12px 250rpx 0 20rpx;
height: 37px;
color: #000;
}
</style>

View File

@@ -0,0 +1,157 @@
<template>
<view :class="theme_view">
<view class="flex-row flex-wrap">
<block v-if="propData.length > 0">
<view v-for="(item, index) in propData" :key="index" class="item margin-right-lg pr">
<text class="delete-icon pa z-i" @tap="upload_delete_event" :data-index="index">
<iconfont name="icon-bjdz-guanbi" size="36rpx" color="rgba(87,91,102,0.65)"></iconfont>
</text>
<image :src="item" @tap="upload_show_event" :data-index="index" mode="aspectFill" class="img border-radius-main oh"></image>
</view>
</block>
<view v-if="(propData || null) == null || propData.length < propMaxNum" class="img bg-grey-f5 border-radius-main flex-col align-c jc-c" @tap="file_upload_event">
<iconfont name="icon-wytw-sctp" size="52rpx" color="#999"></iconfont>
<text class="text-size-xs cr-grey-9">{{$t('upload.upload.b33f08')}}</text>
</view>
</view>
</view>
</template>
<script>
const app = getApp();
var common_static_url = app.globalData.get_static_url('common');
export default {
props: {
// 初始图片数据
propData: {
type: Array,
default: () => {
return [];
},
},
// 最大上传数量
propMaxNum: {
type: [Number, String],
default: 3,
},
// 路径类型 默认common
propPathType: {
type: String,
default: 'common',
},
},
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
common_static_url: common_static_url,
form_images_list: [],
};
},
mounted() {
this.setData({
form_images_list: this.propData,
});
},
created: function () {},
methods: {
// 采用递归的方式上传多张
upload_one_by_one(img_paths, success, fail, count, length) {
var self = this;
if (self.form_images_list.length <= this.propMaxNum) {
uni.uploadFile({
url: app.globalData.get_request_url('index', 'ueditor'),
filePath: img_paths[count],
name: 'upfile',
formData: {
action: 'uploadimage',
path_type: self.propPathType,
},
success: function (res) {
success++;
if (res.statusCode == 200) {
var data = typeof res.data == 'object' ? res.data : JSON.parse(res.data);
if (data.code == 0 && (data.data.url || null) != null) {
var list = self.form_images_list;
list.push(data.data.url);
self.setData({
form_images_list: list,
});
self.$emit('call-back', self.form_images_list);
} else {
app.globalData.showToast(data.msg);
}
}
},
fail: function (e) {
fail++;
},
complete: function (e) {
count++;
// 下一张
if (count >= length) {
// 上传完毕,作一下提示
//app.showToast('上传成功' + success +'张', 'success');
} else {
// 递归调用,上传下一张
self.upload_one_by_one(img_paths, success, fail, count, length);
}
},
});
}
},
// 文件上传
file_upload_event(e) {
var self = this;
uni.chooseImage({
count: self.propMaxNum,
success(res) {
var success = 0;
var fail = 0;
var length = res.tempFilePaths.length;
var count = 0;
self.upload_one_by_one(res.tempFilePaths, success, fail, count, length);
},
});
},
// 图片删除
upload_delete_event(e) {
var self = this;
uni.showModal({
title: this.$t('common.warm_tips'),
content: this.$t('order.order.psi67g'),
success(res) {
if (res.confirm) {
var list = self.form_images_list;
list.splice(e.currentTarget.dataset.index, 1);
self.setData({
form_images_list: list,
});
self.$emit('call-back', self.form_images_list);
}
},
});
},
// 上传图片预览
upload_show_event(e) {
uni.previewImage({
current: this.form_images_list[e.currentTarget.dataset.index],
urls: this.form_images_list,
});
},
},
};
</script>
<style scoped>
.img {
width: 120rpx;
height: 120rpx;
}
.delete-icon {
top: -16rpx;
right: -16rpx;
}
</style>

View File

@@ -0,0 +1,485 @@
<template>
<view :class="theme_view">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_close_event">
<view :class="'user-base-popup bg-white ' + (propIsGrayscale ? 'grayscale' : '')">
<view class="close fr oh">
<view class="fr" @tap.stop="popup_close_event">
<iconfont name="icon-close-o" size="24rpx" color="#999"></iconfont>
</view>
</view>
<form @submit="form_submit" class="form-container">
<view class="padding-top-sm">
<view class="text-size-md spacing-mb">
<text>{{$t('user-base.user-base.g5663y')}}</text>
<text v-if="(check_field.is_avatar || 0) == 1">{{$t('personal.personal.cw1d8p')}}</text>
<text v-if="(check_field.is_nickname || 0) == 1">{{$t('personal.personal.gw8br3')}}</text>
<text v-if="(check_field.is_mobile || 0) == 1">{{$t('login.login.28k91h')}}</text>
</view>
<view class="padding-bottom-main cr-grey-9 br-b">
<text>{{$t('user-base.user-base.913g4e')}}</text>
<text v-if="(check_field.is_avatar || 0) == 1">{{$t('personal.personal.cw1d8p')}}</text>
<text v-if="(check_field.is_nickname || 0) == 1">{{$t('personal.personal.gw8br3')}}</text>
<text v-if="(check_field.is_mobile || 0) == 1">{{$t('login.login.28k91h')}}</text>
<text>{{$t('user-base.user-base.yujeaf')}}</text>
</view>
<view v-if="(check_field.is_avatar || 0) == 1" class="form-gorup oh flex-row align-c br-b">
<view class="form-gorup-title text-size-md">{{$t('personal.personal.cw1d8p')}}<text class="form-group-tips-must">*</text></view>
<button class="bg-white br-0 padding-0 margin-left-xxl flex-row jc-sb align-c flex-1" hover-class="none" open-type="chooseAvatar" @chooseavatar="choose_avatar_event" @tap="choose_avatar_event">
<image :src="user_avatar || default_avatar" mode="widthFix" class="circle br user-base-avatar"></image>
<iconfont name="icon-arrow-right" size="24rpx" color="#ccc"></iconfont>
</button>
</view>
<view v-if="(check_field.is_nickname || 0) == 1" class="form-gorup oh flex-row align-c br-b">
<view class="form-gorup-title text-size-md">{{$t('personal.personal.gw8br3')}}<text class="form-group-tips-must">*</text></view>
<view class="user-nickname-container padding-left-xxl">
<input :type="client_value == 'weixin' ? 'nickname': 'text'" maxlength="16" placeholder-class="cr-grey-c" class="cr-base" :placeholder="$t('user-base.user-base.o19lj3')" @input="on_input_nickname" @blur="on_input_nickname" />
</view>
</view>
<view v-if="(check_field.is_mobile || 0) == 1" class="form-gorup oh flex-row align-c br-b">
<view class="form-gorup-title text-size-md">{{$t('login.login.1p7843')}}<text class="form-group-tips-must">*</text></view>
<view class="user-mobile-container padding-left-xxl">
<button class="bg-white br-white text-size-md margin-top-sm padding-left-0 tl" :class="(user_mobile || null) == null ? 'cr-green' : ''" type="default" hover-class="none" open-type="getPhoneNumber" @getphonenumber="confirm_phone_number_event">{{ user_mobile || $t('login.login.8fghjs') }}</button>
</view>
</view>
<view class="tc padding-top-xxl">
<button class="sub-btn cr-white text-size" :class="form_submit_disabled_status ? 'bg-grey-d br-grey-d' : 'bg-main br-main'" type="default" form-type="submit" hover-class="none">{{$t('common.save')}}</button>
</view>
</view>
</form>
</view>
</component-popup>
</view>
</template>
<script>
const app = getApp();
import componentPopup from '@/components/popup/popup';
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
client_value: app.globalData.application_client_type(),
default_avatar: app.globalData.data.default_user_head_src,
cache_key: app.globalData.data.cache_user_base_personal_interval_time_key,
popup_status: false,
user: {},
check_field: {},
user_avatar: '',
nickname: '',
user_mobile: '',
pages: [],
client: [],
interval_time: 0,
is_weixin_force: false,
form_submit_disabled_status: true,
back_object: null,
back_method: null,
back_params: null
};
},
components: {
componentPopup,
},
props: {
propIsGrayscale: {
type: Boolean,
default: false,
},
},
created: function () {
// 初始化配置
this.init_config();
},
methods: {
// 初始化配置
init_config(status) {
if ((status || false) == true) {
this.setData({
pages: app.globalData.get_config('config.common_app_user_base_popup_pages', []),
client: app.globalData.get_config('config.common_app_user_base_popup_client', []),
interval_time: parseInt(app.globalData.get_config('config.common_app_user_base_popup_interval_time', 1800)),
is_weixin_force: this.client_value == 'weixin' && parseInt(app.globalData.get_config('config.common_app_is_weixin_force_user_base', 0)) == 1,
});
} else {
app.globalData.is_config(this, 'init_config');
}
},
// 初始配置
init(params = {}) {
// 初始化配置
this.init_config(true);
// 未指定类型则自动匹配页面
var type = '';
if((params.type || null) == null) {
var page = '/'+app.globalData.current_page(false);
var obj = {
'index': '/pages/index/index',
'goods-category': '/pages/goods-category/goods-category',
'cart': '/pages/cart/cart',
'user': '/pages/user/user',
};
var index = Object.values(obj).indexOf(page);
if(index != -1) {
type = Object.keys(obj)[index];
}
} else {
type = params.type;
}
if((type || null) != null) {
// 是否需要展示弹窗提示
if (!this.popup_status && this.pages.indexOf(type) != -1 && this.client.indexOf(this.client_value) != -1) {
// 当前缓存用户
var user = app.globalData.get_user_cache_info() || null;
// 需要填写字段数据
var result = this.form_write_field_check_data(user);
// 非微信 或者 微信小程序是否强制填写基础信息、强制则不验证时间
if(this.client_value != 'weixin' || !this.is_weixin_force) {
// 间隔时间
var cache_time = parseInt(uni.getStorageSync(this.cache_key) || 0);
var current_time = Date.parse(new Date()) / 1000;
if (result.popup_status && !this.popup_status && cache_time > 0 && current_time < cache_time + parseInt(this.interval_time)) {
result['popup_status'] = false;
}
}
// 1秒后再提示用户填写信息
clearTimeout(this.timer);
var self = this;
var timer = setTimeout(function () {
self.setData({
...result,
...{
back_object: params.object || null,
back_method: params.method || null,
back_params: params.params || params,
}
});
}, 500);
this.setData({
timer: timer
});
}
}
},
// 表单需要填写的验证数据
form_write_field_check_data(user, is_set_data = false) {
var check_field = {
is_nickname: 0,
is_avatar: 0,
is_mobile: 0,
};
if((user || null) != null) {
// 默认昵称则赋空值
var arr = [this.$t('user-base.user-base.8u9on2'), this.$t('user-base.user-base.t8i9l4'), this.$t('user-base.user-base.0imw74'), this.$t('user-base.user-base.27q5af'), this.$t('user-base.user-base.211pk4'), this.$t('user-base.user-base.5x8o43'), 'WeChat User', 'Usuarios de Wechat'];
if ((user.nickname || null) == null || arr.indexOf(user.nickname) != -1) {
user['nickname'] = '';
check_field['is_nickname'] = 1;
}
// 头像是默认则置为空
if ((user.avatar || null) == null || user.avatar.indexOf('default-user-avatar') != -1) {
user['avatar'] = '';
check_field['is_avatar'] = 1;
}
// 手机
if(parseInt(user.is_mandatory_bind_mobile || 0) == 1 && (user.mobile || null) == null) {
var onekey_bind_mobile = app.globalData.get_config('config.common_user_onekey_bind_mobile_list', []);
if(onekey_bind_mobile.length > 0) {
if(onekey_bind_mobile.indexOf(this.client_value) != -1) {
user['mobile'] = '';
check_field['is_mobile'] = 1;
}
}
}
}
var result = {
check_field: check_field,
user: user,
popup_status: check_field.is_nickname + check_field.is_avatar + check_field.is_mobile > 0,
};
// 是否设置数据
if(is_set_data) {
this.setData(result);
}
return result;
},
// 基础信息填写打开
user_base_open(object, method, params) {
clearTimeout(this.timer);
this.setData({
popup_status: true,
back_object: object,
back_method: method,
back_params: params
});
},
// 弹层关闭
popup_close_event(e) {
this.setData({
popup_status: false,
});
uni.setStorageSync(this.cache_key, Date.parse(new Date()) / 1000);
},
// 头像事件
choose_avatar_event(e) {
var self = this;
if (this.client_value == 'weixin') {
self.upload_handle(e.detail.avatarUrl);
} else {
uni.chooseImage({
count: 1,
success(res) {
if (res.tempFilePaths.length > 0) {
self.upload_handle(res.tempFilePaths[0]);
}
},
});
}
},
// 上传处理
upload_handle(image) {
var self = this;
uni.uploadFile({
url: app.globalData.get_request_url('useravatarupload', 'personal'),
filePath: image,
name: 'file',
formData: {},
success: function (res) {
if (res.statusCode == 200) {
var data = typeof res.data == 'object' ? res.data : JSON.parse(res.data);
if (data.code == 0) {
self.setData({
user_avatar: data.data
});
// 提交按钮状态处理
self.submit_button_status_handle();
} else {
app.globalData.showToast(data.msg);
}
}
},
});
},
// 输入框值监听
on_input_nickname(e) {
this.setData({
nickname: e.detail.value || '',
});
// 提交按钮状态处理
this.submit_button_status_handle();
},
// 一键获取手机号码
confirm_phone_number_event(e) {
var encrypted_data = e.detail.encryptedData || null;
var iv = e.detail.iv || null;
var code = e.detail.code || null;
if ((encrypted_data != null && iv != null) || code != null) {
uni.showLoading({
title: this.$t('common.processing_in_text'),
});
var user = this.user || {};
var data = {
encrypted_data: encrypted_data,
iv: iv,
code: code,
openid: user[this.client_value + '_openid'] || '',
};
var field_unionid = this.client_value + '_unionid';
data[field_unionid] = user[field_unionid] || '';
uni.request({
url: app.globalData.get_request_url('onekeyusermobiledecrypt', 'user'),
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
uni.hideLoading();
if (res.data.code == 0) {
this.setData({
user_mobile: res.data.data
});
// 提交按钮状态处理
this.submit_button_status_handle();
} else {
app.globalData.showToast(res.data.msg);
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
} else {
var msg = e.errmsg || e.errMsg || e.detail.errmsg || e.detail.errMsg || null;
if (msg != null) {
app.globalData.showToast(msg);
}
}
},
// 提交按钮状态处理
submit_button_status_handle() {
var status = false;
// 头像
if(parseInt(this.check_field.is_avatar || 0) == 1 && (this.user_avatar || null) == null) {
status = true;
}
// 昵称
if(!status && parseInt(this.check_field.is_nickname || 0) == 1 && (this.nickname || null) == null) {
status = true;
}
// 手机
if(!status && parseInt(this.check_field.is_mobile || 0) == 1 && (this.user_mobile || null) == null) {
status = true;
}
this.setData({
form_submit_disabled_status: status,
});
},
// 数据提交
form_submit(e) {
// 验证数据项
var validation = [];
if (parseInt(this.check_field.is_avatar || 0) == 1) {
validation.push({
fields: 'avatar',
msg: this.$t('user-base.user-base.gzc3y4'),
});
}
if (parseInt(this.check_field.is_nickname || 0) == 1) {
validation.push({
fields: 'nickname',
msg: this.$t('user-base.user-base.lro9u7'),
});
}
if (parseInt(this.check_field.is_mobile || 0) == 1) {
validation.push({
fields: 'mobile',
msg: this.$t('login.login.tghjer'),
});
}
// 数据
var data = {
nickname: this.nickname || '',
avatar: this.user_avatar || '',
mobile: this.user_mobile || ''
};
// 当前用户是否已经存在
var user = this.user || {};
if((user.id || null) == null) {
data['openid'] = user[this.client_value + '_openid'] || '';
data['province'] = user.province || '';
data['city'] = user.city || '';
data['gender'] = user.gender || 0;
var field_unionid = this.client_value + '_unionid';
data[field_unionid] = user[field_unionid] || '';
// 接口地址
var url = app.globalData.get_request_url('userbasereg', 'user');
} else {
// 存在用户则先存储用户信息
uni.setStorageSync(app.globalData.data.cache_user_info_key, user);
// 验证数据项
if (parseInt(this.check_field.is_avatar || 0) != 1) {
delete data['avatar'];
}
if (parseInt(this.check_field.is_nickname || 0) != 1) {
delete data['nickname'];
}
if (parseInt(this.check_field.is_mobile || 0) != 1) {
delete data['mobile'];
}
// 接口地址
var url = app.globalData.get_request_url('save', 'personal');
}
if (app.globalData.fields_check(data, validation)) {
// 数据保存
uni.showLoading({
title: this.$t('common.processing_in_text'),
});
uni.request({
url: url,
method: 'POST',
data: data,
dataType: 'json',
success: (res) => {
uni.hideLoading();
if (res.data.code == 0) {
uni.setStorageSync(app.globalData.data.cache_user_info_key, res.data.data);
app.globalData.showToast(res.data.msg, 'success');
this.setData({
popup_status: false,
});
// 回调
if (typeof this.back_object === 'object' && (this.back_method || null) != null) {
this.back_object[this.back_method](this.back_params);
}
} else {
if (app.globalData.is_login_check(res.data)) {
app.globalData.showToast(res.data.msg);
} else {
app.globalData.showToast(this.$t('common.sub_error_retry_tips'));
}
}
},
fail: () => {
uni.hideLoading();
app.globalData.showToast(this.$t('common.internet_error_tips'));
},
});
}
}
}
};
</script>
<style scoped>
.user-base-popup {
padding: 36rpx;
position: relative;
}
.user-base-popup .close {
position: absolute;
top: 36rpx;
right: 36rpx;
z-index: 2;
}
.user-base-avatar {
width: 80rpx;
height: 80rpx !important;
}
.user-base-popup .form-gorup-title {
line-height: 70rpx;
margin-bottom: 0;
}
.user-base-popup .user-nickname-container,
.user-base-popup .user-mobile-container {
width: calc(100% - 120rpx);
}
.form-container .form-gorup {
padding: 24rpx 0;
border-radius: 0;
}
.form-container .form-gorup-title {
font-weight: 400;
}
.sub-btn {
width: 336rpx;
height: 84rpx;
line-height: 84rpx;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 12rpx;
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<view :class="theme_view">
<!-- 批发规则展示 -->
<view v-if="(data || null) != null" class="plugins-wholesale-container scroll-view-horizontal" :class="propIsAlone ? 'is-alone' : ''">
<scroll-view scroll-x="true">
<view :class="'plugins-wholesale-container-rules-view wh-auto padding-main item-number-'+data.rules.length" @tap="popup_wholesale_event">
<view v-for="(item, index) in data.rules" :key="index" class="item">
<view class="price">
<text v-if="item.arr.type == 1" class="cr-red text-size-xs">{{propCurrencySymbol}}</text>
<text class="sales-price text-size-lg">{{item.range_val}}</text>
<text v-if="item.arr.type == 0" class="unit text-size-xsss">{{item.arr.unit}}</text>
</view>
<view class="msg text-size-sm cr-base margin-top-xs cr-black">{{item.range_msg}}</view>
</view>
</view>
</scroll-view>
</view>
<!-- 批发规则弹层 -->
<block v-if="propIsPopup">
<component-popup :propShow="popup_status" propPosition="bottom" @onclose="popup_wholesale_close_event">
<view class="padding-horizontal-main padding-top-main bg-white">
<view class="close oh">
<view class="fr" @tap.stop="popup_wholesale_close_event">
<iconfont name="icon-close-o" size="28rpx" color="#999"></iconfont>
</view>
</view>
<view class="plugins-wholesale-container-rules">
<block v-if="(data || null) != null">
<view class="oh flex-row flex-wrap">
<block v-for="(item, index) in data.rules" :key="index">
<view class="item flex-width-half margin-bottom">
<view class="item-content padding-main bg-base border-radius-main oh tc">
<text class="cr-base">{{ item.range_msg }}</text>
<text v-if="item.arr.type == 1" class="cr-main text-size-xs">{{propCurrencySymbol}}</text>
<text class="cr-main fw-b text-size-lg">{{ item.range_val }}</text>
<text v-if="item.arr.type == 0" class="cr-grey text-size-xsss">{{ item.arr.unit }}</text>
</view>
</view>
</block>
</view>
</block>
<block v-else>
<view class="cr-grey tc padding-top-xl padding-bottom-xxxl">{{$t('goods-detail.goods-detail.m3op38')}}</view>
</block>
</view>
</view>
</component-popup>
</block>
</view>
</template>
<script>
const app = getApp();
import componentPopup from "@/components/popup/popup";
export default {
data() {
return {
theme_view: app.globalData.get_theme_value_view(),
data: null,
popup_status: false,
};
},
components: {
componentPopup
},
props: {
// 是否展示弹窗
propIsPopup: {
type: Boolean,
default: false,
},
// 价格符号
propCurrencySymbol: {
type: String,
default: app.globalData.currency_symbol(),
},
// 列表数据
propData: {
type: [Array, Object, String],
default: {},
},
// 是否独立行
propIsAlone: {
type: Boolean,
default: false,
},
},
// 属性值改变监听
watch: {
// 数据
propData(value, old_value) {
this.setData({
data: value,
});
}
},
// 页面被展示
created: function () {
this.setData({
data: this.propData,
});
},
methods: {
// 批发开启弹层
popup_wholesale_event(e) {
if(this.propIsPopup) {
this.setData({
popup_status: true,
});
}
},
// 批发弹层关闭
popup_wholesale_close_event(e) {
this.setData({
popup_status: false,
});
},
}
}
</script>
<style scoped>
/**
* 批发 - 插件
*/
.plugins-wholesale-container.is-alone {
background: linear-gradient(to bottom, #fafafa 0%, #fff 100%);
}
.plugins-wholesale-container-rules-view {
display: flex;
justify-content: flex-start;
gap: 60rpx;
}
.plugins-wholesale-container-rules-view.item-number-2,
.plugins-wholesale-container-rules-view.item-number-3,
.plugins-wholesale-container-rules-view.item-number-4 {
justify-content: space-around;
gap: 0;
}
.plugins-wholesale-container-rules-view .item {
margin-bottom: 0 !important;
}
.plugins-wholesale-container-rules-view .item .price > .unit {
color: #969696;
margin-left: 5rpx;
}
.plugins-wholesale-container-rules {
max-height: 50vh;
overflow-y: scroll;
overflow-x: hidden;
margin-top: 20rpx;
}
.plugins-wholesale-container-rules .item:nth-child(2n)>.item-content {
margin-left: 10rpx;
}
.plugins-wholesale-container-rules .item:nth-child(2n+1)>.item-content {
margin-right: 10rpx;
}
.plugins-wholesale-container-rules .spec-tips {
color: #ffbf00;
border: 1px solid #333;
background: #333;
padding: 2rpx 10rpx;
top: 22rpx;
left: 20rpx;
}
</style>