Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
taobao-mini-template
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
2
Issues
2
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
qinhaitao
taobao-mini-template
Commits
4f592ad7
Commit
4f592ad7
authored
Apr 25, 2021
by
mqf_0707
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
wnf
parent
41b179d2
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
0 additions
and
1276 deletions
+0
-1276
index.js
c_client/client/tbcc-sdk/index.js
+0
-22
capi.js
c_client/client/tbcc-sdk/lib/capi.js
+0
-18
capiFn.js
c_client/client/tbcc-sdk/lib/capiFn.js
+0
-62
constants.js
c_client/client/tbcc-sdk/lib/constants.js
+0
-23
request.js
c_client/client/tbcc-sdk/lib/request.js
+0
-149
tb.js
c_client/client/tbcc-sdk/lib/tb.js
+0
-584
tbMock.js
c_client/client/tbcc-sdk/lib/tbMock.js
+0
-295
utils.js
c_client/client/tbcc-sdk/lib/utils.js
+0
-123
No files found.
c_client/client/tbcc-sdk/index.js
deleted
100644 → 0
View file @
41b179d2
import
*
as
tb
from
'./lib/tb'
;
import
*
as
tbMock
from
'./lib/tbMock'
;
import
*
as
utils
from
'./lib/utils'
;
import
*
as
constants
from
'./lib/constants'
;
import
capi
from
'./lib/capi'
;
import
request
from
'./lib/request'
;
const
isH5
=
'undefined'
!==
typeof
window
;
// 如果为h5环境走mock数据
let
_tb
=
{};
Object
.
entries
(
tb
).
forEach
(([
key
,
value
])
=>
{
_tb
[
key
]
=
isH5
?
tbMock
[
key
]
:
value
;
});
export
default
{
capi
,
tb
:
_tb
,
utils
,
request
,
constants
};
\ No newline at end of file
c_client/client/tbcc-sdk/lib/capi.js
deleted
100644 → 0
View file @
41b179d2
const
capi
=
{
getMyPrizeList
:
{
method
:
'GET'
,
handle
:
'getMyPrizeList'
,
isShowLoading
:
true
},
receiveObjectPrize
:
{
method
:
'GET'
,
handle
:
'receiveObjectPrize'
},
receiveEnamePrize
:
{
method
:
'GET'
,
handle
:
'receiveEnamePrize'
},
getItemListByItemIds
:
{
method
:
'GET'
,
handle
:
'getItemListByItemIds'
},
getCollectGoodsList
:
{
method
:
'GET'
,
handle
:
'getCollectGoodsList'
},
doCollectGoodsTask
:
{
method
:
'GET'
,
handle
:
'doCompleteTask'
,
defaultParams
:
{
taskType
:
'collectGoods'
}
},
doBrowseGoodsTask
:
{
method
:
'GET'
,
handle
:
'doCompleteTask'
,
defaultParams
:
{
taskType
:
'browseGoods'
}
},
doJumpLinkTask
:
{
method
:
'GET'
,
handle
:
'doCompleteTask'
,
defaultParams
:
{
taskType
:
'jumpLink'
}
},
doExchangeCreditsTask
:
{
method
:
'GET'
,
handle
:
'doCompleteTask'
,
defaultParams
:
{
taskType
:
'exchangeCredits'
}
},
getVipInfo
:
{
method
:
'GET'
,
handle
:
'getVipInfo'
}
};
export
default
capi
;
c_client/client/tbcc-sdk/lib/capiFn.js
deleted
100644 → 0
View file @
41b179d2
import
{
getUserAddress
,
commonToast
,
commonConfirm
}
from
'./tb'
;
import
{
getRequestParams
}
from
'./utils'
;
// 通用API封装方法
const
capiFn
=
(
apiList
,
request
)
=>
{
return
{
async
receiveObjectPrize
(
params
)
{
const
{
handle
,
method
,
ext
}
=
getRequestParams
(
apiList
[
'receiveObjectPrize'
]);
const
userAddress
=
await
getUserAddress
().
catch
(
err
=>
{
commonToast
(
err
.
errorMessage
);
});
if
(
!
userAddress
)
return
false
const
{
name
,
telNumber
,
provinceName
,
cityName
,
cityCode
,
countyName
,
detailInfo
,
streetName
}
=
userAddress
||
{};
params
=
{
name
,
phone
:
telNumber
,
addressDetail
:
detailInfo
,
cityCode
,
city
:
cityName
,
province
:
provinceName
,
area
:
countyName
,
streetName
,
...
params
}
await
commonConfirm
(
'提示'
,
'确认使用该收货地址:'
+
name
+
telNumber
+
userAddress
.
duibaAddress
.
address
,
async
function
()
{
const
result
=
await
request
(
handle
,
method
,
params
,
ext
).
catch
(
res
=>
{
commonToast
(
res
&&
res
.
message
);
});
if
(
result
&&
result
.
success
)
{
return
true
;
}
})
return
false
},
async
getVipInfo
(
params
)
{
const
{
handle
,
method
,
ext
}
=
getRequestParams
(
apiList
[
'getVipInfo'
]);
const
result
=
await
request
(
handle
,
method
,
params
,
ext
).
catch
(
res
=>
{
commonToast
(
res
&&
res
.
message
);
});
if
(
result
&&
result
.
success
)
{
const
{
isVip
}
=
result
.
data
return
isVip
;
}
return
false
;
},
async
receiveEnamePrize
(
params
)
{
const
{
handle
,
method
,
ext
}
=
getRequestParams
(
apiList
[
'receiveEnamePrize'
]);
const
result
=
await
request
(
handle
,
method
,
params
,
ext
).
catch
(
res
=>
{
commonToast
(
res
&&
res
.
message
);
});
if
(
result
&&
result
.
success
)
{
return
true
;
}
return
false
;
},
}
};
export
default
capiFn
;
c_client/client/tbcc-sdk/lib/constants.js
deleted
100644 → 0
View file @
41b179d2
// 领取奖品状态
export
const
DRAW_STATUS
=
{
// 待领取
WAITAWARD
:
1
,
// 处理中
PROCESSING
:
2
,
// 领取成功
SUCCESS
:
3
,
// 领取失败
FAIL
:
4
,
// 已过期
EXPIRED
:
5
,
// 重新领取
RETRY
:
6
}
// 奖品类型
export
const
PRIZE_TYPE
=
{
ENAME
:
1
,
CREDITS
:
2
,
OBJECT
:
3
,
THANKS
:
5
};
\ No newline at end of file
c_client/client/tbcc-sdk/lib/request.js
deleted
100644 → 0
View file @
41b179d2
import
*
as
tb
from
'./tb'
;
const
{
getAuthUserInfo
}
=
tb
;
var
Buffer
=
require
(
"buffer"
).
Buffer
var
crypto
=
require
(
"crypto"
)
const
request
=
({
cloud
,
cloudName
,
requestType
=
'cloud'
,
mockUrl
})
=>
{
if
(
!
cloud
)
{
console
.
error
(
'请传入cloud'
);
return
false
;
}
if
(
!
cloudName
)
{
console
.
error
(
'请传入云函数名称'
);
return
false
;
}
return
async
(
handle
,
method
=
'POST'
,
params
,
ext
=
{})
=>
{
const
{
activityId
,
tornadoAPI
,
nickName
=
''
,
tbcc
}
=
getApp
();
// 默认注入activityId
params
=
{
activityId
,
...
params
};
if
(
!
params
.
activityId
)
{
console
.
error
(
`
${
handle
}
:请传入activityId`
);
return
false
;
}
console
.
log
(
params
,
handle
)
const
_cloudName
=
ext
.
cloudName
||
cloudName
;
const
{
isShowLoading
}
=
ext
;
const
hideMyLoading
=
()
=>
{
if
(
isShowLoading
)
{
my
.
hideLoading
();
}
};
if
(
isShowLoading
)
{
my
.
showLoading
();
}
const
requestMock
=
()
=>
{
// 若请求的是风驰台的api
if
(
requestType
===
'tornadoAPI'
)
{
// 处理涉及的三个参数
let
matchRes
=
tornadoAPI
.
match
(
/db=
(\w
*
)
&proxyIp=
([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3})
/
)
let
db
=
matchRes
[
1
]
let
proxyIp
=
matchRes
[
2
]
let
data_
=
{
handler
:
handle
,
data
:
params
}
console
.
log
(
`data_`
,
data_
)
let
params_
=
{
db
,
proxyIp
,
data
:
data_
}
console
.
log
(
`params_`
,
params_
)
// nickName 转 md5 作为唯一的openId
if
(
!
nickName
)
{
return
getAuthUserInfo
().
catch
(
err
=>
{
console
.
log
(
'未授权成功'
,
err
);
}).
then
(
res
=>
{
return
cb
(
res
)
})
}
// return cb({nickName: '卞龙亭'})
function
cb
({
nickName
})
{
if
(
!
db
||
!
proxyIp
)
{
my
.
confirm
({
title
:
'风驰台地址错误'
,
content
:
'请确认风驰台的地址是否正确,未查找到db和proxyIp'
,
confirmButtonText
:
'确定'
,
cancelButtonText
:
'取消'
});
return
false
}
params_
.
openId
=
md5
(
nickName
)
return
new
Promise
((
resolve
,
reject
)
=>
{
my
.
request
({
url
:
tornadoAPI
,
method
:
'post'
,
data
:
params_
,
dataType
:
'json'
}).
then
(({
data
:
res
})
=>
{
console
.
log
(
`调用风驰台返回结果`
,
res
)
hideMyLoading
();
if
(
res
&&
res
.
success
)
{
resolve
(
res
);
}
else
{
reject
(
res
);
}
}).
catch
((
e
)
=>
{
hideMyLoading
();
console
.
log
(
e
)
reject
();
});
})
function
md5
(
data
)
{
var
buf
=
new
Buffer
(
data
);
var
str
=
buf
.
toString
(
"binary"
);
return
crypto
.
createHash
(
"md5"
).
update
(
str
).
digest
(
"hex"
).
slice
(
0
,
30
);
}
}
}
else
{
const
mockUrlPrefix
=
{
ams
:
'https://ams.dui88.com/server/index.php?g=Web&c=Mock&o=simple&projectID=218&uri='
,
yapi
:
'https://docs.dui88.com/mock/140/'
};
const
requestPrefix
=
mockUrl
||
mockUrlPrefix
[
requestType
];
return
new
Promise
((
resolve
,
reject
)
=>
{
my
.
request
({
url
:
requestPrefix
+
_cloudName
+
'.'
+
handle
,
method
,
data
:
params
,
dataType
:
'json'
}).
then
(({
data
:
res
})
=>
{
hideMyLoading
();
if
(
res
&&
res
.
success
)
{
resolve
(
res
);
}
else
{
resolve
(
res
);
// reject(res);
}
}).
catch
(()
=>
{
hideMyLoading
();
reject
();
});
})
}
};
const
requestCloud
=
()
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
cloud
.
function
.
invoke
(
_cloudName
,
params
,
handle
).
then
(
res
=>
{
console
.
log
(
handle
,
params
,
res
);
hideMyLoading
();
if
(
res
&&
res
.
success
)
{
resolve
(
res
);
}
else
{
reject
(
res
);
}
return
res
;
}).
catch
(()
=>
{
hideMyLoading
();
reject
();
});
});
};
return
[
'ams'
,
'yapi'
,
'tornadoAPI'
].
includes
(
requestType
)
?
requestMock
()
:
requestCloud
();
};
};
export
default
request
;
\ No newline at end of file
c_client/client/tbcc-sdk/lib/tb.js
deleted
100644 → 0
View file @
41b179d2
This diff is collapsed.
Click to expand it.
c_client/client/tbcc-sdk/lib/tbMock.js
deleted
100644 → 0
View file @
41b179d2
/**
* 通用toast
* @param {string} content toast内容
* @param {number} duration 显示时长
* @param {function} successCb 成功回调
*/
export
const
commonToast
=
(
content
,
duration
=
3000
,
successCb
)
=>
{
return
'暂不支持toast'
;
};
/**
* 简易alert
* @param {string} content toast内容
*/
export
const
simpleAlert
=
content
=>
{
alert
(
content
);
};
/**
* 通用确认弹窗
* @param {string} title 标题
* @param {string} content 内容
* @param {function} successCb 成功回调
*/
export
const
commonConfirm
=
(
title
=
'提示'
,
content
,
successCb
)
=>
{
my
.
confirm
({
title
,
content
,
confirmButtonText
:
'确定'
,
cancelButtonText
:
'取消'
,
success
:
result
=>
{
const
{
confirm
}
=
result
;
if
(
confirm
)
{
successCb
&&
successCb
();
}
}
});
};
/**
* 获取授权用户信息
* @returns {object} 用户登录信息
*/
export
const
getAuthUserInfo
=
async
()
=>
{
return
{
nickName
:
'nickName'
,
avatar
:
'123'
,
mixNick
:
'1234'
};
};
/**
* 获取系统信息
* @returns {object} 小程序系统信息
*/
export
const
getSystemInfo
=
async
()
=>
{
return
{
version
:
'9.5.0'
};
};
/**
* 执行关注店铺
* @param {number} sellerId 店铺归属的卖家Id
* @returns {boolean} 关注状态
*/
export
const
favorShop
=
async
sellerId
=>
{
if
(
!
sellerId
)
{
console
.
error
(
'favorShop: 请传入卖家Id'
);
return
false
;
}
return
{};
};
/**
* 判断是否关注店铺
* @param {number} sellerId 店铺归属的卖家Id
* @returns {boolean} 关注状态
*/
export
const
checkShopFavoredStatus
=
async
sellerId
=>
{
if
(
!
sellerId
)
{
console
.
error
(
'checkShopFavoredStatus: 请传入卖家Id'
);
return
false
;
}
return
true
;
};
/**
* 跳转到外部链接
* @param {string} url 跳转链接
*/
export
const
navigateToOutside
=
url
=>
{
if
(
!
url
)
{
console
.
error
(
'navigateToOutside: 请传入url'
);
return
false
;
}
window
.
location
=
url
;
};
/**
* 跳转到内部链接(新开窗口)
* @param {string} url 跳转链接
*/
export
const
navigateTo
=
url
=>
{
if
(
!
url
)
{
console
.
error
(
'navigateTo: 请传入url'
);
return
false
;
}
window
.
location
=
url
;
};
/**
* 跳转到内部链接(不新开窗口)
* @param {string} url 跳转链接
*/
export
const
redirectTo
=
url
=>
{
if
(
!
url
)
{
console
.
error
(
'redirectTo: 请传入url'
);
return
false
;
}
window
.
replace
(
url
);
};
/**
* 获取服务器时间
* @returns {number} 服务器时间戳
*/
export
const
getServerTime
=
async
()
=>
{
return
+
new
Date
();
};
/**
* 收藏商品
* @param {number} 商品id
* @returns {object} 收藏结果
*/
export
const
collectGoods
=
async
id
=>
{
if
(
!
id
)
{
console
.
error
(
'collectGoods: 请传入商品id'
);
return
false
;
}
return
{};
};
/**
* 查询商品收藏状态
* @param {number} 商品id
* @returns {boolean} 商品收场状态
*/
export
const
checkGoodsCollectedStatus
=
async
id
=>
{
if
(
!
id
)
{
console
.
error
(
'checkGoodsCollectedStatus: 请传入商品id'
);
return
false
;
}
return
true
;
};
/**
* 跳转到淘宝商品页
* @param {string} itemId 商品ID
*/
export
const
openDetail
=
async
itemId
=>
{
if
(
!
itemId
)
{
console
.
error
(
'openDetail: 请传入商品id'
);
return
false
;
}
window
.
location
=
'https://item.taobao.com/item.htm?id='
+
itemId
;
};
/**
* 获取淘宝用户收货地址
*/
export
const
getUserAddress
=
async
(
opts
=
{})
=>
{
return
{
name
:
1
,
phone
:
1
,
address
:
123
}
};
/**
* 选择图片
*/
export
const
chooseImage
=
async
(
opts
=
{})
=>
{
let
defaults
=
{
count
:
1
,
sourceType
:
[
'camera'
,
'album'
],
compressLevel
:
1
};
let
_opts
=
Object
.
assign
({},
defaults
,
opts
);
const
{
count
,
sourceType
,
compressLevel
}
=
_opts
;
return
new
Promise
((
resolve
,
reject
)
=>
{
if
(
!
my
.
chooseImage
)
{
reject
({
noSupport
:
true
,
errorMessage
:
'当前版本不支持选择收货地址,请升级到最新版本'
});
return
false
;
}
my
.
chooseImage
({
count
,
sourceType
,
success
:
res
=>
{
my
.
compressImage
({
apFilePaths
:
res
.
apFilePaths
,
level
:
compressLevel
,
success
:
data
=>
{
resolve
(
data
);
},
fail
:
err
=>
{
resolve
(
err
);
}
});
},
fail
:
err
=>
{
// error code 2001 为摄像头授权取消、ios 11为用户取消操作不认作失败场景 、安卓 为1
if
(
err
.
error
!==
2001
&&
err
.
error
!==
11
&&
err
.
error
!==
1
)
{
reject
(
err
);
}
else
{
resolve
(
false
);
}
}
});
});
};
/**
* 获取图片信息
* @param {string} src 图片地址
*/
export
const
getImageInfo
=
async
src
=>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
my
.
getImageInfo
({
src
,
success
:
res
=>
{
resolve
(
res
);
},
fail
:
err
=>
{
reject
(
err
);
}
});
});
};
/**
* 获取图片信息
* @param {*} opts chooseImage配置参数
*/
export
const
uploadImage
=
async
(
opts
=
{})
=>
{
const
{
cloud
}
=
getApp
();
if
(
!
cloud
)
{
console
.
error
(
'chattingSendCard: 请传入app.cloud'
);
return
false
;
}
const
chooseImageResult
=
await
chooseImage
(
opts
).
catch
(
err
=>
{
commonToast
(
err
&&
(
err
.
errorMessage
||
'选择图片失败'
));
});
if
(
chooseImageResult
)
{
const
{
apFilePaths
}
=
chooseImageResult
;
const
filePath
=
apFilePaths
[
0
];
const
imageInfo
=
await
getImageInfo
(
filePath
).
catch
(
err
=>
{
commonToast
(
err
&&
err
.
errorMessage
);
});
// 找不到图片信息
if
(
!
imageInfo
)
{
return
false
;
}
const
{
path
:
imagePath
}
=
imageInfo
;
const
{
url
}
=
await
cloud
.
file
.
uploadFile
({
filePath
,
fileType
:
'image'
,
fileName
:
imagePath
.
split
(
'/'
).
pop
()
});
return
url
;
}
return
false
;
}
/**
* 分享到群聊卡片
* @param {object} requestInfo 分享到群聊卡片所需请求参数
*/
export
const
chattingSendCard
=
async
(
requestInfo
)
=>
{
alert
(
'无法模拟'
);
return
false
;
};
/**
* 分享到群聊卡片
* @param {*} cloud 小程序云
* @param {object} requestInfo 分享到群聊卡片所需请求参数
*/
export
const
textRiskIdentification
=
async
(
text
)
=>
{
return
true
;
}
\ No newline at end of file
c_client/client/tbcc-sdk/lib/utils.js
deleted
100644 → 0
View file @
41b179d2
import
capiFn
from
'./capiFn'
;
/**
* 时间格式化
* @param date 接收可以被new Date()方法转换的内容
* @param format 字符串,需要的格式例如:'yyyy/MM/dd hh:mm:ss'
* @returns {String}
*/
export
const
dateFormatter
=
(
date
,
format
=
'yyyy/MM/dd'
)
=>
{
if
(
!
date
)
return
'-'
;
date
=
new
Date
(
typeof
date
===
'string'
&&
isNaN
(
date
)
?
date
.
replace
(
/-/g
,
'/'
)
:
date
);
let
o
=
{
'M+'
:
date
.
getMonth
()
+
1
,
'd+'
:
date
.
getDate
(),
'h+'
:
date
.
getHours
(),
'm+'
:
date
.
getMinutes
(),
's+'
:
date
.
getSeconds
(),
'q+'
:
Math
.
floor
((
date
.
getMonth
()
+
3
)
/
3
),
S
:
date
.
getMilliseconds
()
};
if
(
/
(
y+
)
/
.
test
(
format
))
{
format
=
format
.
replace
(
RegExp
.
$1
,
(
date
.
getFullYear
()
+
''
).
substr
(
4
-
RegExp
.
$1
.
length
)
);
}
for
(
let
k
in
o
)
{
if
(
new
RegExp
(
'('
+
k
+
')'
).
test
(
format
))
{
format
=
format
.
replace
(
RegExp
.
$1
,
RegExp
.
$1
.
length
===
1
?
o
[
k
]
:
(
'00'
+
o
[
k
]).
substr
((
''
+
o
[
k
]).
length
)
);
}
}
return
format
;
};
/**
* 版本比较
* @param {string} cur 当前版本
* @param {string} compare 比较版本
*/
export
const
versionCompare
=
(
cur
,
compare
)
=>
{
const
_cur
=
cur
.
split
(
'.'
);
const
_compare
=
compare
.
split
(
'.'
);
if
(
+
_cur
[
0
]
<
+
_compare
[
0
])
{
return
false
;
}
else
if
(
+
_cur
[
0
]
===
+
_compare
[
0
]
&&
+
_cur
[
1
]
<
+
_compare
[
1
]
)
{
return
false
;
}
else
if
(
+
_cur
[
0
]
===
+
_compare
[
0
]
&&
+
_cur
[
1
]
===
+
_compare
[
1
]
&&
+
_cur
[
2
]
<
+
_compare
[
2
])
{
return
false
;
}
return
true
;
};
/**
* 获取request请求所需参数
* @param {object/string} value API项配置
*/
export
const
getRequestParams
=
value
=>
{
if
(
typeof
value
===
'string'
)
{
return
{
handle
:
value
,
method
:
'POST'
,
ext
:
{}
}
}
else
if
(
typeof
value
===
'object'
)
{
const
{
handle
,
method
=
'POST'
,
defaultParams
=
{},
...
ext
}
=
value
;
return
{
handle
,
method
,
defaultParams
,
ext
:
{
...
ext
}
}
}
else
{
console
.
error
(
'getRequestParams: 传参有误'
);
}
};
/**
* 生成API
* @param {array} apiList API数据数组
* @param {function} request 请求
*/
export
const
generateAPI
=
(
apiList
,
request
)
=>
{
const
api
=
{};
Object
.
entries
(
apiList
).
forEach
(([
key
,
value
])
=>
{
const
{
handle
,
method
,
ext
,
defaultParams
}
=
getRequestParams
(
value
);
api
[
key
]
=
params
=>
request
(
handle
,
method
,
Object
.
assign
({},
defaultParams
,
params
)
,
ext
);
});
api
.
fn
=
capiFn
(
apiList
,
request
);
return
api
;
};
/**
* 函数节流,普通防连点
* @param {fun} fun 函数
* @param {delay} delay 时间
*/
export
const
throttleHandle
=
(
fun
,
delay
=
1000
)
=>
{
let
last
,
deferTimer
;
return
function
()
{
let
now
=
+
new
Date
();
if
(
last
&&
now
<
last
+
delay
)
{
clearTimeout
(
deferTimer
);
deferTimer
=
setTimeout
(()
=>
{
last
=
now
;
},
delay
);
}
else
{
last
=
now
;
fun
.
apply
(
this
,
arguments
);
}
};
}
// 获取分享图地址
export
const
getImgShareUrl
=
async
(
cloud
,
fileId
)
=>
{
let
list
=
await
cloud
.
file
.
getTempFileURL
({
fileId
})
return
list
&&
list
[
0
].
url
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment