通过微信开发者工具 商城模板 创建新小程序
This commit is contained in:
36
mini-program/components/filter-popup/index.js
Normal file
36
mini-program/components/filter-popup/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
options: {
|
||||
multipleSlots: true,
|
||||
},
|
||||
|
||||
properties: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
observer(show) {
|
||||
this.setData({ visible: show });
|
||||
},
|
||||
},
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
|
||||
data: { visible: false },
|
||||
|
||||
methods: {
|
||||
reset() {
|
||||
this.triggerEvent('reset');
|
||||
},
|
||||
confirm() {
|
||||
this.triggerEvent('confirm');
|
||||
},
|
||||
close() {
|
||||
this.triggerEvent('showFilterPopupClose');
|
||||
|
||||
this.setData({ visible: false });
|
||||
},
|
||||
},
|
||||
});
|
||||
6
mini-program/components/filter-popup/index.json
Normal file
6
mini-program/components/filter-popup/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-popup": "tdesign-miniprogram/popup/popup"
|
||||
}
|
||||
}
|
||||
18
mini-program/components/filter-popup/index.wxml
Normal file
18
mini-program/components/filter-popup/index.wxml
Normal file
@@ -0,0 +1,18 @@
|
||||
<t-popup
|
||||
visible="{{visible}}"
|
||||
placement="right"
|
||||
bind:visible-change="close"
|
||||
data-index="5"
|
||||
close-btn="{{closeBtn}}"
|
||||
>
|
||||
<view class="content">
|
||||
<slot name="filterSlot" />
|
||||
<view class="filter-btns-wrap">
|
||||
<view class="filter-btn btn-reset" bind:tap="reset">重置</view>
|
||||
<view class="filter-btn btn-confirm" bind:tap="confirm" data-index="5">
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</t-popup>
|
||||
|
||||
39
mini-program/components/filter-popup/index.wxss
Normal file
39
mini-program/components/filter-popup/index.wxss
Normal file
@@ -0,0 +1,39 @@
|
||||
.content .filter-btns-wrap {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 10rpx 0 0 10rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
border-top: 1rpx solid #e5e5e5;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
color: #fa4126;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
position: relative;
|
||||
border: 1rpx solid #fa4126;
|
||||
border-radius: 84rpx 0 0 84rpx;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
border-radius: 0 84rpx 84rpx 0;
|
||||
border: 1rpx solid #fa4126;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
color: #fff;
|
||||
background: #fa4126;
|
||||
}
|
||||
84
mini-program/components/filter/index.js
Normal file
84
mini-program/components/filter/index.js
Normal file
@@ -0,0 +1,84 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
options: {
|
||||
multipleSlots: true,
|
||||
},
|
||||
|
||||
properties: {
|
||||
overall: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
observer(overall) {
|
||||
this.setData({
|
||||
overall,
|
||||
});
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
observer(layout) {
|
||||
this.setData({
|
||||
layout,
|
||||
});
|
||||
},
|
||||
},
|
||||
sorts: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer(sorts) {
|
||||
this.setData({
|
||||
sorts,
|
||||
});
|
||||
},
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
value: '#FA550F',
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
layout: 1,
|
||||
overall: 1,
|
||||
sorts: '',
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangeShowAction() {
|
||||
const { layout } = this.data;
|
||||
const nextLayout = layout === 1 ? 0 : 1;
|
||||
this.triggerEvent('change', { ...this.properties, layout: nextLayout });
|
||||
},
|
||||
|
||||
handlePriseSort() {
|
||||
const { sorts } = this.data;
|
||||
this.triggerEvent('change', {
|
||||
...this.properties,
|
||||
overall: 0,
|
||||
sorts: sorts === 'desc' ? 'asc' : 'desc',
|
||||
});
|
||||
},
|
||||
|
||||
open() {
|
||||
this.triggerEvent('showFilterPopup', {
|
||||
show: true,
|
||||
});
|
||||
},
|
||||
|
||||
onOverallAction() {
|
||||
const { overall } = this.data;
|
||||
const nextOverall = overall === 1 ? 0 : 1;
|
||||
const nextData = {
|
||||
sorts: '',
|
||||
prices: [],
|
||||
};
|
||||
this.triggerEvent('change', {
|
||||
...this.properties,
|
||||
...nextData,
|
||||
overall: nextOverall,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
6
mini-program/components/filter/index.json
Normal file
6
mini-program/components/filter/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
37
mini-program/components/filter/index.wxml
Normal file
37
mini-program/components/filter/index.wxml
Normal file
@@ -0,0 +1,37 @@
|
||||
<!-- 过滤组件 -->
|
||||
<view class="wr-class filter-wrap">
|
||||
<view class="filter-left-content">
|
||||
<view class="filter-item {{overall === 1 ? 'filter-active-item' : ''}}" bindtap="onOverallAction">
|
||||
综合
|
||||
</view>
|
||||
<view class="filter-item" bind:tap="handlePriseSort">
|
||||
<text style="color: {{sorts !== '' ? color : '' }}">价格</text>
|
||||
<view class="filter-price">
|
||||
<t-icon
|
||||
prefix="wr"
|
||||
name="arrow_drop_up"
|
||||
size="18rpx"
|
||||
style="color:{{sorts === 'asc' ? color : '#bbb'}}"
|
||||
/>
|
||||
<t-icon
|
||||
prefix="wr"
|
||||
name="arrow_drop_down"
|
||||
size="18rpx"
|
||||
style="color:{{sorts === 'desc' ? color : '#bbb'}}"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-item {{prices.length ? 'filter-active-item' : ''}}" bindtap="open" data-index="5">
|
||||
筛选
|
||||
<t-icon
|
||||
name="filter"
|
||||
prefix="wr"
|
||||
color="#333"
|
||||
size="32rpx"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 筛选弹框 -->
|
||||
<slot name="filterPopup" />
|
||||
|
||||
50
mini-program/components/filter/index.wxss
Normal file
50
mini-program/components/filter/index.wxss
Normal file
@@ -0,0 +1,50 @@
|
||||
.filter-wrap {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.filter-right-content {
|
||||
height: 100%;
|
||||
flex-basis: 100rpx;
|
||||
text-align: center;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
.filter-left-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-grow: 2;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filter-left-content .filter-item {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
font-weight: 400;
|
||||
color: rgba(51, 51, 51, 1);
|
||||
}
|
||||
|
||||
.filter-left-content .filter-item .filter-price {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 6rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filter-left-content .filter-item .wr-filter {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.filter-left-content .filter-active-item {
|
||||
color: #fa550f;
|
||||
}
|
||||
141
mini-program/components/goods-card/index.js
Normal file
141
mini-program/components/goods-card/index.js
Normal file
@@ -0,0 +1,141 @@
|
||||
Component({
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
},
|
||||
|
||||
properties: {
|
||||
id: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer(id) {
|
||||
this.genIndependentID(id);
|
||||
if (this.properties.thresholds?.length) {
|
||||
this.createIntersectionObserverHandle();
|
||||
}
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
observer(data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let isValidityLinePrice = true;
|
||||
if (data.originPrice && data.price && data.originPrice < data.price) {
|
||||
isValidityLinePrice = false;
|
||||
}
|
||||
this.setData({ goods: data, isValidityLinePrice });
|
||||
},
|
||||
},
|
||||
currency: {
|
||||
type: String,
|
||||
value: '¥',
|
||||
},
|
||||
|
||||
thresholds: {
|
||||
type: Array,
|
||||
value: [],
|
||||
observer(thresholds) {
|
||||
if (thresholds && thresholds.length) {
|
||||
this.createIntersectionObserverHandle();
|
||||
} else {
|
||||
this.clearIntersectionObserverHandle();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
independentID: '',
|
||||
goods: { id: '' },
|
||||
isValidityLinePrice: false,
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
ready() {
|
||||
this.init();
|
||||
},
|
||||
detached() {
|
||||
this.clear();
|
||||
},
|
||||
},
|
||||
|
||||
pageLifeTimes: {},
|
||||
|
||||
methods: {
|
||||
clickHandle() {
|
||||
this.triggerEvent('click', { goods: this.data.goods });
|
||||
},
|
||||
|
||||
clickThumbHandle() {
|
||||
this.triggerEvent('thumb', { goods: this.data.goods });
|
||||
},
|
||||
|
||||
addCartHandle(e) {
|
||||
const { id } = e.currentTarget;
|
||||
const { id: cardID } = e.currentTarget.dataset;
|
||||
this.triggerEvent('add-cart', {
|
||||
...e.detail,
|
||||
id,
|
||||
cardID,
|
||||
goods: this.data.goods,
|
||||
});
|
||||
},
|
||||
|
||||
genIndependentID(id) {
|
||||
let independentID;
|
||||
if (id) {
|
||||
independentID = id;
|
||||
} else {
|
||||
independentID = `goods-card-${~~(Math.random() * 10 ** 8)}`;
|
||||
}
|
||||
this.setData({ independentID });
|
||||
},
|
||||
|
||||
init() {
|
||||
const { thresholds, id } = this.properties;
|
||||
this.genIndependentID(id);
|
||||
if (thresholds && thresholds.length) {
|
||||
this.createIntersectionObserverHandle();
|
||||
}
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.clearIntersectionObserverHandle();
|
||||
},
|
||||
|
||||
intersectionObserverContext: null,
|
||||
|
||||
createIntersectionObserverHandle() {
|
||||
if (this.intersectionObserverContext || !this.data.independentID) {
|
||||
return;
|
||||
}
|
||||
this.intersectionObserverContext = this.createIntersectionObserver({
|
||||
thresholds: this.properties.thresholds,
|
||||
}).relativeToViewport();
|
||||
|
||||
this.intersectionObserverContext.observe(
|
||||
`#${this.data.independentID}`,
|
||||
(res) => {
|
||||
this.intersectionObserverCB(res);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
intersectionObserverCB() {
|
||||
this.triggerEvent('ob', {
|
||||
goods: this.data.goods,
|
||||
context: this.intersectionObserverContext,
|
||||
});
|
||||
},
|
||||
|
||||
clearIntersectionObserverHandle() {
|
||||
if (this.intersectionObserverContext) {
|
||||
try {
|
||||
this.intersectionObserverContext.disconnect();
|
||||
} catch (e) {}
|
||||
this.intersectionObserverContext = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
8
mini-program/components/goods-card/index.json
Normal file
8
mini-program/components/goods-card/index.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"price": "/components/price/index",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-image": "/components/webp-image/index"
|
||||
}
|
||||
}
|
||||
63
mini-program/components/goods-card/index.wxml
Normal file
63
mini-program/components/goods-card/index.wxml
Normal file
@@ -0,0 +1,63 @@
|
||||
<view
|
||||
id="{{independentID}}"
|
||||
class="goods-card"
|
||||
bind:tap="clickHandle"
|
||||
data-goods="{{ goods }}"
|
||||
>
|
||||
<view class="goods-card__main">
|
||||
<view class="goods-card__thumb" bind:tap="clickThumbHandle">
|
||||
<t-image
|
||||
wx:if="{{ !!goods.thumb }}"
|
||||
t-class="goods-card__img"
|
||||
src="{{ goods.thumb }}"
|
||||
mode="aspectFill"
|
||||
lazy-load
|
||||
/>
|
||||
</view>
|
||||
<view class="goods-card__body">
|
||||
<view class="goods-card__upper">
|
||||
<view wx:if="{{ goods.title }}" class="goods-card__title">
|
||||
{{ goods.title }}
|
||||
</view>
|
||||
<view wx:if="{{ goods.tags && !!goods.tags.length }}" class="goods-card__tags">
|
||||
<view
|
||||
wx:for="{{ goods.tags }}"
|
||||
wx:key="index"
|
||||
wx:for-item="tag"
|
||||
class="goods-card__tag"
|
||||
data-index="{{index}}"
|
||||
>
|
||||
{{tag}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-card__down">
|
||||
<price
|
||||
wx:if="{{ goods.price }}"
|
||||
wr-class="spec-for-price"
|
||||
symbol-class="spec-for-symbol"
|
||||
symbol="{{currency}}"
|
||||
price="{{goods.price}}"
|
||||
/>
|
||||
<price
|
||||
wx:if="{{ goods.originPrice && isValidityLinePrice }}"
|
||||
wr-class="goods-card__origin-price"
|
||||
symbol="{{currency}}"
|
||||
price="{{goods.originPrice}}"
|
||||
type="delthrough"
|
||||
/>
|
||||
<t-icon
|
||||
class="goods-card__add-cart"
|
||||
prefix="wr"
|
||||
name="cartAdd"
|
||||
id="{{independentID}}-cart"
|
||||
data-id="{{independentID}}"
|
||||
catchtap="addCartHandle"
|
||||
size="48rpx"
|
||||
color="#FA550F"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
133
mini-program/components/goods-card/index.wxss
Normal file
133
mini-program/components/goods-card/index.wxss
Normal file
@@ -0,0 +1,133 @@
|
||||
.goods-card {
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.goods-card__main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
width: 342rpx;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.goods-card__thumb {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
width: 340rpx;
|
||||
height: 340rpx;
|
||||
}
|
||||
|
||||
.goods-card__thumb:empty {
|
||||
display: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.goods-card__img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-card__body {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
background: #fff;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
padding: 16rpx 24rpx 18rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.goods-card__upper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.goods-card__title {
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
display: -webkit-box;
|
||||
height: 72rpx;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.goods-card__tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 8rpx 0 0 0;
|
||||
}
|
||||
|
||||
.goods-card__tag {
|
||||
color: #fa4126;
|
||||
background: transparent;
|
||||
font-size: 20rpx;
|
||||
border: 1rpx solid #fa4126;
|
||||
padding: 0 8rpx;
|
||||
border-radius: 16rpx;
|
||||
line-height: 30rpx;
|
||||
margin: 0 8rpx 8rpx 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.goods-card__down {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: baseline;
|
||||
line-height: 32rpx;
|
||||
margin: 8rpx 0 0 0;
|
||||
}
|
||||
|
||||
.goods-card__origin-price {
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
order: 2;
|
||||
color: #bbbbbb;
|
||||
font-size: 24rpx;
|
||||
margin: 0 0 0 8rpx;
|
||||
}
|
||||
|
||||
.goods-card__add-cart {
|
||||
order: 3;
|
||||
margin: auto 0 0 auto;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.spec-for-price {
|
||||
font-size: 36rpx;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
order: 1;
|
||||
color: #fa4126;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spec-for-symbol {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
62
mini-program/components/goods-list/index.js
Normal file
62
mini-program/components/goods-list/index.js
Normal file
@@ -0,0 +1,62 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
properties: {
|
||||
goodsList: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer: (id) => {
|
||||
this.genIndependentID(id);
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
independentID: '',
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
ready() {
|
||||
this.init();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClickGoods(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
this.triggerEvent('click', { ...e.detail, index });
|
||||
},
|
||||
|
||||
onAddCart(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
this.triggerEvent('addcart', { ...e.detail, index });
|
||||
},
|
||||
|
||||
onClickGoodsThumb(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
this.triggerEvent('thumb', { ...e.detail, index });
|
||||
},
|
||||
|
||||
init() {
|
||||
this.genIndependentID(this.id || '');
|
||||
},
|
||||
|
||||
genIndependentID(id) {
|
||||
if (id) {
|
||||
this.setData({ independentID: id });
|
||||
} else {
|
||||
this.setData({
|
||||
independentID: `goods-list-${~~(Math.random() * 10 ** 8)}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
6
mini-program/components/goods-list/index.json
Normal file
6
mini-program/components/goods-list/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"goods-card": "/components/goods-card/index"
|
||||
}
|
||||
}
|
||||
16
mini-program/components/goods-list/index.wxml
Normal file
16
mini-program/components/goods-list/index.wxml
Normal file
@@ -0,0 +1,16 @@
|
||||
<view class="goods-list-wrap wr-class" id="{{independentID}}">
|
||||
<block wx:for="{{goodsList}}" wx:for-item="item" wx:key="index">
|
||||
<goods-card
|
||||
id="{{independentID}}-gd-{{index}}"
|
||||
data="{{item}}"
|
||||
currency="{{item.currency || '¥'}}"
|
||||
thresholds="{{thresholds}}"
|
||||
class="goods-card-inside"
|
||||
data-index="{{index}}"
|
||||
bind:thumb="onClickGoodsThumb"
|
||||
bind:click="onClickGoods"
|
||||
bind:add-cart="onAddCart"
|
||||
/>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
7
mini-program/components/goods-list/index.wxss
Normal file
7
mini-program/components/goods-list/index.wxss
Normal file
@@ -0,0 +1,7 @@
|
||||
.goods-list-wrap {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
}
|
||||
54
mini-program/components/load-more/index.js
Normal file
54
mini-program/components/load-more/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class', 'wr-class--no-more'],
|
||||
|
||||
options: { multipleSlots: true },
|
||||
|
||||
properties: {
|
||||
status: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
value: '加载中...',
|
||||
},
|
||||
noMoreText: {
|
||||
type: String,
|
||||
value: '没有更多了',
|
||||
},
|
||||
failedText: {
|
||||
type: String,
|
||||
value: '加载失败,点击重试',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
value: '#BBBBBB',
|
||||
},
|
||||
failedColor: {
|
||||
type: String,
|
||||
value: '#FA550F',
|
||||
},
|
||||
size: {
|
||||
type: null,
|
||||
value: '40rpx',
|
||||
},
|
||||
loadingBackgroundColor: {
|
||||
type: String,
|
||||
value: '#F5F5F5',
|
||||
},
|
||||
listIsEmpty: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/** 点击处理 */
|
||||
tapHandle() {
|
||||
// 失败重试
|
||||
if (this.data.status === 3) {
|
||||
this.triggerEvent('retry');
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
7
mini-program/components/load-more/index.json
Normal file
7
mini-program/components/load-more/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-divider": "tdesign-miniprogram/divider/divider"
|
||||
}
|
||||
}
|
||||
31
mini-program/components/load-more/index.wxml
Normal file
31
mini-program/components/load-more/index.wxml
Normal file
@@ -0,0 +1,31 @@
|
||||
<view
|
||||
class="load-more wr-class"
|
||||
style="{{listIsEmpty && (status === 0 || status === 2) ? 'display: none' : '' }}"
|
||||
bindtap="tapHandle"
|
||||
>
|
||||
<!-- 加载中 -->
|
||||
|
||||
<t-loading
|
||||
t-class="t-class-loading"
|
||||
t-class-text="t-class-loading-text"
|
||||
t-class-indicator="t-class-indicator"
|
||||
loading="{{status === 1}}"
|
||||
text="加载中..."
|
||||
theme="circular"
|
||||
size="40rpx"
|
||||
/>
|
||||
|
||||
<!-- 已全部加载 -->
|
||||
<t-divider wx:if="{{status === 2}}" t-class="t-class-divider" t-class-content="t-class-divider-content">
|
||||
<text slot="content">{{noMoreText}}</text>
|
||||
</t-divider>
|
||||
|
||||
<!-- 加载失败 -->
|
||||
<view class="load-more__error" wx:if="{{status===3}}">
|
||||
加载失败
|
||||
<text class="load-more__refresh-btn" bind:tap="tapHandle">刷新</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支持通过slot传入页面/列表的空态,load-more来控制空态的显示状态 -->
|
||||
<slot wx:if="{{listIsEmpty && (status === 0 || status === 2)}}" name="empty" />
|
||||
35
mini-program/components/load-more/index.wxss
Normal file
35
mini-program/components/load-more/index.wxss
Normal file
@@ -0,0 +1,35 @@
|
||||
.load-more {
|
||||
font-size: 24rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.load-more .t-class-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
--td-loading-color: #fa4126;
|
||||
}
|
||||
|
||||
.load-more .t-class-loading-text {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
||||
.t-class-divider-content {
|
||||
margin: 0 10rpx;
|
||||
color: #bbbbbb;
|
||||
}
|
||||
.load-more .t-class-indicator {
|
||||
color: #b9b9b9 !important;
|
||||
}
|
||||
|
||||
.load-more__error {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.load-more__refresh-btn {
|
||||
margin-left: 16rpx;
|
||||
color: #fa4126;
|
||||
}
|
||||
23
mini-program/components/loading-content/index.js
Normal file
23
mini-program/components/loading-content/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
properties: {
|
||||
position: {
|
||||
type: String,
|
||||
value: 'static',
|
||||
},
|
||||
noMask: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
value: 'circular',
|
||||
},
|
||||
vertical: Boolean,
|
||||
size: {
|
||||
type: String,
|
||||
value: '50rpx',
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
value: 'rgba(0, 0, 0, .6)',
|
||||
},
|
||||
},
|
||||
});
|
||||
6
mini-program/components/loading-content/index.json
Normal file
6
mini-program/components/loading-content/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
11
mini-program/components/loading-content/index.wxml
Normal file
11
mini-program/components/loading-content/index.wxml
Normal file
@@ -0,0 +1,11 @@
|
||||
<view class="t-class loading-content {{position}}" style="{{(position === 'static' || noMask) ? 'visibility: hidden;' : ''}} background-color: {{backgroundColor}}">
|
||||
<t-loading
|
||||
t-class="loading"
|
||||
theme="{{type}}"
|
||||
layout="{{vertical}}"
|
||||
size="{{size}}"
|
||||
>
|
||||
<slot/>
|
||||
</t-loading>
|
||||
</view>
|
||||
|
||||
23
mini-program/components/loading-content/index.wxss
Normal file
23
mini-program/components/loading-content/index.wxss
Normal file
@@ -0,0 +1,23 @@
|
||||
.loading-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
position: relative;
|
||||
}
|
||||
.loading-content.absolute {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.loading-content.fixed {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.loading-content .loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
visibility: visible;
|
||||
}
|
||||
71
mini-program/components/price/index.js
Normal file
71
mini-program/components/price/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class', 'symbol-class', 'decimal-class'],
|
||||
useStore: [],
|
||||
properties: {
|
||||
priceUnit: {
|
||||
type: String,
|
||||
value: 'fen',
|
||||
}, // 价格单位,分 | 元, fen,yuan
|
||||
price: {
|
||||
type: null,
|
||||
value: '',
|
||||
observer(price) {
|
||||
this.format(price);
|
||||
},
|
||||
}, // 价格, 以分为单位
|
||||
type: {
|
||||
type: String,
|
||||
value: '', //
|
||||
}, // main 粗体, lighter 细体, mini 黑色, del 中划线, delthrough 中划线,包括货币符号
|
||||
symbol: {
|
||||
type: String,
|
||||
value: '¥', // '¥',
|
||||
}, // 货币符号,默认是人民币符号¥
|
||||
fill: Boolean, // 是否自动补齐两位小数
|
||||
decimalSmaller: Boolean, // 小数字号小一点
|
||||
lineThroughWidth: {
|
||||
type: null,
|
||||
value: '0.12em',
|
||||
}, // 划线价线条高度
|
||||
},
|
||||
|
||||
data: {
|
||||
pArr: [],
|
||||
},
|
||||
|
||||
methods: {
|
||||
format(price) {
|
||||
price = parseFloat(`${price}`);
|
||||
const pArr = [];
|
||||
if (!isNaN(price)) {
|
||||
const isMinus = price < 0;
|
||||
if (isMinus) {
|
||||
price = -price;
|
||||
}
|
||||
if (this.properties.priceUnit === 'yuan') {
|
||||
const priceSplit = price.toString().split('.');
|
||||
pArr[0] = priceSplit[0];
|
||||
pArr[1] = !priceSplit[1]
|
||||
? '00'
|
||||
: priceSplit[1].length === 1
|
||||
? `${priceSplit[1]}0`
|
||||
: priceSplit[1];
|
||||
} else {
|
||||
price = Math.round(price * 10 ** 8) / 10 ** 8; // 恢复精度丢失
|
||||
price = Math.ceil(price); // 向上取整
|
||||
pArr[0] = price >= 100 ? `${price}`.slice(0, -2) : '0';
|
||||
pArr[1] = `${price + 100}`.slice(-2);
|
||||
}
|
||||
if (!this.properties.fill) {
|
||||
// 如果 fill 为 false, 不显示小数末尾的0
|
||||
if (pArr[1] === '00') pArr[1] = '';
|
||||
else if (pArr[1][1] === '0') pArr[1] = pArr[1][0];
|
||||
}
|
||||
if (isMinus) {
|
||||
pArr[0] = `-${pArr[0]}`;
|
||||
}
|
||||
}
|
||||
this.setData({ pArr });
|
||||
},
|
||||
},
|
||||
});
|
||||
4
mini-program/components/price/index.json
Normal file
4
mini-program/components/price/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
21
mini-program/components/price/index.wxml
Normal file
21
mini-program/components/price/index.wxml
Normal file
@@ -0,0 +1,21 @@
|
||||
<wxs module="utils">
|
||||
var REGEXP = getRegExp('^\d+(\.\d+)?$');
|
||||
function addUnit(value) {
|
||||
if (value == null) {
|
||||
return '';
|
||||
}
|
||||
return REGEXP.test('' + value) ? value + 'rpx' : value;
|
||||
}
|
||||
module.exports = {
|
||||
addUnit: addUnit
|
||||
};
|
||||
</wxs>
|
||||
<view class="price {{type}} wr-class">
|
||||
<view wx:if="{{type === 'delthrough'}}" class="line" style="height:{{utils.addUnit(lineThroughWidth)}};" />
|
||||
<view class="symbol symbol-class">{{symbol}}</view>
|
||||
<view class="pprice">
|
||||
<view class="integer inline">{{pArr[0]}}</view>
|
||||
<view wx:if="{{pArr[1]}}" class="decimal inline {{decimalSmaller ? 'smaller' : ''}} decimal-class">.{{pArr[1]}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
66
mini-program/components/price/index.wxss
Normal file
66
mini-program/components/price/index.wxss
Normal file
@@ -0,0 +1,66 @@
|
||||
:host {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
font-weight: inherit;
|
||||
}
|
||||
.inline {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.price {
|
||||
display: inline;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
.lighter {
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.mini {
|
||||
font-size: 24rpx;
|
||||
color: #5d5d5d;
|
||||
font-weight: 400;
|
||||
}
|
||||
.del .pprice {
|
||||
font-size: 32rpx;
|
||||
color: #9b9b9b;
|
||||
text-decoration: line-through;
|
||||
font-weight: 400;
|
||||
}
|
||||
.delthrough {
|
||||
position: relative;
|
||||
}
|
||||
.delthrough .line {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
margin: 0;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
display: inline;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.pprice {
|
||||
display: inline;
|
||||
margin: 0 0 0 4rpx;
|
||||
}
|
||||
.integer {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.decimal {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.decimal.smaller {
|
||||
font-size: 0.8em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
79
mini-program/components/swipeout/index.js
Normal file
79
mini-program/components/swipeout/index.js
Normal file
@@ -0,0 +1,79 @@
|
||||
let ARRAY = [];
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
options: {
|
||||
multipleSlots: true,
|
||||
},
|
||||
properties: {
|
||||
disabled: Boolean,
|
||||
leftWidth: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
rightWidth: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
},
|
||||
asyncClose: Boolean,
|
||||
},
|
||||
attached() {
|
||||
ARRAY.push(this);
|
||||
},
|
||||
|
||||
detached() {
|
||||
ARRAY = ARRAY.filter((item) => item !== this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Component initial data
|
||||
*/
|
||||
data: {
|
||||
wrapperStyle: '',
|
||||
asyncClose: false,
|
||||
closed: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Component methods
|
||||
*/
|
||||
methods: {
|
||||
open(position) {
|
||||
this.setData({ closed: false });
|
||||
this.triggerEvent('close', {
|
||||
position,
|
||||
instance: this,
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
this.setData({ closed: true });
|
||||
},
|
||||
|
||||
closeOther() {
|
||||
ARRAY.filter((item) => item !== this).forEach((item) => item.close());
|
||||
},
|
||||
|
||||
noop() {
|
||||
return;
|
||||
},
|
||||
|
||||
onClick(event) {
|
||||
const { key: position = 'outside' } = event.currentTarget.dataset;
|
||||
this.triggerEvent('click', position);
|
||||
|
||||
if (this.data.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.data.asyncClose) {
|
||||
this.triggerEvent('close', {
|
||||
position,
|
||||
instance: this,
|
||||
});
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
4
mini-program/components/swipeout/index.json
Normal file
4
mini-program/components/swipeout/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
174
mini-program/components/swipeout/index.wxml
Normal file
174
mini-program/components/swipeout/index.wxml
Normal file
@@ -0,0 +1,174 @@
|
||||
<wxs module="swipe">
|
||||
var THRESHOLD = 0.3;
|
||||
var MIN_DISTANCE = 10;
|
||||
var owner;
|
||||
var state;
|
||||
|
||||
var getState = function(ownerInstance) {
|
||||
owner = ownerInstance;
|
||||
state = owner.getState();
|
||||
state.leftWidth = state.leftWidth || 0;
|
||||
state.rightWidth = state.rightWidth || 0;
|
||||
state.offset = state.offset || 0;
|
||||
state.startOffset = state.startOffset || 0;
|
||||
};
|
||||
|
||||
var initRightWidth = function(newVal, oldVal, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
state.rightWidth = newVal;
|
||||
if (state.offset < 0) {
|
||||
swipeMove(-state.rightWidth);
|
||||
}
|
||||
};
|
||||
|
||||
var initLeftWidth = function(newVal, oldVal, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
state.leftWidth = newVal;
|
||||
if (state.offset > 0) {
|
||||
swipeMove(state.leftWidth);
|
||||
}
|
||||
}
|
||||
|
||||
var resetTouchStatus = function() {
|
||||
state.direction = '';
|
||||
state.deltaX = 0;
|
||||
state.deltaY = 0;
|
||||
state.offsetX = 0;
|
||||
state.offsetY = 0;
|
||||
};
|
||||
|
||||
var touchMove = function(event) {
|
||||
var touchPoint = event.touches[0];
|
||||
state.deltaX = touchPoint.clientX - state.startX;
|
||||
state.deltaY = touchPoint.clientY - state.startY;
|
||||
state.offsetX = Math.abs(state.deltaX);
|
||||
state.offsetY = Math.abs(state.deltaY);
|
||||
state.direction = state.direction || getDirection(state.offsetX, state.offsetY);
|
||||
};
|
||||
|
||||
var getDirection = function(x, y) {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal';
|
||||
}
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
var range = function(num, min, max) {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
};
|
||||
|
||||
var swipeMove = function(_offset = 0) {
|
||||
state.offset = range(
|
||||
_offset,
|
||||
-state.rightWidth,
|
||||
+state.leftWidth,
|
||||
);
|
||||
|
||||
var transform = 'translate3d(' + state.offset + 'px, 0, 0)';
|
||||
var transition = state.dragging
|
||||
? 'none'
|
||||
: 'transform .6s cubic-bezier(0.18, 0.89, 0.32, 1)';
|
||||
owner.selectComponent('#wrapper').setStyle({
|
||||
'-webkit-transform': transform,
|
||||
'-webkit-transition': transition,
|
||||
'transform': transform,
|
||||
'transition': transition
|
||||
});
|
||||
};
|
||||
|
||||
var close = function() {
|
||||
swipeMove(0);
|
||||
};
|
||||
|
||||
var onCloseChange = function(newVal, oldVal, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
if (newVal === oldVal) return;
|
||||
if (newVal) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
var touchStart = function(event) {
|
||||
resetTouchStatus();
|
||||
state.startOffset = state.offset;
|
||||
var touchPoint = event.touches[0];
|
||||
state.startX = touchPoint.clientX;
|
||||
state.startY = touchPoint.clientY;
|
||||
owner.callMethod('closeOther');
|
||||
};
|
||||
|
||||
var startDrag = function(event, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
touchStart(event);
|
||||
};
|
||||
|
||||
var onDrag = function(event, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
touchMove(event);
|
||||
if (state.direction !== 'horizontal') {
|
||||
return;
|
||||
}
|
||||
state.dragging = true;
|
||||
swipeMove(state.startOffset + state.deltaX);
|
||||
};
|
||||
|
||||
var open = function(position) {
|
||||
var _offset = position === 'left' ? +state.leftWidth : -state.rightWidth;
|
||||
owner.callMethod('open', { position: position });
|
||||
swipeMove(_offset);
|
||||
};
|
||||
|
||||
var endDrag = function(event, ownerInstance) {
|
||||
getState(ownerInstance);
|
||||
state.dragging = false;
|
||||
// 左/右侧有可滑动区域,且当前不是已open状态,且滑动幅度超过阈值时open左/右侧(滚动到该侧的最边上)
|
||||
if (+state.rightWidth > 0 && -state.startOffset < +state.rightWidth && -state.offset > +state.rightWidth * THRESHOLD) {
|
||||
open('right');
|
||||
} else if (+state.leftWidth > 0 && state.startOffset < +state.leftWidth && state.offset > +state.leftWidth * THRESHOLD) {
|
||||
open('left');
|
||||
} else {
|
||||
// 仅在有发生侧滑的情况下自动关闭(由js控制是否异步关闭)
|
||||
if (state.startOffset !== state.offset) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
initLeftWidth: initLeftWidth,
|
||||
initRightWidth: initRightWidth,
|
||||
startDrag: startDrag,
|
||||
onDrag: onDrag,
|
||||
endDrag: endDrag,
|
||||
onCloseChange: onCloseChange
|
||||
};
|
||||
</wxs>
|
||||
|
||||
<view
|
||||
class="wr-class wr-swipeout"
|
||||
data-key="cell"
|
||||
capture-bind:tap="onClick"
|
||||
bindtouchstart="{{disabled || swipe.startDrag}}"
|
||||
capture-bind:touchmove="{{disabled || swipe.onDrag}}"
|
||||
bindtouchend="{{disabled || swipe.endDrag}}"
|
||||
bindtouchcancel="{{disabled || swipe.endDrag}}"
|
||||
closed="{{closed}}"
|
||||
change:closed="{{swipe.onCloseChange}}"
|
||||
leftWidth="{{leftWidth}}"
|
||||
rightWidth="{{rightWidth}}"
|
||||
change:leftWidth="{{swipe.initLeftWidth}}"
|
||||
change:rightWidth="{{swipe.initRightWidth}}"
|
||||
>
|
||||
<view id="wrapper">
|
||||
<view wx:if="{{ leftWidth }}" class="wr-swipeout__left" data-key="left" catch:tap="onClick">
|
||||
<slot name="left" />
|
||||
</view>
|
||||
<slot />
|
||||
<view wx:if="{{ rightWidth }}" class="wr-swipeout__right" data-key="right" catch:tap="onClick">
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
18
mini-program/components/swipeout/index.wxss
Normal file
18
mini-program/components/swipeout/index.wxss
Normal file
@@ -0,0 +1,18 @@
|
||||
.wr-swipeout {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.wr-swipeout__left,
|
||||
.wr-swipeout__right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.wr-swipeout__left {
|
||||
left: 0;
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
.wr-swipeout__right {
|
||||
right: 0;
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
86
mini-program/components/webp-image/index.js
Normal file
86
mini-program/components/webp-image/index.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* @Author: rileycai
|
||||
* @Date: 2022-03-14 14:21:26
|
||||
* @LastEditTime: 2022-03-14 15:23:04
|
||||
* @LastEditors: rileycai
|
||||
* @Description: webp-image组件对t-image包裹了一层,主要实现图片裁剪、webp压缩功能
|
||||
* @FilePath: /tdesign-miniprogram-starter/components/webp-image/index.js
|
||||
*/
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
Component({
|
||||
externalClasses: ['t-class', 't-class-load'],
|
||||
properties: {
|
||||
loadFailed: {
|
||||
type: String,
|
||||
value: 'default',
|
||||
},
|
||||
loading: {
|
||||
type: String,
|
||||
value: 'default',
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
value: 'aspectFill',
|
||||
},
|
||||
webp: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
showMenuByLongpress: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
thumbHeight: 375,
|
||||
thumbWidth: 375,
|
||||
systemInfo,
|
||||
},
|
||||
lifetimes: {
|
||||
ready() {
|
||||
const { mode } = this.properties;
|
||||
// 获取容器的真实宽高,设置图片的裁剪宽度
|
||||
this.getRect('.J-image').then((res) => {
|
||||
if (res) {
|
||||
const { width, height } = res;
|
||||
this.setData(
|
||||
mode === 'heightFix'
|
||||
? {
|
||||
thumbHeight: this.px2rpx(height) || 375,
|
||||
}
|
||||
: {
|
||||
thumbWidth: this.px2rpx(width) || 375,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
px2rpx(px) {
|
||||
return (750 / (systemInfo.screenWidth || 375)) * px;
|
||||
},
|
||||
getRect(selector) {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.selectorQuery) {
|
||||
this.selectorQuery = this.createSelectorQuery();
|
||||
}
|
||||
this.selectorQuery.select(selector).boundingClientRect(resolve).exec();
|
||||
});
|
||||
},
|
||||
onLoad(e) {
|
||||
this.triggerEvent('load', e.detail);
|
||||
},
|
||||
onError(e) {
|
||||
this.triggerEvent('error', e.detail);
|
||||
},
|
||||
},
|
||||
});
|
||||
6
mini-program/components/webp-image/index.json
Normal file
6
mini-program/components/webp-image/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-image": "tdesign-miniprogram/image/image"
|
||||
}
|
||||
}
|
||||
14
mini-program/components/webp-image/index.wxml
Normal file
14
mini-program/components/webp-image/index.wxml
Normal file
@@ -0,0 +1,14 @@
|
||||
<wxs src="./utils.wxs" module="Utils" />
|
||||
<t-image
|
||||
t-class="J-image"
|
||||
src="{{Utils.getSrc({src, thumbWidth: thumbWidth || 0, thumbHeight: thumbHeight || 0, systemInfo, webp, mode})}}"
|
||||
t-class="t-class"
|
||||
t-class-load="t-class-load"
|
||||
mode="{{ mode }}"
|
||||
lazy="{{ lazyLoad }}"
|
||||
show-menu-by-longpress="{{showMenuByLongpress}}"
|
||||
error="{{loadFailed}}"
|
||||
loading="{{loading}}"
|
||||
binderror="onError"
|
||||
bindload="onLoad"
|
||||
/>
|
||||
0
mini-program/components/webp-image/index.wxss
Normal file
0
mini-program/components/webp-image/index.wxss
Normal file
140
mini-program/components/webp-image/utils.wxs
Normal file
140
mini-program/components/webp-image/utils.wxs
Normal file
@@ -0,0 +1,140 @@
|
||||
var isString = function (value) {
|
||||
return typeof value === 'string';
|
||||
};
|
||||
|
||||
var isNumber = function (value) {
|
||||
return typeof value === 'number';
|
||||
};
|
||||
|
||||
var getFileExt = function (src) {
|
||||
var fileUrl = src.split('?')[0];
|
||||
var splitUlr = fileUrl.split('/');
|
||||
var filepath = splitUlr[splitUlr.length - 1];
|
||||
return filepath.split('.')[1] || 'jpg';
|
||||
};
|
||||
|
||||
function isUrl(url) {
|
||||
// NOCC:ToolNameCheck(非敏感词)
|
||||
var urlReg = getRegExp(
|
||||
'/[(http(s)?)://(www.)?a-zA-Z0-9@:%._+~#=]{2,256}.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/',
|
||||
'ig',
|
||||
);
|
||||
|
||||
return urlReg.test(url);
|
||||
}
|
||||
|
||||
function rpx2px(rpx, screenWidth) {
|
||||
// px / systemWidth = rpx / 750
|
||||
var result = (rpx * (screenWidth || 375)) / 750;
|
||||
|
||||
return Math.round(result);
|
||||
}
|
||||
|
||||
function imageMogr(url, options) {
|
||||
if (!isString(url) || !url) return '';
|
||||
|
||||
if (
|
||||
url.indexOf('qlogo.cn') !== -1 ||
|
||||
url.indexOf('wxfile://') === 0 ||
|
||||
url.indexOf('http://tmp/wx') === 0 ||
|
||||
url.indexOf('imageMogr2') !== -1
|
||||
) {
|
||||
//qlogo.cn域名或者本地图片不做转换
|
||||
return url;
|
||||
} //强制转https
|
||||
|
||||
if (url.indexOf('http://') === 0) {
|
||||
url = url.replace('http://', 'https://');
|
||||
} else if (url.indexOf('//') === 0) {
|
||||
url = 'https:' + url;
|
||||
}
|
||||
|
||||
if (!options) return url;
|
||||
|
||||
var width = Math.ceil(options.width),
|
||||
height = Math.ceil(options.height),
|
||||
format = options.format,
|
||||
_optionsQuality = options.quality,
|
||||
quality = _optionsQuality === undefined ? 70 : _optionsQuality,
|
||||
_optionsStrip = options.strip,
|
||||
strip = _optionsStrip === undefined ? true : _optionsStrip,
|
||||
crop = options.crop;
|
||||
var isValidWidth = isNumber(width) && width > 0;
|
||||
var isValidHeight = isNumber(height) && height > 0;
|
||||
var imageMogrStr = '';
|
||||
var size = '';
|
||||
|
||||
if (isValidWidth && isValidHeight) {
|
||||
size = ''.concat(width, 'x').concat(height);
|
||||
} else if (isValidWidth) {
|
||||
size = ''.concat(width, 'x');
|
||||
} else if (isValidHeight) {
|
||||
size = 'x'.concat(height);
|
||||
}
|
||||
|
||||
if (size) {
|
||||
//缩放或者裁剪
|
||||
imageMogrStr += '/'.concat(crop ? 'crop' : 'thumbnail', '/').concat(size);
|
||||
|
||||
if (crop) {
|
||||
//裁剪目前需求只有以图片中心为基准
|
||||
imageMogrStr += '/gravity/center';
|
||||
}
|
||||
}
|
||||
|
||||
if (isNumber(quality)) {
|
||||
//质量变换
|
||||
imageMogrStr += '/quality/'.concat(quality);
|
||||
}
|
||||
|
||||
if (strip) {
|
||||
//去除元信息
|
||||
imageMogrStr += '/strip';
|
||||
}
|
||||
|
||||
var ext = getFileExt(url);
|
||||
|
||||
// gif 图片不做格式转换,否则会损坏动图
|
||||
if (ext === 'gif') {
|
||||
imageMogrStr += '/cgif/1';
|
||||
} else if (format) {
|
||||
//格式转换
|
||||
imageMogrStr += '/format/'.concat(format);
|
||||
}
|
||||
|
||||
if (format === 'jpg' || (!format && (ext === 'jpg' || ext === 'jpeg'))) {
|
||||
//渐进式 jpg 加载
|
||||
imageMogrStr += '/interlace/1';
|
||||
}
|
||||
if (!imageMogrStr) return url;
|
||||
return ''
|
||||
.concat(url)
|
||||
.concat(url.indexOf('?') !== -1 ? '&' : '?', 'imageMogr2')
|
||||
.concat(imageMogrStr);
|
||||
}
|
||||
function getSrc(options) {
|
||||
if (!options.src) return '';
|
||||
|
||||
if (options.thumbWidth || options.thumbHeight) {
|
||||
return imageMogr(options.src, {
|
||||
width:
|
||||
options.mode !== 'heightFix'
|
||||
? rpx2px(options.thumbWidth, options.systemInfo.screenWidth) *
|
||||
options.systemInfo.pixelRatio
|
||||
: null,
|
||||
height:
|
||||
options.mode !== 'widthFix'
|
||||
? rpx2px(options.thumbHeight, options.systemInfo.screenWidth) *
|
||||
options.systemInfo.pixelRatio
|
||||
: null,
|
||||
format: options.webp ? 'webp' : null,
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
imageMogr: imageMogr,
|
||||
getSrc: getSrc,
|
||||
};
|
||||
Reference in New Issue
Block a user