feat:v1.4前端更新
This commit is contained in:
245
app/components/base/easy-loadimage.vue
Normal file
245
app/components/base/easy-loadimage.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<view class="easy-loadimage" :style="[boxStyle]" :id="uid">
|
||||
<image class="origin-img" :style="[imageRadius]" :src="imageSrc" mode="scaleToFill" v-if="loadImg&&!isLoadError"
|
||||
v-show="showImg" :class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}"
|
||||
@load="handleImgLoad" @error="handleImgError">
|
||||
</image>
|
||||
<view class="loadfail-img" v-else-if="isLoadError"
|
||||
:style="{'background-image': `url(${urlDomain}crmebimage/presets/loadfail.png) no-repeat center`}"></view>
|
||||
<view :class="['loading-img',loadingMode]" v-show="!showImg&&!isLoadError"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
import {
|
||||
throttle
|
||||
} from '@/utils/validate.js'
|
||||
|
||||
// 生成全局唯一id
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0,
|
||||
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
})
|
||||
}
|
||||
export default {
|
||||
name: 'easyLoadimage',
|
||||
props: {
|
||||
imageSrc: {
|
||||
type: String || null,
|
||||
default () {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
},
|
||||
loadingMode: {
|
||||
type: String,
|
||||
default: 'looming-gray'
|
||||
},
|
||||
openTransition: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
viewHeight: {
|
||||
type: Number,
|
||||
default () {
|
||||
return uni.getSystemInfoSync().windowHeight;
|
||||
}
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
borderRadius: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const that = this;
|
||||
return {
|
||||
urlDomain: this.$Cache.get("imgHost"),
|
||||
uid: 'uid-' + generateUUID(),
|
||||
loadImg: false,
|
||||
showImg: false,
|
||||
isLoadError: false,
|
||||
borderLoaded: 0,
|
||||
showTransition: false,
|
||||
scrollFn: throttle(function() {
|
||||
// 加载img时才执行滚动监听判断是否可加载
|
||||
if (that.loadImg || that.isLoadError) return;
|
||||
const id = that.uid
|
||||
const query = uni.createSelectorQuery().in(that);
|
||||
query.select('#' + id).boundingClientRect(data => {
|
||||
if (!data) return;
|
||||
if (data.top - that.viewHeight < 0) {
|
||||
that.loadImg = !!that.imageSrc;
|
||||
that.isLoadError = !that.loadImg;
|
||||
}
|
||||
}).exec()
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
boxStyle() {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
borderRadius: this.radius * 2 + 'rpx'
|
||||
}
|
||||
},
|
||||
imageRadius() {
|
||||
if (this.radius && this.radius > 0) {
|
||||
return {
|
||||
'border-radius': this.radius * 2 + 'rpx'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.$nextTick(this.onScroll)
|
||||
},
|
||||
handleBorderLoad() {
|
||||
this.borderLoaded = 1;
|
||||
},
|
||||
handleBorderError() {
|
||||
this.borderLoaded = 2;
|
||||
},
|
||||
handleImgLoad(e) {
|
||||
this.showImg = true;
|
||||
setTimeout(() => {
|
||||
this.showTransition = true
|
||||
}, 50)
|
||||
},
|
||||
handleImgError(e) {
|
||||
this.isLoadError = true;
|
||||
},
|
||||
onScroll() {
|
||||
this.scrollFn();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
uni.$on('scroll', this.scrollFn);
|
||||
this.onScroll()
|
||||
},
|
||||
beforeDestroy() {
|
||||
uni.$off('scroll', this.scrollFn);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.easy-loadimage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 官方优化图片tips */
|
||||
image {
|
||||
will-change: transform;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 渐变过渡效果处理 */
|
||||
image.origin-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.3;
|
||||
/* max-height: 360rpx; */
|
||||
/* border-radius: 14rpx;
|
||||
overflow: hidden; */
|
||||
/* min-height: 360rpx; */
|
||||
}
|
||||
|
||||
image.origin-img.show-transition {
|
||||
transition: opacity .5s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
image.origin-img.no-transition {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 加载失败、加载中的占位图样式控制 */
|
||||
.loadfail-img {
|
||||
height: 100%;
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
.loading-img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 动态灰色若隐若现 */
|
||||
.looming-gray {
|
||||
animation: looming-gray 1s infinite linear;
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
|
||||
@keyframes looming-gray {
|
||||
0% {
|
||||
background-color: #e3e3e3aa;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: #e3e3e3aa;
|
||||
}
|
||||
}
|
||||
|
||||
/* 骨架屏1 */
|
||||
.skeleton-1 {
|
||||
background-color: #e3e3e3;
|
||||
background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
|
||||
background-size: 100rpx 100%;
|
||||
background-repeat: repeat-y;
|
||||
background-position: 0 0;
|
||||
animation: skeleton-1 .6s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-1 {
|
||||
to {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 骨架屏2 */
|
||||
.skeleton-2 {
|
||||
background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
|
||||
background-size: 400% 400%;
|
||||
background-position: 0 0;
|
||||
animation: skeleton-2 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-2 {
|
||||
to {
|
||||
background-position: -135% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
184
app/components/base/recommend.vue
Normal file
184
app/components/base/recommend.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<view class='recommend'>
|
||||
<block v-if="tempArr.length">
|
||||
<view v-if="isShowTitle" class="flex-center recommend-box mt-20 mb-24">
|
||||
<image :src="`${urlDomain}crmebimage/presets/haowuzuo.png`"></image>
|
||||
<view class="f-s-32 lh-44rpx ml-4">热门推荐</view>
|
||||
<image class="ml-6" :src="`${urlDomain}crmebimage/presets/haowuyou.png`"></image>
|
||||
</view>
|
||||
<view class='recommendList borderPad' :class="isShowTitle?'':'mt30'">
|
||||
<WaterfallsFlow :wfList='tempArr' :type="1" :isStore="1">
|
||||
</WaterfallsFlow>
|
||||
</view>
|
||||
<view class='loadingicon acea-row row-center-wrapper' :hidden='loading==false'>
|
||||
<text class='loading iconfont icon-jiazai'></text>
|
||||
</view>
|
||||
<view class="mores-txt flex" v-if="goodScroll">
|
||||
<text>我是有底线的</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
import {
|
||||
mapGetters
|
||||
} from "vuex";
|
||||
import animationType from '@/utils/animationType.js'
|
||||
import {
|
||||
getProductslist
|
||||
} from '@/api/store.js';
|
||||
import WaterfallsFlow from '@/components/WaterfallsFlow/WaterfallsFlow.vue';
|
||||
let app = getApp();
|
||||
export default {
|
||||
name: 'recommend',
|
||||
computed: mapGetters(['uid']),
|
||||
components: {
|
||||
WaterfallsFlow
|
||||
},
|
||||
props: {
|
||||
categoryId: {
|
||||
type: Number,
|
||||
default: function() {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
//是否显示头部
|
||||
isShowTitle: {
|
||||
type: Boolean,
|
||||
default: function() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
//是否使用本页面的请求数据
|
||||
isDefault: {
|
||||
type: Boolean,
|
||||
default: function() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
//使用的页面中调用数据传来的商品列表,isDefault为false时使用
|
||||
recommendList: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
urlDomain: this.$Cache.get("imgHost"),
|
||||
theme: app.globalData.theme,
|
||||
goodScroll: false,
|
||||
params: { //精品推荐分页
|
||||
page: 1,
|
||||
limit: 10,
|
||||
cid: 0
|
||||
},
|
||||
loading: false,
|
||||
tempArr: []
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
myCategoryId(){
|
||||
return this.categoryId
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
myCategoryId: function(val) { //监听props中的属性
|
||||
this.params.page = 1;
|
||||
this.tempArr = [];
|
||||
this.goodScroll = false;
|
||||
this.get_host_product()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.isDefault) {
|
||||
this.params.page = 1;
|
||||
this.goodScroll = false;
|
||||
this.tempArr = [];
|
||||
this.get_host_product()
|
||||
} else {
|
||||
this.tempArr = this.recommendList
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取我的推荐
|
||||
*/
|
||||
get_host_product: function() {
|
||||
if (this.goodScroll) return;
|
||||
this.loading = true
|
||||
this.params.cid = this.categoryId;
|
||||
getProductslist(
|
||||
this.params
|
||||
).then((res) => {
|
||||
this.$set(this.params, 'page', this.params.page + 1);
|
||||
this.goodScroll = this.params.page > res.data.totalPage;
|
||||
this.tempArr = this.tempArr.concat(res.data.list || []);
|
||||
// this.$emit('getRecommendLength', this.tempArr.length);
|
||||
this.loading = false
|
||||
}).catch(err => {
|
||||
this.loading = false
|
||||
});
|
||||
}
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.isDefault) this.get_host_product();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.recommend-box {
|
||||
image {
|
||||
width: 42rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
view {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.mores-txt {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 70rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
|
||||
.iconfont {
|
||||
margin-top: 2rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.recommend {
|
||||
.title {
|
||||
height: 120rpx;
|
||||
line-height: 120rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
|
||||
.iconfont {
|
||||
font-size: 170rpx;
|
||||
color: #454545;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0 28rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
248
app/components/base/tui-skeleton.vue
Normal file
248
app/components/base/tui-skeleton.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<view class="tui-skeleton-cmomon tui-skeleton-box" :style="{width: winWidth+'px', height:winHeight+'px', backgroundColor:backgroundColor}">
|
||||
<view class="tui-skeleton-cmomon" v-for="(item,index) in skeletonElements" :key="index" :style="{width: item.width+'px', height:item.height+'px', left: item.left+'px', top: item.top+'px',backgroundColor: skeletonBgColor,borderRadius:getRadius(item.skeletonType,borderRadius)}"></view>
|
||||
<view class="tui-loading" :class="[getLoadingType(loadingType)]" v-if="isLoading"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
export default {
|
||||
name: "tuiSkeleton",
|
||||
props: {
|
||||
//选择器(外层容器)
|
||||
selector: {
|
||||
type: String,
|
||||
default: "tui-skeleton"
|
||||
},
|
||||
//外层容器背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#fff"
|
||||
},
|
||||
//骨架元素背景颜色
|
||||
skeletonBgColor: {
|
||||
type: String,
|
||||
default: "#e9e9e9"
|
||||
},
|
||||
//骨架元素类型:矩形,圆形,带圆角矩形["rect","circular","fillet"]
|
||||
//默认所有,根据页面情况进行传值
|
||||
//页面对应元素class为:tui-skeleton-rect,tui-skeleton-circular,tui-skeleton-fillet
|
||||
//如果传入的值不在下列数组中,则为自定义class值,默认按矩形渲染
|
||||
skeletonType: {
|
||||
type: Array,
|
||||
default () {
|
||||
return ["rect", "circular", "fillet"]
|
||||
}
|
||||
},
|
||||
//圆角值,skeletonType=fillet时生效
|
||||
borderRadius: {
|
||||
type: String,
|
||||
default: "16rpx"
|
||||
},
|
||||
//骨架屏预生成数据:提前生成好的数据,当传入该属性值时,则不会再次查找子节点信息
|
||||
preloadData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//是否需要loading
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//loading类型[1-10]
|
||||
loadingType: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const res = uni.getSystemInfoSync();
|
||||
this.winWidth = res.windowWidth;
|
||||
this.winHeight = res.windowHeight;
|
||||
//如果有预生成数据,则直接使用
|
||||
this.isPreload(true)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.nodesRef(`.${this.selector}`).then((res) => {
|
||||
if(res && res[0]){
|
||||
this.winHeight = res[0].height + Math.abs(res[0].top)
|
||||
}
|
||||
});
|
||||
!this.isPreload() && this.selectorQuery()
|
||||
})
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
winWidth: 375,
|
||||
winHeight: 800,
|
||||
skeletonElements: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getLoadingType: function(type) {
|
||||
let value = 1
|
||||
if (type && type > 0 && type < 11) {
|
||||
value = type
|
||||
}
|
||||
return 'tui-loading-' + value
|
||||
},
|
||||
getRadius: function(type, val) {
|
||||
let radius = "0"
|
||||
if (type == "circular") {
|
||||
radius = "50%"
|
||||
} else if (type == "fillet") {
|
||||
radius = val
|
||||
}
|
||||
return radius;
|
||||
},
|
||||
isPreload(init) {
|
||||
let preloadData = this.preloadData || []
|
||||
if (preloadData.length) {
|
||||
init && (this.skeletonElements = preloadData)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
async selectorQuery() {
|
||||
let skeletonType = this.skeletonType || []
|
||||
let nodes = []
|
||||
for (let item of skeletonType) {
|
||||
let className = '';
|
||||
// #ifndef MP-WEIXIN
|
||||
className = `.${item}`;
|
||||
if (~'rect_circular_fillet'.indexOf(item)) {
|
||||
className = `.${this.selector}-${item}`;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
className = `.${this.selector} >>> .${item}`;
|
||||
if (~'rect_circular_fillet'.indexOf(item)) {
|
||||
className = `.${this.selector} >>> .${this.selector}-${item}`;
|
||||
}
|
||||
// #endif
|
||||
await this.nodesRef(className).then((res) => {
|
||||
res.map(d => {
|
||||
d.skeletonType = item
|
||||
})
|
||||
nodes = nodes.concat(res)
|
||||
})
|
||||
}
|
||||
this.skeletonElements = nodes
|
||||
},
|
||||
async nodesRef(className) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery().selectAll(className).boundingClientRect((res) => {
|
||||
if (res) {
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
}).exec();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-skeleton-cmomon {
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.tui-skeleton-box {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tui-loading {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: 0 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
animation: tui-rotate 0.7s linear infinite;
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -20rpx;
|
||||
margin-top: -20rpx;
|
||||
}
|
||||
|
||||
.tui-loading-1 {
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #5677fc;
|
||||
}
|
||||
|
||||
.tui-loading-2 {
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
|
||||
}
|
||||
|
||||
.tui-loading-3 {
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) #fff;
|
||||
}
|
||||
|
||||
.tui-loading-4 {
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #35b06a;
|
||||
}
|
||||
|
||||
.tui-loading-5 {
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #fc872d;
|
||||
}
|
||||
|
||||
.tui-loading-6 {
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #eb0909;
|
||||
}
|
||||
|
||||
.tui-loading-7 {
|
||||
border-color: #5677fc transparent #5677fc transparent;
|
||||
}
|
||||
|
||||
.tui-loading-8 {
|
||||
border-color: #35b06a transparent #35b06a transparent;
|
||||
}
|
||||
|
||||
.tui-loading-9 {
|
||||
border-color: #fc872d transparent #fc872d transparent;
|
||||
}
|
||||
|
||||
.tui-loading-10 {
|
||||
border-color: #eb0909 transparent #eb0909 transparent;
|
||||
}
|
||||
|
||||
@-webkit-keyframes tui-rotate {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tui-rotate {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user