Commit 924930ca authored by wanglongchao's avatar wanglongchao

feat: 首次提交

parents
/*
* Eslint config file
* Documentation: https://eslint.org/docs/user-guide/configuring/
* Install the Eslint extension before using this feature.
*/
module.exports = {
env: {
es6: true,
browser: true,
node: true,
},
ecmaFeatures: {
modules: true,
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
globals: {
wx: true,
App: true,
Page: true,
getCurrentPages: true,
getApp: true,
Component: true,
requirePlugin: true,
requireMiniProgram: true,
},
// extends: 'eslint:recommended',
rules: {},
}
miniprogram_npm
node_modules
\ No newline at end of file
## 安装
### 使用 NPM
小程序已经支持使用 NPM 安装第三方包。
具体使用方式,可以参考小程序官网文档: [《NPM 支持》](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html?search-key=npm)
```bash
npm i tdesign-miniprogram -S --production
```
> 建议使用 NPM,不再推荐“源码拷贝的方式”
## 使用组件
以按钮组件为例,只需要在 `JSON` 文件中引入按钮对应的自定义组件即可
```json
{
"usingComponents": {
"t-button": "tdesign-miniprogram/button/button"
}
}
\ No newline at end of file
const { loginStatus } = require("./utils/common");
App({
onLaunch() {
const TOKEN = wx.getStorageSync('token') == '' ? '' : 'Bearer ' + wx.getStorageSync('token')
this.globalData.token = TOKEN
this.globalData.userId = wx.getStorageSync('userId')
loginStatus(TOKEN.length > 0)
},
globalData:{
token: '',
cateId: 0,
roleType: 0,
userId: 0
}
});
{
"pages": [
"pages/home/home",
"pages/mine/mine",
"pages/service/service",
"pages/service/service-detail",
"pages/address/index",
"pages/address/add-address",
"pages/auth/login",
"pages/order/order-service",
"pages/order/order-result",
"pages/order/order-list",
"pages/order/order-detail",
"pages/mine/user-info",
"pages/mine/change-phone",
"pages/process/choose",
"pages/process/user-list",
"pages/process/dispatch",
"pages/workbench/index",
"pages/workbench/order-list",
"pages/search/index",
"pages/mine/about-us",
"pages/service/service-agreement",
"pages/mine/official-account"
],
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup"
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f6f6f6",
"navigationBarTitleText": "城市匠人",
"navigationBarTextStyle": "black"
},
"tabBar": {
"color": "#9F9F9F",
"selectedColor": "#437CFD",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/assets/tabbar/home-unselect.png",
"selectedIconPath": "/assets/tabbar/home-selected.png"
},
{
"pagePath": "pages/service/service",
"text": "服务",
"iconPath": "/assets/tabbar/service-unselect.png",
"selectedIconPath": "/assets/tabbar/service-selected.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "/assets/tabbar/mine-unselect.png",
"selectedIconPath": "/assets/tabbar/mine-selected.png"
}
]
},
"sitemapLocation": "sitemap.json"
}
\ No newline at end of file
page {
--bgcolor: #f8f8f8;
--position-fixed: fixed;
--default-font-size: 14px;
--at: 'auto';
--primary-color: #437CFD;
background: var(--bgcolor);
}
.flex-column{
display: flex;
flex-direction: column;
}
.flex-row{
display: flex;
flex-direction: row;
}
.width100{
width: 100vw;
}
.column-center{
align-items: center;
}
.flex-1{
flex: 1;
}
\ No newline at end of file
// components/date-picker/date-picker.js
Component({
/**
* 组件的属性列表
*/
properties: {
range: { //可预约的日期范围。默认日期从今天开始,到第range天后为止,这里设为10天
type: Number,
value: 10
},
start_time: { //开始时间,设为整点
type: Number,
value: 8
},
step: { //预约时间的步长,设置为30,表示30分钟
type: Number
},
end_time: { //结束时间,设为整点
type: Number,
value: 22
}
},
/**
* 组件的初始数据
*/
data: {
isShow: false,
selectDate: "",
dialogh: 0,
//日期列表和时间列表
date_list: [],
time_list: [],
value: []
},
lifetimes:{
attached(){
let minute = new Date().getMinutes()
let startTime = 8
if(minute > 20){
startTime = new Date().getHours() + 2
}else{
startTime = new Date().getHours() + 1
}
this.setData({
start_time: startTime
})
console.log("---" + this.properties.start_time)
let start_day = this.ts_string(new Date().getTime())
let end_day = this.ts_string(new Date().setDate(new Date().getDate() + this.properties.range))
//获取日期列表
let date_list = this.getDiffDate(start_day, end_day)
//获取时间列表
let time_list = this.getTimeList(this.properties.start_time, this.properties.end_time, this.properties.step)
this.setData({
// date_time: [date_column, time_column],
date_list: date_list,
time_list: time_list,
})
//动画
this.animation = wx.createAnimation({
duration: 250
})
//500rpx转成px
let dialoghpx = 800 / 750 * wx.getSystemInfoSync().windowWidth
this.setData({
dialogh: dialoghpx,
selectDate: this.data.date_list[0]+this.data.time_list[0]
})
}
},
methods: {
getDiffDate(start, end) {
console.log(start)
let startTime = new Date(start.replace(/-/g,'/'))
let endTime = new Date(end.replace(/-/g,'/'))
console.log(startTime)
let dateArr = []
while ((endTime.getTime() - startTime.getTime()) >= 0) {
dateArr.push(this.ts_string(startTime.getTime()))
startTime.setDate(startTime.getDate() + 1)
}
return dateArr
},
zfill(num, length) {
return (Array(length).join('0') + num).slice(-length)
},
//把日期转换成xxxx-xx-xx的形式
ts_string(timestamp) {
let d = new Date(timestamp)
let string = (d.getFullYear()) + "-" +
this.zfill((d.getMonth() + 1), 2) + "-" +
this.zfill((d.getDate()), 2) + " "
return string
},
//获取时间区间列表,输入(起始时间,结束时间,步长)
getTimeList(start, end, step) {
let start_time = new Date()
//设置起始时间
start_time.setHours(start, 0, 0)
//设置结束时间
let end_time = new Date()
end_time.setHours(end, 0, 0)
let startG = start_time.getTime() //起始时间的格林时间
let endG = end_time.getTime() //起始时间的格林时间
let step_ms = step * 60 * 1000
let timeArr = []
while (startG < endG) {
let time = this.timeAdd(startG, step_ms)
timeArr.push(time)
startG += step_ms
}
return timeArr
},
timeAdd(time1, add) {
var nd = new Date(time1) //创建时间对象
//获取起始时间的时分秒
var hh1 = nd.getHours()
var mm1 = nd.getMinutes()
if (hh1 <= 9) hh1 = "0" + hh1
if (mm1 <= 9) mm1 = "0" + mm1
nd = nd.valueOf() //转换为毫秒数
nd = nd + Number(add)
nd = new Date(nd)
var hh2 = nd.getHours()
var mm2 = nd.getMinutes()
if (hh2 <= 9) hh2 = "0" + hh2
if (mm2 <= 9) mm2 = "0" + mm2
var time = hh1 + ":" + mm1
return time //时间段
},
change: function (e) {
const val = e.detail.value
//val[0]表示选择的第一列序号,val[1]表示选择的第二列序号
let select = this.data.date_list[val[0]] + this.data.time_list[val[1]]
console.log(select)
this.setData({
selectDate: select,
value: val
})
},
showDialog() {
this.setData({
isShow: true
})
//先向下移动dialog高度,然后恢复原位从而形成从下向上弹出效果
this.animation.translateY(this.data.dialogh).translateY(0).step()
this.setData({
animation: this.animation.export()
})
},
dimsss() {
//从原位向下移动dailog高度,形成从上向下的收起效果
this.animation.translateY(this.data.dialogh).step()
this.setData({
animation: this.animation.export()
})
//动画结束后蒙层消失
setTimeout(() => {
this.setData({
isShow: false
})
}, 250)
},
cancel() {
this.triggerEvent("cancel")
this.dimsss()
},
confirm() {
this.triggerEvent("confirm", {
selectDate: this.data.selectDate
})
this.dimsss()
}
}
})
\ No newline at end of file
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/date-picker/date-picker.wxml-->
<view class="mask" wx:if="{{isShow}}" catchtap="cancel">
<view class="content" style="height:800rpx" animation="{{animation}}">
<view class="top">
<view class="top-text top-left-color" hover-class="top-left-color-hover" catchtap="cancel">取消</view>
<view class="top-text top-right-color" hover-class="top-right-color-hover" catchtap="confirm">确定</view>
</view>
<picker-view style="width: 100%; height: 80%;" value="{{value}}" bindchange="change" catchtap="no">
<picker-view-column>
<view wx:for="{{date_list}}" wx:key="date_list" class="item">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{time_list}}" wx:key="time_list" class="item">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</view>
\ No newline at end of file
/* components/date-picker/date-picker.wxss */
.mask {
position: fixed;
width: 100%;
height: 100%;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
background-color: rgba(0, 0, 0, 0.7);
z-index: 9999;
flex-direction: column;
justify-content: flex-end;
}
.content {
display: flex;
flex-direction: column;
width: 100%;
background: white;
border-top-right-radius: 10rpx;
border-top-left-radius: 10rpx;
}
.top {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 100rpx;
border-bottom: 1px solid #e3e3e3;
}
.top-text {
font-size: 30rpx;
width: 150rpx;
height: 100rpx;
line-height: 100rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.top-left-color {
color: #878787;
}
.top-left-color-hover {
color: #f1eaea;
}
.top-right-color {
color: #1296DB;
}
.top-right-color-hover {
color: #82ccf3;
}
.item {
width: 100%;
align-items: center;
justify-content: center;
display: flex;
flex-direction: row;
font-size: 28rpx;
}
\ No newline at end of file
// components/empty-view/empty-view.js
Component({
options: {
addGlobalClass: true,
},
/**
* 组件的属性列表
*/
properties: {
imageName: {
type: String,
value: ""
},
emptyTitle: {
type: String,
value: "暂无数据"
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/empty-view/empty-view.wxml-->
<view class="empty-content">
<image class="empty-icon" src="{{imageName}}"/>
<view class="empty-text">{{emptyTitle ? emptyTitle : '暂无数据'}}</view>
<slot></slot>
</view>
/* components/empty-view/empty-view.wxss */
.empty-content{
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-icon{
width: 224rpx;
height: 224rpx;
margin-top: -200rpx;
}
.empty-text{
height: 36rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9F9F9F;
line-height: 36rpx;
margin-top: 24rpx;
}
\ No newline at end of file
// components/order-item/manager-order-item.js
Component({
/**
* 组件的属性列表
*/
properties: {
item: {
value: null,
type: Object
},
roleType: {
value: null,
type: Number
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
dealAction(){
this.triggerEvent("dealAction",{id: this.data.item.id})
},
cancelAction(){
this.triggerEvent("cancelAction",{id: this.data.item.id})
},
finishAction(){
this.triggerEvent("finishAction",{id: this.data.item.id})
},
dispatchAction(){
this.triggerEvent("dispatchAction",{id: this.data.item.id})
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/order-item/manager-order-item.wxml-->
<view class="order-item-wrapper">
<view class="group-1">
<view class="t1">{{item.serviceSubclassName}}</view>
<view style="flex:1" />
<view class="status-t1">{{item.orderStatusValue}}</view>
</view>
<view class="order-no">订单编号:{{item.orderNo}}</view>
<view class="line1" />
<view class="group-2">
<image class="group-image" src="{{item.subclassImg}}"/>
<view class="group-2-column">
<view class="c1">{{item.serviceName}}</view>
<view class="c2">x{{item.num}}</view>
<view class="status-t1" style="margin-top: 10rpx;" wx:if="{{item.price}}">¥{{item.price}}</view>
</view>
</view>
<view class="line1" style="margin-bottom: 20rpx;" />
<view class="flex-row">
<image class="mini-icon" src="/assets/order/order-addr.png" />
<view class="time-text">{{item.address}}</view>
</view>
<view class="flex-row">
<image class="mini-icon" src="/assets/order/order-time.png" />
<view class="time-text">{{item.createTime}}</view>
</view>
<view class="flex-row">
<image class="mini-icon" src="/assets/order/order-user.png" />
<view class="time-text">{{item.name}}</view>
</view>
<view class="group-3" wx:if="{{item.orderStatus == 1 || item.orderStatus == 3 || item.orderStatus == 4|| item.orderStatus == 5 || roleType == 1}}">
<view style="flex: 1;" />
<view class="cp-1" wx:if="{{item.orderStatus == 1 || item.orderStatus == 2}}" catchtap="dealAction">处理</view>
<view class="cp-2" wx:if="{{item.orderStatus == 1 || item.orderStatus == 3}}" catchtap="cancelAction">取消订单</view>
<view class="cp-1" wx:if="{{item.orderStatus == 4}}" catchtap="dispatchAction">派单</view>
<view class="cp-1" wx:if="{{item.orderStatus == 5}}" catchtap="finishAction">完成</view>
</view>
</view>
\ No newline at end of file
/* components/order-item/manager-order-item.wxss */
.order-item-wrapper {
width: 630rpx;
background: #FFFFFF;
border-radius: 16rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
padding: 0 30rpx;
margin-top: 30rpx;
}
.group-1 {
display: flex;
flex-direction: row;
height: 94rpx;
justify-content: space-between;
align-items: center;
}
.order-no {
height: 40rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9F9F9F;
line-height: 40rpx;
margin-bottom: 18rpx;
}
.t1 {
height: 40rpx;
font-size: 28rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #3E3E3E;
line-height: 40rpx;
}
.status-t1 {
height: 36rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 600;
color: #FF5A47;
line-height: 36rpx;
margin-right: 10rpx;
}
.line1 {
width: 626rpx;
height: 1px;
background: #F1F1F1;
}
.group-2 {
display: flex;
flex-direction: row;
height: 212rpx;
align-items: center;
}
.group-image {
width: 164rpx;
height: 164rpx;
border-radius: 8rpx;
}
.group-2-column {
display: flex;
flex-direction: column;
margin-left: 30rpx;
height: 100%;
}
.c1 {
height: 44rpx;
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #3E3E3E;
line-height: 44rpx;
margin-top: 35rpx;
}
.c2 {
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
margin-top: 10rpx;
}
.time-text {
height: 56rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
line-height: 56rpx;
margin-left: 10rpx;
}
.group-3 {
width: 100%;
height: 118rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.price {
height: 42rpx;
font-size: 30rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FF5A47;
line-height: 42rpx;
}
.cp-1 {
width: 148rpx;
height: 60rpx;
background: #437CFD;
border-radius: 34rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 60rpx;
text-align: center;
margin-left: 20rpx;
}
.djs {
height: 36rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #437CFD;
line-height: 36rpx;
}
.cp-2 {
width: 148rpx;
height: 60rpx;
border: 1px solid #437CFD;
border-radius: 34rpx;
margin-left: 20rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #437CFD;
line-height: 60rpx;
text-align: center;
}
.flex-row{
display: flex;
flex-direction: row;
align-items: center;
}
.mini-icon{
width: 34rpx;
height: 34rpx;
}
\ No newline at end of file
const {
timeConvert
} = require("../../utils/common")
// components/order-item/order-item.js
Component({
/**
* 组件的属性列表
*/
properties: {
itemData: {
value: null,
type: Object
}
},
/**
* 组件的初始数据
*/
data: {
timeStr: '',
leftTime: null
},
/**
* 组件的方法列表
*/
methods: {
payAction(){
this.triggerEvent("payAction",{orderId: this.data.itemData.id})
},
cancelAction(){
this.triggerEvent("cancelAction",{orderId: this.data.itemData.id})
},
serviceAction(){
this.triggerEvent("serviceAction")
},
finishAction(){
this.triggerEvent("finishAction",{orderId: this.data.itemData.id})
}
},
lifetimes: {
attached() {
if (this.data.itemData.leftTime) {
let timeStr = timeConvert(this.data.itemData.leftTime)
this.setData({
leftTime: this.data.itemData.leftTime,
timeStr
})
let that = this
this.timer = setInterval(function(){
let leftTime = that.data.leftTime
let timeStr = timeConvert(leftTime)
leftTime -= 1
if(leftTime < 0){
leftTime = 0
}
that.setData({
leftTime,
timeStr
})
}, 1000)
}
},
},
detached(){
clearInterval(this.timer)
}
})
\ No newline at end of file
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/order-item/order-item.wxml-->
<view class="order-item-wrapper">
<view class="group-1">
<view class="t1">{{itemData.serviceSubclassName}}</view>
<view class="status-t1">{{itemData.orderStatusValue}}</view>
</view>
<view class="line1"/>
<view class="group-2">
<image class="group-image" src="{{itemData.subclassImg}}"/>
<view class="group-2-column">
<view class="c1">{{itemData.serviceName}}</view>
<view class="c2">x{{itemData.num}}</view>
</view>
</view>
<view class="time-text">下单时间:{{itemData.createTime}}</view>
<view class="line1" wx:if="{{itemData.orderStatus == 3 || itemData.orderStatus == 5 || itemData.orderStatus == 6 || itemData.price}}"/>
<view class="group-3" wx:if="{{itemData.orderStatus == 3 || itemData.orderStatus == 5 || itemData.orderStatus == 6 || itemData.price}}">
<view class="price" wx:if="{{itemData.price}}">¥{{itemData.price}}</view>
<view style="flex: 1;"/>
<view class="djs" wx:if="{{itemData.leftTime}}">还剩{{timeStr}}</view>
<view class="cp-3" wx:if="{{itemData.orderStatus == 3}}" catchtap="cancelAction">取消订单</view>
<view class="cp-1" wx:if="{{itemData.orderStatus == 3}}" catchtap="payAction">去支付</view>
<view class="cp-2" wx:if="{{itemData.orderStatus == 6}}" catchtap="serviceAction">再来一单</view>
<view class="cp-1" wx:if="{{itemData.orderStatus == 5}}" catchtap="finishAction">完成服务</view>
</view>
</view>
/* components/order-item/order-item.wxss */
.order-item-wrapper {
width: 630rpx;
background: #FFFFFF;
border-radius: 16rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
padding: 0 30rpx;
margin-bottom: 20rpx;
}
.group-1 {
display: flex;
flex-direction: row;
height: 94rpx;
justify-content: space-between;
align-items: center;
}
.t1 {
height: 40rpx;
font-size: 28rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #3E3E3E;
line-height: 40rpx;
}
.status-t1 {
height: 36rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FF5A47;
line-height: 36rpx;
}
.line1 {
width: 626rpx;
height: 1px;
background: #F1F1F1;
}
.group-2 {
display: flex;
flex-direction: row;
height: 212rpx;
align-items: center;
}
.group-image {
width: 164rpx;
height: 164rpx;
border-radius: 8rpx;
}
.group-2-column {
display: flex;
flex-direction: column;
margin-left: 30rpx;
height: 100%;
}
.c1 {
height: 44rpx;
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #3E3E3E;
line-height: 44rpx;
margin-top: 35rpx;
}
.c2 {
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
margin-top: 10rpx;
}
.time-text {
height: 76rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9F9F9F;
line-height: 76rpx;
}
.group-3 {
width: 100%;
height: 118rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.price {
height: 42rpx;
font-size: 30rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #FF5A47;
line-height: 42rpx;
}
.cp-1 {
width: 148rpx;
height: 60rpx;
background: #437CFD;
border: 1px solid #437CFD;
border-radius: 34rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 60rpx;
text-align: center;
margin-left: 20rpx;
}
.djs {
height: 36rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #437CFD;
line-height: 36rpx;
}
.cp-2 {
width: 148rpx;
height: 60rpx;
background: #F1F1F1;
border-radius: 34rpx;
margin-left: 20rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #828282;
line-height: 60rpx;
text-align: center;
}
.cp-3 {
width: 148rpx;
height: 60rpx;
border: 1px solid #437CFD;
border-radius: 34rpx;
margin-left: 20rpx;
font-size: 26rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #437CFD;
line-height: 60rpx;
text-align: center;
}
const itemHeight = 56 * 2;
Component({
data: {
childBoxHeight: 0,
},
externalClasses: ['t-class'],
properties: {
defaultOpen: {
type: Boolean,
value: false,
},
name: {
type: String,
value: '',
},
icon: {
type: String,
value: '',
},
childArr: {
type: Array,
value: [],
observer(childArr) {
this.setData({
childBoxHeight: this.data.defaultOpen ? itemHeight * childArr.length : 0,
});
},
},
},
methods: {
switchHandle() {
const { childArr, childBoxHeight } = this.data;
this.setData({
childBoxHeight: childBoxHeight > 0 ? 0 : childArr.length * itemHeight,
});
},
tapChild(e) {
this.triggerEvent('click', e.target.dataset);
},
},
});
{
"component": true
}
\ No newline at end of file
<view class="pullDownList t-class {{ childBoxHeight > 0 ? 'actived' : '' }}">
<view class="switchBox" catch:tap="switchHandle">
<view class="name">{{ name }}</view>
<t-icon name="{{icon}}" size="48rpx" color="#A6A6A6" t-class="icon" />
</view>
<view class="childBox" style="height: {{ childBoxHeight }}rpx">
<view class="child" wx:for="{{childArr}}" wx:key="name" data-item="{{item}}" bind:tap="tapChild">
{{ item.name }} {{ item.label }}
<t-icon name="chevron-right" color="#bbb" />
</view>
</view>
</view>
.pullDownList {
width: 100%;
box-sizing: border-box;
background-color: #fff;
border-radius: 8rpx;
margin-bottom: 24rpx;
overflow: hidden;
}
.pullDownList .switchBox {
height: 120rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32rpx;
font-size: 32rpx;
line-height: 48rpx;
color: #333;
}
.pullDownList .name,
.pullDownList .icon {
transition: opacity 0.3s;
}
.pullDownList .name {
opacity: 0.9;
}
.pullDownList.actived .name {
opacity: 0.4;
}
.pullDownList.actived .icon {
opacity: 0.4;
}
.pullDownList .childBox {
transition: height 0.3s;
}
.pullDownList .childBox .child {
box-sizing: border-box;
border-bottom: 1rpx solid #e5e5e5;
height: 112rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 32rpx;
margin-right: 32rpx;
font-size: 32rpx;
opacity: 0.9;
}
.pullDownList .childBox .child:last-of-type {
border-bottom-color: transparent;
}
// 获取授权token
const ApiAuth = '/weixin/login'
//服务列表
const ApiService = '/category/list'
//首页
const ApiHome = '/common/home'
//服务详情
const ApiServiceDetail = '/subclass/get/'
//用户信息
const ApiUserInfo = '/user/info'
//绑定手机号
const ApiBindPhone = '/weixin/phone'
//我的地址
const ApiAddressList = '/address/page'
//区域树
const ApiAreaTree = '/common/tree'
//新增地址
const ApiSaveAddr = '/address/save'
//删除地址
const ApiDeleteAddr = '/address/delete/'
//设置默认地址
const ApiDefAddr = '/address/def-address/'
//地址详情
const ApiAddrDt = '/address/get/'
//编辑地址
const ApiAddrUpdate = '/address/update'
//上传文件
const ApiUpload = '/upload'
//获取默认地址
const ApiDefAddrInfo = '/order/def-detail'
//预约下单
const ApiOrder = '/order/reserve'
//订单列表
const ApiOrderList = '/order/mine-page'
//家政服务协议
const ApiOrderAgreement = '/order/agreement'
//订单详情
const ApiOrderDetail = '/order/get/'
//管理员订单
const ApiManagerOrderList = '/order/page'
//估价员列表
const ApiValuator = '/valuator/get'
//订单估价
const ApiOrderValuator = '/order/valuation'
//取消订单
const ApiOrderCancel = '/order/cancel'
//发送订单
const ApiSendOrder = '/order/send'
//发起支付
const ApiOrderPay = '/order/pay/'
//完成订单
const ApiFinishOrder = '/order/finish'
//发送验证码
const ApiSendCode = '/user/send_code'
//换绑手机
const ApiRebind = '/user/rebind'
//修改估价员
const ApiRevaluator = '/order/revaluation'
//获取维修工
const ApiWorker = '/worker/get'
//派单
const ApiDispatch = '/order/dispatch'
//搜索服务
const ApiSearch = '/category/list'
//关于我们
const ApiAboutUs = '/common/about-us'
//公众号信息
const ApiMpInfo = '/common/mp-info'
//微信二维码
const ApiWXCode = '/weixin/qr-code'
//获取用户身份
const ApiGetRoleType = '/user/role-type'
module.exports = {
ApiAuth,
ApiService,
ApiHome,
ApiServiceDetail,
ApiUserInfo,
ApiBindPhone,
ApiAddressList,
ApiAreaTree,
ApiSaveAddr,
ApiDeleteAddr,
ApiDefAddr,
ApiAddrDt,
ApiAddrUpdate,
ApiUpload,
ApiDefAddrInfo,
ApiOrder,
ApiOrderList,
ApiOrderAgreement,
ApiOrderDetail,
ApiManagerOrderList,
ApiValuator,
ApiOrderValuator,
ApiOrderCancel,
ApiSendOrder,
ApiOrderPay,
ApiFinishOrder,
ApiSendCode,
ApiRebind,
ApiRevaluator,
ApiWorker,
ApiDispatch,
ApiSearch,
ApiAboutUs,
ApiMpInfo,
ApiWXCode,
ApiGetRoleType
}
\ No newline at end of file
const { loginStatus } = require("../utils/common")
const { ApiUpload } = require("./api")
const APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8"
const APPLICATION_JSON_UTF8_FORM = 'application/x-www-form-urlencoded;charset=UTF-8'
const RESPONSE_OK = 200
const TOKEN_BEOVERDUE = 400
const BASE_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NTg5MDY2NjEsInN1YiI6IntcInVzZXJJZFwiOjB9IiwiaXNzIjoib25zaXRlLXNlcnZpY2UtbWluaS1hcHAifQ.o8f8sa9CTyGjpg-Bw5Qqp80QckkudFzk6UKK97CagCk'
const app = getApp()
const HOST = 'http://172.16.1.55:9261'
// const HOST = 'https://chengshijiangren.antaikeji.top'
function fun(url, type, data, otherType) {
let TOKEN = app.globalData.token ? app.globalData.token : 'Bearer ' + BASE_TOKEN
let promise = new Promise((resolve, reject) => {
console.log('发起请求')
wx.request({
url: HOST + url,
method: type,
data: type == 'GET' ? data : JSON.stringify(data),
header: {
'content-type': otherType ? APPLICATION_JSON_UTF8_FORM : APPLICATION_JSON_UTF8_VALUE,
'Authorization': TOKEN
},
success: function (res) {
wx.hideLoading()
console.log('==========================================================================')
console.log('== token:' + TOKEN)
console.log('== 接口地址:' + url)
console.log('== 接口参数:' + JSON.stringify(data))
console.log('== 请求类型:' + type)
console.log("== 接口状态:" + res.statusCode);
console.log('== 请求结果:' + JSON.stringify(res.data))
console.log('==========================================================================')
wx.stopPullDownRefresh();
if (res.statusCode == RESPONSE_OK && res.data.code == RESPONSE_OK) {
resolve(res.data)
} else {
if(res.data.code == 401){
loginStatus(false)
app.globalData.token = ""
wx.removeStorageSync('token')
}
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
reject()
}
},
fail: function (res) {
wx.hideLoading()
wx.stopPullDownRefresh();
reject(res)
//服务器连接异常
console.log('==========================================================================')
console.log('== 接口地址:' + url)
console.log('== 接口参数:' + JSON.stringify(data))
console.log('== 请求类型:' + type)
console.log("== 服务器连接异常")
console.log('==========================================================================')
}
})
})
return promise
}
module.exports = {
wxGet: function (url, data) {
return fun(url, 'GET', data)
},
wxPost: function (url, data) {
return fun(url, 'POST', data)
},
wxFormPost: function (url, data) {
return fun(url, 'POST', data, 'formPost')
},
wxPut: function (url, data) {
return fun(url, 'PUT', data)
},
wxDelete: function (url, data) {
return fun(url, 'DELETE', data)
},
wxUpload: function (path,fileType) {
let promise = new Promise((resolve, reject) => {
wx.uploadFile({
filePath: path,
name: 'files',
url: HOST + ApiUpload,
header: {
"Authorization": app.globalData.token
},
formData: {
fileType: fileType
},
success:function(res){
let data = JSON.parse(res.data)
if (res.statusCode == RESPONSE_OK && data.code == RESPONSE_OK) {
resolve(data)
} else {
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 2000
})
reject(data)
}
},
fail(){
reject()
}
})
})
return promise
},
HOST
}
\ No newline at end of file
const {
ApiAreaTree, ApiSaveAddr, ApiAddrDt, ApiAddrUpdate
} = require("../../http/api")
const {
wxGet, wxPost, wxPut
} = require("../../http/network")
const { showToast } = require("../../utils/common")
// pages/address/add-address.js
Page({
/**
* 页面的初始数据
*/
data: {
id: 0,
visible: false,
cityList: [],
countyList: [],
streetList: [],
cityName: "",
countyName: "",
streetName: "",
currentPos: 0,
namePath: "",
areaId: 0
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let id = options.id
if(id){
wx.setNavigationBarTitle({
title: '修改地址',
})
this.setData({
id
})
this.getData()
}
this.getArea()
},
getData(){
wxGet(ApiAddrDt + this.data.id).then(res=>{
this.setData({
address: res.data.address,
areaId: res.data.areaId,
name: res.data.name,
namePath: res.data.namePath,
phone: res.data.phone
})
})
},
getArea() {
wxGet(ApiAreaTree).then(res => {
this.setData({
cityList: res.data
})
})
},
chooseAddress() {
this.setData({
visible: true
})
},
dismissAction() {
this.setData({
visible: false
})
},
chooseCity(e){
let index = e.currentTarget.dataset.index
this.setData({
cityName: this.data.cityList[index].name,
countyList: this.data.cityList[index].children,
currentPos: 1
})
},
chooseCounty(e){
let index = e.currentTarget.dataset.index
this.setData({
countyName: this.data.countyList[index].name,
streetList: this.data.countyList[index].children,
currentPos: 2
})
},
chooseStreet(e){
let index = e.currentTarget.dataset.index
this.setData({
streetName: this.data.streetList[index].name,
visible: false,
areaId: this.data.streetList[index].id
})
this.setData({
namePath: this.data.cityName + this.data.countyName + this.data.streetName
})
},
changeCity(){
this.setData({
currentPos: 0
})
},
changeCounty(){
if(this.data.cityName.length == 0){
showToast("请先选择市")
return
}
this.setData({
currentPos: 1
})
},
changeStreet(){
if(this.data.countyName.length == 0){
showToast("请先选择区/县")
return
}
this.setData({
currentPos: 2
})
},
saveAction(){
if(this.data.name.length == 0){
showToast("请输入姓名")
return
}
if(this.data.phone.length == 0){
showToast("请输入手机号")
return
}
if(this.data.namePath.length == 0){
showToast("请选择地址")
return
}
if(this.data.address.length == 0){
showToast("请输入详细地址")
return
}
if(this.data.id > 0){
wxPut(ApiAddrUpdate,{
"address": this.data.address,
"areaId": this.data.areaId,
"name": this.data.name,
"namePath": this.data.namePath,
"phone": this.data.phone,
"id": this.data.id
}).then(res=>{
wx.showToast({
icon: 'none',
title: '保存成功',
complete:function(){
wx.navigateBack()
}
})
})
}else{
wxPost(ApiSaveAddr,{
"address": this.data.address,
"areaId": this.data.areaId,
"name": this.data.name,
"namePath": this.data.namePath,
"phone": this.data.phone
}).then(res=>{
wx.showToast({
icon: 'none',
title: '保存成功',
complete:function(){
wx.navigateBack()
}
})
})
}
}
})
\ No newline at end of file
{
"usingComponents": {
"t-popup": "tdesign-miniprogram/popup/popup"
},
"navigationBarTitleText": "新增地址"
}
\ No newline at end of file
<!--pages/address/add-address.wxml-->
<view class="flex-column">
<view class="content-item">
<view class="item-name">联系人</view>
<input class="item-input" placeholder="姓名" model:value="{{name}}"/>
</view>
<view class="content-item">
<view class="item-name">手机号</view>
<input class="item-input" placeholder="手机号" type="number" maxlength="11" model:value="{{phone}}"/>
</view>
<view class="content-item">
<view class="item-name">选择地址</view>
<input class="item-input" placeholder="选择服务地区" bindtap="chooseAddress" model:value="{{namePath}}"/>
</view>
<view class="content-item">
<view class="item-name">详细地址</view>
<input class="item-input" placeholder="街道门牌信息" model:value="{{address}}"/>
</view>
<image class="save-image" src="{{name.length > 0 && phone.length > 0 && namePath.length > 0 && address.length > 0 ? '/assets/address/address-save.png' : '/assets/address/address-dont.png'}}" bindtap="saveAction"/>
</view>
<t-popup visible="{{visible}}" placement="bottom">
<view slot="content" class="pop-wrapper">
<view class="pop-h">
<image class="pop-c" src="/assets/address/down-circle.png" bindtap="dismissAction"/>
<view class="pop-t">选择地区</view>
<view class="pop-c" />
</view>
<view class="pop-tab-wrapper">
<view bindtap="changeCity" class="pop-tab {{cityName.length > 0 ? 'pop-tab-selected' : ''}}">{{cityName.length > 0 ? cityName : '市'}}</view>
<view bindtap="changeCounty" class="pop-tab {{countyName.length > 0 ? 'pop-tab-selected' : ''}}">{{countyName.length > 0 ? countyName : '区/县'}}</view>
<view bindtap="changeStreet" class="pop-tab {{streetName.length > 0 ? 'pop-tab-selected' : ''}}">{{streetName.length > 0 ? streetName : '街道/镇'}}</view>
</view>
<scroll-view class="pop-sc" wx:if="{{currentPos == 0}}">
<view class="pop-sc-item" wx:for="{{cityList}}" wx:key="*this" data-index="{{index}}" bindtap="chooseCity">{{item.name}}</view>
</scroll-view>
<scroll-view class="pop-sc" wx:if="{{currentPos == 1}}">
<view class="pop-sc-item" wx:for="{{countyList}}" wx:key="*this" data-index="{{index}}" bindtap="chooseCounty">{{item.name}}</view>
</scroll-view>
<scroll-view class="pop-sc" wx:if="{{currentPos == 2}}">
<view class="pop-sc-item" wx:for="{{streetList}}" wx:key="*this" data-index="{{index}}" bindtap="chooseStreet">{{item.name}}</view>
</scroll-view>
</view>
</t-popup>
\ No newline at end of file
/* pages/address/add-address.wxss */
page{
background: white;
}
.content-item {
display: flex;
flex-direction: row;
margin: 0 30rpx;
height: 110rpx;
border-bottom: 1px #F1F1F1 solid;
}
.item-name {
width: 120rpx;
height: 110rpx;
font-size: 30rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
line-height: 110rpx;
}
.item-input {
height: 110rpx;
font-size: 30rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
line-height: 110rpx;
flex: 1;
margin-left: 40rpx;
}
.save-image {
width: 690rpx;
height: 90rpx;
margin: 0 auto;
margin-top: 80rpx;
}
.pop-wrapper {
width: 750rpx;
height: 858rpx;
background: #FFFFFF;
border-radius: 28rpx 28rpx 0px 0px;
display: flex;
flex-direction: column;
}
.pop-h {
display: flex;
flex-direction: row;
width: 100%;
height: 120rpx;
align-items: center;
justify-content: space-between;
}
.pop-t {
height: 40rpx;
font-size: 28rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #3E3E3E;
line-height: 40rpx;
}
.pop-c {
width: 40rpx;
height: 40rpx;
margin: 0 30rpx;
}
.pop-tab-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.pop-tab {
height: 80rpx;
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9F9F9F;
line-height: 80rpx;
margin: 0 30rpx;
}
.pop-tab-selected{
color: #3E3E3E;
}
.pop-sc {
width: 100%;
height: 658rpx;
}
.pop-sc-item {
width: 670rpx;
height: 102rpx;
border-bottom: 1px #EEEEEE solid;
margin: 0 auto;
font-size: 30rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
line-height: 102rpx;
}
\ No newline at end of file
// pages/address/index.js
import {
ApiAddressList, ApiDefAddr, ApiDeleteAddr
} from '../../http/api'
import {
wxDelete,
wxPost
} from '../../http/network'
import {
jumpPage, showToast
} from '../../utils/common'
var canLoadMore = true
var page = 1
Page({
/**
* 页面的初始数据
*/
data: {
list: null
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.path = options.path
},
onShow(){
this.getData(true)
},
addAddressAction: jumpPage(function (e) {
wx.navigateTo({
url: './add-address',
})
}, true),
getData(refresh) {
if (refresh) {
page = 1
}
wxPost(ApiAddressList, {
"page": page
}).then(res => {
var list = this.data.list
if (refresh) {
list = res.data.list
} else {
list = list.concat(res.data.list)
}
this.setData({
list
})
canLoadMore = res.data.hasNextPage
})
},
onPullDownRefresh(e) {
this.getData(true)
},
onReachBottom(e) {
if (canLoadMore) {
this.getData(false)
}
},
defaultAction(e){
let id = e.currentTarget.dataset.id
let that = this
wxPost(ApiDefAddr + id).then(res=>{
that.getData(true)
showToast("设置成功")
})
},
deleteAction(e){
let id = e.currentTarget.dataset.id
let that = this
wx.showModal({
title:"删除提示",
content: "您是否要删除该地址?",
complete:function(e){
if(e.confirm){
wxDelete(ApiDeleteAddr + id).then(res=>{
showToast(res.msg)
that.getData(true)
})
}
}
})
},
modifyAction(e){
let id = e.currentTarget.dataset.id
wx.navigateTo({
url: './add-address?id=' + id,
})
},
chooseAction(e){
let index = e.currentTarget.dataset.index
if(this.path){
let data = this.data.list[index]
const eventChannel = this.getOpenerEventChannel()
eventChannel.emit("getBackData",data)
wx.navigateBack()
}
}
})
\ No newline at end of file
{
"usingComponents": {
"empty-view":"/components/empty-view/empty-view"
},
"navigationBarTitleText": "地址",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}
\ No newline at end of file
<!--pages/address/index.wxml-->
<import src="/templates/address-item-template"/>
<empty-view imageName="/assets/address/no-address.png" wx:if="{{list.length == 0}}">
<view class="add-btn" bindtap="addAddressAction">添加地址</view>
</empty-view>
<view class="list-wrapper">
<template is="address-item" wx:for="{{list}}" wx:key="*this" data="{{...item,index}}"></template>
</view>
<view class="add-fixed" bindtap="addAddressAction" wx:if="{{list.length > 0}}">添加新地址</view>
\ No newline at end of file
/* pages/address/index.wxss */
@import "/templates/address-item-template.wxss";
page {
background: #F8F8F8;
}
.add-btn {
width: 236rpx;
height: 74rpx;
background: #437CFD;
border-radius: 56rpx;
color: white;
font-size: 30rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
line-height: 74rpx;
margin-top: 50rpx;
text-align: center;
}
.add-fixed {
width: 690rpx;
height: 88rpx;
border-radius: 44rpx;
background: #437CFD;
position: fixed;
z-index: 2;
bottom: 100rpx;
left: 30rpx;
line-height: 88rpx;
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FFFFFF;
text-align: center;
}
\ No newline at end of file
// pages/login/login.js
import api from '../../http/api'
import {
wxPost
} from '../../http/network'
import { loginStatus } from '../../utils/common'
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
wx.login({
timeout: 2000,
})
},
getUserInfo(e) {
let that = this
wx.showLoading({
title: '授权中'
})
wx.getUserProfile({
desc: 'desc',
success(e) {
wx.hideLoading()
that.loginAction(e)
},
fail(e) {
wx.hideLoading()
console.log(e)
}
})
},
loginAction(data) {
let that = this
wx.login({
timeout: 3000,
success(e) {
data['code'] = e.code
that.getAuth(data)
}
})
},
getAuth(data) {
wx.showLoading({
title: '登录中'
})
const recommendUserId = wx.getStorageSync('recommendUserId')
if(recommendUserId){
data.shareUserId = recommendUserId
}
wxPost(api.ApiAuth, data).then((res) => {
wx.hideLoading()
app.globalData.token = res.data.token
app.globalData.userId = res.data.userId
wx.setStorageSync('token', res.data.token)
wx.setStorageSync('userId', res.data.userId)
app.globalData.roleType = res.data.roleType
loginStatus(true)
wx.showToast({
icon: 'none',
title: res.msg,
success(){
wx.navigateBack()
}
})
}).catch(()=>{
wx.hideLoading()
})
}
})
\ No newline at end of file
{
"usingComponents": {},
"navigationBarTitleText": "登录"
}
\ No newline at end of file
<!--pages/login/login.wxml-->
<view class="container center">
<image src="/assets/mine/logo.png" class="logo" />
<text class="auth">城市匠人申请获得以下权限</text>
<text class="auth-info">获得您的公开信息(昵称、头像等)</text>
<image bindtap="getUserInfo" class="login" src="/assets/mine/login-btn.png" />
</view>
\ No newline at end of file
/* pages/login/login.wxss */
page{
background: white;
}
.container{
display: flex;
flex-direction: column;
}
.center {
align-items: center;
position: relative;
height: 100vh;
}
.logo {
width: 350rpx;
height: 350rpx;
margin-top: 100rpx;
}
.auth {
height: 40rpx;
font-size: 28rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #3E3E3E;
line-height: 40rpx;
margin-top: 40rpx;
}
.auth-info {
height: 40rpx;
font-size: 28rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #9F9F9F;
line-height: 40rpx;
margin-top: 10rpx;
}
.login{
width: 624rpx;
height: 112rpx;
position: absolute;
bottom: 160rpx;
border: none;
background: none;
padding: 0;
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment