1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

通过微信开发者工具 商城模板 创建新小程序

This commit is contained in:
2023-03-06 23:52:24 +08:00
parent ada464a8cc
commit c21ff901d5
393 changed files with 52952 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# Sidebar 侧边导航
### 引入
全局引入在miniprogram根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
```json
// app.json 或 index.json
"usingComponents": {
"wr-sidebar": "path/to/components/goods-category/wr-sidebar/index",
"wr-sidebar-item": "path/to/component/goods-category/wr-sidebar/wr-sidebar-item/index"
}
```
## 代码演示
### 基础用法
通过在`wr-sidebar`上设置`activeKey`属性来控制选中项
```html
<wr-sidebar active-key="{{ activeKey }}" bind:change="onChange">
<wr-sidebar-item title="标签名称" />
<wr-sidebar-item title="标签名称" />
<wr-sidebar-item title="标签名称" />
</wr-sidebar>
```
``` javascript
Page({
data: {
activeKey: 0
},
onChange(event) {
wx.showToast({
icon: 'none',
title: `切换至第${event.detail}项`
});
}
});
```
### 提示气泡(暂未实现)
设置`dot`属性后,会在右上角展示一个小红点。设置`info`属性后,会在右上角展示相应的徽标
```html
<wr-sidebar active-key="{{ activeKey }}">
<wr-sidebar-item title="标签名称" dot />
<wr-sidebar-item title="标签名称" info="5" />
<wr-sidebar-item title="标签名称" info="99+" />
</wr-sidebar>
```
## API
### Sidebar Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|-----------|-----------|-----------|-------------|-------------|
| activeKey | 选中项的索引 | *string \| number* | `0` | - |
### Sidebar Event
| 事件名 | 说明 | 参数 |
|------|------|------|
| change | 切换选项时触发 | 当前选中选项的索引 |
### Sidebar 外部样式类
| 类名 | 说明 |
|-----------|-----------|
| custom-class | 根节点样式类 |
### SidebarItem Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|-----------|-----------|-----------|-------------|-------------|
| title | 内容 | *string* | `''` | - |
| disabled | 是否禁用 | | *boolean* | `false` | - |
| dot | 是否显示右上角小红点 | *boolean* | `false` | - |
| info | 提示消息 | *string \| number* | `''` | - |
### SidebarItem Event
| 事件名 | 说明 | 参数 |
|------|------|------|
| click | 点击徽章时触发 | 当前徽章的索引 |
### SidebarItem 外部样式类
| 类名 | 说明 |
|-----------|-----------|
| custom-class | 根节点样式类 |

View File

@@ -0,0 +1,51 @@
Component({
relations: {
'../../c-sidebar/index': {
type: 'ancestor',
linked(target) {
this.parent = target;
},
},
},
externalClasses: ['custom-class'],
properties: {
title: String,
disabled: Boolean,
},
data: {
topRightRadius: false,
bottomRightRadius: false,
},
methods: {
setActive(selected) {
return this.setData({ selected });
},
onClick() {
const { parent } = this;
if (!parent || this.properties.disabled) {
return;
}
const index = parent.children.indexOf(this);
parent.setActive(index).then(() => {
this.triggerEvent('click', index);
parent.triggerEvent('change', { index });
});
},
setTopRightRadius(val) {
return this.setData({
topRightRadius: val,
});
},
setBottomRightRadius(val) {
return this.setData({
bottomRightRadius: val,
});
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,10 @@
<view class="c-sidebar-item-container">
<view
class="c-sidebar-item {{ selected ? 'active' : '' }} {{ disabled ? 'disabled' : '' }} {{topRightRadius ? 'top-right-radius' : ''}} {{bottomRightRadius ? 'bottom-right-radius' : ''}} custom-class"
hover-class="c-sidebar-item--hover"
hover-stay-time="70"
bind:tap="onClick"
>
<view class="c-sidebar-item__text text-overflow"> {{ title }} </view>
</view>
</view>

View File

@@ -0,0 +1,60 @@
.c-sidebar-item {
display: flex;
justify-content: center;
text-align: center;
background-color: #f5f5f5;
color: #222427;
padding: 20rpx 0;
font-size: 26rpx;
}
.c-sidebar-item.active {
position: relative;
background: white;
}
.c-sidebar-item.active::before {
content: '';
position: absolute;
width: 6rpx;
height: 48rpx;
background-color: #fa4126;
left: 0;
top: 50%;
transform: translate(0, -50%);
border-radius: 64rpx;
}
.c-sidebar-item__text {
width: 136rpx;
height: 36rpx;
padding: 8rpx 0;
line-height: 36rpx;
text-align: center;
font-size: 28rpx;
color: #666666;
}
.c-sidebar-item.active .c-sidebar-item__text {
background-color: white;
border-radius: 36rpx;
color: #fa4126;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.top-right-radius {
border-top-right-radius: 16rpx;
}
.bottom-right-radius {
border-bottom-right-radius: 16rpx;
}
.c-sidebar-item-container {
background-color: white;
}

View File

@@ -0,0 +1,106 @@
Component({
relations: {
'./c-sidebar-item/index': {
type: 'descendant',
linked(target) {
this.children.push(target);
this.setActive(this.properties.activeKey, true);
},
unlinked(target) {
this.children = this.children.filter((item) => item !== target);
this.setActive(this.properties.activeKey, true);
},
},
},
externalClasses: ['custom-class'],
properties: {
activeKey: {
type: Number,
value: 0,
},
},
observers: {
activeKey(newVal) {
this.setActive(newVal);
},
},
created() {
this.children = [];
this.currentActive = -1;
this.topRightRadiusItemIndexs = [];
this.bottomRightRadiusItemIndexs = [];
},
methods: {
setActive(activeKey, isChildrenChange) {
const {
children,
currentActive,
topRightRadiusItemIndexs: preTopRightRadiusItemIndexs,
bottomRightRadiusItemIndexs: preBottomRightRadiusItemIndexs,
} = this;
if (!children.length) {
return Promise.resolve();
}
if (activeKey === currentActive && !isChildrenChange) {
return Promise.resolve();
}
this.currentActive = activeKey;
this.topRightRadiusItemIndexs = this.getTopRightRadiusItemIndexs(
activeKey,
children,
);
this.bottomRightRadiusItemIndexs = this.getBottomRightRadiusItemIndexs(
activeKey,
children,
);
const stack = []; // 任务列表存放调用子组件的setActive后返回的一堆promise
// 将旧的选中项改为false
if (currentActive !== activeKey && children[currentActive]) {
stack.push(children[currentActive].setActive(false));
}
// 将新的选中项改为true
if (children[activeKey]) {
stack.push(children[activeKey].setActive(true));
}
preTopRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setTopRightRadius(false));
});
preBottomRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setBottomRightRadius(false));
});
this.topRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setTopRightRadius(true));
});
this.bottomRightRadiusItemIndexs.forEach((item) => {
stack.push(children[item].setBottomRightRadius(true));
});
return Promise.all(stack);
},
getTopRightRadiusItemIndexs(activeKey, children) {
const { length } = children;
if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
if (activeKey !== 0) return [0];
if (activeKey < length - 1) return [activeKey + 1];
return [];
},
getBottomRightRadiusItemIndexs(activeKey) {
if (activeKey !== 0) return [activeKey - 1];
return [];
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true
}

View File

@@ -0,0 +1,3 @@
<scroll-view class="c-sidebar custom-class" scroll-y>
<slot />
</scroll-view>

View File

@@ -0,0 +1,9 @@
.c-sidebar {
width: 176rpx;
height: 100vh;
}
.c-sidebar::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}

View File

@@ -0,0 +1,37 @@
Component({
externalClasses: ['custom-class'],
properties: {
tabList: Array,
},
data: {
unfolded: false,
boardMaxHeight: null,
},
attached() {
wx.createSelectorQuery()
.in(this)
.select('.c-tabbar-more')
.boundingClientRect((rect) => {
this.setData({ boardMaxHeight: rect.height });
})
.exec();
},
methods: {
changeFold() {
this.setData({
unfolded: !this.data.unfolded,
});
const { unfolded } = this.data;
this.triggerEvent('change', { unfolded });
},
onSelect(event) {
const activeKey = event.currentTarget.dataset.index;
this.triggerEvent('select', activeKey);
this.changeFold();
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,25 @@
<view class="c-tabbar-more">
<view class="c-tabbar-more__btn" bind:tap="changeFold">
<view class="wr {{unfolded ? 'wr-arrow-up':'wr-arrow-down'}}"></view>
</view>
<view class="t-tabbar-more__boardwrapper" wx:if="{{ unfolded }}">
<view class="t-tabbar-more__mask"></view>
<scroll-view
class="c-tabbar-more__board"
style="{{ boardMaxHeight ? 'height:' + boardMaxHeight + 'px;' : '' }}"
scroll-y
>
<view class="c-tabbar-more__boardinner">
<view
class="c-tabbar-more__item text-overflow"
wx:for="{{ tabList }}"
wx:key="index"
data-index="{{ index }}"
bind:tap="onSelect"
>
{{ item.name }}
</view>
</view>
</scroll-view>
</view>
</view>

View File

@@ -0,0 +1,63 @@
.c-tabbar-more {
width: 100%;
height: calc(100% - var(--tabbar-height, 100rpx));
position: absolute;
top: var(--tabbar-height, 100rpx);
}
.c-tabbar-more__btn {
position: absolute;
top: calc(0rpx - var(--tabbar-height, 100rpx));
right: 0;
width: 80rpx;
height: var(--tabbar-height, 100rpx);
line-height: var(--tabbar-height, 100rpx);
background-color: var(--tabbar-background-color, white);
box-shadow: -20rpx 0 20rpx -10rpx var(--tabbar-background-color, white);
text-align: center;
}
.c-tabbar-more__btn .market {
font-size: 20rpx;
}
.t-tabbar-more__boardwrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.t-tabbar-more__mask {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.c-tabbar-more__board {
position: absolute;
top: 0;
left: 0;
width: 100%;
max-height: 100%;
}
.c-tabbar-more__boardinner {
padding: 20rpx 0 20rpx 20rpx;
background-color: var(--tabbar-background-color, white);
display: flex;
flex-flow: row wrap;
}
.c-tabbar-more__item {
margin: 0 20rpx 20rpx 0;
flex: 0 0 calc((100% - 60rpx) / 3);
box-sizing: border-box;
padding: 0 10rpx;
border-radius: 30rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
font-size: 22rpx;
color: #5d5d5d;
background-color: #eee;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@@ -0,0 +1,68 @@
Component({
externalClasses: ['custom-class'],
properties: {
activeKey: {
type: Number,
value: 0,
},
tabList: {
type: Array,
value: [],
},
showMore: Boolean, // 是否需要下拉功能
},
observers: {
activeKey(newVal) {
if (this.properties.tabList && newVal) {
this.setActive(newVal).catch((e) => {
console.error(e);
});
}
},
},
data: {
currentActive: -1,
},
attached() {
this.setActive(this.properties.activeKey).catch((e) => {
console.error(e);
});
},
methods: {
setActive(activeKey) {
if (
!this.properties.tabList[activeKey] ||
this.properties.tabList[activeKey].disabled
) {
return Promise.reject('数据异常或不可操作');
}
return new Promise((resolve) => {
this.setData(
{
currentActive: activeKey,
},
() => resolve(),
);
});
},
onClick(event) {
let activeKey;
if (event.type === 'select') {
activeKey = event.detail;
} else {
activeKey = event.currentTarget.dataset.index;
}
this.setActive(activeKey)
.then(() => {
const { currentActive } = this.data;
this.triggerEvent('change', { index: currentActive });
})
.catch((e) => {
console.error(e);
});
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"c-tabbar-more": "./c-tabbar-more/index"
}
}

View File

@@ -0,0 +1,29 @@
<view class="c-tabbar custom-class">
<scroll-view
wx:if="{{ tabList.length > 0 }}"
class="c-tabbar__scroll"
scroll-x="true"
scroll-into-view="{{ 'id-' + currentActive }}"
>
<view
class="c-tabbar__inner {{showMore && tabList.length > 4 ? 'c-tabbar__inner_more' : ''}}"
>
<view
wx:for="{{ tabList }}"
wx:key="index"
id="{{ 'id-' + index }}"
class="c-tabbar-item {{ currentActive === index ? 'active' : '' }} {{ item.disabled ? 'disabled' : '' }}"
bind:tap="onClick"
data-index="{{index}}"
>
<view class="c-tabbar-item__text"> {{ item.name }} </view>
</view>
</view>
</scroll-view>
<c-tabbar-more
wx:if="{{ showMore && tabList.length > 4 }}"
tabList="{{tabList}}"
bindselect="onClick"
/>
<slot />
</view>

View File

@@ -0,0 +1,53 @@
.c-tabbar {
width: 100%;
height: 100%;
position: relative;
--tabbar-height: 100rpx;
--tabbar-fontsize: 28rpx;
--tabbar-background-color: white;
}
.c-tabbar__inner {
display: flex;
flex-flow: row nowrap;
}
.c-tabbar__scroll {
position: relative;
}
.c-tabbar__scroll::after {
content: '';
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: -1px;
height: 1px;
background-color: #eee;
z-index: 1;
}
.c-tabbar__inner.c-tabbar__inner_more::after {
content: '';
display: block;
width: 100rpx;
height: 100rpx;
flex: none;
}
.c-tabbar-item {
flex: none;
height: 100rpx;
color: #282828;
font-size: 28rpx;
padding: 0 20rpx;
}
.c-tabbar-item.active:not(.disabled) {
color: #0071ce;
position: relative;
}
.c-tabbar-item.disabled {
color: #ccc;
}
.c-tabbar-item__text {
width: 100%;
text-align: center;
height: 100rpx;
line-height: 100rpx;
}

View File

@@ -0,0 +1,75 @@
Component({
externalClasses: ['custom-class'],
properties: {
category: {
type: Array,
},
initActive: {
type: Array,
value: [],
observer(newVal, oldVal) {
if (newVal[0] !== oldVal[0]) {
this.setActiveKey(newVal[0], 0);
}
},
},
isSlotRight: {
type: Boolean,
value: false,
},
level: {
type: Number,
value: 3,
},
},
data: {
activeKey: 0,
subActiveKey: 0,
},
attached() {
if (this.properties.initActive && this.properties.initActive.length > 0) {
this.setData({
activeKey: this.properties.initActive[0],
subActiveKey: this.properties.initActive[1] || 0,
});
}
},
methods: {
onParentChange(event) {
this.setActiveKey(event.detail.index, 0).then(() => {
this.triggerEvent('change', [
this.data.activeKey,
this.data.subActiveKey,
]);
});
},
onChildChange(event) {
this.setActiveKey(this.data.activeKey, event.detail.index).then(() => {
this.triggerEvent('change', [
this.data.activeKey,
this.data.subActiveKey,
]);
});
},
changCategory(event) {
const { item } = event.currentTarget.dataset;
this.triggerEvent('changeCategory', {
item,
});
},
setActiveKey(key, subKey) {
return new Promise((resolve) => {
this.setData(
{
activeKey: key,
subActiveKey: subKey,
},
() => {
resolve();
},
);
});
},
},
});

View File

@@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"c-tabbar": "./components/c-tabbar/index",
"c-sidebar": "./components/c-sidebar/index",
"c-sidebar-item": "./components/c-sidebar/c-sidebar-item/index",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,61 @@
<view class="goods-category custom-class">
<c-sidebar custom-class="custom-sidebar" bindchange="onParentChange" activeKey="{{activeKey}}">
<c-sidebar-item
wx:for="{{ category }}"
wx:key="index"
title="{{ item.name }}"
disabled="{{ item.disabled }}"
/>
</c-sidebar>
<view class="goods-category__right">
<c-tabbar
wx:if="{{isSlotRight}}"
activeKey="{{subActiveKey}}"
bindchange="onChildChange"
showMore
>
<slot/>
</c-tabbar>
<view wx:if="{{!isSlotRight}}" class="goods-category-normal">
<view class="goods-category-normal-item" wx:if="{{category[activeKey].children && category[activeKey].children.length > 0}}">
<block wx:for="{{category[activeKey].children}}" wx:key="index" wx:if="{{level === 3 && item.children && item.children.length > 0}}">
<view class="flex goods-category-normal-item-title">
{{item.name}}
</view>
<view class="goods-category-normal-item-container">
<view
class="goods-category-normal-item-container-item"
wx:for="{{item.children}}"
wx:for-index="subIndex"
wx:key="subIndex"
wx:for-item="subItem"
bindtap="changCategory"
data-item="{{subItem}}"
>
<t-image src="{{subItem.thumbnail}}" t-class="image" />
<view class="flex goods-category-normal-item-container-item-title">
{{subItem.name}}
</view>
</view>
</view>
</block>
<view class="goods-category-normal-item-second-container" wx:if="{{level === 2}}">
<block wx:for="{{category[activeKey].children}}" wx:key="index">
<view
class="goods-category-normal-item-second-container-item"
wx:for-key="index"
bindtap="changCategory"
data-item="{{item}}"
>
<t-image src="{{item.thumbnail}}" t-class="image" />
<view class="flex goods-category-normal-item-container-item-title">
{{item.name}}
</view>
</view>
</block>
</view>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,102 @@
.goods-category {
display: flex;
}
.custom-sidebar {
height: 100%;
}
.goods-category__right {
height: 100%;
flex: auto;
width: 0;
position: relative;
overflow: scroll;
-webkit-overflow-scrolling: touch;
background-color: white;
}
.flex {
display: flex;
}
.goods-category-normal {
margin: 28rpx 34rpx 0rpx 32rpx;
}
.goods-category-normal-item-title {
font-size: 28rpx;
font-weight: 500;
}
.goods-category-normal-item-container {
background-color: #fff;
border-radius: 8rpx;
padding-top: 28rpx;
margin-top: -24rpx;
margin-bottom: 30rpx;
display: flex;
flex-wrap: wrap;
}
.goods-category-normal-item-container-item {
height: 196rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 24rpx;
width: 33.3%;
}
.goods-category-normal-item-container-item .image {
width: 144rpx;
height: 144rpx;
}
.goods-category-normal-item-container-item-title {
justify-content: center;
font-size: 24rpx;
color: #666666;
margin-top: 20rpx;
}
.goods-category .custom-sidebar {
background-color: #f5f5f5;
}
.custom-sidebar {
width: 180rpx;
height: 100vh;
}
.custom-sidebar::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.goods-category-normal-item-second-container {
background-color: #fff;
border-radius: 8rpx;
margin-top: 8rpx;
margin-bottom: 30rpx;
display: grid;
grid-template-columns: 33.33% 33.33% 33.33%;
}
.goods-category-normal-item-second-container-item {
height: 200rpx;
text-align: center;
margin-top: 20rpx;
}
.goods-category-normal-item-second-container-item .image {
width: 144rpx;
height: 144rpx;
}
.goods-category-normal-item-second-container-item-title {
justify-content: center;
font-size: 24rpx;
color: #222427;
}

View File

@@ -0,0 +1,28 @@
import { getCategoryList } from '../../../services/good/fetchCategoryList';
Page({
data: {
list: [],
},
async init() {
try {
const result = await getCategoryList();
this.setData({
list: result,
});
} catch (error) {
console.error('err:', error);
}
},
onShow() {
this.getTabBar().init();
},
onChange() {
wx.navigateTo({
url: '/pages/goods/list/index',
});
},
onLoad() {
this.init(true);
},
});

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "分类",
"usingComponents": {
"goods-category": "./components/goods-category/index"
}
}

View File

@@ -0,0 +1,8 @@
<view class="wrap">
<goods-category
level="{{3}}"
custom-class="goods-category-class"
category="{{list}}"
bind:changeCategory="onChange"
/>
</view>

View File

@@ -0,0 +1,23 @@
.tabbar-position {
position: fixed !important;
bottom: 0;
left: 0;
width: 100%;
}
.wrap {
height: 100vh;
overflow: hidden;
}
.goods-category-class {
background-color: #f6f6f6 !important;
height: 100%;
}
.goods-category-class .goods-category-normal-item-container-item {
margin-top: 20rpx;
}
page {
min-height: none;
padding-bottom: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,36 @@
// pages/goods/comments/components/comments-card/images-videos/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
resources: {
type: Array,
value: [],
},
},
/**
* 组件的初始数据
*/
data: {
classType: 'single',
},
observers: {
resources: function (newVal) {
if (newVal.length <= 1) {
this.setData({ classType: 'single' });
} else if (newVal.length === 2) {
this.setData({ classType: 'double' });
} else {
this.setData({ classType: 'multiple' });
}
},
},
/**
* 组件的方法列表
*/
methods: {},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"my-video": "../my-video/index",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,15 @@
<view class="images-videos-container container-{{classType}}">
<view
class="resource-container resource-container-{{classType}}"
wx:for="{{resources}}"
wx:for-item="resource"
wx:key="*this"
>
<t-image wx:if="{{resource.type === 'image'}}" t-class="resource-item-{{classType}}" src="{{resource.src}}" />
<my-video wx:else videoSrc="{{resource.src}} " my-video="resource-item-{{classType}}">
<t-image t-class="resource-item resource-item-{{classType}}" slot="cover-img" src="{{resource.coverSrc}}" />
<image class="play-icon" slot="play-icon" src="./assets/play.png" />
</my-video>
</view>
</view>

View File

@@ -0,0 +1,68 @@
.resource-item-single {
width: 360rpx;
height: 360rpx;
border-radius: 8rpx;
}
.resource-item-double {
width: 334rpx;
height: 334rpx;
border-radius: 8rpx;
}
.resource-item-multiple {
width: 218rpx;
height: 218rpx;
border-radius: 8rpx;
}
.resource-container-single {
padding-left: 0;
padding-top: 0;
}
.resource-container-double {
padding-left: 18rpx;
padding-top: 18rpx;
}
.resource-container-multiple {
padding-left: 16rpx;
padding-top: 16rpx;
}
.container-single {
margin-left: 0;
}
.container-double {
margin-left: -18rpx;
margin-top: -18rpx;
}
.container-multiple {
margin-left: -16rpx;
margin-top: -16rpx;
}
.resource-container {
display: flex;
}
.play-icon {
width: 96rpx;
height: 96rpx;
}
.images-videos-container {
display: flex;
flex-wrap: wrap;
}
.image {
border-radius: 8rpx;
}
.cover-img-container {
background-color: white;
}

View File

@@ -0,0 +1,55 @@
Component({
externalClasses: ['my-video', 'my-cover-img', 'my-play-icon'],
properties: {
videoSrc: { type: String },
},
data: {
isShow: true,
},
options: {
multipleSlots: true, // 在组件定义时的选项中启用多slot支持
},
attached() {
this.videoContext = wx.createVideoContext('myVideo', this);
},
fullScreen: false,
methods: {
// 点击封面自定义播放按钮时触发
bindplay(e) {
this.setData({
isShow: false,
});
this.videoContext.play();
this.triggerEvent('play', e);
},
bindplayByVideo(e) {
this.setData({
isShow: false,
});
this.triggerEvent('play', e);
},
// 监听播放到末尾时触发
bindended(e) {
if (!this.fullScreen) {
this.setData({
isShow: true,
});
}
this.triggerEvent('ended', e);
},
// 监听暂停播放时触发
bindpause(e) {
this.triggerEvent('pause', e);
},
bindfullscreenchange(e) {
const fullScreen = e?.detail?.fullScreen;
this.fullScreen = fullScreen;
},
},
});

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,26 @@
<video
id="myVideo"
src="{{videoSrc}}"
enable-danmu
controls
show-fullscreen-btn
show-center-play-btn="{{false}}"
auto-pause-if-navigate
auto-pause-if-open-native
show-play-btn
object-fit="contain"
bindpause="bindpause"
bindended="bindended"
bindplay="bindplayByVideo"
class="video my-video"
bindfullscreenchange="bindfullscreenchange"
>
<view class="video_cover" wx:if="{{isShow}}">
<view class="my-cover-img">
<slot name="cover-img" />
</view>
<view class="video_play_icon my-play-icon" bindtap="bindplay">
<slot name="play-icon" />
</view>
</view>
</video>

View File

@@ -0,0 +1,21 @@
.video .video_cover {
width: 100%;
height: 100%;
position: relative;
}
.video .video_play_icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 5;
}
.video .video_txt {
margin: 10rpx auto;
}
.video {
display: flex;
}

View File

@@ -0,0 +1,51 @@
Component({
externalClasses: ['wr-class'],
options: {
multipleSlots: true,
},
properties: {
goodsDetailInfo: {
type: String,
value: '',
},
sellerReply: {
type: String,
value: '',
},
userHeadUrl: {
type: String,
value: '',
},
userName: {
type: String,
default: '',
},
commentContent: {
type: String,
value: '',
},
commentScore: {
type: Number,
value: 0,
},
commentTime: {
type: String,
value: '',
},
commentResources: {
type: Array,
value: [],
},
},
data: {
showMoreStatus: false,
showContent: false,
hideText: false,
eleHeight: null,
overText: false,
isDisabled: true,
startColors: ['#FFC51C', '#DDDDDD'],
},
methods: {},
});

View File

@@ -0,0 +1,8 @@
{
"component": true,
"usingComponents": {
"t-rate": "tdesign-miniprogram/rate/rate",
"images-videos": "./components/images-videos",
"t-image": "/components/webp-image/index"
}
}

View File

@@ -0,0 +1,27 @@
<view class="comments-card-item wr-class">
<view class="comments-card-item-container">
<view class="comments-title">
<view class="comments-card-item-userImg">
<t-image t-class="userImg" src="{{userHeadUrl}}" />
</view>
<view class="userName">{{userName}}</view>
<text class="commentTime">{{commentTime}}</text>
</view>
<view class="comments-info">
<view class="rate">
<t-rate value="{{commentScore}}" size="14" gap="2" color="{{['#ffc51c', '#ddd']}}" />
</view>
<view class="goods-info-text" wx:if="{{goodsDetailInfo}}">{{goodsDetailInfo}}</view>
</view>
<view class="comments-card-item-container-content">
<view class="content-text" hidden="{{showContent}}"> {{commentContent}} </view>
</view>
<view class="comments-card-item-container-image" wx:if="{{commentResources.length > 0}}">
<images-videos resources="{{commentResources}}" />
</view>
<view class="comments-card-reply" wx:if="{{sellerReply}}">
<text class="prefix">店家回复:</text>
<text class="content">{{sellerReply}}</text>
</view>
</view>
</view>

View File

@@ -0,0 +1,172 @@
@import '../../../../../style/theme.wxss';
.comments-card-item {
padding: 32rpx;
display: flex;
background-color: #fff;
position: relative;
}
.comments-card-item::after {
content: '';
position: absolute;
bottom: 0rpx;
width: 686rpx;
height: 2rpx;
background-color: #f5f5f5;
}
.comments-card-item-userImg {
display: flex;
}
.comments-card-item-userImg .userImg {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
}
.comments-card-item-container {
width: 100%;
}
.comments-card-item-container-name {
display: flex;
font-size: 28rpx;
color: #333;
font-weight: 600;
align-items: center;
}
.comments-card-item-container-name .userName {
margin-right: 12rpx;
}
.comments-card-item-container-date {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
display: flex;
}
.comments-card-item-container-content {
margin-top: 16rpx;
position: relative;
}
.comments-card-item-container-content .content-text {
font-size: 28rpx;
white-space: normal;
word-break: break-all;
font-weight: normal;
}
.comments-card-item-container-content .hide-text {
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 5;
text-align: justify;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.comments-card-item-container-content .showMore {
position: absolute;
width: 112rpx;
height: 36rpx;
bottom: 0;
right: 0;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.2) 0,
rgba(255, 255, 255, 0.45) 20%,
rgba(255, 255, 255, 0.7) 25%,
rgba(255, 255, 255, 0.9) 30%,
rgba(255, 255, 255, 0.95) 35%,
#ffffff 50%,
#fff 100%
);
font-size: 26rpx;
color: #fa550f;
line-height: 36rpx;
text-align: right;
}
.comments-card-item-container-image {
margin-top: 24rpx;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.comments-card-item-container-image .commentImg {
border-radius: 8rpx;
margin-top: 12rpx;
}
.comments-card-item-container-image .commentImg3 {
width: 196rpx;
height: 196rpx;
}
.comments-card-item-container-image .commentImg2 {
width: 300rpx;
height: 300rpx;
}
.comments-card-item-container-image .commentImg1 {
width: 404rpx;
height: 404rpx;
}
.comments-card-item-container .comments-title {
display: flex;
align-items: center;
position: relative;
}
.comments-title .userName {
font-size: 26rpx;
color: #333333;
margin-left: 24rpx;
}
.comments-title .commentTime {
font-size: 24rpx;
color: #999999;
position: absolute;
right: 0;
}
.comments-info {
display: flex;
align-items: center;
margin-top: 18rpx;
}
.comments-info .rate {
margin-right: 24rpx;
}
.comments-info .goods-info-text {
font-size: 24rpx;
color: #999999;
}
.comments-card-item-container .comments-card-reply {
background-color: #f5f5f5;
padding: 24rpx 16rpx;
margin-top: 24rpx;
}
.comments-card-item-container .comments-card-reply .prefix {
font-size: 26rpx;
font-weight: bold;
color: #666666;
}
.comments-card-item-container .comments-card-reply .content {
font-size: 26rpx;
color: #666666;
}

View File

@@ -0,0 +1,86 @@
// import { getCommentDetail } from '../../../../services/good/comments/fetchCommentDetail';
import Toast from 'tdesign-miniprogram/toast/index';
Page({
data: {
serviceRateValue: 1,
goodRateValue: 1,
conveyRateValue: 1,
isAnonymous: false,
uploadFiles: [],
gridConfig: {
width: 218,
height: 218,
column: 3,
},
isAllowedSubmit: false,
imgUrl: '',
title: '',
goodsDetail: '',
imageProps: {
mode: 'aspectFit',
},
},
onLoad(options) {
this.setData({
imgUrl: options.imgUrl,
title: options.title,
goodsDetail: options.specs,
});
},
onRateChange(e) {
const { value } = e?.detail;
const item = e?.currentTarget?.dataset?.item;
this.setData({ [item]: value }, () => {
this.updateButtonStatus();
});
},
onAnonymousChange(e) {
const status = !!e?.detail?.checked;
this.setData({ isAnonymous: status });
},
handleSuccess(e) {
const { files } = e.detail;
this.setData({
uploadFiles: files,
});
},
handleRemove(e) {
const { index } = e.detail;
const { uploadFiles } = this.data;
uploadFiles.splice(index, 1);
this.setData({
uploadFiles,
});
},
onTextAreaChange(e) {
const value = e?.detail?.value;
this.textAreaValue = value;
this.updateButtonStatus();
},
updateButtonStatus() {
const { serviceRateValue, goodRateValue, conveyRateValue, isAllowedSubmit } = this.data;
const { textAreaValue } = this;
const temp = serviceRateValue && goodRateValue && conveyRateValue && textAreaValue;
if (temp !== isAllowedSubmit) this.setData({ isAllowedSubmit: temp });
},
onSubmitBtnClick() {
const { isAllowedSubmit } = this.data;
if (!isAllowedSubmit) return;
Toast({
context: this,
selector: '#t-toast',
message: '评价提交成功',
icon: 'check-circle',
});
wx.navigateBack();
},
});

View File

@@ -0,0 +1,13 @@
{
"navigationBarTitleText": "评价商品",
"usingComponents": {
"t-image": "/components/webp-image/index",
"t-rate": "tdesign-miniprogram/rate/rate",
"t-textarea": "tdesign-miniprogram/textarea/textarea",
"t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
"t-button": "tdesign-miniprogram/button/button",
"t-upload": "tdesign-miniprogram/upload/upload",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}

View File

@@ -0,0 +1,90 @@
<view class="page-container">
<view class="comment-card">
<view class="goods-info-container">
<view class="goods-image-container">
<t-image t-class="goods-image" src="{{imgUrl}}" />
</view>
<view class="goods-title-container">
<view class="goods-title">{{title}}</view>
<view class="goods-detail">{{goodsDetail}}</view>
</view>
</view>
<view class="rate-container">
<text class="rate-title">商品评价</text>
<view class="rate">
<t-rate
value="{{goodRateValue}}"
bind:change="onRateChange"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="goodRateValue"
/>
</view>
</view>
<view class="textarea-container">
<t-textarea
t-class="textarea"
maxlength="{{500}}"
indicator
placeholder="对商品满意吗?评论一下"
bind:change="onTextAreaChange"
/>
</view>
<view class="upload-container">
<t-upload
media-type="{{['image','video']}}"
files="{{uploadFiles}}"
bind:remove="handleRemove"
bind:success="handleSuccess"
gridConfig="{{gridConfig}}"
imageProps="{{imageProps}}"
/>
</view>
<view class="anonymous-box">
<t-checkbox bind:change="onAnonymousChange" checked="{{isAnonymous}}" color="#FA4126" />
<view class="name">匿名评价</view>
</view>
</view>
</view>
<view class="comment-card convey-card">
<view class="convey-comment-title">物流服务评价</view>
<view class="rate-container">
<text class="rate-title">物流评价</text>
<view class="rate">
<t-rate
value="{{conveyRateValue}}"
bind:change="onRateChange"
variant="filled"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="conveyRateValue"
/>
</view>
</view>
<view class="rate-container">
<text class="rate-title">服务评价</text>
<view class="rate">
<t-rate
value="{{serviceRateValue}}"
bind:change="onRateChange"
size="26"
gap="6"
color="{{['#ffc51c', '#ddd']}}"
data-item="serviceRateValue"
/>
</view>
</view>
</view>
<view class="submit-button-container">
<t-button
content="提交"
block
shape="round"
t-class="submit-button{{isAllowedSubmit ? '' : '-disabled'}}"
bind:tap="onSubmitBtnClick"
/>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,168 @@
page {
background-color: #f5f5f5;
}
.page-container .comment-card {
padding: 24rpx 32rpx 28rpx;
background-color: #ffffff;
}
.comment-card .goods-info-container .goods-image {
width: 112rpx;
height: 112rpx;
border-radius: 8rpx;
}
.comment-card .goods-info-container {
display: flex;
align-items: center;
}
.comment-card .goods-info-container .goods-title-container {
padding-left: 24rpx;
}
.comment-card .goods-info-container .goods-title {
font-size: 28rpx;
font-weight: normal;
}
.comment-card .goods-info-container .goods-detail {
font-size: 24rpx;
font-weight: normal;
color: #999999;
margin-top: 16rpx;
}
.comment-card .rate-container {
display: flex;
align-items: center;
margin-top: 22rpx;
}
.comment-card .rate-container .rate-title {
font-size: 28rpx;
font-weight: bold;
margin-right: 12rpx;
}
.comment-card .textarea-container {
margin-top: 22rpx;
}
.comment-card .textarea-container .textarea {
height: 294rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
font-size: 28rpx;
font-weight: normal;
}
.page-container .t-checkbox__bordered {
display: none;
}
.page-container .anonymous-box {
display: flex;
align-items: center;
padding-top: 52rpx;
}
.page-container .anonymous-box .name {
font-size: 28rpx;
font-weight: normal;
color: #999999;
padding-left: 28rpx;
}
.page-container .t-checkbox {
padding: 0rpx !important;
}
.page-container .t-checkbox__content {
display: none;
}
.comment-card .convey-comment-title {
font-size: 28rpx;
font-weight: bold;
}
.convey-card {
background-color: #ffffff;
margin-top: 24rpx;
padding: 32rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
.convey-card .rate-container .rate-title {
font-weight: normal;
}
.page-container .t-checkbox__icon-left {
margin-right: 0rpx !important;
}
.submit-button-container {
padding: 12rpx 32rpx;
display: flex;
width: 100vw;
box-sizing: border-box;
justify-content: center;
position: fixed;
bottom: 0;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
background-color: #ffffff;
z-index: 99;
}
.submit-button-container .submit-button {
--td-button-default-color: #fff;
--td-button-default-bg-color: #fa4126;
--td-button-default-border-color: #fa4126;
--td-button-default-active-bg-color: #fa42269c;
}
.submit-button-container .submit-button-disabled {
--td-button-default-color: #fff;
--td-button-default-bg-color: #ccc;
--td-button-default-border-color: #ccc;
--td-button-default-active-bg-color: rgba(204, 204, 204, 0.789);
}
.page-container .upload-container {
margin-top: 24rpx;
}
.page-container .t-upload__wrapper {
border-radius: 8rpx;
overflow: hidden;
}
.page-container .submmit-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 12;
padding: 12rpx 32rpx;
padding-bottom: env(safe-area-inset-bottom);
background-color: #fff;
height: 112rpx;
}
.page-container .submmit-bar-button {
border-radius: 48rpx !important;
padding: 0 !important;
}
.page-container .t-upload__close-btn {
background-color: rgba(0, 0, 0, 0.4);
border-bottom-left-radius: 8rpx;
width: 36rpx;
height: 36rpx;
}
.upload-container .upload-addcontent-slot {
font-size: 26rpx;
}

View File

@@ -0,0 +1,227 @@
import { fetchComments } from '../../../services/comments/fetchComments';
import { fetchCommentsCount } from '../../../services/comments/fetchCommentsCount';
import dayjs from 'dayjs';
const layoutMap = {
0: 'vertical',
};
Page({
data: {
pageLoading: false,
commentList: [],
pageNum: 1,
myPageNum: 1,
pageSize: 10,
total: 0,
myTotal: 0,
hasLoaded: false,
layoutText: layoutMap[0],
loadMoreStatus: 0,
myLoadStatus: 0,
spuId: '1060004',
commentLevel: '',
hasImage: '',
commentType: '',
totalCount: 0,
countObj: {
badCount: '0',
commentCount: '0',
goodCount: '0',
middleCount: '0',
hasImageCount: '0',
uidCount: '0',
},
},
onLoad(options) {
this.getCount(options);
this.getComments(options);
},
async getCount(options) {
try {
const result = await fetchCommentsCount(
{
spuId: options.spuId,
},
{
method: 'POST',
},
);
this.setData({
countObj: result,
});
// const { data, code = '' } = result;
// if (code.toUpperCase() === 'SUCCESS') {
// wx.setNavigationBarTitle({
// title: `全部评价(${data.commentCount})`,
// });
// this.setData({
// countObj: data,
// });
// } else {
// wx.showToast({
// title: '查询失败,请稍候重试',
// });
// }
} catch (error) {}
},
generalQueryData(reset) {
const { hasImage, pageNum, pageSize, spuId, commentLevel } = this.data;
const params = {
pageNum: 1,
pageSize: 30,
queryParameter: {
spuId,
},
};
if (
Number(commentLevel) === 3 ||
Number(commentLevel) === 2 ||
Number(commentLevel) === 1
) {
params.queryParameter.commentLevel = Number(commentLevel);
}
if (hasImage && hasImage === '1') {
params.queryParameter.hasImage = true;
} else {
delete params.queryParameter.hasImage;
}
// 重置请求
if (reset) return params;
return {
...params,
pageNum: pageNum + 1,
pageSize,
};
},
async init(reset = true) {
const { loadMoreStatus, commentList = [] } = this.data;
const params = this.generalQueryData(reset);
// 在加载中或者无更多数据,直接返回
if (loadMoreStatus !== 0) return;
this.setData({
loadMoreStatus: 1,
});
try {
const data = await fetchComments(params, {
method: 'POST',
});
const code = 'SUCCESS';
if (code.toUpperCase() === 'SUCCESS') {
const { pageList, totalCount = 0 } = data;
pageList.forEach((item) => {
// eslint-disable-next-line no-param-reassign
item.commentTime = dayjs(Number(item.commentTime)).format(
'YYYY/MM/DD HH:mm',
);
});
if (Number(totalCount) === 0 && reset) {
this.setData({
commentList: [],
hasLoaded: true,
total: totalCount,
loadMoreStatus: 2,
});
return;
}
const _commentList = reset ? pageList : commentList.concat(pageList);
const _loadMoreStatus =
_commentList.length === Number(totalCount) ? 2 : 0;
this.setData({
commentList: _commentList,
pageNum: params.pageNum || 1,
totalCount: Number(totalCount),
loadMoreStatus: _loadMoreStatus,
});
} else {
wx.showToast({
title: '查询失败,请稍候重试',
});
}
} catch (error) {}
this.setData({
hasLoaded: true,
});
},
getScoreArray(score) {
var array = [];
for (let i = 0; i < 5; i++) {
if (i < score) {
array.push(2);
} else {
array.push(0);
}
}
return array;
},
getComments(options) {
const { commentLevel = -1, spuId, hasImage = '' } = options;
if (commentLevel !== -1) {
this.setData({
commentLevel: commentLevel,
});
}
this.setData({
hasImage: hasImage,
commentType: hasImage ? '4' : '',
spuId: spuId,
});
this.init(true);
},
changeTag(e) {
var { commenttype } = e.currentTarget.dataset;
var { commentType } = this.data;
if (commentType === commenttype) return;
this.setData({
loadMoreStatus: 0,
commentList: [],
total: 0,
myTotal: 0,
myPageNum: 1,
pageNum: 1,
});
if (commenttype === '' || commenttype === '5') {
this.setData({
hasImage: '',
commentLevel: '',
});
} else if (commenttype === '4') {
this.setData({
hasImage: '1',
commentLevel: '',
});
} else {
this.setData({
hasImage: '',
commentLevel: commenttype,
});
}
if (commenttype === '5') {
this.setData({
myLoadStatus: 1,
commentType: commenttype,
});
this.getMyCommentsList();
} else {
this.setData({
myLoadStatus: 0,
commentType: commenttype,
});
this.init(true);
}
},
onReachBottom() {
const { total = 0, commentList } = this.data;
if (commentList.length === total) {
this.setData({
loadMoreStatus: 2,
});
return;
}
this.init(false);
},
});

View File

@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "全部评价",
"usingComponents": {
"t-tag": "tdesign-miniprogram/tag/tag",
"comments-card": "./components/comments-card/index",
"t-load-more": "/components/load-more/index"
}
}

View File

@@ -0,0 +1,50 @@
<view class="comments-header">
<t-tag t-class="comments-header-tag {{commentType === '' ? 'comments-header-active' : ''}}" data-commentType="" bindtap="changeTag">
全部({{countObj.commentCount}})
</t-tag>
<t-tag
t-class="comments-header-tag {{commentType === '5' ? 'comments-header-active' : ''}}"
wx:if="{{countObj.uidCount !== '0'}}"
data-commentType="5"
bindtap="changeTag"
>
自己({{countObj.uidCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '4' ? 'comments-header-active' : ''}}" data-commentType="4" bindtap="changeTag">
带图({{countObj.hasImageCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '3' ? 'comments-header-active' : ''}}" data-commentType="3" bindtap="changeTag">
好评({{countObj.goodCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '2' ? 'comments-header-active' : ''}}" data-commentType="2" bindtap="changeTag">
中评({{countObj.middleCount}})
</t-tag>
<t-tag t-class="comments-header-tag {{commentType === '1' ? 'comments-header-active' : ''}}" data-commentType="1" bindtap="changeTag">
差评({{countObj.badCount}})
</t-tag>
</view>
<view class="comments-card-list">
<block wx:for="{{commentList}}" wx:key="index">
<comments-card
commentScore="{{item.commentScore}}"
userName="{{item.userName}}"
commentResources="{{item.commentResources || []}}"
commentContent="{{item.commentContent}}"
isAnonymity="{{item.isAnonymity}}"
commentTime="{{item.commentTime}}"
isAutoComment="{{item.isAutoComment}}"
userHeadUrl="{{item.userHeadUrl}}"
specInfo="{{item.specInfo}}"
sellerReply="{{item.sellerReply || ''}}"
goodsDetailInfo="{{item.goodsDetailInfo || ''}}"
/>
</block>
<t-load-more
t-class="no-more"
status="{{loadMoreStatus}}"
no-more-text="没有更多了"
color="#BBBBBB"
failedColor="#FA550F"
/>
</view>

View File

@@ -0,0 +1,49 @@
/* 层级定义
@z-index-0: 1;
@z-index-1: 100;
@z-index-2: 200;
@z-index-5: 500;
@z-index-component: 1000; // 通用组件级别
@z-index-dropdown: @z-index-component;
@z-index-sticky: @z-index-component + 20;
@z-index-fixed: @z-index-component + 30;
@z-index-modal-backdrop:@z-index-component + 40;
@z-index-modal:@z-index-component + 50;
@z-index-popover:@z-index-component + 60;
@z-index-tooltip:@z-index-component + 70;
*/
/* var() css变量适配*/
page {
background-color: #FFFFFF;
}
.comments-header {
display: flex;
flex-wrap: wrap;
padding: 32rpx 32rpx 0rpx;
background-color: #fff;
margin-top: -24rpx;
margin-left: -24rpx;
}
.comments-header-tag {
margin-top: 24rpx;
margin-left: 24rpx;
height: 56rpx !important;
font-size: 24rpx !important;
justify-content: center;
background-color: #F5F5F5 !important;
border-radius: 8rpx !important;
border: 1px solid #F5F5F5 !important;
}
.comments-header-active {
background-color: #FFECE9 !important;
color: #FA4126 !important;
border: 1px solid #FA4126 !important;
}
.no-more {
padding-left: 20rpx;
padding-right: 20rpx;
}

View File

@@ -0,0 +1,66 @@
Component({
externalClasses: ['wr-sold-out', 'wr-class'],
options: { multipleSlots: true },
properties: {
soldout: {
// 商品是否下架
type: Boolean,
value: false,
},
jumpArray: {
type: Array,
value: [],
},
isStock: {
type: Boolean,
value: true,
}, // 是否有库存
isSlotButton: {
type: Boolean,
value: false,
}, // 是否开启按钮插槽
shopCartNum: {
type: Number, // 购物车气泡数量
},
buttonType: {
type: Number,
value: 0,
},
minDiscountPrice: {
type: String,
value: '',
},
minSalePrice: {
type: String,
value: '',
},
},
data: {
fillPrice: false,
},
methods: {
toAddCart() {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toAddCart');
},
toBuyNow(e) {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('toBuyNow', e);
},
toNav(e) {
const { url } = e.currentTarget.dataset;
return this.triggerEvent('toNav', {
e,
url,
});
},
},
});

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,38 @@
<view class="flex soldout flex-center wr-sold-out" wx:if="{{soldout || !isStock}}">
{{soldout ? '商品已下架' : '商品已售馨'}}
</view>
<view class="footer-cont flex flex-between wr-class">
<view class="flex flex-between bottom-operate-left" wx:if="{{jumpArray.length > 0}}">
<view
wx:for="{{jumpArray}}"
wx:key="index"
class="icon-warp operate-wrap"
bindtap="toNav"
data-ele="foot_navigation"
data-index="{{index}}"
data-url="{{item.url}}"
>
<view>
<text wx:if="{{shopCartNum > 0 && item.showCartNum}}" class="tag-cart-num">
{{shopCartNum > 99 ? '99+' : shopCartNum}}
</text>
<t-icon prefix="wr" name="{{item.iconName}}" size="40rpx" />
<view class="operate-text">{{item.title}}</view>
</view>
</view>
</view>
<block wx:if="{{buttonType === 1}}">
<view class="flex buy-buttons">
<view class="bar-separately {{soldout || !isStock ? 'bar-addCart-disabled' : ''}}" bindtap="toAddCart">
加入购物车
</view>
<view class="bar-buy {{soldout || !isStock ? 'bar-buyNow-disabled' : ''}}" bindtap="toBuyNow">
立即购买
</view>
</view>
</block>
<block wx:if="{{isSlotButton}}">
<slot name="buyButton" />
</block>
</view>

View File

@@ -0,0 +1,107 @@
.footer-cont {
background-color: #fff;
padding: 16rpx;
}
.icon-warp {
width: 110rpx;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.operate-wrap {
position: relative;
}
.bottom-operate-left {
width: 100%;
}
.bottom-operate-left .icon-warp {
width: 50%;
}
.tag-cart-num {
display: inline-block;
position: absolute;
left: 50rpx;
right: auto;
top: 6rpx;
color: #fff;
line-height: 24rpx;
text-align: center;
z-index: 99;
white-space: nowrap;
min-width: 28rpx;
border-radius: 14rpx;
background-color: #fa550f !important;
font-size: 20rpx;
font-weight: 400;
padding: 2rpx 6rpx;
}
.operate-text {
color: #666;
font-size: 20rpx;
}
.soldout {
height: 80rpx;
background: rgba(170, 170, 170, 1);
width: 100%;
color: #fff;
}
.addCart-disabled,
.bar-addCart-disabled {
background: rgba(221, 221, 221, 1) !important;
color: #fff !important;
font-size: 28rpx;
}
.buyNow-disabled,
.bar-buyNow-disabled {
background: rgba(198, 198, 198, 1) !important;
color: #fff !important;
font-size: 28rpx;
}
.bar-separately,
.bar-buy {
width: 254rpx;
height: 80rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.bar-separately {
background: #ffece9;
color: #fa4126;
border-radius: 40rpx 0 0 40rpx;
}
.bar-buy {
background-color: #fa4126;
border-radius: 0rpx 40rpx 40rpx 0rpx;
}
.flex {
display: flex;
display: -webkit-flex;
}
.flex-center {
justify-content: center;
-webkit-justify-content: center;
align-items: center;
-webkit-align-items: center;
}
.flex-between {
justify-content: space-between;
-webkit-justify-content: space-between;
}

View File

@@ -0,0 +1,339 @@
/* eslint-disable no-param-reassign */
/* eslint-disable no-nested-ternary */
import Toast from 'tdesign-miniprogram/toast/index';
Component({
options: {
multipleSlots: true,
addGlobalClass: true,
},
properties: {
src: {
type: String,
},
title: String,
show: {
type: Boolean,
value: false,
},
limitBuyInfo: {
type: String,
value: '',
},
isStock: {
type: Boolean,
value: true,
},
limitMaxCount: {
type: Number,
value: 999,
},
limitMinCount: {
type: Number,
value: 1,
},
skuList: {
type: Array,
value: [],
observer(skuList) {
if (skuList && skuList.length > 0) {
if (this.initStatus) {
this.initData();
}
}
},
},
specList: {
type: Array,
value: [],
observer(specList) {
if (specList && specList.length > 0) {
this.initData();
}
},
},
outOperateStatus: {
type: Boolean,
value: false,
},
hasAuth: {
type: Boolean,
value: false,
},
count: {
type: Number,
value: 1,
observer(count) {
this.setData({
buyNum: count,
});
},
},
},
initStatus: false,
selectedSku: {},
selectSpecObj: {},
data: {
buyNum: 1,
isAllSelectedSku: false,
},
methods: {
initData() {
const { skuList } = this.properties;
const { specList } = this.properties;
specList.forEach((item) => {
if (item.specValueList.length > 0) {
item.specValueList.forEach((subItem) => {
const obj = this.checkSkuStockQuantity(subItem.specValueId, skuList);
subItem.hasStockObj = obj;
});
}
});
const selectedSku = {};
specList.forEach((item) => {
selectedSku[item.specId] = '';
});
this.setData({
specList,
});
this.selectSpecObj = {};
this.selectedSku = {};
this.initStatus = true;
},
checkSkuStockQuantity(specValueId, skuList) {
let hasStock = false;
const array = [];
skuList.forEach((item) => {
(item.specInfo || []).forEach((subItem) => {
if (subItem.specValueId === specValueId && item.quantity > 0) {
const subArray = [];
(item.specInfo || []).forEach((specItem) => {
subArray.push(specItem.specValueId);
});
array.push(subArray);
hasStock = true;
}
});
});
return {
hasStock,
specsArray: array,
};
},
chooseSpecValueId(specValueId, specId) {
const { selectSpecObj } = this;
const { skuList, specList } = this.properties;
if (selectSpecObj[specId]) {
selectSpecObj[specId] = [];
this.selectSpecObj = selectSpecObj;
} else {
selectSpecObj[specId] = [];
}
const itemAllSpecArray = [];
const itemUnSelectArray = [];
const itemSelectArray = [];
specList.forEach((item) => {
if (item.specId === specId) {
const subSpecValueItem = item.specValueList.find((subItem) => subItem.specValueId === specValueId);
let specSelectStatus = false;
item.specValueList.forEach((n) => {
itemAllSpecArray.push(n.hasStockObj.specsArray);
if (n.isSelected) {
specSelectStatus = true;
}
if (n.hasStockObj.hasStock) {
itemSelectArray.push(n.specValueId);
} else {
itemUnSelectArray.push(n.specValueId);
}
});
if (specSelectStatus) {
selectSpecObj[specId] = this.flatten(subSpecValueItem?.hasStockObj.specsArray.concat(itemSelectArray));
} else {
const subSet = function (arr1, arr2) {
const set2 = new Set(arr2);
const subset = [];
arr1.forEach((val) => {
if (!set2.has(val)) {
subset.push(val);
}
});
return subset;
};
selectSpecObj[specId] = subSet(this.flatten(itemAllSpecArray), this.flatten(itemUnSelectArray));
}
} else {
// 未点击规格的逻辑
const itemSelectArray = [];
let specSelectStatus = false;
item.specValueList.map(
// 找到有库存的规格数组
(n) => {
itemSelectArray.push(n.hasStockObj.specsArray);
if (n.isSelected) {
specSelectStatus = true;
}
n.hasStockObj.hasStock = true;
return n;
},
);
if (specSelectStatus) {
selectSpecObj[item.specId] = this.flatten(itemSelectArray);
} else {
delete selectSpecObj[item.specId];
}
}
this.selectSpecObj = selectSpecObj;
});
const combatArray = Object.values(selectSpecObj);
if (combatArray.length > 0) {
const showArray = combatArray.reduce((x, y) => this.getIntersection(x, y));
const lastResult = Array.from(new Set(showArray));
specList.forEach((item) => {
item.specValueList.forEach((subItem) => {
if (lastResult.includes(subItem.specValueId)) {
subItem.hasStockObj.hasStock = true;
} else {
subItem.hasStockObj.hasStock = false;
}
});
});
} else {
specList.forEach((item) => {
if (item.specValueList.length > 0) {
item.specValueList.forEach((subItem) => {
const obj = this.checkSkuStockQuantity(subItem.specValueId, skuList);
subItem.hasStockObj = obj;
});
}
});
}
this.setData({
specList,
});
},
flatten(input) {
const stack = [...input];
const res = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
res.push(next);
}
}
return res.reverse();
},
getIntersection(array, nextArray) {
return array.filter((item) => nextArray.includes(item));
},
toChooseItem(e) {
const { isStock } = this.properties;
if (!isStock) return;
const { id } = e.currentTarget.dataset;
const specId = e.currentTarget.dataset.specid;
const hasStock = e.currentTarget.dataset.hasstock;
if (!hasStock) {
Toast({
context: this,
selector: '#t-toast',
message: '该规格已售罄',
icon: '',
duration: 1000,
});
return;
}
let { selectedSku } = this;
const { specList } = this.properties;
selectedSku =
selectedSku[specId] === id ? { ...this.selectedSku, [specId]: '' } : { ...this.selectedSku, [specId]: id };
specList.forEach((item) => {
item.specValueList.forEach((valuesItem) => {
if (item.specId === specId) {
valuesItem.isSelected = valuesItem.specValueId === selectedSku[specId];
}
});
});
this.chooseSpecValueId(id, specId);
const isAllSelectedSku = this.isAllSelected(specList, selectedSku);
if (!isAllSelectedSku) {
this.setData({
selectSkuSellsPrice: 0,
selectSkuImg: '',
});
}
this.setData({
specList,
isAllSelectedSku,
});
this.selectedSku = selectedSku;
this.triggerEvent('change', {
specList,
selectedSku,
isAllSelectedSku,
});
},
// 判断是否所有的sku都已经选中
isAllSelected(skuTree, selectedSku) {
const selected = Object.keys(selectedSku).filter((skuKeyStr) => selectedSku[skuKeyStr] !== '');
return skuTree.length === selected.length;
},
handlePopupHide() {
this.triggerEvent('closeSpecsPopup', {
show: false,
});
},
specsConfirm() {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('specsConfirm');
},
addCart() {
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('addCart');
},
buyNow() {
const { isAllSelectedSku } = this.data;
const { isStock } = this.properties;
if (!isStock) return;
this.triggerEvent('buyNow', {
isAllSelectedSku,
});
},
// 总处理
setBuyNum(buyNum) {
this.setData({
buyNum,
});
this.triggerEvent('changeNum', {
buyNum,
});
},
handleBuyNumChange(e) {
const { value } = e.detail;
this.setData({
buyNum: value,
});
},
},
});

View File

@@ -0,0 +1,10 @@
{
"component": true,
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "/components/webp-image/index",
"t-stepper": "tdesign-miniprogram/stepper/stepper",
"t-toast": "tdesign-miniprogram/toast/toast"
}
}

View File

@@ -0,0 +1,84 @@
<t-popup visible="{{show}}" placement="bottom" bind:visible-change="handlePopupHide">
<view class="popup-container">
<view class="popup-close" bindtap="handlePopupHide">
<t-icon name="close" size="36rpx" />
</view>
<view class="popup-sku-header">
<t-image t-class="popup-sku-header__img" src="{{src}}" />
<view class="popup-sku-header__goods-info">
<view class="popup-sku__goods-name">{{title}}</view>
<view class="goods-price-container">
<slot name="goods-price" />
</view>
<!-- 已选规格 -->
<view class="popup-sku__selected-spec">
<view>选择:</view>
<view wx:for="{{specList}}" wx:key="specId">
<view
class="popup-sku__selected-item"
wx:for="{{item.specValueList}}"
wx:for-item="selectedItem"
wx:if="{{selectedItem.isSelected}}"
wx:key="specValueId"
>
{{selectedItem.specValue}}
</view>
</view>
</view>
</view>
</view>
<view class="popup-sku-body">
<view class="popup-sku-group-container">
<view class="popup-sku-row" wx:for="{{specList}}" wx:key="specId">
<view class="popup-sku-row__title">{{item.title}}</view>
<block
wx:for="{{item.specValueList}}"
wx:for-item="valuesItem"
wx:for-index="valuesIndex"
wx:key="specValueId"
>
<view
class="popup-sku-row__item {{valuesItem.isSelected ? 'popup-sku-row__item--active' : ''}} {{!valuesItem.hasStockObj.hasStock || !isStock ? 'disabled-sku-selected' : ''}}"
data-specid="{{item.specId}}"
data-id="{{valuesItem.specValueId}}"
data-val="{{valuesItem.specValue}}"
data-hasStock="{{valuesItem.hasStockObj.hasStock}}"
bindtap="toChooseItem"
>
{{valuesItem.specValue}}
</view>
</block>
</view>
</view>
<view class="popup-sku-stepper-stock">
<view class="popup-sku-stepper-container">
<view class="popup-sku__stepper-title">
购买数量
<view class="limit-text" wx:if="{{limitBuyInfo}}"> ({{limitBuyInfo}}) </view>
</view>
<t-stepper value="{{buyNum}}" min="{{1}}" max="{{2}}" theme="filled" bind:change="handleBuyNumChange" />
</view>
</view>
</view>
<view wx:if="{{outOperateStatus}}" class="single-confirm-btn {{!isStock ? 'disabled' : ''}}" bindtap="specsConfirm">
确定
</view>
<view
class="popup-sku-actions flex flex-between {{!isStock ? 'popup-sku-disabled' : ''}}"
wx:if="{{!outOperateStatus}}"
>
<view class="sku-operate">
<view class="selected-sku-btn sku-operate-addCart {{!isStock ? 'disabled' : ''}}" bindtap="addCart">
加入购物车
</view>
</view>
<view class="sku-operate">
<view class="selected-sku-btn sku-operate-buyNow {{!isStock ? 'disabled' : ''}}" bindtap="buyNow">
立即购买
</view>
</view>
</view>
<slot name="bottomSlot" />
</view>
</t-popup>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,300 @@
.popup-container {
background-color: #ffffff;
position: relative;
z-index: 100;
border-radius: 16rpx 16rpx 0 0;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
.popup-container .popup-close {
position: absolute;
right: 30rpx;
top: 30rpx;
z-index: 9;
color: #999999;
}
.popup-sku-header {
display: flex;
padding: 30rpx 28rpx 0 30rpx;
}
.popup-sku-header .popup-sku-header__img {
width: 176rpx;
height: 176rpx;
border-radius: 8rpx;
background: #d8d8d8;
margin-right: 24rpx;
}
.popup-sku-header .popup-sku-header__goods-info {
position: relative;
width: 500rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__goods-name {
font-size: 28rpx;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: normal;
overflow: hidden;
width: 430rpx;
text-overflow: ellipsis;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__selected-spec {
display: flex;
color: #333333;
font-size: 26rpx;
line-height: 36rpx;
}
.popup-sku-header
.popup-sku-header__goods-info
.popup-sku__selected-spec
.popup-sku__selected-item {
margin-right: 10rpx;
}
.popup-sku-body {
margin: 0 30rpx 40rpx;
max-height: 600rpx;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.popup-sku-body .popup-sku-group-container .popup-sku-row {
padding: 32rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.popup-sku-row__title {
font-size: 26rpx;
color: #333;
}
.popup-sku-body .popup-sku-group-container .popup-sku-row .popup-sku-row__item {
font-size: 24rpx;
color: #333;
min-width: 128rpx;
height: 56rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
border: 2rpx solid #f5f5f5;
margin: 19rpx 26rpx 0 0;
padding: 0 16rpx;
display: inline-flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.popup-sku-row__item.popup-sku-row__item--active {
border: 2rpx solid #fa4126;
color: #fa4126;
background: rgba(255, 95, 21, 0.04);
}
.popup-sku-body
.popup-sku-group-container
.popup-sku-row
.disabled-sku-selected {
background: #f5f5f5 !important;
color: #cccccc;
}
.popup-sku-body .popup-sku-stepper-stock .popup-sku-stepper-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40rpx 0;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-sku__stepper-title {
display: flex;
font-size: 26rpx;
color: #333;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-sku__stepper-title
.limit-text {
margin-left: 10rpx;
color: #999999;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper {
display: flex;
flex-flow: row nowrap;
align-items: center;
font-size: 28px;
height: 48rpx;
line-height: 62rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-btn,
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap {
position: relative;
height: 100%;
text-align: center;
background-color: #f5f5f5;
border-radius: 4rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap {
color: #282828;
display: flex;
max-width: 76rpx;
align-items: center;
justify-content: space-between;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-num-wrap
.input-num {
height: 100%;
width: auto;
font-weight: 600;
font-size: 30rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.input-btn {
width: 48rpx;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__minus {
margin-right: 4rpx;
border-radius: 4rpx;
color: #9a979b;
display: flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus {
margin-left: 4rpx;
border-radius: 4rpx;
color: #9a979b;
display: flex;
align-items: center;
justify-content: center;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus::after {
width: 24rpx;
height: 3rpx;
background-color: #999999;
}
.popup-sku-body
.popup-sku-stepper-stock
.popup-sku-stepper-container
.popup-stepper
.popup-stepper__plus::before {
width: 3rpx;
height: 24rpx;
background-color: #999999;
}
.popup-sku-actions {
font-size: 32rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
padding: 0 20rpx;
}
.popup-sku-actions .sku-operate {
height: 80rpx;
width: 50%;
color: #fff;
border-radius: 48rpx;
}
.popup-sku-actions .sku-operate .sku-operate-addCart {
background-color: #ffece9;
color: #fa4126;
border-radius: 48rpx 0 0 48rpx;
}
.popup-sku-actions .sku-operate .sku-operate-addCart.disabled {
background: rgb(221, 221, 221);
color: #fff;
}
.popup-sku-actions .sku-operate .sku-operate-buyNow {
background-color: #fa4126;
border-radius: 0 48rpx 48rpx 0;
}
.popup-sku-actions .sku-operate .sku-operate-buyNow.disabled {
color: #fff;
background: rgb(198, 198, 198);
}
.popup-sku-actions .sku-operate .selected-sku-btn {
width: 100%;
}
.popup-container .single-confirm-btn {
border-radius: 48rpx;
color: #ffffff;
margin: 0 32rpx;
font-size: 32rpx;
height: 80rpx;
text-align: center;
line-height: 88rpx;
background-color: #fa4126;
}
.popup-container .single-confirm-btn.disabled {
font-size: 32rpx;
color: #fff;
background-color: #dddddd;
}

View File

@@ -0,0 +1,35 @@
Component({
options: {
multipleSlots: true,
},
properties: {
list: Array,
title: {
type: String,
value: '促销说明',
},
show: {
type: Boolean,
},
},
// data: {
// list: [],
// },
methods: {
change(e) {
const { index } = e.currentTarget.dataset;
this.triggerEvent('promotionChange', {
index,
});
},
closePromotionPopup() {
this.triggerEvent('closePromotionPopup', {
show: false,
});
},
},
});

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,34 @@
<t-popup visible="{{show}}" placement="bottom" bind:visible-change="closePromotionPopup">
<view class="promotion-popup-container">
<view class="promotion-popup-close" bindtap="closePromotionPopup">
<t-icon name="close" size="36rpx" />
</view>
<view class="promotion-popup-title">
<view class="title">{{title}}</view>
</view>
<view class="promotion-popup-content">
<view class="promotion-detail-list">
<view
class="list-item"
wx:for="{{list}}"
wx:key="index"
bindtap="change"
data-index="{{index}}"
>
<view class="tag">{{item.tag}}</view>
<view class="content">
<text class="list-content">{{item.label ? item.label : ''}}</text>
</view>
<t-icon
class="collect-btn"
name="chevron-right"
size="40rpx"
color="#bbb"
/>
</view>
</view>
</view>
<slot name="promotion-bottom" />
</view>
</t-popup>

View File

@@ -0,0 +1,131 @@
.promotion-popup-container {
background-color: #ffffff;
position: relative;
z-index: 100;
border-radius: 16rpx 16rpx 0 0;
}
.promotion-popup-container .promotion-popup-close {
position: absolute;
right: 30rpx;
top: 30rpx;
z-index: 9;
color: rgba(153, 153, 153, 1);
}
.promotion-popup-container .promotion-popup-close .market {
font-size: 25rpx;
color: #999;
}
.promotion-popup-container .promotion-popup-title {
height: 100rpx;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.promotion-popup-container .promotion-popup-title {
font-size: 32rpx;
color: #222427;
font-weight: 600;
}
.promotion-popup-container .promotion-popup-content {
min-height: 400rpx;
max-height: 600rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.promotion-popup-container .promotion-popup-content .promotion-detail-list {
margin: 0 30rpx;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item:last-child {
margin-bottom: env(safe-area-inset-bottom);
border-bottom: 0;
padding-bottom: calc(28rpx + env(safe-area-inset-bottom));
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item {
display: flex;
justify-content: space-between;
padding: 10rpx 0 28rpx;
position: relative;
font-size: 24rpx;
color: #222427;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.tag {
box-sizing: border-box;
font-size: 20rpx;
line-height: 32rpx;
padding: 2rpx 12rpx;
background-color: #ffece9;
margin-right: 16rpx;
display: inline-flex;
color: #fa4126;
border-radius: 54rpx;
flex-shrink: 0;
position: relative;
top: 2rpx;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.content {
font-size: 28rpx;
color: #222427;
flex: 1;
line-height: 40rpx;
display: flex;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.content
.list-content {
width: 440rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.collect-btn {
font-size: 24rpx;
flex-shrink: 0;
margin-left: 20rpx;
display: flex;
align-items: center;
}
.promotion-popup-container
.promotion-popup-content
.promotion-detail-list
.list-item
.collect-btn
.linkText {
margin-right: 8rpx;
}

View File

@@ -0,0 +1,443 @@
import Toast from 'tdesign-miniprogram/toast/index';
import { fetchGood } from '../../../services/good/fetchGood';
import { fetchActivityList } from '../../../services/activity/fetchActivityList';
import {
getGoodsDetailsCommentList,
getGoodsDetailsCommentsCount,
} from '../../../services/good/fetchGoodsDetailsComments';
import { cdnBase } from '../../../config/index';
const imgPrefix = `${cdnBase}/`;
const recLeftImg = `${imgPrefix}common/rec-left.png`;
const recRightImg = `${imgPrefix}common/rec-right.png`;
const obj2Params = (obj = {}, encode = false) => {
const result = [];
Object.keys(obj).forEach((key) =>
result.push(`${key}=${encode ? encodeURIComponent(obj[key]) : obj[key]}`),
);
return result.join('&');
};
Page({
data: {
commentsList: [],
commentsStatistics: {
badCount: 0,
commentCount: 0,
goodCount: 0,
goodRate: 0,
hasImageCount: 0,
middleCount: 0,
},
isShowPromotionPop: false,
activityList: [],
recLeftImg,
recRightImg,
details: {},
goodsTabArray: [
{
name: '商品',
value: '', // 空字符串代表置顶
},
{
name: '详情',
value: 'goods-page',
},
],
storeLogo: `${imgPrefix}common/store-logo.png`,
storeName: '云mall标准版旗舰店',
jumpArray: [
{
title: '首页',
url: '/pages/home/home',
iconName: 'home',
},
{
title: '购物车',
url: '/pages/cart/index',
iconName: 'cart',
showCartNum: true,
},
],
isStock: true,
cartNum: 0,
soldout: false,
buttonType: 1,
buyNum: 1,
selectedAttrStr: '',
skuArray: [],
primaryImage: '',
specImg: '',
isSpuSelectPopupShow: false,
isAllSelectedSku: false,
buyType: 0,
outOperateStatus: false, // 是否外层加入购物车
operateType: 0,
selectSkuSellsPrice: 0,
maxLinePrice: 0,
minSalePrice: 0,
maxSalePrice: 0,
list: [],
spuId: '',
navigation: { type: 'fraction' },
current: 0,
autoplay: true,
duration: 500,
interval: 5000,
soldNum: 0, // 已售数量
},
handlePopupHide() {
this.setData({
isSpuSelectPopupShow: false,
});
},
showSkuSelectPopup(type) {
this.setData({
buyType: type || 0,
outOperateStatus: type >= 1,
isSpuSelectPopupShow: true,
});
},
buyItNow() {
this.showSkuSelectPopup(1);
},
toAddCart() {
this.showSkuSelectPopup(2);
},
toNav(e) {
const { url } = e.detail;
wx.switchTab({
url: url,
});
},
showCurImg(e) {
const { index } = e.detail;
const { images } = this.data.details;
wx.previewImage({
current: images[index],
urls: images, // 需要预览的图片http链接列表
});
},
onPageScroll({ scrollTop }) {
const goodsTab = this.selectComponent('#goodsTab');
goodsTab && goodsTab.onScroll(scrollTop);
},
chooseSpecItem(e) {
const { specList } = this.data.details;
const { selectedSku, isAllSelectedSku } = e.detail;
if (!isAllSelectedSku) {
this.setData({
selectSkuSellsPrice: 0,
});
}
this.setData({
isAllSelectedSku,
});
this.getSkuItem(specList, selectedSku);
},
getSkuItem(specList, selectedSku) {
const { skuArray, primaryImage } = this.data;
const selectedSkuValues = this.getSelectedSkuValues(specList, selectedSku);
let selectedAttrStr = ``;
selectedSkuValues.forEach((item) => {
selectedAttrStr += `${item.specValue} `;
});
// eslint-disable-next-line array-callback-return
const skuItem = skuArray.filter((item) => {
let status = true;
(item.specInfo || []).forEach((subItem) => {
if (
!selectedSku[subItem.specId] ||
selectedSku[subItem.specId] !== subItem.specValueId
) {
status = false;
}
});
if (status) return item;
});
this.selectSpecsName(selectedSkuValues.length > 0 ? selectedAttrStr : '');
if (skuItem) {
this.setData({
selectItem: skuItem,
selectSkuSellsPrice: skuItem.price || 0,
});
} else {
this.setData({
selectItem: null,
selectSkuSellsPrice: 0,
});
}
this.setData({
specImg: skuItem && skuItem.skuImage ? skuItem.skuImage : primaryImage,
});
},
// 获取已选择的sku名称
getSelectedSkuValues(skuTree, selectedSku) {
const normalizedTree = this.normalizeSkuTree(skuTree);
return Object.keys(selectedSku).reduce((selectedValues, skuKeyStr) => {
const skuValues = normalizedTree[skuKeyStr];
const skuValueId = selectedSku[skuKeyStr];
if (skuValueId !== '') {
const skuValue = skuValues.filter((value) => {
return value.specValueId === skuValueId;
})[0];
skuValue && selectedValues.push(skuValue);
}
return selectedValues;
}, []);
},
normalizeSkuTree(skuTree) {
const normalizedTree = {};
skuTree.forEach((treeItem) => {
normalizedTree[treeItem.specId] = treeItem.specValueList;
});
return normalizedTree;
},
selectSpecsName(selectSpecsName) {
if (selectSpecsName) {
this.setData({
selectedAttrStr: selectSpecsName,
});
} else {
this.setData({
selectedAttrStr: '',
});
}
},
addCart() {
const { isAllSelectedSku } = this.data;
Toast({
context: this,
selector: '#t-toast',
message: isAllSelectedSku ? '点击加入购物车' : '请选择规格',
icon: '',
duration: 1000,
});
},
gotoBuy(type) {
const { isAllSelectedSku, buyNum } = this.data;
if (!isAllSelectedSku) {
Toast({
context: this,
selector: '#t-toast',
message: '请选择规格',
icon: '',
duration: 1000,
});
return;
}
this.handlePopupHide();
const query = {
quantity: buyNum,
storeId: '1',
spuId: this.data.spuId,
goodsName: this.data.details.title,
skuId:
type === 1 ? this.data.skuList[0].skuId : this.data.selectItem.skuId,
available: this.data.details.available,
price: this.data.details.minSalePrice,
specInfo: this.data.details.specList?.map((item) => ({
specTitle: item.title,
specValue: item.specValueList[0].specValue,
})),
primaryImage: this.data.details.primaryImage,
spuId: this.data.details.spuId,
thumb: this.data.details.primaryImage,
title: this.data.details.title,
};
let urlQueryStr = obj2Params({
goodsRequestList: JSON.stringify([query]),
});
urlQueryStr = urlQueryStr ? `?${urlQueryStr}` : '';
const path = `/pages/order/order-confirm/index${urlQueryStr}`;
wx.navigateTo({
url: path,
});
},
specsConfirm() {
const { buyType } = this.data;
if (buyType === 1) {
this.gotoBuy();
} else {
this.addCart();
}
// this.handlePopupHide();
},
changeNum(e) {
this.setData({
buyNum: e.detail.buyNum,
});
},
closePromotionPopup() {
this.setData({
isShowPromotionPop: false,
});
},
promotionChange(e) {
const { index } = e.detail;
wx.navigateTo({
url: `/pages/promotion-detail/index?promotion_id=${index}`,
});
},
showPromotionPopup() {
this.setData({
isShowPromotionPop: true,
});
},
getDetail(spuId) {
Promise.all([fetchGood(spuId), fetchActivityList()]).then((res) => {
const [details, activityList] = res;
const skuArray = [];
const {
skuList,
primaryImage,
isPutOnSale,
minSalePrice,
maxSalePrice,
maxLinePrice,
soldNum,
} = details;
skuList.forEach((item) => {
skuArray.push({
skuId: item.skuId,
quantity: item.stockInfo ? item.stockInfo.stockQuantity : 0,
specInfo: item.specInfo,
});
});
const promotionArray = [];
activityList.forEach((item) => {
promotionArray.push({
tag: item.promotionSubCode === 'MYJ' ? '满减' : '满折',
label: '满100元减99.9元',
});
});
this.setData({
details,
activityList,
isStock: details.spuStockQuantity > 0,
maxSalePrice: maxSalePrice ? parseInt(maxSalePrice) : 0,
maxLinePrice: maxLinePrice ? parseInt(maxLinePrice) : 0,
minSalePrice: minSalePrice ? parseInt(minSalePrice) : 0,
list: promotionArray,
skuArray: skuArray,
primaryImage,
soldout: isPutOnSale === 0,
soldNum,
});
});
},
async getCommentsList() {
try {
const code = 'Success';
const data = await getGoodsDetailsCommentList();
const { homePageComments } = data;
if (code.toUpperCase() === 'SUCCESS') {
const nextState = {
commentsList: homePageComments.map((item) => {
return {
goodsSpu: item.spuId,
userName: item.userName || '',
commentScore: item.commentScore,
commentContent: item.commentContent || '用户未填写评价',
userHeadUrl: item.isAnonymity
? this.anonymityAvatar
: item.userHeadUrl || this.anonymityAvatar,
};
}),
};
this.setData(nextState);
}
} catch (error) {
console.error('comments error:', error);
}
},
onShareAppMessage() {
// 自定义的返回信息
const { selectedAttrStr } = this.data;
let shareSubTitle = '';
if (selectedAttrStr.indexOf('件') > -1) {
const count = selectedAttrStr.indexOf('件');
shareSubTitle = selectedAttrStr.slice(count + 1, selectedAttrStr.length);
}
const customInfo = {
imageUrl: this.data.details.primaryImage,
title: this.data.details.title + shareSubTitle,
path: `/pages/goods/details/index?spuId=${this.data.spuId}`,
};
return customInfo;
},
/** 获取评价统计 */
async getCommentsStatistics() {
try {
const code = 'Success';
const data = await getGoodsDetailsCommentsCount();
if (code.toUpperCase() === 'SUCCESS') {
const {
badCount,
commentCount,
goodCount,
goodRate,
hasImageCount,
middleCount,
} = data;
const nextState = {
commentsStatistics: {
badCount: parseInt(`${badCount}`),
commentCount: parseInt(`${commentCount}`),
goodCount: parseInt(`${goodCount}`),
/** 后端返回百分比后数据但没有限制位数 */
goodRate: Math.floor(goodRate * 10) / 10,
hasImageCount: parseInt(`${hasImageCount}`),
middleCount: parseInt(`${middleCount}`),
},
};
this.setData(nextState);
}
} catch (error) {
console.error('comments statiistics error:', error);
}
},
/** 跳转到评价列表 */
navToCommentsListPage() {
wx.navigateTo({
url: `/pages/goods/comments/index?spuId=${this.data.spuId}`,
});
},
onLoad(query) {
const { spuId } = query;
this.setData({
spuId: spuId,
});
this.getDetail(spuId);
this.getCommentsList(spuId);
this.getCommentsStatistics(spuId);
},
});

View File

@@ -0,0 +1,18 @@
{
"navigationBarTitleText": "商品详情",
"usingComponents": {
"t-image": "/components/webp-image/index",
"t-tag": "tdesign-miniprogram/tag/tag",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-rate": "tdesign-miniprogram/rate/rate",
"t-swiper": "tdesign-miniprogram/swiper/swiper",
"t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav",
"t-button": "tdesign-miniprogram/button/button",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-popup": "tdesign-miniprogram/popup/popup",
"price": "/components/price/index",
"buy-bar": "./components/buy-bar/index",
"promotion-popup": "./components/promotion-popup/index",
"goods-specs-popup": "./components/goods-specs-popup/index"
}
}

View File

@@ -0,0 +1,153 @@
<view class="goods-detail-page">
<view class="goods-head">
<t-swiper
wx:if="{{details.images.length > 0}}"
height="750rpx"
current="{{current}}"
autoplay="{{autoplay}}"
duration="{{duration}}"
interval="{{interval}}"
navigation="{{navigation}}"
list="{{details.images}}"
></t-swiper>
<view class="goods-info">
<view class="goods-number">
<view class="goods-price">
<price
wr-class="class-goods-price"
symbol-class="class-goods-symbol"
price="{{minSalePrice}}"
type="lighter"
/>
<view class="goods-price-up">起</view>
<price wr-class="class-goods-del" price="{{maxLinePrice}}" type="delthrough" />
</view>
<view class="sold-num">已售{{soldNum}}</view>
</view>
<view wx:if="{{activityList.length > 0}}" class="goods-activity" bindtap="showPromotionPopup">
<view class="tags-container">
<view wx:for="{{activityList}}" data-promotionId="{{item.promotionId}}" wx:key="index" wx:if="{{index<4}}">
<view class="goods-activity-tag">{{item.tag}}</view>
</view>
</view>
<view class="activity-show">
<view class="activity-show-text">领劵</view>
<t-icon name="chevron-right" size="42rpx" />
</view>
</view>
<view class="goods-title">
<view class="goods-name">{{details.title}}</view>
<view class="goods-tag">
<t-button open-type="share" t-class="shareBtn" variant="text">
<view class="btn-icon">
<t-icon name="share" size="40rpx" color="#000" />
<view class="share-text">分享</view>
</view>
</t-button>
</view>
</view>
<view class="goods-intro">{{intro}}</view>
</view>
<view class="spu-select" bindtap="showSkuSelectPopup">
<view class="label">已选</view>
<view class="content">
<view class="{{!selectedAttrStr ? 'tintColor' : ''}}">
{{selectedAttrStr ? buyNum : ''}}{{selectedAttrStr || '请选择'}}
</view>
<t-icon name="chevron-right" size="40rpx" color="#BBBBBB" />
</view>
</view>
<view wx:if="{{ commentsStatistics.commentCount > 0 }}" class="comments-wrap">
<view class="comments-head" bindtap="navToCommentsListPage">
<view class="comments-title-wrap">
<view class="comments-title-label">商品评价</view>
<view class="comments-title-count"> ({{ commentsStatistics.commentCount }}) </view>
</view>
<view class="comments-rate-wrap">
<view class="comments-good-rate">{{commentsStatistics.goodRate}}% 好评</view>
<t-icon name="chevron-right" size="40rpx" color="#BBBBBB" />
</view>
</view>
<view class="comment-item-wrap" wx:for="{{ commentsList }}" wx:for-item="commentItem" wx:key="goodsSpu">
<view class="comment-item-head">
<t-image src="{{commentItem.userHeadUrl}}" t-class="comment-item-avatar" />
<view class="comment-head-right">
<view class="comment-username">{{commentItem.userName}}</view>
<t-rate
value="{{ commentItem.commentScore }}"
count="{{5}}"
size="12"
gap="2"
color="{{['#ffc51c', '#ddd']}}"
/>
</view>
</view>
<view class="comment-item-content"> {{commentItem.commentContent}} </view>
</view>
</view>
</view>
<view class="desc-content">
<view class="desc-content__title" wx:if="{{details.desc.length > 0}}">
<t-image t-class="img" src="{{recLeftImg}}" />
<span class="desc-content__title--text">详情介绍</span>
<t-image t-class="img" src="{{recRightImg}}" />
</view>
<view wx:if="{{details.desc.length > 0}}" wx:for="{{details.desc}}" wx:key="index">
<t-image t-class="desc-content__img" src="{{item}}" mode="widthFix" />
</view>
</view>
<view class="goods-bottom-operation">
<buy-bar
jumpArray="{{jumpArray}}"
soldout="{{soldout}}"
isStock="{{isStock}}"
shopCartNum="{{cartNum}}"
buttonType="{{buttonType}}"
bind:toAddCart="toAddCart"
bind:toNav="toNav"
bind:toBuyNow="buyItNow"
class="goods-details-card"
/>
</view>
<goods-specs-popup
id="goodsSpecsPopup"
show="{{isSpuSelectPopupShow}}"
title="{{details.title || ''}}"
src="{{specImg ? specImg : primaryImage}}"
specList="{{details.specList || []}}"
skuList="{{skuArray}}"
limitBuyInfo="{{details.limitInfo[0].text || ''}}"
bind:closeSpecsPopup="handlePopupHide"
bind:change="chooseSpecItem"
bind:changeNum="changeNum"
bind:addCart="addCart"
bind:buyNow="gotoBuy"
bind:specsConfirm="specsConfirm"
isStock="{{isStock}}"
outOperateStatus="{{outOperateStatus}}"
>
<view slot="goods-price">
<view class="popup-sku__price">
<price
wx:if="{{!isAllSelectedSku || (!promotionSubCode && isAllSelectedSku)}}"
price="{{selectSkuSellsPrice ? selectSkuSellsPrice : minSalePrice }}"
wr-class="popup-sku__price-num"
symbol-class="popup-sku__price-symbol"
/>
<price
wx:if="{{selectSkuSellsPrice === 0 && minSalePrice !== maxSalePrice && !isAllSelectedSku}}"
price="{{maxSalePrice}}"
wr-class="popup-sku__price-del"
type="delthrough"
/>
</view>
</view>
</goods-specs-popup>
<promotion-popup
list="{{list}}"
bind:closePromotionPopup="closePromotionPopup"
show="{{isShowPromotionPop}}"
bind:promotionChange="promotionChange"
/>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,342 @@
@import '../../../style/global.wxss';
page {
width: 100vw;
}
.goods-detail-page .goods-info {
margin: 0 auto;
padding: 26rpx 0 28rpx 30rpx;
background-color: #fff;
}
.goods-detail-page .swipe-img {
width: 100%;
height: 750rpx;
}
.goods-detail-page .goods-info .goods-price {
display: flex;
align-items: baseline;
}
.goods-detail-page .goods-info .goods-price-up {
color: #fa4126;
font-size: 28rpx;
position: relative;
bottom: 4rpx;
left: 8rpx;
}
.goods-detail-page .goods-info .goods-price .class-goods-price {
font-size: 64rpx;
color: #fa4126;
font-weight: bold;
font-family: DIN Alternate;
}
.goods-detail-page .goods-info .goods-price .class-goods-symbol {
font-size: 36rpx;
color: #fa4126;
}
.goods-detail-page .goods-info .goods-price .class-goods-del {
position: relative;
font-weight: normal;
left: 16rpx;
bottom: 2rpx;
color: #999999;
font-size: 32rpx;
}
.goods-detail-page .goods-info .goods-number {
display: flex;
align-items: center;
justify-content: space-between;
}
.goods-detail-page .goods-info .goods-number .sold-num {
font-size: 24rpx;
color: #999999;
display: flex;
align-items: flex-end;
margin-right: 32rpx;
}
.goods-detail-page .goods-info .goods-activity {
display: flex;
margin-top: 16rpx;
justify-content: space-between;
}
.goods-detail-page .goods-info .goods-activity .tags-container {
display: flex;
}
.goods-detail-page .goods-info .goods-activity .tags-container .goods-activity-tag {
background: #ffece9;
color: #fa4126;
font-size: 24rpx;
margin-right: 16rpx;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
.goods-detail-page .goods-info .goods-activity .activity-show {
display: flex;
justify-content: center;
align-items: center;
color: #fa4126;
font-size: 24rpx;
padding-right: 32rpx;
}
.goods-detail-page .goods-info .goods-activity .activity-show-text {
line-height: 42rpx;
}
.goods-detail-page .goods-info .goods-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
}
.goods-detail-page .goods-info .goods-title .goods-name {
width: 600rpx;
font-weight: 500;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
font-size: 32rpx;
word-break: break-all;
color: #333333;
}
.goods-detail-page .goods-info .goods-title .goods-tag {
width: 104rpx;
margin-left: 26rpx;
}
.goods-detail-page .goods-info .goods-title .goods-tag .shareBtn {
border-radius: 200rpx 0px 0px 200rpx;
width: 100rpx;
height: 96rpx;
border: none;
padding-right: 36rpx !important;
}
.goods-detail-page .goods-info .goods-title .goods-tag .shareBtn::after {
border: none;
}
.goods-detail-page .goods-info .goods-title .goods-tag .btn-icon {
font-size: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 96rpx;
color: #999;
}
.goods-detail-page .goods-info .goods-title .goods-tag .btn-icon .share-text {
padding-top: 8rpx;
font-size: 20rpx;
line-height: 24rpx;
}
.goods-detail-page .goods-info .goods-intro {
font-size: 26rpx;
color: #888;
line-height: 36rpx;
word-break: break-all;
padding-right: 30rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: normal;
overflow: hidden;
}
.spu-select {
height: 80rpx;
background-color: #fff;
margin-top: 20rpx;
display: flex;
align-items: center;
padding: 30rpx;
font-size: 28rpx;
}
.spu-select .label {
margin-right: 30rpx;
text-align: center;
flex-shrink: 0;
color: #999999;
font-weight: normal;
}
.spu-select .content {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
}
.spu-select .content .tintColor {
color: #aaa;
}
.goods-detail-page .desc-content {
margin-top: 20rpx;
background-color: #fff;
padding-bottom: 120rpx;
}
.goods-detail-page .desc-content__title {
font-size: 28rpx;
line-height: 36rpx;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 20rpx;
}
.goods-detail-page .desc-content__title .img {
width: 206rpx;
height: 10rpx;
}
.goods-detail-page .desc-content__title--text {
font-size: 26rpx;
margin: 0 32rpx;
font-weight: 600;
}
.goods-detail-page .desc-content__img {
width: 100%;
height: auto;
}
.goods-bottom-operation {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: #fff;
padding-bottom: env(safe-area-inset-bottom);
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price {
display: flex;
align-items: baseline;
color: #fa4126;
margin-top: 48rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-num {
font-size: 64rpx;
color: #fa4126;
font-weight: bold;
font-family: DIN Alternate;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-del {
position: relative;
font-weight: normal;
left: 12rpx;
bottom: 2rpx;
color: #999999;
font-size: 32rpx;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-symbol {
font-size: 36rpx;
color: #fa4126;
}
.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-max-num {
font-size: 48rpx;
}
.goods-detail-page .goods-head {
--td-swiper-radius: 0;
}
.t-toast__content {
z-index: 12000 !important;
}
.comments-wrap {
margin-top: 20rpx;
padding: 32rpx;
background-color: #fff;
}
.comments-wrap .comments-head {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.comments-wrap .comments-head .comments-title-wrap {
display: flex;
}
.comments-title-label,
.comments-title-count {
color: #333333;
font-size: 32rpx;
font-weight: 500;
line-height: 48rpx;
}
.comments-rate-wrap {
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
}
.comments-rate-wrap .comments-good-rate {
color: #999999;
font-size: 26rpx;
font-weight: 400;
font-style: normal;
line-height: 36rpx;
}
.comment-item-wrap .comment-item-head {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 32rpx;
}
.comment-item-wrap .comment-item-head .comment-item-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 64rpx;
}
.comment-item-wrap .comment-item-head .comment-head-right {
margin-left: 24rpx;
}
.comment-head-right .comment-username {
font-size: 26rpx;
color: #333333;
line-height: 36rpx;
font-weight: 400;
}
.comment-item-wrap .comment-item-content {
margin-top: 20rpx;
color: #333333;
line-height: 40rpx;
font-size: 28rpx;
font-weight: 400;
}

View File

@@ -0,0 +1,226 @@
import { fetchGoodsList } from '../../../services/good/fetchGoodsList';
import Toast from 'tdesign-miniprogram/toast/index';
const initFilters = {
overall: 1,
sorts: '',
layout: 0,
};
Page({
data: {
goodsList: [],
layout: 0,
sorts: '',
overall: 1,
show: false,
minVal: '',
maxVal: '',
filter: initFilters,
hasLoaded: false,
loadMoreStatus: 0,
loading: true,
},
pageNum: 1,
pageSize: 30,
total: 0,
handleFilterChange(e) {
const { layout, overall, sorts } = e.detail;
this.pageNum = 1;
this.setData({
layout,
sorts,
overall,
loadMoreStatus: 0,
});
this.init(true);
},
generalQueryData(reset = false) {
const { filter, keywords, minVal, maxVal } = this.data;
const { pageNum, pageSize } = this;
const { sorts, overall } = filter;
const params = {
sort: 0, // 0 综合1 价格
pageNum: 1,
pageSize: 30,
keyword: keywords,
};
if (sorts) {
params.sort = 1;
params.sortType = sorts === 'desc' ? 1 : 0;
}
if (overall) {
params.sort = 0;
} else {
params.sort = 1;
}
params.minPrice = minVal ? minVal * 100 : 0;
params.maxPrice = maxVal ? maxVal * 100 : undefined;
if (reset) return params;
return {
...params,
pageNum: pageNum + 1,
pageSize,
};
},
async init(reset = true) {
const { loadMoreStatus, goodsList = [] } = this.data;
const params = this.generalQueryData(reset);
if (loadMoreStatus !== 0) return;
this.setData({
loadMoreStatus: 1,
loading: true,
});
try {
const result = await fetchGoodsList(params);
const code = 'Success';
const data = result;
if (code.toUpperCase() === 'SUCCESS') {
const { spuList, totalCount = 0 } = data;
if (totalCount === 0 && reset) {
this.total = totalCount;
this.setData({
emptyInfo: {
tip: '抱歉,未找到相关商品',
},
hasLoaded: true,
loadMoreStatus: 0,
loading: false,
goodsList: [],
});
return;
}
const _goodsList = reset ? spuList : goodsList.concat(spuList);
const _loadMoreStatus = _goodsList.length === totalCount ? 2 : 0;
this.pageNum = params.pageNum || 1;
this.total = totalCount;
this.setData({
goodsList: _goodsList,
loadMoreStatus: _loadMoreStatus,
});
} else {
this.setData({
loading: false,
});
wx.showToast({
title: '查询失败,请稍候重试',
});
}
} catch (error) {
this.setData({
loading: false,
});
}
this.setData({
hasLoaded: true,
loading: false,
});
},
onLoad() {
this.init(true);
},
onReachBottom() {
const { goodsList } = this.data;
const { total = 0 } = this;
if (goodsList.length === total) {
this.setData({
loadMoreStatus: 2,
});
return;
}
this.init(false);
},
handleAddCart() {
Toast({
context: this,
selector: '#t-toast',
message: '点击加购',
});
},
tagClickHandle() {
Toast({
context: this,
selector: '#t-toast',
message: '点击标签',
});
},
gotoGoodsDetail(e) {
const { index } = e.detail;
const { spuId } = this.data.goodsList[index];
wx.navigateTo({
url: `/pages/goods/details/index?spuId=${spuId}`,
});
},
showFilterPopup() {
this.setData({
show: true,
});
},
showFilterPopupClose() {
this.setData({
show: false,
});
},
onMinValAction(e) {
const { value } = e.detail;
this.setData({ minVal: value });
},
onMaxValAction(e) {
const { value } = e.detail;
this.setData({ maxVal: value });
},
reset() {
this.setData({ minVal: '', maxVal: '' });
},
confirm() {
const { minVal, maxVal } = this.data;
let message = '';
if (minVal && !maxVal) {
message = `价格最小是${minVal}`;
} else if (!minVal && maxVal) {
message = `价格范围是0-${minVal}`;
} else if (minVal && maxVal && minVal <= maxVal) {
message = `价格范围${minVal}-${this.data.maxVal}`;
} else {
message = '请输入正确范围';
}
if (message) {
Toast({
context: this,
selector: '#t-toast',
message,
});
}
this.pageNum = 1;
this.setData(
{
show: false,
minVal: '',
goodsList: [],
loadMoreStatus: 0,
maxVal: '',
},
() => {
this.init();
},
);
},
});

View File

@@ -0,0 +1,12 @@
{
"navigationBarTitleText": "商品列表",
"usingComponents": {
"t-input": "tdesign-miniprogram/input/input",
"t-empty": "tdesign-miniprogram/empty/empty",
"t-toast": "tdesign-miniprogram/toast/toast",
"goods-list": "/components/goods-list/index",
"filter": "/components/filter/index",
"filter-popup": "/components/filter-popup/index",
"load-more": "/components/load-more/index"
}
}

View File

@@ -0,0 +1,54 @@
<view class="goods-list-container">
<filter
wr-class="filter-container"
bind:change="handleFilterChange"
layout="{{layout}}"
sorts="{{sorts}}"
overall="{{overall}}"
bind:showFilterPopup="showFilterPopup"
>
<filter-popup
slot="filterPopup"
show="{{show}}"
bind:showFilterPopupClose="showFilterPopupClose"
bind:reset="reset"
bind:confirm="confirm"
>
<view class="price-container" slot="filterSlot">
<view class="price-between">价格区间</view>
<view class="price-ipts-wrap">
<t-input
align="center"
type="number"
t-class="price-ipt"
placeholder="最低价"
value="{{minVal}}"
bindchange="onMinValAction"
/>
<view class="price-divided">-</view>
<t-input
align="center"
type="number"
t-class="price-ipt"
placeholder="最高价"
value="{{maxVal}}"
bindchange="onMaxValAction"
/>
</view>
</view>
</filter-popup>
</filter>
<view class="empty-wrap" wx:if="{{goodsList.length === 0 && hasLoaded}}">
<t-empty t-class="empty-tips" size="240rpx" description="暂无相关商品" />
</view>
<view class="category-goods-list" wx:if="{{goodsList.length}}">
<goods-list
wr-class="wr-goods-list"
goodsList="{{goodsList}}"
bind:click="gotoGoodsDetail"
bind:addcart="handleAddCart"
/>
</view>
<load-more wx:if="{{goodsList.length > 0}}" status="{{loadMoreStatus}}" no-more-text="没有更多了" />
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,99 @@
page {
background-color: #fff;
}
.goods-list-container {
display: block;
}
.goods-list-container .t-search {
padding: 0 30rpx;
background-color: #fff;
}
.goods-list-container .t-class__input-container {
height: 64rpx !important;
border-radius: 32rpx !important;
}
.goods-list-container .t-search__left-icon {
display: flex;
align-items: center;
}
.goods-list-container .t-search__input {
font-size: 28rpx !important;
color: rgb(116, 116, 116) !important;
}
.goods-list-container .category-goods-list {
background-color: #f2f2f2;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
padding: 20rpx 24rpx;
-webkit-overflow-scrolling: touch;
}
.goods-list-container .wr-goods-list {
background: #f2f2f2 !important;
}
.goods-list-container .t-image__mask {
display: flex !important;
}
.goods-list-container .empty-wrap {
margin-top: 184rpx;
margin-bottom: 120rpx;
height: 300rpx;
}
.goods-list-container .empty-wrap .empty-tips .empty-content .content-text {
margin-top: 40rpx;
}
.goods-list-container .price-container {
padding: 32rpx;
height: 100vh;
max-width: 632rpx;
background-color: #fff;
border-radius: 30rpx 0 0 30rpx;
box-sizing: border-box;
}
.goods-list-container .price-between {
font-size: 26rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.goods-list-container .price-ipts-wrap {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 24rpx;
--td-input-bg-color: rgba(245, 245, 245, 1);
--td-input-vertical-padding: 4rpx;
--td-input-border-color: transparent;
}
.goods-list-container .price-ipts-wrap .price-divided {
width: 16rpx;
margin: 0 24rpx;
color: #333333;
}
.goods-list-container .price-ipts-wrap .t-input__wrapper {
margin: 0 !important;
}
.goods-list-container .price-ipts-wrap .t-input__content,
.goods-list-container .price-ipts-wrap .t-input__placeholder {
font-size: 24rpx !important;
}
.goods-list-container .price-ipts-wrap .price-ipt {
border-radius: 8rpx;
}

View File

@@ -0,0 +1,262 @@
/* eslint-disable no-param-reassign */
import { getSearchResult } from '../../../services/good/fetchSearchResult';
import Toast from 'tdesign-miniprogram/toast/index';
const initFilters = {
overall: 1,
sorts: '',
};
Page({
data: {
goodsList: [],
sorts: '',
overall: 1,
show: false,
minVal: '',
maxVal: '',
minSalePriceFocus: false,
maxSalePriceFocus: false,
filter: initFilters,
hasLoaded: false,
keywords: '',
loadMoreStatus: 0,
loading: true,
},
total: 0,
pageNum: 1,
pageSize: 30,
onLoad(options) {
const { searchValue = '' } = options || {};
this.setData(
{
keywords: searchValue,
},
() => {
this.init(true);
},
);
},
generalQueryData(reset = false) {
const { filter, keywords, minVal, maxVal } = this.data;
const { pageNum, pageSize } = this;
const { sorts, overall } = filter;
const params = {
sort: 0, // 0 综合1 价格
pageNum: 1,
pageSize: 30,
keyword: keywords,
};
if (sorts) {
params.sort = 1;
params.sortType = sorts === 'desc' ? 1 : 0;
}
if (overall) {
params.sort = 0;
} else {
params.sort = 1;
}
params.minPrice = minVal ? minVal * 100 : 0;
params.maxPrice = maxVal ? maxVal * 100 : undefined;
if (reset) return params;
return {
...params,
pageNum: pageNum + 1,
pageSize,
};
},
async init(reset = true) {
const { loadMoreStatus, goodsList = [] } = this.data;
const params = this.generalQueryData(reset);
if (loadMoreStatus !== 0) return;
this.setData({
loadMoreStatus: 1,
loading: true,
});
try {
const result = await getSearchResult(params);
const code = 'Success';
const data = result;
if (code.toUpperCase() === 'SUCCESS') {
const { spuList, totalCount = 0 } = data;
if (totalCount === 0 && reset) {
this.total = totalCount;
this.setData({
emptyInfo: {
tip: '抱歉,未找到相关商品',
},
hasLoaded: true,
loadMoreStatus: 0,
loading: false,
goodsList: [],
});
return;
}
const _goodsList = reset ? spuList : goodsList.concat(spuList);
_goodsList.forEach((v) => {
v.tags = v.spuTagList.map((u) => u.title);
v.hideKey = { desc: true };
});
const _loadMoreStatus = _goodsList.length === totalCount ? 2 : 0;
this.pageNum = params.pageNum || 1;
this.total = totalCount;
this.setData({
goodsList: _goodsList,
loadMoreStatus: _loadMoreStatus,
});
} else {
this.setData({
loading: false,
});
wx.showToast({
title: '查询失败,请稍候重试',
});
}
} catch (error) {
this.setData({
loading: false,
});
}
this.setData({
hasLoaded: true,
loading: false,
});
},
handleCartTap() {
wx.switchTab({
url: '/pages/cart/index',
});
},
handleSubmit() {
this.setData(
{
goodsList: [],
loadMoreStatus: 0,
},
() => {
this.init(true);
},
);
},
onReachBottom() {
const { goodsList } = this.data;
const { total = 0 } = this;
if (goodsList.length === total) {
this.setData({
loadMoreStatus: 2,
});
return;
}
this.init(false);
},
handleAddCart() {
Toast({
context: this,
selector: '#t-toast',
message: '点击加购',
});
},
gotoGoodsDetail(e) {
const { index } = e.detail;
const { spuId } = this.data.goodsList[index];
wx.navigateTo({
url: `/pages/goods/details/index?spuId=${spuId}`,
});
},
handleFilterChange(e) {
const { overall, sorts } = e.detail;
const { total } = this;
const _filter = {
sorts,
overall,
};
this.setData({
filter: _filter,
sorts,
overall,
});
this.pageNum = 1;
this.setData(
{
goodsList: [],
loadMoreStatus: 0,
},
() => {
total && this.init(true);
},
);
},
showFilterPopup() {
this.setData({
show: true,
});
},
showFilterPopupClose() {
this.setData({
show: false,
});
},
onMinValAction(e) {
const { value } = e.detail;
this.setData({ minVal: value });
},
onMaxValAction(e) {
const { value } = e.detail;
this.setData({ maxVal: value });
},
reset() {
this.setData({ minVal: '', maxVal: '' });
},
confirm() {
const { minVal, maxVal } = this.data;
let message = '';
if (minVal && !maxVal) {
message = `价格最小是${minVal}`;
} else if (!minVal && maxVal) {
message = `价格范围是0-${minVal}`;
} else if (minVal && maxVal && minVal <= maxVal) {
message = `价格范围${minVal}-${this.data.maxVal}`;
} else {
message = '请输入正确范围';
}
if (message) {
Toast({
context: this,
selector: '#t-toast',
message,
});
}
this.pageNum = 1;
this.setData(
{
show: false,
minVal: '',
goodsList: [],
loadMoreStatus: 0,
maxVal: '',
},
() => {
this.init();
},
);
},
});

View File

@@ -0,0 +1,15 @@
{
"navigationBarTitleText": "搜索",
"usingComponents": {
"t-search": "tdesign-miniprogram/search/search",
"t-input": "tdesign-miniprogram/input/input",
"t-empty": "tdesign-miniprogram/empty/empty",
"t-toast": "tdesign-miniprogram/toast/toast",
"goods-list": "/components/goods-list/index",
"filter": "/components/filter/index",
"filter-popup": "/components/filter-popup/index",
"load-more": "/components/load-more/index",
"t-icon": "tdesign-miniprogram/icon/icon"
},
"onReachBottomDistance": 50
}

View File

@@ -0,0 +1,66 @@
<view class="result-container">
<t-search
t-class="t-search"
t-class-input-container="t-class__input-container"
t-class-left="t-search__left-icon"
t-class-input="t-search__input"
value="{{keywords}}"
leftIcon=""
placeholder="iPhone12pro"
bind:submit="handleSubmit"
>
<t-icon slot="left-icon" prefix="wr" name="search" size="40rpx" color="#bbb" />
</t-search>
<filter
wr-class="filter-container"
bind:change="handleFilterChange"
layout="{{layout}}"
sorts="{{sorts}}"
overall="{{overall}}"
bind:showFilterPopup="showFilterPopup"
>
<filter-popup
show="{{show}}"
slot="filterPopup"
bind:showFilterPopupClose="showFilterPopupClose"
bind:reset="reset"
bind:confirm="confirm"
>
<view class="price-container" slot="filterSlot">
<view class="price-between">价格区间</view>
<view class="price-ipts-wrap">
<t-input
type="number"
t-class="price-ipt"
t-class-input="t-class-input"
placeholder="最低价"
value="{{minVal}}"
bindchange="onMinValAction"
/>
<view class="price-divided">-</view>
<t-input
type="number"
t-class="price-ipt"
t-class-input="t-class-input"
placeholder="最高价"
value="{{maxVal}}"
bindchange="onMaxValAction"
/>
</view>
</view>
</filter-popup>
</filter>
<view class="empty-wrap" wx:if="{{goodsList.length === 0 && hasLoaded}}">
<t-empty t-class="empty-tips" size="240rpx" description="暂无相关商品" />
</view>
<view class="category-goods-list" wx:if="{{goodsList.length}}">
<goods-list
wr-class="wr-goods-list"
goodsList="{{goodsList}}"
bind:click="gotoGoodsDetail"
bind:addcart="handleAddCart"
/>
</view>
<load-more wx:if="{{goodsList.length > 0}}" status="{{loadMoreStatus}}" no-more-text="没有更多了" />
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,114 @@
page {
background-color: #fff;
}
page view {
box-sizing: border-box;
}
.result-container {
display: block;
}
.result-container .t-search {
padding: 0 30rpx;
background-color: #fff;
}
.result-container .t-class__input-container {
height: 64rpx !important;
border-radius: 32rpx !important;
}
.result-container .t-search__left-icon {
display: flex;
align-items: center;
}
.result-container .t-search__input {
font-size: 28rpx !important;
color: #333 !important;
}
.result-container .category-goods-list {
background-color: #f2f2f2;
overflow-y: scroll;
padding: 20rpx 24rpx;
-webkit-overflow-scrolling: touch;
}
.result-container .wr-goods-list {
background: #f2f2f2 !important;
}
.result-container .t-image__mask {
display: flex !important;
}
.result-container .empty-wrap {
margin-top: 184rpx;
margin-bottom: 120rpx;
height: 300rpx;
}
.result-container .empty-wrap .empty-tips .empty-content .content-text {
margin-top: 40rpx;
}
.result-container .price-container {
padding: 32rpx;
height: 100vh;
max-width: 632rpx;
background-color: #fff;
border-radius: 30rpx 0 0 30rpx;
}
.result-container .price-between {
font-size: 26rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.result-container .price-ipts-wrap {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
margin-top: 24rpx;
}
.result-container .price-ipts-wrap .price-divided {
position: relative;
width: 22rpx;
margin: 0 20rpx;
color: #222427;
}
.result-container .price-ipts-wrap .price-ipt {
box-sizing: border-box;
width: 246rpx;
font-size: 24rpx;
height: 56rpx;
padding: 0 24rpx;
text-align: center;
border-radius: 8rpx;
color: #333;
background: rgba(245, 245, 245, 1);
}
.t-class-input {
font-size: 24rpx !important;
}
.t-search__clear {
font-size: 40rpx !important;
}
.result-container .price-ipts-wrap .price-ipt::after {
border: none !important;
}
.result-container .t-input__control {
font-size: 24rpx !important;
text-align: center;
}

View File

@@ -0,0 +1,119 @@
import {
getSearchHistory,
getSearchPopular,
} from '../../../services/good/fetchSearchHistory';
Page({
data: {
historyWords: [],
popularWords: [],
searchValue: '',
dialog: {
title: '确认删除当前历史记录',
showCancelButton: true,
message: '',
},
dialogShow: false,
},
deleteType: 0,
deleteIndex: '',
onShow() {
this.queryHistory();
this.queryPopular();
},
async queryHistory() {
try {
const data = await getSearchHistory();
const code = 'Success';
if (String(code).toUpperCase() === 'SUCCESS') {
const { historyWords = [] } = data;
this.setData({
historyWords,
});
}
} catch (error) {
console.error(error);
}
},
async queryPopular() {
try {
const data = await getSearchPopular();
const code = 'Success';
if (String(code).toUpperCase() === 'SUCCESS') {
const { popularWords = [] } = data;
this.setData({
popularWords,
});
}
} catch (error) {
console.error(error);
}
},
confirm() {
const { historyWords } = this.data;
const { deleteType, deleteIndex } = this;
historyWords.splice(deleteIndex, 1);
if (deleteType === 0) {
this.setData({
historyWords,
dialogShow: false,
});
} else {
this.setData({ historyWords: [], dialogShow: false });
}
},
close() {
this.setData({ dialogShow: false });
},
handleClearHistory() {
const { dialog } = this.data;
this.deleteType = 1;
this.setData({
dialog: {
...dialog,
message: '确认删除所有历史记录',
},
dialogShow: true,
});
},
deleteCurr(e) {
const { index } = e.currentTarget.dataset;
const { dialog } = this.data;
this.deleteIndex = index;
this.setData({
dialog: {
...dialog,
message: '确认删除当前历史记录',
deleteType: 0,
},
dialogShow: true,
});
},
handleHistoryTap(e) {
const { historyWords } = this.data;
const { dataset } = e.currentTarget;
const _searchValue = historyWords[dataset.index || 0] || '';
if (_searchValue) {
wx.navigateTo({
url: `/pages/goods/result/index?searchValue=${_searchValue}`,
});
}
},
handleSubmit(e) {
const { value } = e.detail.value;
if (value.length === 0) return;
wx.navigateTo({
url: `/pages/goods/result/index?searchValue=${value}`,
});
},
});

View File

@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "搜索",
"usingComponents": {
"t-search": "tdesign-miniprogram/search/search",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-dialog": "tdesign-miniprogram/dialog/dialog"
}
}

View File

@@ -0,0 +1,61 @@
<view class="search-page">
<t-search
t-class-input-container="t-class__input-container"
t-class-input="t-search__input"
value="{{searchValue}}"
leftIcon=""
placeholder="iPhone12pro"
bind:submit="handleSubmit"
focus
>
<t-icon slot="left-icon" prefix="wr" name="search" size="40rpx" color="#bbb" />
</t-search>
<view class="search-wrap">
<view class="history-wrap">
<view class="search-header">
<text class="search-title">历史搜索</text>
<text class="search-clear" bind:tap="handleClearHistory">清除</text>
</view>
<view class="search-content">
<view
class="search-item"
hover-class="hover-history-item"
wx:for="{{historyWords}}"
bind:tap="handleHistoryTap"
bindlongpress="deleteCurr"
data-index="{{index}}"
wx:key="*this"
>
{{item}}
</view>
</view>
</view>
<view class="popular-wrap">
<view class="search-header">
<text class="search-title">热门搜索</text>
</view>
<view class="search-content">
<view
class="search-item"
hover-class="hover-history-item"
wx:for="{{popularWords}}"
bind:tap="handleHistoryTap"
data-index="{{index}}"
wx:key="*this"
>
{{item}}
</view>
</view>
</view>
</view>
<t-dialog
visible="{{dialogShow}}"
content="{{dialog.message}}"
bindconfirm="confirm"
bind:close="close"
confirm-btn="确定"
cancel-btn="{{dialog.showCancelButton ? '取消' : null}}"
t-class-confirm="dialog__button-confirm"
t-class-cancel="dialog__button-cancel"
/>
</view>

View File

@@ -0,0 +1,79 @@
.search-page {
box-sizing: border-box;
width: 100vw;
height: 100vh;
padding: 0 30rpx;
}
.search-page .t-class__input-container {
height: 64rpx !important;
border-radius: 32rpx !important;
}
.search-page .t-search__input {
font-size: 28rpx !important;
color: #333 !important;
}
.search-page .search-wrap {
margin-top: 44rpx;
}
.search-page .history-wrap {
margin-bottom: 20px;
}
.search-page .search-header {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
}
.search-page .search-title {
font-size: 30rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: rgba(51, 51, 51, 1);
line-height: 42rpx;
}
.search-page .search-clear {
font-size: 24rpx;
font-family: PingFang SC;
line-height: 32rpx;
color: #999999;
font-weight: normal;
}
.search-page .search-content {
overflow: hidden;
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-start;
margin-top: 24rpx;
}
.search-page .search-item {
color: #333333;
font-size: 24rpx;
line-height: 32rpx;
font-weight: normal;
margin-right: 24rpx;
margin-bottom: 24rpx;
background: #f5f5f5;
border-radius: 38rpx;
padding: 12rpx 24rpx;
}
.search-page .hover-history-item {
position: relative;
top: 3rpx;
left: 3rpx;
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1) inset;
}
.add-notes__confirm {
color: #fa4126 !important;
}