Initial commit
This commit is contained in:
BIN
miniprogram/components/.DS_Store
vendored
Normal file
BIN
miniprogram/components/.DS_Store
vendored
Normal file
Binary file not shown.
35
miniprogram/components/empty-state/index.js
Normal file
35
miniprogram/components/empty-state/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
Component({
|
||||
properties: {
|
||||
// 图标名称
|
||||
icon: {
|
||||
type: String,
|
||||
value: 'shop'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
value: '暂无商品'
|
||||
},
|
||||
// 描述文字
|
||||
description: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
// 是否显示操作按钮
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
// 操作按钮文字
|
||||
actionText: {
|
||||
type: String,
|
||||
value: '刷新'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onActionTap() {
|
||||
this.triggerEvent('action');
|
||||
}
|
||||
}
|
||||
});
|
||||
7
miniprogram/components/empty-state/index.json
Normal file
7
miniprogram/components/empty-state/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-button": "tdesign-miniprogram/button/button"
|
||||
}
|
||||
}
|
||||
16
miniprogram/components/empty-state/index.wxml
Normal file
16
miniprogram/components/empty-state/index.wxml
Normal file
@@ -0,0 +1,16 @@
|
||||
<view class="empty-state wr-class">
|
||||
<view class="empty-state__icon">
|
||||
<t-icon name="{{icon || 'shop'}}" size="120rpx" color="#ddd" />
|
||||
</view>
|
||||
<view class="empty-state__title">{{title || '暂无商品'}}</view>
|
||||
<view class="empty-state__description" wx:if="{{description}}">{{description}}</view>
|
||||
<view class="empty-state__action" wx:if="{{showAction}}">
|
||||
<t-button
|
||||
variant="outline"
|
||||
size="medium"
|
||||
bind:tap="onActionTap"
|
||||
>
|
||||
{{actionText || '刷新'}}
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
32
miniprogram/components/empty-state/index.wxss
Normal file
32
miniprogram/components/empty-state/index.wxss
Normal file
@@ -0,0 +1,32 @@
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
text-align: center;
|
||||
min-height: 400rpx;
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-state__description {
|
||||
font-size: 28rpx;
|
||||
color: #bbb;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 40rpx;
|
||||
max-width: 400rpx;
|
||||
}
|
||||
|
||||
.empty-state__action {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
36
miniprogram/components/filter-popup/index.js
Normal file
36
miniprogram/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
miniprogram/components/filter-popup/index.json
Normal file
6
miniprogram/components/filter-popup/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-popup": "tdesign-miniprogram/popup/popup"
|
||||
}
|
||||
}
|
||||
18
miniprogram/components/filter-popup/index.wxml
Normal file
18
miniprogram/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
miniprogram/components/filter-popup/index.wxss
Normal file
39
miniprogram/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;
|
||||
}
|
||||
133
miniprogram/components/filter/index.js
Normal file
133
miniprogram/components/filter/index.js
Normal file
@@ -0,0 +1,133 @@
|
||||
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,
|
||||
});
|
||||
},
|
||||
},
|
||||
prices: {
|
||||
type: Array,
|
||||
value: [],
|
||||
observer(prices) {
|
||||
this.setData({
|
||||
prices,
|
||||
});
|
||||
},
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
value: '#FA550F',
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
layout: 1,
|
||||
overall: 1,
|
||||
sorts: '',
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangeShowAction() {
|
||||
const { layout } = this.data;
|
||||
const nextLayout = layout === 1 ? 0 : 1;
|
||||
|
||||
console.log('[Filter] 布局切换:', {
|
||||
currentLayout: layout === 1 ? '列表' : '网格',
|
||||
nextLayout: nextLayout === 1 ? '列表' : '网格',
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
this.triggerEvent('change', { ...this.properties, layout: nextLayout });
|
||||
},
|
||||
|
||||
handlePriseSort() {
|
||||
const { sorts } = this.data;
|
||||
const nextSort = sorts === 'desc' ? 'asc' : 'desc';
|
||||
|
||||
console.log('[Filter] 价格排序点击:', {
|
||||
currentSort: sorts || '默认',
|
||||
nextSort: nextSort,
|
||||
sortText: nextSort === 'asc' ? '价格从低到高' : '价格从高到低',
|
||||
timestamp: new Date().toLocaleString(),
|
||||
action: '用户点击价格排序按钮'
|
||||
});
|
||||
|
||||
const changeData = {
|
||||
...this.properties,
|
||||
overall: 0,
|
||||
sorts: nextSort,
|
||||
};
|
||||
|
||||
console.log('[Filter] 触发排序变化事件:', {
|
||||
eventData: changeData,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
this.triggerEvent('change', changeData);
|
||||
},
|
||||
|
||||
open() {
|
||||
this.triggerEvent('showFilterPopup', {
|
||||
show: true,
|
||||
});
|
||||
},
|
||||
|
||||
onOverallAction() {
|
||||
const { overall } = this.data;
|
||||
const nextOverall = overall === 1 ? 0 : 1;
|
||||
const nextData = {
|
||||
sorts: '',
|
||||
prices: [],
|
||||
};
|
||||
|
||||
console.log('[Filter] 综合排序点击:', {
|
||||
currentOverall: overall === 1 ? '综合排序' : '其他排序',
|
||||
nextOverall: nextOverall === 1 ? '综合排序' : '其他排序',
|
||||
resetData: nextData,
|
||||
timestamp: new Date().toLocaleString(),
|
||||
action: '用户点击综合排序,重置所有筛选条件'
|
||||
});
|
||||
|
||||
const changeData = {
|
||||
...this.properties,
|
||||
...nextData,
|
||||
overall: nextOverall,
|
||||
};
|
||||
|
||||
console.log('[Filter] 触发综合排序变化事件:', {
|
||||
eventData: changeData,
|
||||
timestamp: new Date().toLocaleString()
|
||||
});
|
||||
|
||||
this.triggerEvent('change', changeData);
|
||||
},
|
||||
},
|
||||
});
|
||||
6
miniprogram/components/filter/index.json
Normal file
6
miniprogram/components/filter/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
37
miniprogram/components/filter/index.wxml
Normal file
37
miniprogram/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
miniprogram/components/filter/index.wxss
Normal file
50
miniprogram/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;
|
||||
}
|
||||
159
miniprogram/components/goods-card/index.js
Normal file
159
miniprogram/components/goods-card/index.js
Normal file
@@ -0,0 +1,159 @@
|
||||
Component({
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
},
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
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: '¥',
|
||||
},
|
||||
showCart: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
thresholds: {
|
||||
type: Array,
|
||||
value: [],
|
||||
observer(thresholds) {
|
||||
if (thresholds && thresholds.length) {
|
||||
this.createIntersectionObserverHandle();
|
||||
} else {
|
||||
this.clearIntersectionObserverHandle();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
independentID: '',
|
||||
goods: { id: '' },
|
||||
isValidityLinePrice: false,
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
// 组件实例被放到页面节点树后执行
|
||||
},
|
||||
ready() {
|
||||
this.init();
|
||||
},
|
||||
detached() {
|
||||
this.clear();
|
||||
},
|
||||
},
|
||||
|
||||
observers: {
|
||||
'goods.thumb': function(thumb) {
|
||||
// 图片URL变化监听
|
||||
}
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
|
||||
handleImageError(e) {
|
||||
console.warn('商品卡片图片加载失败:', e.detail);
|
||||
// 可以在这里设置默认图片或其他错误处理逻辑
|
||||
},
|
||||
|
||||
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
miniprogram/components/goods-card/index.json
Normal file
8
miniprogram/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"
|
||||
}
|
||||
}
|
||||
72
miniprogram/components/goods-card/index.wxml
Normal file
72
miniprogram/components/goods-card/index.wxml
Normal file
@@ -0,0 +1,72 @@
|
||||
<view
|
||||
id="{{independentID}}"
|
||||
class="goods-card wr-class"
|
||||
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
|
||||
bind:error="handleImageError"
|
||||
/>
|
||||
<!-- 售罄遮罩 -->
|
||||
<view wx:if="{{ !goods.isStock }}" class="goods-card__soldout">
|
||||
<text class="goods-card__soldout-text">售罄</text>
|
||||
</view>
|
||||
</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.specs }}" class="goods-card__specs">
|
||||
{{ goods.specs }}
|
||||
</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
|
||||
wx:if="{{showCart}}"
|
||||
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>
|
||||
|
||||
166
miniprogram/components/goods-card/index.wxss
Normal file
166
miniprogram/components/goods-card/index.wxss
Normal file
@@ -0,0 +1,166 @@
|
||||
.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: 333rpx;
|
||||
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: 333rpx;
|
||||
height: 333rpx;
|
||||
}
|
||||
|
||||
.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 16rpx 18rpx;
|
||||
flex-direction: column;
|
||||
width: 333rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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__specs {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
line-height: 32rpx;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 售罄遮罩样式 */
|
||||
.goods-card__soldout {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
}
|
||||
|
||||
.goods-card__soldout-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
66
miniprogram/components/goods-list/index.js
Normal file
66
miniprogram/components/goods-list/index.js
Normal file
@@ -0,0 +1,66 @@
|
||||
Component({
|
||||
externalClasses: ['wr-class'],
|
||||
|
||||
properties: {
|
||||
goodsList: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
value: '',
|
||||
observer: (id) => {
|
||||
this.genIndependentID(id);
|
||||
},
|
||||
},
|
||||
showCart: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
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
miniprogram/components/goods-list/index.json
Normal file
6
miniprogram/components/goods-list/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"goods-card": "/components/goods-card/index"
|
||||
}
|
||||
}
|
||||
17
miniprogram/components/goods-list/index.wxml
Normal file
17
miniprogram/components/goods-list/index.wxml
Normal file
@@ -0,0 +1,17 @@
|
||||
<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}}"
|
||||
show-cart="{{showCart}}"
|
||||
wr-class="goods-card-inside"
|
||||
data-index="{{index}}"
|
||||
bind:thumb="onClickGoodsThumb"
|
||||
bind:click="onClickGoods"
|
||||
bind:add-cart="onAddCart"
|
||||
/>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
14
miniprogram/components/goods-list/index.wxss
Normal file
14
miniprogram/components/goods-list/index.wxss
Normal file
@@ -0,0 +1,14 @@
|
||||
.goods-list-wrap {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 12rpx;
|
||||
background: #fff;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
/* 使商品卡按两列栅格排布 */
|
||||
.goods-card-inside {
|
||||
width: calc((100% - 12rpx) / 2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
54
miniprogram/components/load-more/index.js
Normal file
54
miniprogram/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
miniprogram/components/load-more/index.json
Normal file
7
miniprogram/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"
|
||||
}
|
||||
}
|
||||
34
miniprogram/components/load-more/index.wxml
Normal file
34
miniprogram/components/load-more/index.wxml
Normal file
@@ -0,0 +1,34 @@
|
||||
<view
|
||||
class="load-more wr-class"
|
||||
wx:if="{{!(listIsEmpty && (status === 0 || status === 2))}}"
|
||||
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>
|
||||
|
||||
|
||||
|
||||
<view wx:if="{{listIsEmpty && (status === 0 || status === 2)}}" class="load-more__wrap">
|
||||
<slot name="empty" />
|
||||
</view>
|
||||
35
miniprogram/components/load-more/index.wxss
Normal file
35
miniprogram/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
miniprogram/components/loading-content/index.js
Normal file
23
miniprogram/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
miniprogram/components/loading-content/index.json
Normal file
6
miniprogram/components/loading-content/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
11
miniprogram/components/loading-content/index.wxml
Normal file
11
miniprogram/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
miniprogram/components/loading-content/index.wxss
Normal file
23
miniprogram/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
miniprogram/components/price/index.js
Normal file
71
miniprogram/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: ['0', ''], // 确保默认值
|
||||
},
|
||||
|
||||
methods: {
|
||||
format(price) {
|
||||
price = parseFloat(`${price}`);
|
||||
const pArr = ['0', '']; // 默认初始化
|
||||
if (!isNaN(price)) {
|
||||
const isMinus = price < 0;
|
||||
if (isMinus) {
|
||||
price = -price;
|
||||
}
|
||||
if (this.properties.priceUnit === 'yuan') {
|
||||
const priceSplit = price.toString().split('.');
|
||||
pArr[0] = priceSplit.length > 0 ? priceSplit[0] : '0';
|
||||
pArr[1] = !priceSplit[1]
|
||||
? '00'
|
||||
: priceSplit[1].length === 1
|
||||
? `${priceSplit[1]}0`
|
||||
: priceSplit[1].substring(0, 2);
|
||||
} 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] && pArr[1].length > 1 && pArr[1][1] === '0') pArr[1] = pArr[1][0];
|
||||
}
|
||||
if (isMinus) {
|
||||
pArr[0] = `-${pArr[0]}`;
|
||||
}
|
||||
}
|
||||
this.setData({ pArr });
|
||||
},
|
||||
},
|
||||
});
|
||||
4
miniprogram/components/price/index.json
Normal file
4
miniprogram/components/price/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
21
miniprogram/components/price/index.wxml
Normal file
21
miniprogram/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 && pArr.length > 0 ? pArr[0] : '0'}}</view>
|
||||
<view wx:if="{{pArr && pArr.length > 1 && pArr[1]}}" class="decimal inline {{decimalSmaller ? 'smaller' : ''}} decimal-class">.{{pArr[1]}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
66
miniprogram/components/price/index.wxss
Normal file
66
miniprogram/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;
|
||||
}
|
||||
98
miniprogram/components/promotion/ui-coupon-card/index.js
Normal file
98
miniprogram/components/promotion/ui-coupon-card/index.js
Normal file
@@ -0,0 +1,98 @@
|
||||
Component({
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
multipleSlots: true,
|
||||
},
|
||||
|
||||
externalClasses: ['coupon-class'],
|
||||
|
||||
properties: {
|
||||
mask: {
|
||||
type: Boolean,
|
||||
value: false, // 是否添加遮罩
|
||||
},
|
||||
superposable: {
|
||||
type: Boolean,
|
||||
value: false, // 是否可叠加
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
value: 0, // 优惠券类型:CouponType
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
value: '', // 优惠金额
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
value: '', // 优惠标签,优惠券名字标签,img
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
value: '', // 优惠金额描述,金额下方
|
||||
},
|
||||
title: {
|
||||
type: String, // 优惠券名称
|
||||
value: '',
|
||||
},
|
||||
timeLimit: {
|
||||
type: String, // 优惠券时限
|
||||
value: '',
|
||||
},
|
||||
ruleDesc: {
|
||||
type: String, // 优惠券适用规则描述
|
||||
value: '',
|
||||
},
|
||||
currency: {
|
||||
type: String,
|
||||
value: '¥', // 优惠货币
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
value: 'default',
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
userCouponId: {
|
||||
type: Number,
|
||||
value: 0, // 用户优惠券ID
|
||||
},
|
||||
couponTemplateId: {
|
||||
type: Number,
|
||||
value: 0, // 优惠券模板ID
|
||||
},
|
||||
},
|
||||
|
||||
data: {
|
||||
CouponType: {
|
||||
MJ_COUPON: 1,
|
||||
ZK_COUPON: 2,
|
||||
MJF_COUPON: 3,
|
||||
MYF_COUPON: 3, // 免邮券的另一种类型,与MJF_COUPON相同
|
||||
GIFT_COUPON: 4,
|
||||
MERCHANT_MJ_COUPON: 1, // 商家满减券,与MJ_COUPON相同
|
||||
MERCHANT_ZK_COUPON: 2, // 商家折扣券,与ZK_COUPON相同
|
||||
},
|
||||
theme: 'primary',
|
||||
},
|
||||
|
||||
observers: {
|
||||
status: function (value) {
|
||||
let theme = 'primary';
|
||||
// 已过期或已使用的券 颜色置灰
|
||||
if (value === 'useless' || value === 'disabled') {
|
||||
theme = 'weak';
|
||||
}
|
||||
|
||||
this.setData({ theme });
|
||||
},
|
||||
},
|
||||
|
||||
attached() {
|
||||
this.setData({
|
||||
color: `color${this.properties.colorStyle}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-image": "/components/webp-image/index"
|
||||
}
|
||||
}
|
||||
63
miniprogram/components/promotion/ui-coupon-card/index.wxml
Normal file
63
miniprogram/components/promotion/ui-coupon-card/index.wxml
Normal file
@@ -0,0 +1,63 @@
|
||||
<wxs module="tools">
|
||||
function isBigValue(value) {
|
||||
if (!value) return false;
|
||||
var values = (value + '').split('.');
|
||||
if (values[1] && values[0].length >= 3) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function getBigValues(value) {
|
||||
if (!value) return ['0', ''];
|
||||
return (value + '').split('.');
|
||||
}
|
||||
module.exports = {
|
||||
isBigValue:isBigValue,
|
||||
getBigValues: getBigValues
|
||||
};
|
||||
</wxs>
|
||||
<view class="wr-coupon coupon-class theme-{{theme}}">
|
||||
<view class="wr-coupon__left">
|
||||
<view wx:if="{{type == CouponType.ZK_COUPON || type === CouponType.MERCHANT_ZK_COUPON}}">
|
||||
<text class="wr-coupon__left--value">{{value / 10}}</text>
|
||||
<text class="wr-coupon__left--unit">折</text>
|
||||
<view class="wr-coupon__left--desc">{{desc}}</view>
|
||||
</view>
|
||||
<view wx:if="{{type == CouponType.MJ_COUPON || type === CouponType.MERCHANT_MJ_COUPON}}">
|
||||
<text class="wr-coupon__left--value" wx:if="{{tools.isBigValue(value / 100)}}">
|
||||
<text class="wr-coupon__left--value-int">{{tools.getBigValues(value / 100) && tools.getBigValues(value / 100).length > 0 ? tools.getBigValues(value / 100)[0] : '0'}}</text>
|
||||
<text class="wr-coupon__left--value-decimal" wx:if="{{tools.getBigValues(value / 100) && tools.getBigValues(value / 100).length > 1}}">.{{tools.getBigValues(value / 100)[1]}}</text>
|
||||
</text>
|
||||
<text class="wr-coupon__left--value" wx:else>{{value / 100}}</text>
|
||||
<text class="wr-coupon__left--unit">元</text>
|
||||
<view class="wr-coupon__left--desc">{{desc}}</view>
|
||||
</view>
|
||||
<view wx:if="{{type === CouponType.MJF_COUPON || type === CouponType.MYF_COUPON}}">
|
||||
<text class="wr-coupon__left--value" style="font-family: PingFang SC; font-size: 44rpx">免邮</text>
|
||||
<view class="wr-coupon__left--desc">{{desc}}</view>
|
||||
</view>
|
||||
<view wx:if="{{type == CouponType.GIFT_COUPON}}">
|
||||
<t-image t-class="wr-coupon__left--image" src="{{image}}" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="wr-coupon__right">
|
||||
<view class="wr-coupon__right--title">
|
||||
<text class="coupon-title">{{title}}</text>
|
||||
<view class="coupon-time">{{timeLimit}}</view>
|
||||
<view class="coupon-desc">
|
||||
<view wx:if="{{ruleDesc}}">{{ruleDesc}}</view>
|
||||
</view>
|
||||
<!-- 显示用户优惠券ID,用于调试 -->
|
||||
<view class="coupon-debug-info" style="font-size: 20rpx; color: #999; margin-top: 8rpx;">
|
||||
用户券ID: {{userCouponId}} | 模板ID: {{couponTemplateId}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="wr-coupon__right--oper">
|
||||
<slot name="operator" />
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{status === 'useless' || status === 'disabled'}}" class="wr-coupon__seal seal-{{status}}}" />
|
||||
<view wx:if="{{mask}}" class="wr-coupon__mask" />
|
||||
<view wx:if="{{superposable}}" class="wr-coupon__tag">可叠加</view>
|
||||
</view>
|
||||
147
miniprogram/components/promotion/ui-coupon-card/index.wxss
Normal file
147
miniprogram/components/promotion/ui-coupon-card/index.wxss
Normal file
@@ -0,0 +1,147 @@
|
||||
.wr-coupon {
|
||||
display: flex;
|
||||
background-image: url('https://tdesign.gtimg.com/miniprogram/template/retail/coupon/coupon-bg-nocorners.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.theme-weak.wr-coupon {
|
||||
background-image: url('https://tdesign.gtimg.com/miniprogram/template/retail/coupon/coupon-bg-grey2.png');
|
||||
}
|
||||
|
||||
.wr-coupon__left {
|
||||
width: 200rpx;
|
||||
height: 180rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #fa4126;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.theme-weak .wr-coupon__left {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.wr-coupon__left--value {
|
||||
font-size: 64rpx;
|
||||
line-height: 88rpx;
|
||||
font-weight: bold;
|
||||
font-family: 'DIN Alternate', cursive;
|
||||
}
|
||||
.wr-coupon__left--value-int {
|
||||
font-size: 48rpx;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
.wr-coupon__left--value-decimal {
|
||||
font-size: 36rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.wr-coupon__left--image {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
border-radius: 8px;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.wr-coupon__left--unit {
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
.wr-coupon__left--desc {
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
color: #fa4126;
|
||||
}
|
||||
|
||||
.theme-weak .wr-coupon__left--desc {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.wr-coupon__right {
|
||||
flex-grow: 1;
|
||||
padding: 0 20rpx;
|
||||
height: 180rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.wr-coupon__right--title {
|
||||
display: flex;
|
||||
-webkit-display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #999999;
|
||||
font-size: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
.wr-coupon__right--title .coupon-title {
|
||||
max-width: 320rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
font-weight: bold;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
}
|
||||
.wr-coupon__right--title .coupon-time {
|
||||
margin-top: 16rpx;
|
||||
/* // letter-spacing: -0.05em; */
|
||||
}
|
||||
.wr-coupon__right--title .coupon-desc {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.wr-coupon__right--title .coupon-arrow {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.wr-coupon__right--oper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.wr-coupon__mask {
|
||||
width: 702rpx;
|
||||
height: 182rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.wr-coupon__tag {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: -24rpx;
|
||||
text-align: center;
|
||||
width: 106rpx;
|
||||
height: 28rpx;
|
||||
opacity: 0.9;
|
||||
font-size: 20rpx;
|
||||
line-height: 28rpx;
|
||||
color: #fa4126;
|
||||
border: 0.5px solid #fa4126;
|
||||
box-sizing: border-box;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.wr-coupon__seal {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.wr-coupon__seal.seal-useless {
|
||||
background-image: url('https://tdesign.gtimg.com/miniprogram/template/retail/coupon/seal-used.png');
|
||||
}
|
||||
|
||||
.wr-coupon__seal.seal-disabled {
|
||||
background-image: url('https://tdesign.gtimg.com/miniprogram/template/retail/coupon/coupon-expired.png');
|
||||
}
|
||||
79
miniprogram/components/swipeout/index.js
Normal file
79
miniprogram/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
miniprogram/components/swipeout/index.json
Normal file
4
miniprogram/components/swipeout/index.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
176
miniprogram/components/swipeout/index.wxml
Normal file
176
miniprogram/components/swipeout/index.wxml
Normal file
@@ -0,0 +1,176 @@
|
||||
<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 && event.touches[0];
|
||||
if (!touchPoint) return;
|
||||
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 && event.touches[0];
|
||||
if (!touchPoint) return;
|
||||
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
miniprogram/components/swipeout/index.wxss
Normal file
18
miniprogram/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);
|
||||
}
|
||||
88
miniprogram/components/webp-image/index.js
Normal file
88
miniprogram/components/webp-image/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
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);
|
||||
},
|
||||
// 添加错误处理方法,防止组件报错
|
||||
handleError(e) {
|
||||
console.warn('[webp-image] 图片加载失败:', e.detail);
|
||||
this.triggerEvent('error', e.detail);
|
||||
},
|
||||
// 添加onError方法的别名,确保兼容性
|
||||
error(e) {
|
||||
console.warn('[webp-image] 图片错误事件:', e);
|
||||
this.onError(e);
|
||||
},
|
||||
},
|
||||
});
|
||||
6
miniprogram/components/webp-image/index.json
Normal file
6
miniprogram/components/webp-image/index.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"t-image": "tdesign-miniprogram/image/image"
|
||||
}
|
||||
}
|
||||
13
miniprogram/components/webp-image/index.wxml
Normal file
13
miniprogram/components/webp-image/index.wxml
Normal file
@@ -0,0 +1,13 @@
|
||||
<wxs src="./utils.wxs" module="Utils" />
|
||||
<t-image
|
||||
t-class="t-class J-image"
|
||||
t-class-load="t-class-load"
|
||||
src="{{Utils.getSrc({src, thumbWidth: thumbWidth || 0, thumbHeight: thumbHeight || 0, systemInfo, webp, mode})}}"
|
||||
mode="{{ mode }}"
|
||||
lazy="{{ lazyLoad }}"
|
||||
show-menu-by-longpress="{{showMenuByLongpress}}"
|
||||
error="{{loadFailed}}"
|
||||
loading="{{loading}}"
|
||||
binderror="onError"
|
||||
bindload="onLoad"
|
||||
/>
|
||||
0
miniprogram/components/webp-image/index.wxss
Normal file
0
miniprogram/components/webp-image/index.wxss
Normal file
153
miniprogram/components/webp-image/utils.wxs
Normal file
153
miniprogram/components/webp-image/utils.wxs
Normal file
@@ -0,0 +1,153 @@
|
||||
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 ||
|
||||
url.indexOf('tral.cc') !== -1 ||
|
||||
url.indexOf('localhost') !== -1 ||
|
||||
url.indexOf('192.168.') !== -1 ||
|
||||
url.indexOf('127.0.0.1') !== -1
|
||||
) {
|
||||
//qlogo.cn域名、tral.cc域名、本地开发环境或者本地图片不做转换
|
||||
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 '';
|
||||
|
||||
// 检查是否为本地开发环境,如果是则直接返回原始URL
|
||||
if (
|
||||
options.src.indexOf('localhost') !== -1 ||
|
||||
options.src.indexOf('192.168.') !== -1 ||
|
||||
options.src.indexOf('127.0.0.1') !== -1
|
||||
) {
|
||||
return options.src;
|
||||
}
|
||||
|
||||
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