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
7bc8cef5
Commit
7bc8cef5
authored
Jul 13, 2020
by
秦海涛
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update
parent
fbd674d0
Changes
29
Show whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
807 additions
and
129 deletions
+807
-129
api.js
client/api.js
+2
-0
side-bar.acss
client/components/basic/side-bar/side-bar.acss
+43
-0
side-bar.axml
client/components/basic/side-bar/side-bar.axml
+16
-0
side-bar.js
client/components/basic/side-bar/side-bar.js
+79
-4
prize-dialog.axml
client/components/dialog/prize-dialog/prize-dialog.axml
+2
-3
prize-dialog.js
client/components/dialog/prize-dialog/prize-dialog.js
+12
-11
rank-dialog.axml
client/components/dialog/rank-dialog/rank-dialog.axml
+8
-8
rank-dialog.js
client/components/dialog/rank-dialog/rank-dialog.js
+53
-2
task-dialog.js
client/components/dialog/task-dialog/task-dialog.js
+1
-1
tb-image-upload.acss
client/components/form/tb-image-upload/tb-image-upload.acss
+5
-0
tb-image-upload.axml
client/components/form/tb-image-upload/tb-image-upload.axml
+2
-1
tb-image-upload.js
client/components/form/tb-image-upload/tb-image-upload.js
+12
-3
list-table.axml
client/components/list/list-table/list-table.axml
+2
-1
list-table.js
client/components/list/list-table/list-table.js
+6
-0
rank-table.acss
client/components/rank/rank-table/rank-table.acss
+13
-0
rank-table.axml
client/components/rank/rank-table/rank-table.axml
+10
-2
rank-table.js
client/components/rank/rank-table/rank-table.js
+9
-1
add.acss
client/pages/activity/add/add.acss
+6
-0
add.axml
client/pages/activity/add/add.axml
+45
-26
add.js
client/pages/activity/add/add.js
+150
-58
validate.js
client/pages/activity/add/validate.js
+31
-6
base.axml
client/pages/activity/base/base.axml
+1
-0
base.json
client/pages/activity/base/base.json
+2
-1
data.acss
client/pages/activity/data/data.acss
+66
-0
data.axml
client/pages/activity/data/data.axml
+63
-0
data.js
client/pages/activity/data/data.js
+159
-0
data.json
client/pages/activity/data/data.json
+3
-0
index.js
client/router/index.js
+5
-1
validate.js
client/utils/validate.js
+1
-0
No files found.
client/api.js
View file @
7bc8cef5
...
...
@@ -9,6 +9,8 @@ const API = {
delActivity
:
(
params
)
=>
request
(
"delActivity"
,
"POST"
,
params
),
// 授权信息保存 sellerSave
sellerSave
:
(
params
)
=>
request
(
"sellerSave"
,
"POST"
,
params
),
// 生成活动规则 generateRule
generateRule
:
params
=>
request
(
'generateRule'
,
'POST'
,
params
),
// 获取中奖名单 findWinnerInfoList
findWinnerInfoList
:
(
params
)
=>
request
(
"findWinnerInfoList"
,
"POST"
,
params
),
// 获取活动列表云函数 getActivityList
...
...
client/components/basic/side-bar/side-bar.acss
View file @
7bc8cef5
...
...
@@ -3,6 +3,7 @@
min-width: 180px;
min-height: calc(100vh - 50px);
background-color: #fff;
position: relative;
}
.db-infos {
width: 100%;
...
...
@@ -32,3 +33,45 @@
.db-menu-list_active {
background:rgba(235,243,255,1);
}
.db-side-bar .expireTime {
color: red;
margin-top: 12px;
display: block;
}
.db-side-bar .renevwal {
font-size: 13px;
width:150px;
height:38px;
line-height:38px;
color: white;
display: block;
margin: 0 auto;
text-align: center;
background:rgba(51,134,241,1);
border-radius:19px;
}
.db-side-bar .bottom {
width:150px;
left: 50%;
transform: translateX(-50%);
text-align: center;
position: absolute;
bottom: 38px
}
.db-side-bar .help {
color: rgb(51, 134, 241);
margin-top: 20px;
font-size: 14px;
text-align:center;
display: flex;
justify-content: space-between;
}
.db-side-bar .help text {
cursor: pointer;
}
\ No newline at end of file
client/components/basic/side-bar/side-bar.axml
View file @
7bc8cef5
...
...
@@ -10,4 +10,20 @@
</view>
</view>
<view class="bottom">
<view style="margin-bottom: 20px; font-size: 14px" a:if="{{time}}">
<view style="font-size: 16px; color: #333333">{{time}} 到期</view>
<text a:if="{{diffDay <= 10 && diffDay > 0}}" class="expireTime" style="color: #FFA033">剩余{{diffDay}}天到期</text>
<text a:if="{{diffDay === 0}}" class="expireTime">今天到期</text>
<text a:if="{{diffDay < 0 }}" class="expireTime">已过期</text>
</view>
<view class="renevwal" onTap="renevwal" type="primary">软件续费</view>
<view class="help">
<text onTap="service">联系客服</text>
<text style="color: #D7DBE0">|</text>
<text onTap="help">帮助文档</text>
</view>
</view>
</view>
\ No newline at end of file
client/components/basic/side-bar/side-bar.js
View file @
7bc8cef5
const
{
cloud
}
=
getApp
()
const
{
function
:
fc
}
=
cloud
import
dayjs
from
'dayjs'
;
Component
({
mixins
:
[],
data
:
{},
data
:
{
dingNumber
:
'30549862'
,
diffDay
:
''
,
time
:
''
},
props
:
{
info
:
()
=>
{
return
{
...
...
@@ -9,16 +17,83 @@ Component({
logo
:
'//yun.duiba.com.cn/duiba_miniProgram/logo.png'
}
},
deadline
:
''
,
activeKey
:
''
,
onMenuChange
:
()
=>
{}
},
didMount
()
{},
didUpdate
()
{},
didMount
()
{
this
.
setValidTime
()
},
didUpdate
(
props
,
newProps
)
{
console
.
log
(
props
,
newProps
);
},
didUnmount
()
{},
methods
:
{
handleMenuClick
(
event
)
{
let
{
key
,
path
}
=
event
.
target
.
dataset
;
this
.
props
.
onMenuChange
({
key
,
path
});
},
setValidTime
()
{
const
{
deadline
}
=
this
.
props
;
let
time
let
diffDay
if
(
deadline
)
{
diffDay
=
dayjs
(
deadline
).
diff
(
+
new
Date
(),
'd'
)
time
=
dayjs
(
deadline
).
format
(
'YYYY-MM-DD'
)
this
.
setData
({
diffDay
,
time
})
}
},
help
()
{
my
.
qn
.
navigateToWebPage
({
url
:
"https://www.yuque.com/pangkaifeng/gfpf5s"
,
success
:
res
=>
{
},
fail
:
res
=>
{
}
})
},
renevwal
()
{
my
.
qn
.
navigateToWebPage
({
url
:
"https://fuwu.taobao.com/ser/detail.htm?service_code=FW_GOODS-1001068300&tracelog=search&from_key=%E6%98%9F%E7%90%83%E5%A4%A7%E4%BD%9C%E6%88%98"
,
success
:
res
=>
{
},
fail
:
res
=>
{
}
})
},
service
()
{
// my.qn.navigateToWebPage({
// url: "http://amos.alicdn.com/getcid.aw?v=2&uid=%E6%9D%AD%E5%B7%9E%E5%85%91%E5%95%8A&site=cntaobao&s=1&groupid=0&charset=utf-8",
// success: res => {
// },
// fail: res => {
// }
// })
const
{
dingNumber
}
=
this
.
data
my
.
alert
({
title
:
'钉钉商家群'
,
content
:
dingNumber
,
buttonText
:
'复制'
,
success
:
()
=>
{
my
.
setClipboard
({
text
:
dingNumber
,
success
:
()
=>
{
my
.
showToast
({
type
:
'success'
,
content
:
'复制成功'
})
}
})
},
})
},
},
});
client/components/dialog/prize-dialog/prize-dialog.axml
View file @
7bc8cef5
...
...
@@ -11,9 +11,8 @@
<form-item style="width:100%" size="large" class="probablity-prize-content-formItem" label="奖品类型" required>
<view class="probablity-prize-content-formitem-choosePrize-wrap">
<select onChange="onPrizeTypeChange" defaultValue="{{isEdit ? prizeDialogData.record.type : prizeInitData.type}}">
<option value="1">优惠券</option>
<option value="3">实物</option>
<option value="2">积分</option>
<option value="{{1}}">优惠券</option>
<option value="{{3}}">实物</option>
</select>
</view>
</form-item>
...
...
client/components/dialog/prize-dialog/prize-dialog.js
View file @
7bc8cef5
...
...
@@ -13,7 +13,6 @@ const THANKS_TYPE = 4;
const
INIT_DATA
=
{
ename
:
""
,
id
:
""
,
stock
:
""
,
type
:
1
,
image
:
""
,
...
...
@@ -128,7 +127,7 @@ Component({
prizeInitData
:
{
...
that
.
data
.
prizeInitData
,
name
:
benefitName
,
type
:
rightTypeId
,
type
:
+
rightTypeId
,
startTime
,
endTime
,
stock
:
stock
...
...
@@ -150,24 +149,24 @@ Component({
plugin
.
setBridge
(
bridge
);
},
// 编辑更换类型时重置数据
resetPrizeData
(
type
)
{
resetPrizeData
(
type
,
rank
)
{
this
.
setData
({
prizeInitData
:
{
...
INIT_DATA
,
type
type
,
rank
},
});
},
onPrizeTypeChange
(
e
)
{
let
value
=
e
.
detail
.
value
;
const
{
type
}
=
this
.
data
.
prizeInitData
;
const
{
type
,
rank
}
=
this
.
data
.
prizeInitData
;
let
initData
=
{
ename
:
""
,
id
:
""
,
stock
:
""
,
useStock
:
""
,
type
:
value
,
type
:
+
value
,
image
:
""
,
credits
:
""
,
probablity
:
""
,
...
...
@@ -175,7 +174,7 @@ Component({
limitStock
:
0
};
if
(
type
!==
value
)
{
this
.
resetPrizeData
(
value
);
this
.
resetPrizeData
(
+
value
,
rank
);
return
;
}
this
.
setData
({
...
...
@@ -237,6 +236,7 @@ Component({
let
imgBool
=
~
path
.
indexOf
(
".png"
)
||
~
path
.
indexOf
(
".jpg"
);
if
(
height
!==
imageLimit
[
1
]
||
width
!==
imageLimit
[
0
]
||
!
imgBool
)
{
this
.
showItemTips
(
"imageTips"
,
"error"
,
"请按要求上传图片"
);
return
;
}
else
{
this
.
showItemTips
(
"imageTips"
,
"success"
,
""
);
}
...
...
@@ -376,20 +376,20 @@ Component({
}
const
isEquietyPass
=
type
==
EQUITY_TYPE
&&
name
&&
name
.
trim
()
&&
isImagePass
&&
ename
&&
probablityTips
.
status
!==
"error"
;
const
isObjectPass
=
type
==
OBJECT_TYPE
&&
name
&&
name
.
trim
()
&&
stockPass
&&
isImagePass
&&
probablityTips
.
status
!==
"error"
&&
prizeNumberTips
.
status
!==
"error"
const
isCreditsPass
=
type
==
CREDITS_TYPE
&&
name
&&
name
.
trim
()
&&
credits
&&
isImagePass
&&
probablityTips
.
status
!==
"error"
&&
...
...
@@ -410,6 +410,7 @@ Component({
};
if
(
isEdit
)
{
console
.
log
(
prizeData
,
'--prizeData--'
)
onUpdate
&&
onUpdate
(
prizeData
,
prizeDialogData
.
index
);
}
else
{
onAdd
&&
onAdd
(
prizeData
)
...
...
client/components/dialog/rank-dialog/rank-dialog.axml
View file @
7bc8cef5
...
...
@@ -5,27 +5,27 @@
width="800"
>
<view class="rank-dialog-wrap">
<view class="other-winner-list">
<
!-- <
view class="other-winner-list">
<button type="text">导出其它中奖名单</button>
</view>
<tab shape="wrapped">
</view>
-->
<
!-- <
tab shape="wrapped">
<tab-item
a:for="{{tabs}}"
title="{{item.tab}}"
key="{{item.key}}"
>
>
-->
<view class="now-winner-list">
<button type="primary">导出
本期
中奖名单</button>
<button type="primary">导出中奖名单</button>
</view>
<table dataSource="{{dataSource}}">
<table-column a:for="{{columns}}" title="{{item.title}}" dataIndex="{{item.prop}}"/>
<table-column a:for="{{columns}}"
a:key="*this"
title="{{item.title}}" dataIndex="{{item.prop}}"/>
</table>
<view class="rank-pagination">
<text class="rank-pagination-text">共{{pageInfo.total}}条</text>
<pagination shape="arrow-only" hideOnlyOnePage="true" defaultCurrent="1" current="{{pageInfo.pageNo}}" pageSize="{{pageInfo.pageSize}}" onChange="changePagination" pageShowCount="100" total="{{pageInfo.total}}" />
</view>
</tab-item>
</tab>
<
!-- <
/tab-item>
</tab>
-->
</view>
</dialog-wrap>
\ No newline at end of file
client/components/dialog/rank-dialog/rank-dialog.js
View file @
7bc8cef5
import
{
findWinnerInfoList
,
uploadDataCreateFile
,
}
from
"/api"
;
import
{
appId
}
from
"/config"
;
import
{
setClipboard
}
from
"/utils"
;
Component
({
mixins
:
[],
data
:
{
...
...
@@ -45,9 +53,52 @@ Component({
total
:
0
}
},
props
:
{},
props
:
{
avtivityId
:
''
,
},
didMount
()
{},
didUpdate
()
{},
didUnmount
()
{},
methods
:
{},
methods
:
{
async
getWinnerList
()
{
const
{
avtivityId
}
=
this
.
props
;
const
{
data
}
=
await
findWinnerInfoList
({
avtivityId
})
this
.
setData
({
dataSource
:
data
})
},
// 导出中奖名单
async
exportWinnerList
(
evt
)
{
const
{
avtivityId
}
=
this
.
props
;
const
{
type
}
=
evt
.
target
.
dataset
;
my
.
showLoading
({
content
:
"生成文件中..."
});
try
{
const
{
success
,
data
,
message
}
=
await
uploadDataCreateFile
({
activityId
,
isObject
:
false
,
});
my
.
hideLoading
();
if
(
success
)
{
await
setClipboard
({
text
:
data
.
url
.
replace
(
/amp;/g
,
""
)
});
this
.
setData
({
exportDialogVisible
:
true
,
exportUrl
:
data
.
url
.
replace
(
/amp;/g
,
""
)
})
}
else
{
my
.
showToast
({
type
:
"fail"
,
content
:
message
,
});
}
}
catch
(
error
)
{
my
.
hideLoading
();
console
.
log
(
error
,
"exportList-error"
);
}
},
},
});
client/components/dialog/task-dialog/task-dialog.js
View file @
7bc8cef5
...
...
@@ -119,7 +119,7 @@ Component({
return
;
}
let
taskData
=
{
title
,
link
,
value
,
taskRateType
,
times
,
itemIds
,
type
:
this
.
props
.
type
};
let
taskData
=
{
...
this
.
props
.
taskData
,
title
,
link
,
value
,
taskRateType
,
times
,
itemIds
,
type
:
this
.
props
.
type
};
this
.
props
.
onUpdate
&&
this
.
props
.
onUpdate
(
taskData
);
...
...
client/components/form/tb-image-upload/tb-image-upload.acss
View file @
7bc8cef5
...
...
@@ -17,3 +17,8 @@
margin-bottom:5px;
color:#666666
}
.upload-image-error {
font-size: 12px;
color: red;
margin: 10px 0 0 0;
}
\ No newline at end of file
client/components/form/tb-image-upload/tb-image-upload.axml
View file @
7bc8cef5
...
...
@@ -7,3 +7,4 @@
<button onTap="uploadImage" type="primary">上传图片</button>
</view>
</view>
<view class="upload-image-error" a:if="{{isError}}">请上传尺寸为{{imageLimit[0]}}px * {{imageLimit[1]}}px的图片</view>
\ No newline at end of file
client/components/form/tb-image-upload/tb-image-upload.js
View file @
7bc8cef5
...
...
@@ -4,7 +4,9 @@ const { function: fc } = cloud;
Component
({
mixins
:
[],
data
:
{},
data
:
{
isError
:
false
},
props
:
{
imageLimit
:
[
200
,
200
],
onSuccess
:
()
=>
{},
...
...
@@ -17,7 +19,7 @@ Component({
didUnmount
()
{},
methods
:
{
async
uploadImage
()
{
const
{
onSuccess
,
onError
,
dataName
}
=
this
.
props
;
const
{
onSuccess
,
onError
,
dataName
,
imageLimit
}
=
this
.
props
;
try
{
const
res
=
await
chooseImage
();
...
...
@@ -28,9 +30,16 @@ Component({
});
let
imgBool
=
~
path
.
indexOf
(
".png"
)
||
~
path
.
indexOf
(
".jpg"
);
if
(
height
!==
300
||
width
!==
300
||
!
imgBool
)
{
if
(
height
!==
imageLimit
[
1
]
||
width
!==
imageLimit
[
0
]
||
!
imgBool
)
{
onError
&&
onError
({
height
,
width
,
type
,
path
})
this
.
setData
({
isError
:
true
})
return
;
}
this
.
setData
({
isError
:
false
})
const
{
url
}
=
await
cloud
.
file
.
uploadFile
({
filePath
:
res
.
apFilePaths
[
0
],
...
...
client/components/list/list-table/list-table.axml
View file @
7bc8cef5
...
...
@@ -14,10 +14,11 @@
<table-column title="操作" dataIndex="id" alignHeader="left">
<view slot-scope="x">
<button a:if="{{buttons.includes('edit')}}" class="tb-list-edit" onTap="handleClickEdit" type="primary" text="true" data-x="{{x}}" >编辑活动</button>
<button a:if="{{buttons.includes('export')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}"
data-type="object" onTap="exportWinnerList">导出实物
中奖名单</button>
<button a:if="{{buttons.includes('export')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}"
disabled="{{!x.record.isEnd}}" data-type="object" onTap="exportWinnerList">查看
中奖名单</button>
<button a:if="{{buttons.includes('delete')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="handleTapDelete">删除</button>
<button a:if="{{buttons.includes('copyLink')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="onCopyLink">复制活动链接</button>
<button a:if="{{buttons.includes('copyNewActivity')}}" class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="onCreateNewActivity">复制新活动</button>
<button class="tb-list-edit" type="primary" text="true" data-x="{{x}}" onTap="toDataPage">活动数据</button>
</view>
</table-column>
</table>
...
...
client/components/list/list-table/list-table.js
View file @
7bc8cef5
...
...
@@ -66,6 +66,8 @@ Component({
let
formatList
=
list
.
map
((
i
)
=>
{
return
{
...
i
,
isEnd
:
+
i
.
endTime
<
Date
.
now
(),
isStart
:
+
i
.
startTime
<
Date
.
now
(),
startTime
:
dayjs
(
+
i
.
startTime
).
format
(
"YYYY-MM-DD HH:mm:ss"
),
endTime
:
dayjs
(
+
i
.
endTime
).
format
(
"YYYY-MM-DD HH:mm:ss"
),
};
...
...
@@ -119,6 +121,10 @@ Component({
type
:
"fail"
,
});
}
},
toDataPage
(
evt
)
{
const
{
activityId
}
=
evt
.
target
.
dataset
.
x
.
record
;
this
.
$page
.
$router
.
push
(
`/activity/data/
${
activityId
}
`
);
},
// 点击列表删除
handleTapDelete
(
evt
)
{
...
...
client/components/rank/rank-table/rank-table.acss
View file @
7bc8cef5
...
...
@@ -27,10 +27,23 @@
font-size: 12px;
margin: 0 4px 0 0;
}
.rank-add-icons {
width: 42px;
height: 42px;
border: 1px dashed #C9CED4;
display: flex;
justify-content: center;
align-items: center;
margin: 0 6px 0 0;
}
.tb-rank-table-prize-image {
display: flex;
align-items: center;
}
.tb-rank-table-prize-image_empty {
display: flex;
align-items: center;
}
.edit-content-formItem-addTableList {
height: 54px;
...
...
client/components/rank/rank-table/rank-table.axml
View file @
7bc8cef5
...
...
@@ -13,12 +13,19 @@
<view a:if="{{x.record.image}}">
<image style="width:42px;height:42px;margin: 0 6px 0 0" src="{{x.record.image}}"></image>
</view>
<view class="tb-rank-table-prize-image_empty" a:else>
<view class="rank-add-icons" onTap="onEditPrize" data-x="{{x}}" >
<icon size="xs" type="add" style="color: #ccc;" />
</view>
<view>奖品名称</view>
</view>
<view>{{x.record.name}} </view>
</view>
</table-column>
<table-column title="操作">
<text class="tb-rank-table-operate" onTap="onEditPrize" data-x="{{x}}" data-name="edit" slot-scope="x">编辑
</text>
<text slot-scope="x" class="tb-rank-table-operate edit-content-formItem-delete" onTap="handleDeleteClick" data-x="{{x}}" slot-scope="x">删除
</text>
<text class="tb-rank-table-operate" onTap="onEditPrize" data-x="{{x}}" data-name="edit" slot-scope="x">{{x.record.name ? `编辑`: ''}}
</text>
<text slot-scope="x" class="tb-rank-table-operate edit-content-formItem-delete" onTap="handleDeleteClick" data-x="{{x}}" slot-scope="x" >{{(x.record.name && !fixedRank.includes(x.record.rank)) ? `删除`: ''}}
</text>
</table-column>
</table>
<view class="tb-rank-table-add {{list.length >= limit && 'tb-rank-table-add_disabled'}}" onTap="addPrize">
...
...
@@ -31,6 +38,7 @@
visible="{{dialogVisible}}"
isEdit="{{isEdit}}"
prizeDialogData="{{prizeDialogData}}"
imageLimit="{{imageLimit}}"
onClose="onCloseDialog"
type="rank"
onUpdate="onPrizeUpdate"
...
...
client/components/rank/rank-table/rank-table.js
View file @
7bc8cef5
...
...
@@ -7,6 +7,8 @@ Component({
},
props
:
{
limit
:
20
,
imageLimit
:
[
200
,
200
],
fixedRank
:
[],
list
:
[],
onChange
:
()
=>
{}
},
...
...
@@ -63,7 +65,13 @@ Component({
onPrizeAdd
(
data
)
{
const
{
list
,
onChange
}
=
this
.
props
;
onChange
&&
onChange
([...
list
,
data
])
let
newList
=
[...
list
,
data
].
sort
((
a
,
b
)
=>
{
let
prevRank
=
a
.
rank
.
split
(
'-'
)[
0
]
let
nextRank
=
b
.
rank
.
split
(
'-'
)[
0
]
return
prevRank
-
nextRank
;
})
onChange
&&
onChange
(
newList
)
}
},
});
client/pages/activity/add/add.acss
View file @
7bc8cef5
...
...
@@ -35,3 +35,9 @@
.submit-btn Button {
margin-left: 20px;
}
.form-flex {
display: flex;
}
.form-check {
margin-right: 10px;
}
\ No newline at end of file
client/pages/activity/add/add.axml
View file @
7bc8cef5
...
...
@@ -47,7 +47,7 @@
label="活动副标题"
asterisk="{{false}}">
<tb-input
placeholder="
请输入活动副标题
"
placeholder="
漂流寻宝赢取100元现金红包
"
maxLength="16"
value="{{subtitle}}"
dataName="subtitle"
...
...
@@ -65,7 +65,13 @@
validateState="{{formState.prizeInfoList.status}}"
help="{{formState.prizeInfoList.message}}"
asterisk="{{false}}">
<rank-table list="{{prizeInfoList}}" onChange="onPrizeListChange"/>
<rank-table
list="{{prizeInfoList}}"
onChange="onPrizeListChange"
limit="{{8}}"
fixedRank="{{['1', '2', '3']}}"
imageLimit="{{[250, 250]}}"
/>
</form-item>
...
...
@@ -77,6 +83,8 @@
validateState="{{formState['taskMap.beMembership'].status}}"
help="{{formState['taskMap.beMembership'].message}}"
asterisk="{{false}}">
<view class="form-flex">
<checkbox class="form-check" checked="{{taskMap.beMembership.checked}}" data-name="taskMap.beMembership.checked" onChange="onCheckChange"></checkbox>
<tb-config-input
textBefore="获得"
textAfter="体力值"
...
...
@@ -86,6 +94,7 @@
onChange="onTaskInputChange",
placeholder="1-99"
/>
</view>
</form-item>
<form-item
...
...
@@ -94,6 +103,8 @@
validateState="{{formState['taskMap.inviteFriends'].status}}"
help="{{formState['taskMap.inviteFriends'].message}}"
asterisk="{{false}}">
<view class="form-flex">
<checkbox class="form-check" checked="{{taskMap.inviteFriends.checked}}" data-name="taskMap.inviteFriends.checked" onChange="onCheckChange"></checkbox>
<task-config
unit="体力值"
type="inviteFriends"
...
...
@@ -101,6 +112,7 @@
dataName="taskMap.inviteFriends"
onUpdate="onTaskChange"
/>
</view>
</form-item>
<form-item
...
...
@@ -109,6 +121,8 @@
validateState="{{formState['taskMap.jumpLink'].status}}"
help="{{formState['taskMap.jumpLink'].message}}"
asterisk="{{false}}">
<view class="form-flex">
<checkbox class="form-check" checked="{{taskMap.jumpLink.checked}}" data-name="taskMap.jumpLink.checked" onChange="onCheckChange"></checkbox>
<task-config
unit="体力值"
type="jumpLink"
...
...
@@ -117,6 +131,7 @@
dataName="taskMap.jumpLink"
onUpdate="onTaskChange"
/>
</view>
</form-item>
<form-item
...
...
@@ -125,16 +140,20 @@
validateState="{{formState['taskMap.browseGoods'].status}}"
help="{{formState['taskMap.browseGoods'].message}}"
asterisk="{{false}}">
<view class="form-flex">
<checkbox class="form-check" checked="{{taskMap.browseGoods.checked}}" disabled="{{true}}"></checkbox>
<items-config itemIds="{{taskMap.browseGoods.itemIds}}" dataName="taskMap.browseGoods.itemIds" onUpdate="onTaskChange"/>
</view>
</form-item>
<form-item
validateState="{{formState.logoIm
age
.status}}"
help="{{formState.logoIm
age
.message}}"
validateState="{{formState.logoIm
g
.status}}"
help="{{formState.logoIm
g
.message}}"
class="edit-content-form-item"
label="logo图片"
asterisk="{{false}}">
<tb-image-upload url="{{logoIm
age}}" dataName="logoImage" onSuccess="onChangeByDataName
"/>
<tb-image-upload url="{{logoIm
g}}" dataName="logoImg" onSuccess="onChangeByDataName" imageLimit="{{[200, 90]}}
"/>
</form-item>
...
...
@@ -148,7 +167,7 @@
asterisk="{{false}}">
<tb-input
placeholder="请输入邀请者淘口令名称"
maxLength="
12
"
maxLength="
20
"
value="{{commandTitle}}"
dataName="commandTitle"
hasLimitHint="{{true}}"
...
...
@@ -163,7 +182,7 @@
asterisk="{{false}}">
<tb-input
placeholder="请输入被邀请者文案"
maxLength="
12
"
maxLength="
30
"
value="{{beenInvitedText}}"
dataName="beenInvitedText"
hasLimitHint="{{true}}"
...
...
@@ -177,7 +196,7 @@
class="edit-content-form-item"
label="淘口令图片"
asterisk="{{false}}">
<tb-image-upload url="{{commandImg}}" dataName="commandImg" onSuccess="onChangeByDataName"/>
<tb-image-upload url="{{commandImg}}" dataName="commandImg" onSuccess="onChangeByDataName"
imageLimit="{{[400, 300]}}"
/>
</form-item>
<tb-label title="规则配置"/>
...
...
client/pages/activity/add/add.js
View file @
7bc8cef5
...
...
@@ -3,7 +3,8 @@ import schema from 'async-validator';
import
{
descriptor
,
formatValidator
}
from
'./validate'
;
import
{
getActivityDetail
,
saveActivityInfo
saveActivityInfo
,
generateRule
}
from
'/api'
import
{
addFloat
...
...
@@ -23,75 +24,114 @@ Component({
endTime
:
''
,
timeRange
:
[],
rule
:
''
,
prizeInfoList
:
[],
commandTitle
:
''
,
commandImg
:
''
,
beenInvitedText
:
''
,
taskList
:[
{
type
:
"beMembership"
,
//会员
value
:
20
,
//完成任务获得值
},
{
type
:
"attentionStore"
,
value
:
20
,
},
{
type
:
"sign"
,
value
:
20
,
},
prizeInfoList
:
[
{
type
:
"exchangeCredits"
,
value
:
20
,
times
:
1
,
ename
:
""
,
id
:
""
,
stock
:
""
,
type
:
1
,
image
:
""
,
credits
:
""
,
name
:
""
,
limitStock
:
0
,
useStock
:
0
,
rank
:
"1"
},
{
type
:
"inviteFriends"
,
title
:
"标题"
,
//任务标题
taskRateType
:
1
,
//任务频率类型
times
:
3
,
//任务为每日限次次数值
value
:
30
,
ename
:
""
,
id
:
""
,
stock
:
""
,
type
:
1
,
image
:
""
,
credits
:
""
,
name
:
""
,
limitStock
:
0
,
useStock
:
0
,
rank
:
"2"
},
{
type
:
"orderGoods"
,
title
:
"标题"
,
taskRateType
:
2
,
times
:
3
,
value
:
90
,
itemIds
:
"111,222,333"
,
//商品类型任务商品id
},
{
type
:
"browseGoods"
,
title
:
"标题"
,
taskRateType
:
3
,
times
:
3
,
value
:
90
,
itemIds
:
"111,222,333"
,
},
{
type
:
"jumpLink"
,
title
:
"标题"
,
taskRateType
:
3
,
link
:
"http://www.taobao.com"
,
//跳转链接
times
:
3
,
value
:
90
,
},
{
type
:
"collectGoods"
,
title
:
"标题"
,
taskRateType
:
1
,
times
:
3
,
value
:
90
,
itemIds
:
"111,222,333"
,
ename
:
""
,
id
:
""
,
stock
:
""
,
type
:
1
,
image
:
""
,
credits
:
""
,
name
:
""
,
limitStock
:
0
,
useStock
:
0
,
rank
:
"3"
}
],
commandTitle
:
''
,
commandImg
:
''
,
logoImg
:
''
,
beenInvitedText
:
''
,
taskList
:[
// {
// type: "beMembership",//会员
// value: 20,//完成任务获得值
// },
// {
// type: "attentionStore",
// value: 20,
// },
// {
// type: "sign",
// value: 20,
// },
// {
// type: "exchangeCredits",
// value: 20,
// times: 1,
// },
// {
// type: "inviteFriends",
// title: "标题",//任务标题
// taskRateType: 1,//任务频率类型
// times: 3,//任务为每日限次次数值
// value: 30,
// },
// {
// type: "orderGoods",
// title: "标题",
// taskRateType: 2,
// times: 3,
// value: 90,
// itemIds: "111,222,333",//商品类型任务商品id
// },
// {
// type: "browseGoods",
// title: "标题",
// taskRateType: 3,
// times: 3,
// value: 90,
// itemIds: "111,222,333",
// },
// {
// type: "jumpLink",
// title: "标题",
// taskRateType: 3,
// link: "http://www.taobao.com",//跳转链接
// times: 3,
// value: 90,
// },
// {
// type: "collectGoods",
// title: "标题",
// taskRateType: 1,
// times: 3,
// value: 90,
// itemIds: "111,222,333",
// }
],
taskMap
:
{
// attentionStore: {
// value: '',
// },
beMembership
:
{
value
:
''
value
:
''
,
checked
:
false
},
// sign: {
// value: ''
...
...
@@ -105,6 +145,7 @@ Component({
taskRateType
:
1
,
// 任务频率类型
times
:
3
,
// 任务为每日限次次数值
value
:
''
,
// 任务奖励
checked
:
false
},
browseGoods
:
{
title
:
"寻宝屋商品"
,
...
...
@@ -112,7 +153,8 @@ Component({
times
:
3
,
value
:
''
,
link
:
''
,
itemIds
:
''
itemIds
:
''
,
checked
:
true
},
// collectGoods: {
// title: "",
...
...
@@ -127,6 +169,7 @@ Component({
link
:
""
,
//跳转链接
times
:
3
,
value
:
''
,
checked
:
false
},
},
image
:
''
,
...
...
@@ -211,6 +254,11 @@ Component({
const
{
value
}
=
e
.
detail
;
this
.
setDataByKey
(
name
,
value
);
},
onCheckChange
(
e
)
{
const
{
name
}
=
e
.
target
.
dataset
;
const
{
value
}
=
e
.
detail
;
this
.
setDataByKey
(
name
,
value
);
},
onTaskChange
(
data
,
key
)
{
console
.
log
(
data
,
key
)
this
.
setDataByKey
(
key
,
data
);
...
...
@@ -355,6 +403,7 @@ Component({
formatTaskMapToList
(
taskMap
)
{
let
list
=
[];
Object
.
keys
(
taskMap
).
forEach
(
type
=>
{
if
(
!
taskMap
[
type
].
checked
)
return
;
let
task
=
{
type
,
...
taskMap
[
type
]
...
...
@@ -432,6 +481,49 @@ Component({
}
},
//生成规则
generateRule
()
{
const
{
title
,
startTime
,
endTime
,
prizeInfoList
}
=
this
.
data
;
if
(
title
&&
startTime
&&
endTime
&&
prizeInfoList
.
length
)
{
let
bool
=
true
;
prizeInfoList
.
forEach
(
i
=>
{
if
(
!
i
.
image
||
!
i
.
name
)
{
bool
=
false
}
})
if
(
!
bool
)
{
this
.
showFailToast
(
'请检查奖品配置是否正确'
)
return
}
console
.
log
({
title
,
startTime
:
startTime
,
endTime
:
endTime
,
prizeInfoList
},
'time'
);
generateRule
({
title
,
startTime
:
new
Date
(
startTime
).
getTime
(),
endTime
:
new
Date
(
endTime
).
getTime
(),
prizeInfoList
}).
then
(
res
=>
{
if
(
res
.
success
)
{
this
.
setData
({
rule
:
res
.
data
,
hasEditChangePrize
:
false
})
}
})
}
else
{
this
.
showFailToast
(
'请填写完整信息'
)
}
},
onCloseDialog
(
data
)
{
switch
(
data
)
{
case
"group"
:
...
...
client/pages/activity/add/validate.js
View file @
7bc8cef5
...
...
@@ -7,12 +7,18 @@ import { rankTableValidator } from "../../../utils/validate";
export
const
descriptor
=
{
title
:
{
required
:
true
,
validator
:
(
rule
,
value
)
=>
!!
value
&&
value
.
length
<=
12
,
validator
:
(
rule
,
value
)
=>
{
value
=
value
.
trim
();
return
!!
value
&&
value
.
length
<=
12
},
message
:
"请输入正确的活动名称"
},
subtitle
:
{
required
:
true
,
validator
:
(
rule
,
value
)
=>
!!
value
&&
value
.
length
<=
16
,
validator
:
(
rule
,
value
)
=>
{
value
=
value
.
trim
();
return
!!
value
&&
value
.
length
<=
16
},
message
:
"请输入正确的活动副标题"
},
prizeInfoList
:
{
...
...
@@ -22,16 +28,24 @@ export const descriptor = {
timeRange
:
{
required
:
true
},
logoIm
age
:
{
logoIm
g
:
{
required
:
true
,
message
:
'请配置图片'
},
commandTitle
:
{
required
:
true
,
validator
:
(
rule
,
value
)
=>
{
value
=
value
.
trim
();
return
!!
value
;
},
message
:
'请输入淘口令名称'
},
beenInvitedText
:
{
required
:
true
,
validator
:
(
rule
,
value
)
=>
{
value
=
value
.
trim
();
return
!!
value
;
},
message
:
'请输入被邀请者文案'
},
commandImg
:
{
...
...
@@ -40,6 +54,10 @@ export const descriptor = {
},
rule
:
{
required
:
true
,
validator
:
(
rule
,
value
)
=>
{
value
=
value
.
trim
();
return
!!
value
;
},
message
:
'请输入活动规则'
},
taskMap
:
{
...
...
@@ -47,15 +65,22 @@ export const descriptor = {
required
:
true
,
fields
:
{
beMembership
:
{
validator
:
(
rule
,
val
)
=>
!!
val
.
value
,
validator
:
(
rule
,
val
,
cb
,
source
)
=>
{
if
(
!
source
.
beMembership
.
checked
)
return
true
;
return
!!
val
.
value
},
message
:
'请输入成为会员任务的奖励'
},
inviteFriends
:
{
validator
:
(
rule
,
val
)
=>
!!
(
val
.
title
&&
val
.
value
),
validator
:
(
rule
,
val
,
cb
,
source
)
=>
{
if
(
!
source
.
inviteFriends
.
checked
)
return
true
;
return
!!
(
val
.
title
&&
val
.
value
)
},
message
:
'请输入邀请任务'
},
jumpLink
:
{
validator
:
(
rule
,
val
)
=>
{
validator
:
(
rule
,
val
,
cb
,
source
)
=>
{
if
(
!
source
.
jumpLink
.
checked
)
return
true
;
return
!!
(
val
.
title
&&
val
.
value
&&
val
.
link
);
},
message
:
'请输入浏览指定页面任务'
...
...
client/pages/activity/base/base.axml
View file @
7bc8cef5
...
...
@@ -2,5 +2,6 @@
<router-view>
<list slot="list" />
<add slot="add" />
<data slot="data" />
</router-view>
</view>
client/pages/activity/base/base.json
View file @
7bc8cef5
...
...
@@ -3,6 +3,7 @@
"usingComponents"
:
{
"router-view"
:
"miniapp-router/router-view/router-view"
,
"list"
:
"../list/list"
,
"add"
:
"../add/add"
"add"
:
"../add/add"
,
"data"
:
"../data/data"
}
}
\ No newline at end of file
client/pages/activity/data/data.acss
0 → 100644
View file @
7bc8cef5
.data-section-wrap {
background: #fff;
}
.data-title {
font-size: 14px;
font-weight: 600;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
padding: 10px 16px;
}
.data-people {
display: flex;
justify-content: space-between;
padding: 0 30px;
}
.data-people-every {
padding: 10px;
}
.data-people-every-child {
padding: 20px 0;
}
.data-people-every-child-title {
font-weight: 600;
font-size: 16px;
font-family: fantasy;
padding-bottom: 10px;
text-align: center;
color: #333;
}
.data-people-every-child-tips {
font-weight: 600;
height: 20px;
font-size: 12px;
text-align: center;
color: #666;
}
.data-people-every-child-num {
font-size: 30px;
color: #3386F1;
text-align: center;
}
.new-section {
margin-top: 40px;
background-color: #fff;
}
.data-table {
font-size: 10px;
margin-top: 20px;
}
.data-title-select {
display: flex;
justify-content: space-between;
}
.data-table-wrap {
padding: 0 16px;
}
\ No newline at end of file
client/pages/activity/data/data.axml
0 → 100644
View file @
7bc8cef5
<view class="data-section-wrap">
<view class="edit-breadcrumb">
<text class="edit-title" onTap="goToActivityList">我的活动</text><text class="edit-title edit-title-separate">/</text><text class="edit-title">「 店铺漂流记 」 活动数据</text>
</view>
<view class="data-title">活动累计数据</view>
<view class="data-people">
<view class="data-people-every" a:for="{{accumulateData}}">
<view class="data-people-every-child">
<view class="data-people-every-child-title">{{item[0].name}}</view>
<view class="data-people-every-child-tips">{{item[0].tips ? item[0].tips : ''}}</view>
<view class="data-people-every-child-num">{{item[0].num}}</view>
</view>
<view class="data-people-every-child">
<view class="data-people-every-child-title">{{item[1].name}}</view>
<view class="data-people-every-child-tips">{{item[1].tips ? item[1].tips : ''}}</view>
<view class="data-people-every-child-num">{{item[1].num}}</view>
</view>
</view>
</view>
<view>
<view class="data-title">游戏参与数据</view>
<view class="data-table-wrap">
<table dataSource="{{userJoinData}}" class="data-table" hasBorder="{{false}}">
<table-column title="日期" alignHeader="left" align="left" dataIndex="date"/>
<table-column title="打开人数" alignHeader="left" align="left" dataIndex="openUV"/>
<table-column title="打开次数" alignHeader="left" align="left" dataIndex="openCount"/>
<table-column title="参与人数" alignHeader="left" align="left" dataIndex="joinUV"/>
<table-column title="参与次数" alignHeader="left" align="left" dataIndex="joinCount"/>
<table-column title="人均参与次数" alignHeader="left" align="left" dataIndex="joinRate"/>
</table>
</view>
</view>
</view>
<view class="new-section">
<view class="data-title">游戏任务数据</view>
<view class="data-table-wrap">
<table dataSource="{{userTaskData}}" class="data-table" hasBorder="{{false}}">
<table-column title="日期" alignHeader="left" align="left" dataIndex="date"/>
<table-column title="新增会员数量" alignHeader="left" align="left" dataIndex="beMemeberCount"/>
<table-column title="关注店铺数量" alignHeader="left" align="left" dataIndex="attentionStoreCount"/>
<table-column title="发起邀请人数" alignHeader="left" align="left" dataIndex="doShareCount"/>
<table-column title="成功邀请人数" alignHeader="left" align="left" dataIndex="friendsCount"/>
<table-column title="浏览指定页面人数" alignHeader="left" align="left" dataIndex="pageUrlUV"/>
<table-column title="浏览指定页面次数" alignHeader="left" align="left" dataIndex="pageUrlCount"/>
</table>
</view>
</view>
<view class="new-section">
<view class="data-title data-title-select"><text>商品以及发奖数据</text>
</view>
<view class="data-table-wrap">
<table dataSource="{{openPrizeData}}" class="data-table" hasBorder="{{false}}">
<table-column title="日期" alignHeader="left" align="left" dataIndex="date"/>
<table-column title="商品曝光数量" alignHeader="left" align="left" dataIndex="itemShowCount"/>
<table-column title="商品点击次数" alignHeader="left" align="left" dataIndex="itemTapCount"/>
<table-column title="商品点击人数" alignHeader="left" align="left" dataIndex="itemTapUV"/>
</table>
</view>
</view>
client/pages/activity/data/data.js
0 → 100644
View file @
7bc8cef5
import
moment
from
'moment'
;
const
{
cloud
}
=
getApp
();
const
{
function
:
fc
}
=
cloud
;
import
{
getStatTotalData
,
getStatJoinData
,
getStatTaskDataData
,
getStatItemData
}
from
'../../../api'
;
Component
({
mixins
:
[],
data
:
{
userJoinData
:
[],
userTaskData
:
[],
openPrizeData
:
[],
taskData
:
[],
accumulateData
:
[
[{
name
:
'活动打开次数'
,
type
:
'totalOpenCount'
,
num
:
0
},
{
name
:
'新增关注数'
,
type
:
'newAttentionStoreCount'
,
num
:
0
}],
[{
name
:
'活动参与次数'
,
type
:
'totalJoinCount'
,
num
:
0
},
{
name
:
'商品曝光数量'
,
type
:
'itemShowCount'
,
num
:
0
}],
[{
name
:
'新增会员数'
,
type
:
'newMemberCount'
,
num
:
0
}]
]
},
props
:
{},
didMount
()
{
let
{
id
}
=
this
.
$page
.
$router
.
params
;
// my.showLoading()
this
.
getAccumulateData
(
id
)
this
.
getUserJoinData
(
id
)
this
.
getTaskData
(
id
)
this
.
getOpenPrizeData
(
id
);
},
didUpdate
()
{},
didUnmount
()
{},
methods
:
{
invokeFn
(
name
,
params
,
handler
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
fc
.
invoke
(
name
,
params
,
handler
).
then
(
res
=>
{
if
(
res
.
success
)
{
resolve
(
res
.
data
);
}
else
{
this
.
showFailToast
(
res
.
message
);
}
}).
catch
(
err
=>
{
reject
(
err
);
})
})
},
async
getOpenPrizeData
(
id
)
{
const
{
data
,
message
,
success
}
=
await
getStatItemData
({
activityId
:
id
});
if
(
success
)
{
this
.
setData
({
openPrizeData
:
Object
.
keys
(
data
).
map
(
v
=>
{
return
{
...
data
[
v
],
date
:
v
}
})
})
}
},
async
getAccumulateData
(
id
)
{
const
{
data
,
success
,
message
}
=
await
getStatTotalData
({
activityId
:
id
});
if
(
success
)
{
const
{
totalOpenCount
,
newAttentionStoreCount
,
totalJoinCount
,
itemShowCount
,
newMemberCount
}
=
data
;
this
.
setData
({
accumulateData
:
[
[{
name
:
'活动打开次数'
,
type
:
'totalOpenCount'
,
num
:
totalOpenCount
},
{
name
:
'新增关注数'
,
type
:
'newAttentionStoreCount'
,
num
:
newAttentionStoreCount
}],
[{
name
:
'活动参与次数'
,
type
:
'totalJoinCount'
,
num
:
totalJoinCount
},
{
name
:
'商品曝光数量'
,
type
:
'itemShowCount'
,
num
:
itemShowCount
}],
[{
name
:
'新增会员数'
,
type
:
'newMemberCount'
,
num
:
newMemberCount
}]
]
})
}
},
goToActivityList
()
{
this
.
$page
.
$router
.
push
(
'/activity/list'
)
},
showFailToast
(
content
)
{
my
.
showToast
({
type
:
'fail'
,
content
:
content
})
},
async
getUserJoinData
(
id
)
{
const
{
data
,
success
,
message
}
=
await
getStatJoinData
({
activityId
:
id
});
if
(
success
)
{
this
.
setData
({
userJoinData
:
Object
.
keys
(
data
).
map
(
v
=>
{
return
{
...
data
[
v
],
date
:
v
}
})
})
}
// this.invokeFn('duiba', {
// activityOutId: id
// }, 'backstage.reportData.userJoinData').then(data => {
// this.setData({
// userJoinData: data
// })
// })
},
async
getTaskData
(
id
,
type
=
'groupInvite'
)
{
const
{
data
,
message
,
success
}
=
await
getStatTaskDataData
({
activityId
:
id
});
if
(
success
)
{
this
.
setData
({
userTaskData
:
Object
.
keys
(
data
).
map
(
v
=>
{
return
{
...
data
[
v
],
date
:
v
}
})
})
}
}
},
});
\ No newline at end of file
client/pages/activity/data/data.json
0 → 100644
View file @
7bc8cef5
{
"component"
:
true
}
\ No newline at end of file
client/router/index.js
View file @
7bc8cef5
export
const
basePath
=
`/activity/
add
`
;
export
const
basePath
=
`/activity/
list
`
;
export
default
{
routes
:
[
...
...
@@ -18,6 +18,10 @@ export default {
path
:
"/add"
,
component
:
"add"
,
},
{
path
:
'/data/:id'
,
component
:
'data'
},
{
path
:
"/edit/:id"
,
component
:
"add"
,
...
...
client/utils/validate.js
View file @
7bc8cef5
...
...
@@ -51,6 +51,7 @@ export const rankTableValidator = (rule, value, callback, source, options) => {
// rank字段不存在或为0
if
(
value
.
some
(
v
=>
!
v
.
rank
))
return
new
Error
(
'名次配置错误, 请检查'
);
if
(
value
.
some
(
v
=>
!
v
.
name
))
return
new
Error
(
'名次配置错误, 请检查'
);
const
rankArr
=
(
value
.
map
(
v
=>
v
.
rank
.
split
(
'-'
)));
...
...
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