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,356 @@
import Toast from 'tdesign-miniprogram/toast/index';
import { fetchDeliveryAddress } from '../../../../services/address/fetchAddress';
import { areaData } from '../../../../config/index';
import { resolveAddress, rejectAddress } from './util';
const innerPhoneReg = '^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$';
const innerNameReg = '^[a-zA-Z\\d\\u4e00-\\u9fa5]+$';
const labelsOptions = [
{ id: 0, name: '家' },
{ id: 1, name: '公司' },
];
Page({
options: {
multipleSlots: true,
},
externalClasses: ['theme-wrapper-class'],
data: {
locationState: {
labelIndex: null,
addressId: '',
addressTag: '',
cityCode: '',
cityName: '',
countryCode: '',
countryName: '',
detailAddress: '',
districtCode: '',
districtName: '',
isDefault: false,
name: '',
phone: '',
provinceCode: '',
provinceName: '',
isEdit: false,
isOrderDetail: false,
isOrderSure: false,
},
areaData: areaData,
labels: labelsOptions,
areaPickerVisible: false,
submitActive: false,
visible: false,
labelValue: '',
columns: 3,
},
privateData: {
verifyTips: '',
},
onLoad(options) {
const { id } = options;
this.init(id);
},
onUnload() {
if (!this.hasSava) {
rejectAddress();
}
},
hasSava: false,
init(id) {
if (id) {
this.getAddressDetail(Number(id));
}
},
getAddressDetail(id) {
fetchDeliveryAddress(id).then((detail) => {
this.setData({ locationState: detail }, () => {
const { isLegal, tips } = this.onVerifyInputLegal();
this.setData({
submitActive: isLegal,
});
this.privateData.verifyTips = tips;
});
});
},
onInputValue(e) {
const { item } = e.currentTarget.dataset;
if (item === 'address') {
const { selectedOptions = [] } = e.detail;
this.setData(
{
'locationState.provinceCode': selectedOptions[0].value,
'locationState.provinceName': selectedOptions[0].label,
'locationState.cityName': selectedOptions[1].label,
'locationState.cityCode': selectedOptions[1].value,
'locationState.districtCode': selectedOptions[2].value,
'locationState.districtName': selectedOptions[2].label,
areaPickerVisible: false,
},
() => {
const { isLegal, tips } = this.onVerifyInputLegal();
this.setData({
submitActive: isLegal,
});
this.privateData.verifyTips = tips;
},
);
} else {
const { value = '' } = e.detail;
this.setData(
{
[`locationState.${item}`]: value,
},
() => {
const { isLegal, tips } = this.onVerifyInputLegal();
this.setData({
submitActive: isLegal,
});
this.privateData.verifyTips = tips;
},
);
}
},
onPickArea() {
this.setData({ areaPickerVisible: true });
},
onPickLabels(e) {
const { item } = e.currentTarget.dataset;
const {
locationState: { labelIndex = undefined },
labels = [],
} = this.data;
let payload = {
labelIndex: item,
addressTag: labels[item].name,
};
if (item === labelIndex) {
payload = { labelIndex: null, addressTag: '' };
}
this.setData({
'locationState.labelIndex': payload.labelIndex,
});
this.triggerEvent('triggerUpdateValue', payload);
},
addLabels() {
this.setData({
visible: true,
});
},
confirmHandle() {
const { labels, labelValue } = this.data;
this.setData({
visible: false,
labels: [...labels, { id: labels[labels.length - 1].id + 1, name: labelValue }],
labelValue: '',
});
},
cancelHandle() {
this.setData({
visible: false,
labelValue: '',
});
},
onCheckDefaultAddress({ detail }) {
const { value } = detail;
this.setData({
'locationState.isDefault': value,
});
},
onVerifyInputLegal() {
const { name, phone, detailAddress, districtName } = this.data.locationState;
const prefixPhoneReg = String(this.properties.phoneReg || innerPhoneReg);
const prefixNameReg = String(this.properties.nameReg || innerNameReg);
const nameRegExp = new RegExp(prefixNameReg);
const phoneRegExp = new RegExp(prefixPhoneReg);
if (!name || !name.trim()) {
return {
isLegal: false,
tips: '请填写收货人',
};
}
if (!nameRegExp.test(name)) {
return {
isLegal: false,
tips: '收货人仅支持输入中文、英文(区分大小写)、数字',
};
}
if (!phone || !phone.trim()) {
return {
isLegal: false,
tips: '请填写手机号',
};
}
if (!phoneRegExp.test(phone)) {
return {
isLegal: false,
tips: '请填写正确的手机号',
};
}
if (!districtName || !districtName.trim()) {
return {
isLegal: false,
tips: '请选择省市区信息',
};
}
if (!detailAddress || !detailAddress.trim()) {
return {
isLegal: false,
tips: '请完善详细地址',
};
}
if (detailAddress && detailAddress.trim().length > 50) {
return {
isLegal: false,
tips: '详细地址不能超过50个字符',
};
}
return {
isLegal: true,
tips: '添加成功',
};
},
builtInSearch({ code, name }) {
return new Promise((resolve, reject) => {
wx.getSetting({
success: (res) => {
if (res.authSetting[code] === false) {
wx.showModal({
title: `获取${name}失败`,
content: `获取${name}失败,请在【右上角】-小程序【设置】项中,将【${name}】开启。`,
confirmText: '去设置',
confirmColor: '#FA550F',
cancelColor: '取消',
success(res) {
if (res.confirm) {
wx.openSetting({
success(settinRes) {
if (settinRes.authSetting[code] === true) {
resolve();
} else {
console.warn('用户未打开权限', name, code);
reject();
}
},
});
} else {
reject();
}
},
fail() {
reject();
},
});
} else {
resolve();
}
},
fail() {
reject();
},
});
});
},
onSearchAddress() {
this.builtInSearch({ code: 'scope.userLocation', name: '地址位置' }).then(() => {
wx.chooseLocation({
success: (res) => {
if (res.name) {
this.triggerEvent('addressParse', {
address: res.address,
name: res.name,
latitude: res.latitude,
longitude: res.longitude,
});
} else {
Toast({
context: this,
selector: '#t-toast',
message: '地点为空,请重新选择',
icon: '',
duration: 1000,
});
}
},
fail: function (res) {
console.warn(`wx.chooseLocation fail: ${JSON.stringify(res)}`);
if (res.errMsg !== 'chooseLocation:fail cancel') {
Toast({
context: this,
selector: '#t-toast',
message: '地点错误,请重新选择',
icon: '',
duration: 1000,
});
}
},
});
});
},
formSubmit() {
const { submitActive } = this.data;
if (!submitActive) {
Toast({
context: this,
selector: '#t-toast',
message: this.privateData.verifyTips,
icon: '',
duration: 1000,
});
return;
}
const { locationState } = this.data;
this.hasSava = true;
resolveAddress({
saasId: '88888888',
uid: `88888888205500`,
authToken: null,
id: locationState.addressId,
addressId: locationState.addressId,
phone: locationState.phone,
name: locationState.name,
countryName: locationState.countryName,
countryCode: locationState.countryCode,
provinceName: locationState.provinceName,
provinceCode: locationState.provinceCode,
cityName: locationState.cityName,
cityCode: locationState.cityCode,
districtName: locationState.districtName,
districtCode: locationState.districtCode,
detailAddress: locationState.detailAddress,
isDefault: locationState.isDefault === 1 ? 1 : 0,
addressTag: locationState.addressTag,
latitude: locationState.latitude,
longitude: locationState.longitude,
storeId: null,
});
wx.navigateBack({ delta: 1 });
},
getWeixinAddress(e) {
const { locationState } = this.data;
const weixinAddress = e.detail;
this.setData(
{
locationState: { ...locationState, ...weixinAddress },
},
() => {
const { isLegal, tips } = this.onVerifyInputLegal();
this.setData({
submitActive: isLegal,
});
this.privateData.verifyTips = tips;
},
);
},
});

View File

@@ -0,0 +1,16 @@
{
"navigationBarTitleText": "添加新地址",
"usingComponents": {
"t-textarea": "tdesign-miniprogram/textarea/textarea",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-input": "tdesign-miniprogram/input/input",
"t-button": "tdesign-miniprogram/button/button",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-switch": "tdesign-miniprogram/switch/switch",
"t-location": "/pages/usercenter/components/t-location/index",
"t-cascader": "tdesign-miniprogram/cascader/cascader"
}
}

View File

@@ -0,0 +1,134 @@
<view class="address-detail">
<view class="divider-line" />
<t-location
title="获取微信收获地址"
isCustomStyle
t-class="address-detail-wx-location"
bind:change="getWeixinAddress"
>
<t-icon class="address-detail-wx-arrow" name="arrow_forward" prefix="wr" color="#bbb" size="32rpx" />
</t-location>
<view class="divider-line" />
<view class="form-address">
<form class="form-content">
<t-cell-group>
<t-cell class="form-cell" t-class-title="t-cell-title" title="收货人" t-class-note="t-cell-note">
<t-input
class="t-input"
slot="note"
t-class="field-text"
borderless
data-item="name"
maxlength="20"
type="text"
value="{{locationState.name}}"
placeholder="您的姓名"
bind:change="onInputValue"
/>
</t-cell>
<t-cell class="form-cell" t-class-title="t-cell-title" title="手机号">
<t-input
slot="note"
class="t-input"
t-class="field-text"
borderless
type="number"
value="{{locationState.phone}}"
maxlength="11"
placeholder="联系您的手机号"
bind:change="onInputValue"
data-item="phone"
/>
</t-cell>
<t-cell class="form-cell" t-class-title="t-cell-title" title="地区">
<t-input
slot="note"
class="t-input"
t-class="field-text"
borderless
placeholder="省/市/区"
data-item="address"
value="{{locationState.provinceName ? locationState.provinceName+'/':'' }}{{locationState.cityName ? locationState.cityName+'/':''}}{{locationState.districtName}}"
catch:tap="onPickArea"
disabled
/>
<t-icon slot="right-icon" t-class="map" prefix="wr" name="location" catch:tap="onSearchAddress" />
</t-cell>
<t-cell class="form-cell" t-class-title="t-cell-title" title="详细地址" bordered="{{false}}">
<view slot="note" class="textarea__wrapper">
<t-textarea
slot="note"
type="text"
value="{{locationState.detailAddress}}"
placeholder="门牌号等(例如:10栋1001号)"
autosize
bind:change="onInputValue"
data-item="detailAddress"
/>
</view>
</t-cell>
<view class="divider-line" />
<t-cell
class="form-cell"
t-class-note="t-cell-note address__tag"
t-class-title="t-cell-title"
title="标签"
bordered="{{false}}"
>
<view class="t-input address-flex-box" slot="note">
<t-button
wx:for="{{labels}}"
wx:for-item="label"
wx:key="index"
size="extra-small"
t-class="label-list {{locationState.labelIndex === index ? 'active-btn':''}}"
bindtap="onPickLabels"
data-item="{{index}}"
>
{{label.name}}
</t-button>
<t-button size="extra-small" t-class="label-list" bindtap="addLabels">
<t-icon name="add" size="40rpx" color="#bbb" />
</t-button>
</view>
</t-cell>
<view class="divider-line" />
<t-cell title="设置为默认收货地址" bordered="{{false}}">
<t-switch
value="{{locationState.isDefault}}"
slot="note"
colors="{{['#0ABF5B', '#c6c6c6']}}"
bind:change="onCheckDefaultAddress"
/>
</t-cell>
</t-cell-group>
<view class="submit">
<t-button shape="round" block disabled="{{!submitActive}}" bind:tap="formSubmit"> 保存 </t-button>
</view>
</form>
</view>
<t-cascader
data-item="address"
data-type="1"
visible="{{areaPickerVisible}}"
theme="tab"
options="{{areaData}}"
value="{{locationState.districtCode}}"
title="选择地区"
bind:change="onInputValue"
></t-cascader>
</view>
<t-dialog
visible="{{visible}}"
t-class-confirm="dialog__button-confirm"
t-class-cancel="dialog__button-cancel"
title="填写标签名称"
confirm-btn="确定"
cancel-btn="取消"
bind:confirm="confirmHandle"
bind:cancel="cancelHandle"
>
<t-input slot="content" class="dialog__input" model:value="{{labelValue}}" placeholder="请输入标签名称" borderless />
</t-dialog>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,95 @@
page {
background-color: #f5f5f5;
}
page .divider-line {
width: 100%;
height: 20rpx;
background-color: #f5f5f5;
}
.address-flex-box {
display: flex;
flex-wrap: wrap;
}
.address-detail {
font-size: 30rpx;
}
.address-detail-wx-location {
background: #fff;
padding: 24rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.address-detail-wx-arrow {
align-items: flex-end;
}
.form-cell .t-cell__title {
width: 144rpx;
padding-right: 32rpx;
flex: none !important;
}
.textarea__wrapper {
width: 100%;
}
.textarea__wrapper .t-textarea {
padding: 0 !important;
}
.form-address .map {
font-size: 48rpx !important;
margin-left: 20rpx;
color: #9d9d9f;
}
.address__tag {
justify-content: flex-start !important;
}
.form-address .label-list {
background: #f5f5f5;
color: #333;
min-width: 100rpx;
margin-right: 32rpx;
font-size: 26rpx;
border: 2rpx solid transparent;
width: auto;
}
.form-address .label-list::after {
content: none;
}
.form-address .active-btn {
color: #fa4126;
border: 2rpx solid #fa4126;
background: rgba(255, 95, 21, 0.04);
}
.form-address .active-btn::after {
border: 4rpx solid #ff5f15;
}
.submit {
box-sizing: border-box;
padding: 64rpx 30rpx 88rpx 30rpx;
}
.submit .btn-submit-address {
background: #fa4126 !important;
color: #fff !important;
}
.dialog__button-confirm {
color: #fa4126 !important;
}
.form-address .form-content {
--td-input-vertical-padding: 0;
}
.dialog__input {
margin-top: 32rpx;
border-radius: 8rpx;
box-sizing: border-box;
--td-input-vertical-padding: 12px;
--td-input-bg-color: #f3f3f3;
}

View File

@@ -0,0 +1,33 @@
let addressPromise = [];
/** 地址编辑Promise */
export const getAddressPromise = () => {
let resolver;
let rejecter;
const nextPromise = new Promise((resolve, reject) => {
resolver = resolve;
rejecter = reject;
});
addressPromise.push({ resolver, rejecter });
return nextPromise;
};
/** 用户保存了一个地址 */
export const resolveAddress = (address) => {
const allAddress = [...addressPromise];
addressPromise = [];
console.info('用户保存了一个地址', address);
allAddress.forEach(({ resolver }) => resolver(address));
};
/** 取消编辑 */
export const rejectAddress = () => {
const allAddress = [...addressPromise];
addressPromise = [];
allAddress.forEach(({ rejecter }) => rejecter(new Error('cancel')));
};

View File

@@ -0,0 +1,191 @@
/* eslint-disable no-param-reassign */
import { fetchDeliveryAddressList } from '../../../../services/address/fetchAddress';
import Toast from 'tdesign-miniprogram/toast/index';
import { resolveAddress, rejectAddress } from './util';
import { getAddressPromise } from '../edit/util';
Page({
data: {
addressList: [],
deleteID: '',
showDeleteConfirm: false,
isOrderSure: false,
},
/** 选择模式 */
selectMode: false,
/** 是否已经选择地址不置为true的话页面离开时会触发取消选择行为 */
hasSelect: false,
onLoad(query) {
const { selectMode = '', isOrderSure = '', id = '' } = query;
this.setData({
isOrderSure: !!isOrderSure,
id,
});
this.selectMode = !!selectMode;
this.init();
},
init() {
this.getAddressList();
},
onUnload() {
if (this.selectMode && !this.hasSelect) {
rejectAddress();
}
},
getAddressList() {
const { id } = this.data;
fetchDeliveryAddressList().then((addressList) => {
addressList.forEach((address) => {
if (address.id === id) {
address.checked = true;
}
});
this.setData({ addressList });
});
},
getWXAddressHandle() {
wx.chooseAddress({
success: (res) => {
if (res.errMsg.indexOf('ok') === -1) {
Toast({
context: this,
selector: '#t-toast',
message: res.errMsg,
icon: '',
duration: 1000,
});
return;
}
Toast({
context: this,
selector: '#t-toast',
message: '添加成功',
icon: '',
duration: 1000,
});
const { length: len } = this.data.addressList;
this.setData({
[`addressList[${len}]`]: {
name: res.userName,
phoneNumber: res.telNumber,
address: `${res.provinceName}${res.cityName}${res.countryName}${res.detailInfo}`,
isDefault: 0,
tag: '微信地址',
id: len,
},
});
},
});
},
confirmDeleteHandle({ detail }) {
const { id } = detail || {};
if (id !== undefined) {
this.setData({ deleteID: id, showDeleteConfirm: true });
Toast({
context: this,
selector: '#t-toast',
message: '地址删除成功',
theme: 'success',
duration: 1000,
});
} else {
Toast({
context: this,
selector: '#t-toast',
message: '需要组件库发新版才能拿到地址ID',
icon: '',
duration: 1000,
});
}
},
deleteAddressHandle(e) {
const { id } = e.currentTarget.dataset;
this.setData({
addressList: this.data.addressList.filter((address) => address.id !== id),
deleteID: '',
showDeleteConfirm: false,
});
},
editAddressHandle({ detail }) {
this.waitForNewAddress();
const { id } = detail || {};
wx.navigateTo({ url: `/pages/usercenter/address/edit/index?id=${id}` });
},
selectHandle({ detail }) {
if (this.selectMode) {
this.hasSelect = true;
resolveAddress(detail);
wx.navigateBack({ delta: 1 });
} else {
this.editAddressHandle({ detail });
}
},
createHandle() {
this.waitForNewAddress();
wx.navigateTo({ url: '/pages/usercenter/address/edit/index' });
},
waitForNewAddress() {
getAddressPromise()
.then((newAddress) => {
let addressList = [...this.data.addressList];
newAddress.phoneNumber = newAddress.phone;
newAddress.address = `${newAddress.provinceName}${newAddress.cityName}${newAddress.districtName}${newAddress.detailAddress}`;
newAddress.tag = newAddress.addressTag;
if (!newAddress.addressId) {
newAddress.id = `${addressList.length}`;
newAddress.addressId = `${addressList.length}`;
if (newAddress.isDefault === 1) {
addressList = addressList.map((address) => {
address.isDefault = 0;
return address;
});
} else {
newAddress.isDefault = 0;
}
addressList.push(newAddress);
} else {
addressList = addressList.map((address) => {
if (address.addressId === newAddress.addressId) {
return newAddress;
}
return address;
});
}
addressList.sort((prevAddress, nextAddress) => {
if (prevAddress.isDefault && !nextAddress.isDefault) {
return -1;
}
if (!prevAddress.isDefault && nextAddress.isDefault) {
return 1;
}
return 0;
});
this.setData({
addressList: addressList,
});
})
.catch((e) => {
if (e.message !== 'cancel') {
Toast({
context: this,
selector: '#t-toast',
message: '地址编辑发生错误',
icon: '',
duration: 1000,
});
}
});
},
});

View File

@@ -0,0 +1,11 @@
{
"navigationBarTitleText": "收货地址",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "/components/webp-image/index",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-address-item": "../../components/ui-address-item/index",
"t-location": "../../components/t-location/index",
"t-empty": "tdesign-miniprogram/empty/empty"
}
}

View File

@@ -0,0 +1,49 @@
<view class="address-container">
<view class="address-list" wx:if="{{addressList.length > 0}}">
<block
wx:for="{{addressList}}"
wx:for-index="index"
wx:for-item="address"
wx:key="addressId"
>
<t-address-item
isDrawLine="{{index+1 !== addressList.length}}"
extra-space="{{extraSpace}}"
class-prefix="ym"
address="{{address}}"
data-id="{{address.id}}"
bind:onSelect="selectHandle"
bind:onDelete="deleteAddressHandle"
bind:onEdit="editAddressHandle"
/>
</block>
</view>
<view wx:else class="no-address">
<t-empty icon="" description="暂无收货地址,赶快添加吧" />
</view>
<view class="bottom-fixed">
<view class="btn-wrap">
<t-location
title="微信地址导入"
isOrderSure="{{isOrderSure}}"
isDisabledBtn="{{addressList.length >= 20}}"
navigateUrl="/pages/usercenter/address/edit/index"
navigateEvent="onWeixinAddressPassed"
t-class="location-btn"
isCustomStyle="{{true}}"
bind:navigate="waitForNewAddress"
/>
<view class="address-btn {{addressList.length >= 20 ? 'btn-default':''}}" bind:tap="createHandle">
<t-icon
name="add"
size="48rpx"
color="#fff"
t-class="custom-class"
/>
<text>新建收货地址</text>
</view>
</view>
<view class="footer" wx:if="{{addressList.length >= 20}}">最多支持添加20个收货地址</view>
</view>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,109 @@
page {
background: #f5f5f5;
height: 100%;
}
.address-container {
display: flex;
flex-direction: column;
align-items: stretch;
padding-bottom: calc(env(safe-area-inset-bottom) + 172rpx);
}
.address-container .address-list {
font-size: 24rpx;
background-color: #ffffff;
-webkit-overflow-scrolling: touch;
}
.address-list .no-address {
width: 750rpx;
padding-top: 30vh;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.address-list .no-address__icon {
width: 224rpx;
height: 224rpx;
}
.address-list .no-address__text {
font-size: 28rpx;
line-height: 40rpx;
color: #999999;
margin-top: 24rpx;
}
.address-container .bottom-fixed {
border-top: 1rpx solid #e5e5e5;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background: #fff;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
padding: 12rpx 32rpx calc(env(safe-area-inset-bottom) + 12rpx) 32rpx;
}
.address-container .btn-wrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
font-weight: bold;
}
.address-container .btn-wrap .location-btn {
width: 332rpx;
height: 88rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
color: #333;
position: relative;
}
.address-container .btn-wrap .location-btn::after {
content: '';
position: absolute; /* 把父视图设置为relative方便定位*/
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: 0 0;
box-sizing: border-box;
border-radius: 88rpx;
border: #dddddd 2rpx solid;
}
.address-container .btn-wrap .address-btn {
width: 332rpx;
height: 88rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #fa4126;
border-radius: 44rpx;
color: #fff;
}
.address-container .btn-wrap .btn-default {
background: #c6c6c6;
}
.address-container .bottom-fixed .footer {
margin-top: 10rpx;
display: inline-block;
width: 100%;
text-align: center;
font-size: 24rpx;
font-weight: 400;
color: #ff2525;
line-height: 60rpx;
height: 60rpx;
}
.address-container .message {
margin-top: 48rpx;
}
.address-container .custom-class {
margin-right: 12rpx;
font-weight: normal;
}

View File

@@ -0,0 +1,31 @@
let addressPromise = [];
/** 获取一个地址选择Promise */
export const getAddressPromise = () => {
let resolver;
let rejecter;
const nextPromise = new Promise((resolve, reject) => {
resolver = resolve;
rejecter = reject;
});
addressPromise.push({ resolver, rejecter });
return nextPromise;
};
/** 用户选择了一个地址 */
export const resolveAddress = (address) => {
const allAddress = [...addressPromise];
addressPromise = [];
allAddress.forEach(({ resolver }) => resolver(address));
};
/** 用户没有选择任何地址只是返回上一页了 */
export const rejectAddress = () => {
const allAddress = [...addressPromise];
addressPromise = [];
allAddress.forEach(({ rejecter }) => rejecter(new Error('cancel')));
};

View File

@@ -0,0 +1,37 @@
Component({
externalClasses: ['title-class', 'icon-class', 'number-class'],
options: {
multipleSlots: true,
},
properties: {
orderTagInfos: {
type: Array,
value: [],
},
title: {
type: String,
value: '我的订单',
},
desc: {
type: String,
value: '全部订单',
},
isTop: {
type: Boolean,
value: true,
},
classPrefix: {
type: String,
value: 'wr',
},
},
methods: {
onClickItem(e) {
this.triggerEvent('onClickItem', e.currentTarget.dataset.item);
},
onClickTop() {
this.triggerEvent('onClickTop', {});
},
},
});

View File

@@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"t-cell": "tdesign-miniprogram/cell/cell",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-badge": "tdesign-miniprogram/badge/badge",
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

View File

@@ -0,0 +1,37 @@
<view class="order-group">
<t-cell-group wx:if="{{isTop}}">
<t-cell
t-class="order-group__top"
t-class-left="order-group__left"
t-class-title="order-group__top__title"
t-class-note="order-group__top__note"
title="{{title}}"
note="{{desc}}"
bordered="{{false}}"
arrow
bind:tap="onClickTop"
/>
</t-cell-group>
<view class="order-group__content">
<view
class="order-group__item"
wx:for="{{orderTagInfos}}"
wx:for-item="item"
wx:key="index"
data-item="{{item}}"
bindtap="onClickItem"
>
<view class="order-group__item__icon icon-class">
<t-badge count="{{item.orderNum}}" max-count="{{99}}" color="#FF4646">
<t-icon
prefix="{{classPrefix}}"
name="{{item.iconName}}"
size="56rpx"
customStyle="background-image: -webkit-linear-gradient(90deg, #6a6a6a 0%,#929292 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;"
/>
</t-badge>
</view>
<view class="order-group__item__title title-class">{{item.title}}</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,56 @@
.order-group {
margin-bottom: 24rpx;
background-color: #ffffff;
border-radius: 16rpx 16rpx 0 0;
}
.order-group .order-group__top {
padding: 24rpx 18rpx 24rpx 32rpx;
border-radius: 16rpx 16rpx 0 0;
}
.order-group__top___title {
font-size: 32rpx;
line-height: 48rpx;
font-weight: bold;
}
.order-group__top__note {
font-size: 28rpx;
}
.order-group__content {
overflow: hidden;
width: 100%;
height: 164rpx;
display: flex;
background-color: #fff;
border-radius: 0 0 16rpx 16rpx;
}
.order-group__item {
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
}
.order-group__item:first-child {
border-radius: 0 0 0 16rpx;
}
.order-group__item:last-child {
border-radius: 0 0 16rpx 0;
}
.order-group__item__title {
font-size: 24rpx;
color: #666;
line-height: 32rpx;
}
.order-group__item__icon {
margin-bottom: 20rpx;
width: 56rpx;
height: 56rpx;
position: relative;
}
.order-group__top__title {
font-weight: bold;
}
.order-group .order-group__left {
margin-right: 0;
}

View File

@@ -0,0 +1,124 @@
import { getPermission } from '../../../../utils/getPermission';
import { phoneRegCheck } from '../../../../utils/util';
import Toast from 'tdesign-miniprogram/toast/index';
import { addressParse } from '../../../../utils/addressParse';
import { resolveAddress, rejectAddress } from '../../address/list/util';
Component({
externalClasses: ['t-class'],
properties: {
title: {
type: String,
},
navigateUrl: {
type: String,
},
navigateEvent: {
type: String,
},
isCustomStyle: {
type: Boolean,
value: false,
},
isDisabledBtn: {
type: Boolean,
value: false,
},
isOrderSure: {
type: Boolean,
value: false,
},
},
methods: {
getWxLocation() {
if (this.properties.isDisabledBtn) return;
getPermission({ code: 'scope.address', name: '通讯地址' }).then(() => {
wx.chooseAddress({
success: async (options) => {
const { provinceName, cityName, countyName, detailInfo, userName, telNumber } = options;
if (!phoneRegCheck(telNumber)) {
Toast({
context: this,
selector: '#t-toast',
message: '请填写正确的手机号',
});
return;
}
const target = {
name: userName,
phone: telNumber,
countryName: '中国',
countryCode: 'chn',
detailAddress: detailInfo,
provinceName: provinceName,
cityName: cityName,
districtName: countyName,
isDefault: false,
isOrderSure: this.properties.isOrderSure,
};
try {
const { provinceCode, cityCode, districtCode } = await addressParse(provinceName, cityName, countyName);
const params = Object.assign(target, {
provinceCode,
cityCode,
districtCode,
});
if (this.properties.isOrderSure) {
this.onHandleSubmit(params);
} else if (this.properties.navigateUrl != '') {
const { navigateEvent } = this.properties;
this.triggerEvent('navigate');
wx.navigateTo({
url: this.properties.navigateUrl,
success: function (res) {
res.eventChannel.emit(navigateEvent, params);
},
});
} else {
this.triggerEvent('change', params);
}
} catch (error) {
wx.showToast({ title: '地址解析出错,请稍后再试', icon: 'none' });
}
},
fail(err) {
console.warn('未选择微信收货地址', err);
},
});
});
},
async queryAddress(addressId) {
try {
const { data } = await apis.userInfo.queryAddress({ addressId });
return data.userAddressVO;
} catch (err) {
console.error('查询地址错误', err);
throw err;
}
},
findPage(pageRouteUrl) {
const currentRoutes = getCurrentPages().map((v) => v.route);
return currentRoutes.indexOf(pageRouteUrl);
},
async onHandleSubmit(params) {
try {
const orderPageDeltaNum = this.findPage('pages/order/order-confirm/index');
if (orderPageDeltaNum > -1) {
wx.navigateBack({ delta: 1 });
resolveAddress(params);
return;
}
} catch (err) {
rejectAddress(params);
console.error(err);
}
},
},
});

View File

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

View File

@@ -0,0 +1,16 @@
<view class="wx-address t-class" bind:tap="getWxLocation">
<block wx:if="{{isCustomStyle}}">
<view class="wx-address-custom">
<t-icon prefix="wr" t-class="weixin" color="#0ABF5B" name="wechat" size="48rpx" />
<text>{{title}}</text>
</view>
<slot />
</block>
<block wx:else>
<t-cell title="{{title}}" title-class="cell__title" wr-class="cell" border="{{false}}">
<t-icon t-class="weixin" slot="icon" color="#0ABF5B" name="logo-windows" size="48rpx" />
<t-icon slot="right-icon" name="chevron-right" class="custom-icon" color="#bbb" />
</t-cell>
</block>
</view>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,19 @@
.wx-address .weixin {
display: inline-block;
font-size: 48rpx !important;
margin-right: 20rpx;
font-weight: normal;
}
.wx-address .cell {
padding: 32rpx 30rpx;
border-radius: 8rpx;
}
.wx-address .cell__title {
font-size: 30rpx;
color: #333333;
}
.wx-address-custom {
display: flex;
align-items: center;
font-size: 32rpx;
}

View File

@@ -0,0 +1,46 @@
Component({
options: {
addGlobalClass: true,
multipleSlots: true,
},
properties: {
address: {
type: Object,
value: {},
},
customIcon: {
type: String,
value: 'edit-1',
},
extraSpace: {
type: Boolean,
value: true,
},
isDrawLine: {
type: Boolean,
value: true,
},
},
externalClasses: [
'item-wrapper-class',
'title-class',
'default-tag-class',
'normal-tag-class',
'address-info-class',
'delete-class',
],
methods: {
onDelete(e) {
const { item } = e.currentTarget.dataset;
this.triggerEvent('onDelete', item);
},
onSelect(e) {
const { item } = e.currentTarget.dataset;
this.triggerEvent('onSelect', item);
},
onEdit(e) {
const { item } = e.currentTarget.dataset;
this.triggerEvent('onEdit', item);
},
},
});

View File

@@ -0,0 +1,8 @@
{
"component": true,
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-tag": "tdesign-miniprogram/tag/tag",
"t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell"
}
}

View File

@@ -0,0 +1,30 @@
<wxs module="phoneReg">
var toHide = function(array) { var mphone = array.substring(0, 3) + '****' + array.substring(7); return mphone; }
module.exports.toHide = toHide;
</wxs>
<view class="address-item-wrapper item-wrapper-class">
<t-swipe-cell class="swipe-out">
<view class="address {{isDrawLine ? 'draw-line' : ''}}" bindtap="onSelect" data-item="{{address}}">
<view class="address-left" wx:if="{{extraSpace}}">
<t-icon wx:if="{{address.checked}}" name="check" color="#FA4126" class-prefix="{{classPrefix}}" size="46rpx" />
</view>
<view class="address-content">
<view class="title title-class">
<text class="text-style">{{address.name}}</text>
<text>{{phoneReg.toHide(address.phoneNumber || '')}}</text>
</view>
<view class="label-adds">
<text class="adds address-info-class">
<text wx:if="{{address.isDefault === 1}}" class="tag tag-default default-tag-class">默认</text>
<text wx:if="{{address.tag}}" class="tag tag-primary normal-tag-class">{{address.tag}}</text>
<text class="address-text">{{address.address}}</text>
</text>
</view>
</view>
<view catch:tap="onEdit" data-item="{{address}}" class="address-edit">
<t-icon name="{{customIcon}}" class-prefix="{{classPrefix}}" size="46rpx" color="#BBBBBB" />
</view>
</view>
<view slot="right" class="swipe-right-del delete-class" bindtap="onDelete" data-item="{{address}}"> 删除 </view>
</t-swipe-cell>
</view>

View File

@@ -0,0 +1,103 @@
.address-item-wrapper {
overflow: hidden;
}
.address-item-wrapper .swipe-out .wr-swiper-cell {
margin-top: 20rpx;
}
.address-item-wrapper .swipe-out .swipe-right-del {
display: flex;
justify-content: center;
align-items: center;
width: 144rpx;
height: 100%;
background-color: #fa4126;
color: #fff;
font-size: 28rpx;
line-height: 40rpx;
}
.address-item-wrapper .draw-line {
position: relative;
}
.address-item-wrapper .draw-line::after {
content: '';
position: absolute;
bottom: 0;
left: 32rpx;
width: 200%;
height: 2rpx;
transform: scale(0.5);
transform-origin: 0 0;
box-sizing: border-box;
border-bottom: #e5e5e5 2rpx solid;
}
.address-item-wrapper .address {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background-color: #fff;
}
.address-item-wrapper .address .address-edit {
padding: 20rpx 0 20rpx 46rpx;
}
.address-item-wrapper .address .address-left {
width: 80rpx;
display: flex;
justify-content: center;
}
.address-item-wrapper .address .address-content {
display: flex;
flex-direction: column;
flex: 1;
}
.address-item-wrapper .address .address-content .title {
font-size: 32rpx;
line-height: 48rpx;
margin-bottom: 16rpx;
color: #333333;
font-weight: bold;
display: flex;
}
.address-item-wrapper .address .address-content .title .text-style {
margin-right: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 280rpx;
}
.address-item-wrapper .address .address-content .label-adds {
display: flex;
}
.address-item-wrapper .address .address-content .label-adds .adds {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
color: #999999;
}
.address-item-wrapper .address .address-content .label-adds .tag {
display: inline-block;
padding: 0rpx 8rpx;
min-width: 40rpx;
height: 32rpx;
border-radius: 18rpx;
font-size: 20rpx;
line-height: 32rpx;
text-align: center;
margin-right: 8rpx;
vertical-align: text-top;
}
.address-item-wrapper .address .address-content .label-adds .tag-default {
background: #ffece9;
color: #fa4126;
}
.address-item-wrapper .address .address-content .label-adds .tag-primary {
background: #f0f1ff;
color: #5a66ff;
}
.address-item-wrapper .address .address-content .label-adds .address-text {
font-size: 28rpx;
line-height: 40rpx;
color: #999999;
}

View File

@@ -0,0 +1,73 @@
Component({
properties: {
show: {
type: Boolean,
observer(show) {
if (!show) return;
this.updateDivisions();
},
},
title: {
type: String,
value: '',
},
value: {
type: String,
value: '',
observer() {
if (!this.data.show) return;
this.updateDivisions();
},
},
pickerOptions: {
type: Array,
value: [],
observer() {
if (!this.data.show) return;
this.updateDivisions();
},
},
headerVisible: {
type: Boolean,
value: true,
},
},
data: {
pickerValue: [],
},
methods: {
updateDivisions() {
const { pickerOptions, value } = this.data;
const index = (pickerOptions || []).findIndex(
(item) => item.code === value,
);
setTimeout(() => {
this.setData({ pickerValue: index >= 0 ? [index] : [0] });
}, 0);
},
getAreaByIndex(indexes) {
const { pickerOptions } = this.data;
return pickerOptions[indexes.toString()];
},
onChange(e) {
const currentValue = e.detail.value;
const target = this.getAreaByIndex(currentValue);
if (target === null) return;
this.setData({ pickerValue: currentValue });
this.triggerEvent('change', { value: target.code, target: target });
},
onConfirm() {
const target = this.getAreaByIndex(this.data.pickerValue);
this.triggerEvent('confirm', { value: target?.code, target });
},
onClose() {
this.triggerEvent('close');
},
},
});

View File

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

View File

@@ -0,0 +1,21 @@
<t-popup visible="{{show}}" placement="bottom">
<view class="city-picker-box" slot="content">
<view wx:if="{{headerVisible}}" class="city-picker-header city-picker-more">
<view class="btn" hover-class="btn__active" catch:tap="onClose">取消</view>
<view wx:if="{{title}}" class="title">{{title}}</view>
<view class="btn primary" hover-class="btn__active" catch:tap="onConfirm">确定</view>
</view>
<view wx:else class="city-picker-header">
<view wx:if="{{title}}" class="title">{{title}}</view>
</view>
<picker-view class="picker" indicator-class="picker-center-row" value="{{pickerValue}}" bind:change="onChange">
<picker-view-column class="picker-column">
<view wx:for="{{ pickerOptions }}" wx:key="code">{{ item.name }}</view>
</picker-view-column>
</picker-view>
<view class="city-picker-footer" wx:if="{{!headerVisible}}">
<view class="btn" hover-class="btn__active" catch:tap="onClose">取消</view>
<view class="btn primary" hover-class="btn__active" catch:tap="onConfirm">确定</view>
</view>
</view>
</t-popup>

View File

@@ -0,0 +1,102 @@
.city-picker-container {
opacity: 0;
position: fixed;
top: 100vh;
left: 0;
right: 0;
height: 100vh;
z-index: 100;
}
.city-picker-container.show {
top: 0;
opacity: 1;
}
.city-picker-container.show .city-picker-box {
bottom: 0;
}
.city-picker-shadow {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.65);
}
.city-picker-header {
height: 100rpx;
line-height: 100rpx;
text-align: center;
font-size: 32rpx;
color: #333333;
}
.city-picker-more {
display: flex;
justify-content: space-between;
align-items: center;
}
.city-picker-footer {
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.city-picker-footer .btn {
width: 330rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
color: #666666;
font-size: 32rpx;
position: relative;
}
.city-picker-footer .btn__active {
opacity: 0.5;
}
.city-picker-footer .btn::after {
display: block;
content: ' ';
position: absolute;
left: -50%;
right: -50%;
top: -50%;
bottom: -50%;
transform: scale(0.5);
border: 1rpx solid #999999;
border-radius: 16rpx;
}
.city-picker-footer .btn.primary {
color: #fa550f;
}
.city-picker-footer .btn.primary::after {
border-color: #fa550f;
}
.picker-column:not(:first-child) {
margin-left: 40rpx;
}
.city-picker-box {
position: absolute;
bottom: -100%;
transition: 0.3s bottom ease-in-out;
left: 0;
right: 0;
z-index: 100;
background-color: #fff;
padding: 0 30rpx;
color: #333333;
font-size: 34rpx;
border-radius: 20rpx 20rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
.show .city-picker-shadow {
display: block;
}
.picker {
height: 300rpx;
margin: 50rpx 0;
line-height: 88rpx;
text-align: center;
}
/* 似乎小程序picker-view的bugindicator-class仅height生效其他诸如line-height、text-align等放到父class中设置 */
.picker-center-row {
height: 88rpx;
}

View File

@@ -0,0 +1,35 @@
const AuthStepType = {
ONE: 1,
TWO: 2,
THREE: 3,
};
Component({
options: {
multipleSlots: true,
},
properties: {
currAuthStep: {
type: Number,
value: AuthStepType.ONE,
},
userInfo: {
type: Object,
value: {},
},
isNeedGetUserInfo: {
type: Boolean,
value: false,
},
},
data: {
defaultAvatarUrl:
'https://cdn-we-retail.ym.tencent.com/miniapp/usercenter/icon-user-center-avatar@2x.png',
AuthStepType,
},
methods: {
gotoUserEditPage() {
this.triggerEvent('gotoUserEditPage');
},
},
});

View File

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

View File

@@ -0,0 +1,34 @@
<view class="user-center-card">
<!-- 未登录的情况 -->
<block wx:if="{{currAuthStep === AuthStepType.ONE}}">
<view class="user-center-card__header" bind:tap="gotoUserEditPage">
<t-avatar image="{{userInfo.avatarUrl || defaultAvatarUrl}}" class="user-center-card__header__avatar" />
<view class="user-center-card__header__name">{{'请登录'}}</view>
</view>
</block>
<!-- 已登录但未授权用户信息情况 -->
<block wx:if="{{currAuthStep === AuthStepType.TWO}}">
<view class="user-center-card__header">
<t-avatar image="{{userInfo.avatarUrl || defaultAvatarUrl}}" class="user-center-card__header__avatar" />
<view class="user-center-card__header__name">{{userInfo.nickName || '微信用户'}}</view>
<!-- 需要授权用户信息通过slot添加弹窗 -->
<view class="user-center-card__header__transparent" wx:if="{{isNeedGetUserInfo}}">
<slot name="getUserInfo" />
</view>
<!-- 不需要授权用户信息仍然触发gotoUserEditPage事件 -->
<view class="user-center-card__header__transparent" bind:tap="gotoUserEditPage" wx:else></view>
</view>
</block>
<!-- 已登录且已经授权用户信息的情况 -->
<block wx:if="{{currAuthStep === AuthStepType.THREE}}">
<view class="user-center-card__header" bind:tap="gotoUserEditPage">
<t-avatar
t-class="avatar"
mode="aspectFill"
class="user-center-card__header__avatar"
image="{{userInfo.avatarUrl || defaultAvatarUrl}}"
/>
<view class="user-center-card__header__name">{{userInfo.nickName || '微信用户'}}</view>
</view>
</block>
</view>

View File

@@ -0,0 +1,48 @@
.user-center-card {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 480rpx;
background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/template/user-center-bg-v1.png');
background-size: cover;
background-repeat: no-repeat;
padding: 0 24rpx;
}
.user-center-card__header {
margin-top: 192rpx;
margin-bottom: 48rpx;
height: 96rpx;
line-height: 48rpx;
display: flex;
justify-content: flex-start;
align-items: center;
color: #333;
position: relative;
}
.user-center-card__header__avatar {
width: 96rpx;
height: 96rpx;
border-radius: 48rpx;
overflow: hidden;
}
.user-center-card__header__name {
font-size: 36rpx;
line-height: 48rpx;
color: #333;
font-weight: bold;
margin-left: 24rpx;
margin-right: 16rpx;
}
.user-center-card__header__transparent {
position: absolute;
left: 0;
top: 0;
background-color: transparent;
height: 100%;
width: 100%;
}
.user-center-card__icon {
line-height: 96rpx;
}

View File

@@ -0,0 +1,240 @@
import { fetchUserCenter } from '../../services/usercenter/fetchUsercenter';
import Toast from 'tdesign-miniprogram/toast/index';
const menuData = [
[
{
title: '收货地址',
tit: '',
url: '',
type: 'address',
},
{
title: '优惠券',
tit: '',
url: '',
type: 'coupon',
},
{
title: '积分',
tit: '',
url: '',
type: 'point',
},
],
[
{
title: '帮助中心',
tit: '',
url: '',
type: 'help-center',
},
{
title: '客服热线',
tit: '',
url: '',
type: 'service',
icon: 'service',
},
],
];
const orderTagInfos = [
{
title: '待付款',
iconName: 'wallet',
orderNum: 0,
tabType: 5,
status: 1,
},
{
title: '待发货',
iconName: 'deliver',
orderNum: 0,
tabType: 10,
status: 1,
},
{
title: '待收货',
iconName: 'package',
orderNum: 0,
tabType: 40,
status: 1,
},
{
title: '待评价',
iconName: 'comment',
orderNum: 0,
tabType: 60,
status: 1,
},
{
title: '退款/售后',
iconName: 'exchang',
orderNum: 0,
tabType: 0,
status: 1,
},
];
const getDefaultData = () => ({
showMakePhone: false,
userInfo: {
avatarUrl: '',
nickName: '正在登录...',
phoneNumber: '',
},
menuData,
orderTagInfos,
customerServiceInfo: {},
currAuthStep: 1,
showKefu: true,
versionNo: '',
});
Page({
data: getDefaultData(),
onLoad() {
this.getVersionInfo();
},
onShow() {
this.getTabBar().init();
this.init();
},
onPullDownRefresh() {
this.init();
},
init() {
this.fetUseriInfoHandle();
},
fetUseriInfoHandle() {
fetchUserCenter().then(
({
userInfo,
countsData,
orderTagInfos: orderInfo,
customerServiceInfo,
}) => {
// eslint-disable-next-line no-unused-expressions
menuData?.[0].forEach((v) => {
countsData.forEach((counts) => {
if (counts.type === v.type) {
// eslint-disable-next-line no-param-reassign
v.tit = counts.num;
}
});
});
const info = orderTagInfos.map((v, index) => ({
...v,
...orderInfo[index],
}));
this.setData({
userInfo,
menuData,
orderTagInfos: info,
customerServiceInfo,
currAuthStep: 2,
});
wx.stopPullDownRefresh();
},
);
},
onClickCell({ currentTarget }) {
const { type } = currentTarget.dataset;
switch (type) {
case 'address': {
wx.navigateTo({ url: '/pages/usercenter/address/list/index' });
break;
}
case 'service': {
this.openMakePhone();
break;
}
case 'help-center': {
Toast({
context: this,
selector: '#t-toast',
message: '你点击了帮助中心',
icon: '',
duration: 1000,
});
break;
}
case 'point': {
Toast({
context: this,
selector: '#t-toast',
message: '你点击了积分菜单',
icon: '',
duration: 1000,
});
break;
}
case 'coupon': {
wx.navigateTo({ url: '/pages/coupon/coupon-list/index' });
break;
}
default: {
Toast({
context: this,
selector: '#t-toast',
message: '未知跳转',
icon: '',
duration: 1000,
});
break;
}
}
},
jumpNav(e) {
const status = e.detail.tabType;
if (status === 0) {
wx.navigateTo({ url: '/pages/order/after-service-list/index' });
} else {
wx.navigateTo({ url: `/pages/order/order-list/index?status=${status}` });
}
},
jumpAllOrder() {
wx.navigateTo({ url: '/pages/order/order-list/index' });
},
openMakePhone() {
this.setData({ showMakePhone: true });
},
closeMakePhone() {
this.setData({ showMakePhone: false });
},
call() {
wx.makePhoneCall({
phoneNumber: this.data.customerServiceInfo.servicePhone,
});
},
gotoUserEditPage() {
const { currAuthStep } = this.data;
if (currAuthStep === 2) {
wx.navigateTo({ url: '/pages/usercenter/person-info/index' });
} else {
this.fetUseriInfoHandle();
}
},
getVersionInfo() {
const versionInfo = wx.getAccountInfoSync();
const { version, envVersion = __wxConfig } = versionInfo.miniProgram;
this.setData({
versionNo: envVersion === 'release' ? version : envVersion,
});
},
});

View File

@@ -0,0 +1,14 @@
{
"navigationBarTitleText": "个人中心",
"navigationStyle": "custom",
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-user-center-card": "./components/user-center-card/index",
"t-order-group": "./components/order-group/index",
"t-toast": "tdesign-miniprogram/toast/toast"
},
"enablePullDownRefresh": true
}

View File

@@ -0,0 +1,47 @@
<t-user-center-card
userInfo="{{userInfo}}"
isPhoneHide="{{true}}"
name-class="custom-name-class"
phone-class="custom-phone-class"
avatar-class="customer-avatar-class"
currAuthStep="{{currAuthStep}}"
bind:gotoUserEditPage="gotoUserEditPage"
/>
<view class="content-wrapper">
<view class="order-group-wrapper">
<t-order-group orderTagInfos="{{orderTagInfos}}" bind:onClickTop="jumpAllOrder" bind:onClickItem="jumpNav" />
</view>
<view wx:for="{{menuData}}" wx:key="item" class="cell-box">
<t-cell-group>
<t-cell
wx:for="{{item}}"
wx:for-item="xitem"
wx:for-index="xindex"
wx:key="xindex"
title="{{xitem.title}}"
arrow="{{!xitem.icon}}"
note="{{xitem.tit}}"
data-type="{{xitem.type}}"
bordered="{{false}}"
bind:click="onClickCell"
t-class="t-cell-padding"
t-class-note="order-group-note"
t-class-left="order-group__left"
>
<t-icon name="{{xitem.icon}}" size="48rpx" slot="note" />
</t-cell>
</t-cell-group>
</view>
</view>
<view class="footer__version" wx:if="{{versionNo !== ''}}">当前版本 {{versionNo}}</view>
<t-popup visible="{{showMakePhone}}" placement="bottom" bind:visible-change="closeMakePhone" data-index="2">
<view class="popup-content">
<view class="popup-title border-bottom-1px" wx:if="{{customerServiceInfo.serviceTimeDuration}}">
服务时间: {{customerServiceInfo.serviceTimeDuration}}
</view>
<view class="popup-phone {{showKefu ? 'border-bottom-1px' : ''}}" bind:tap="call">电话客服</view>
<button class="popup-phone border-bottom-1px online" open-type="contact" wx:if="{{showKefu}}">在线客服</button>
<view class="popup-close" bind:tap="closeMakePhone">取消</view>
</view>
</t-popup>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,146 @@
page {
background-color: #f5f5f5;
}
.content-wrapper {
margin-top: 340rpx;
position: relative;
padding: 0 30rpx;
}
.main-content {
height: 500rpx;
}
.order-group-wrapper {
margin-bottom: 16rpx;
}
.order-group-note {
font-size: 28rpx;
}
.cell-box {
border-radius: 10rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.icon-color {
color: #aaa;
}
.cell-class {
height: 100rpx;
display: flex;
align-items: center;
}
.order-content {
overflow: hidden;
width: 100%;
display: flex;
background-color: #fff;
border-radius: 16rpx;
}
.order-item {
flex: 1;
height: 180rpx;
overflow: hidden;
position: relative;
text-align: center;
}
.order-content-box {
margin: auto;
position: absolute;
width: 100%;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.order-content-t {
margin-top: 10rpx;
font-size: 24rpx;
color: #333;
letter-spacing: 0;
text-align: center;
}
.popup-content {
background: #f5f5f5;
margin-bottom: env(safe-area-inset-bottom);
border-radius: 16rpx 16rpx 0 0;
}
.popup-content .popup-title {
background: #fff;
text-align: center;
font-size: 24rpx;
color: #999;
height: 112rpx;
text-align: center;
line-height: 112rpx;
border-radius: 16rpx 16rpx 0 0;
}
.border-bottom-1px {
position: relative;
}
.border-bottom-1px::after {
position: absolute;
display: block;
content: '';
box-sizing: border-box;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: left top;
border-bottom: 2rpx solid #e5e5e5;
}
.popup-content .popup-phone,
.popup-content .popup-close {
background: #fff;
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-size: 30rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333;
}
.popup-content .popup-phone.online {
margin-bottom: 20rpx;
}
.popup-content .popup-phone.online::after {
content: none;
}
.popup-content .popup-close {
color: #333;
border: 0;
margin-top: 16rpx;
}
.my-order {
border-radius: 10rpx;
}
.footer__version {
text-align: center;
margin-top: 50rpx;
color: #999;
margin-bottom: 4rpx;
font-size: 24rpx;
line-height: 32rpx;
}
.cell-box .order-group__left {
margin-right: 0;
}
.cell-box .t-cell-padding {
padding: 24rpx 18rpx 24rpx 32rpx;
}

View File

@@ -0,0 +1,19 @@
Page({
data: {
nameValue: '',
},
onLoad(options) {
const { name } = options;
this.setData({
nameValue: name,
});
},
onSubmit() {
wx.navigateBack({ backRefresh: true });
},
clearContent() {
this.setData({
nameValue: '',
});
},
});

View File

@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "昵称",
"usingComponents": {
"t-input": "tdesign-miniprogram/input/input",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-button": "tdesign-miniprogram/button/button"
}
}

View File

@@ -0,0 +1,14 @@
<view class="name-edit">
<t-input
borderless
model:value="{{nameValue}}"
placeholder="请输入文字"
label="昵称"
clearable
bind:clear="clearContent"
/>
<view class="name-edit__input--desc"> 最多可输入15个字 </view>
<view class="name-edit__wrapper">
<t-button block shape="round" disabled="{{!nameValue}}" bind:tap="onSubmit">保存</t-button>
</view>
</view>

View File

@@ -0,0 +1,18 @@
page {
background-color: #f5f5f5;
}
page view {
box-sizing: border-box;
}
.name-edit {
padding-top: 20rpx;
}
.name-edit .name-edit__input--desc {
font-size: 26rpx;
padding: 16rpx 32rpx;
color: #999;
margin-bottom: 200rpx;
}
.name-edit .name-edit__wrapper {
margin: 0 32rpx;
}

View File

@@ -0,0 +1,122 @@
import { fetchPerson } from '../../../services/usercenter/fetchPerson';
import { phoneEncryption } from '../../../utils/util';
import Toast from 'tdesign-miniprogram/toast/index';
Page({
data: {
personInfo: {
avatarUrl: '',
nickName: '',
gender: 0,
phoneNumber: '',
},
showUnbindConfirm: false,
pickerOptions: [
{
name: '男',
code: '1',
},
{
name: '女',
code: '2',
},
],
typeVisible: false,
genderMap: ['', '男', '女'],
},
onLoad() {
this.init();
},
init() {
this.fetchData();
},
fetchData() {
fetchPerson().then((personInfo) => {
this.setData({
personInfo,
'personInfo.phoneNumber': phoneEncryption(personInfo.phoneNumber),
});
});
},
onClickCell({ currentTarget }) {
const { dataset } = currentTarget;
const { nickName } = this.data.personInfo;
switch (dataset.type) {
case 'gender':
this.setData({
typeVisible: true,
});
break;
case 'name':
wx.navigateTo({
url: `/pages/usercenter/name-edit/index?name=${nickName}`,
});
break;
case 'avatarUrl':
this.toModifyAvatar();
break;
default: {
break;
}
}
},
onClose() {
this.setData({
typeVisible: false,
});
},
onConfirm(e) {
const { value } = e.detail;
this.setData(
{
typeVisible: false,
'personInfo.gender': value,
},
() => {
Toast({
context: this,
selector: '#t-toast',
message: '设置成功',
theme: 'success',
});
},
);
},
async toModifyAvatar() {
try {
const tempFilePath = await new Promise((resolve, reject) => {
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const { path, size } = res.tempFiles[0];
if (size <= 10485760) {
resolve(path);
} else {
reject({ errMsg: '图片大小超出限制,请重新上传' });
}
},
fail: (err) => reject(err),
});
});
const tempUrlArr = tempFilePath.split('/');
const tempFileName = tempUrlArr[tempUrlArr.length - 1];
Toast({
context: this,
selector: '#t-toast',
message: `已选择图片-${tempFileName}`,
theme: 'success',
});
} catch (error) {
if (error.errMsg === 'chooseImage:fail cancel') return;
Toast({
context: this,
selector: '#t-toast',
message: error.errMsg || error.msg || '修改头像出错了',
theme: 'error',
});
}
},
});

View File

@@ -0,0 +1,12 @@
{
"navigationBarTitleText": "个人资料",
"usingComponents": {
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-button": "tdesign-miniprogram/button/button",
"t-image": "/components/webp-image/index",
"t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-toast": "tdesign-miniprogram/toast/toast",
"t-select-picker": "../components/ui-select-picker/index"
}
}

View File

@@ -0,0 +1,54 @@
<view class="person-info">
<t-cell-group>
<t-cell
title="头像"
center="{{true}}"
data-type="avatarUrl"
bind:click="onClickCell"
arrow
t-class-left="order-group__left"
>
<t-image slot="note" src="{{personInfo.avatarUrl}}" t-class="avatarUrl" mode="aspectFill" />
</t-cell>
<t-cell
title="昵称"
arrow
note="{{personInfo.nickName}}"
data-type="name"
bind:click="onClickCell"
t-class="t-cell-class"
t-class-left="order-group__left"
/>
<t-cell
title="性别"
arrow
note="{{genderMap[personInfo.gender]}}"
data-type="gender"
bind:click="onClickCell"
t-class="t-cell-class"
t-class-left="order-group__left"
/>
<t-cell
bordered="{{false}}"
title="手机号"
arrow
note="{{personInfo.phoneNumber ? personInfo.phoneNumber : '去绑定手机号'}}"
data-type="phoneNumber"
bind:click="onClickCell"
t-class="t-cell-class"
t-class-left="order-group__left"
/>
</t-cell-group>
</view>
<view class="person-info__wrapper">
<view class="person-info__btn" bind:tap="openUnbindConfirm"> 切换账号登录 </view>
</view>
<t-select-picker
show="{{typeVisible}}"
picker-options="{{pickerOptions}}"
title="选择性别"
value="{{personInfo.gender}}"
bind:confirm="onConfirm"
bind:close="onClose"
/>
<t-toast id="t-toast" />

View File

@@ -0,0 +1,45 @@
:host {
background-color: #f5f5f5;
}
page view {
box-sizing: border-box;
}
.person-info {
padding-top: 20rpx;
}
.person-info__btn {
width: 100%;
border: 2rpx solid #ddd;
border-radius: 48rpx;
padding: 18rpx 0;
display: flex;
align-self: center;
justify-content: center;
}
.person-info__wrapper {
width: 100%;
padding: 0 32rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
position: absolute;
bottom: 0;
left: 0;
}
.avatarUrl {
width: 80rpx;
height: 80rpx;
border-radius: 50% !important;
overflow: hidden;
}
.t-class-confirm {
color: #fa550f !important;
}
.person-info .order-group__left {
margin-right: 0;
}
.person-info .t-cell-class {
height: 112rpx;
}