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;
}