Commit 986fcc95 by Jason Zhou

too many updates

parent 2c4e7c9f
node_modules
\ No newline at end of file
{ {
"pages": [ "pages": [
"pages/index/index",
"pages/blog/blog", "pages/blog/blog",
"pages/index/index",
"pages/more/storePhoto", "pages/more/storePhoto",
"pages/comment/storeComments", "pages/comment/storeComments",
"pages/comment/writeComments", "pages/comment/writeComments",
......
...@@ -114,11 +114,11 @@ form#formIdGetter button.form_button { ...@@ -114,11 +114,11 @@ form#formIdGetter button.form_button {
border: 2rpx solid #f14b7d; border: 2rpx solid #f14b7d;
box-sizing: border-box; box-sizing: border-box;
} }
text, /* text,
view { view {
font-size: 32rpx; font-size: 32rpx;
color: #333; color: #333;
} } */
scroll-view { scroll-view {
overflow: scroll; overflow: scroll;
} }
......
Page({ Page({
onLoad() { onPostTap(e) {
console.log(1) const { postId } = e.detail
wx.navigateTo({ url: `plugin://blogPlugin/postDetail?postId=${postId}` })
},
onPostCategory(e) {
const { name, id } = e.detail
wx.navigateTo({
url: `plugin://blogPlugin/postList?category=${name}&categoryId=${id}`
})
} }
}) })
<blog /> <blog bindPostTap="onPostTap" />
\ No newline at end of file \ No newline at end of file
import urls from '/constants/blog/urls'
import Fly from '/lib/flyio/index'
const fly = new Fly()
const { REQUESTS } = urls
export const getPostDetail = postId => fly.get(REQUESTS.GET_POST(postId))
export const getPosts = (siteId, category, pageNum) =>
fly.get(REQUESTS.GET_POSTS(siteId, category, pageNum))
export const likePost = (status, postId, nickName, avatarUrl) =>
status ? REQUESTS.SET_LIKE_POST(postId) : REQUESTS.DELETE_LIKE_POST(postId)
export const getCategories = siteId => fly.get(REQUESTS.GET_CATEGORIES(siteId))
export const getComments = (postId, pageNum) => fly.get(REQUESTS.GET_COMMENTS(postId, pageNum))
export const setComments = (postId, comment) => fly.post(REQUESTS.SET_COMMENTS(postId), comment)
\ No newline at end of file
// import { connect } from 'root/wmp-redux' import { uniq } from '/lib/lodash/index'
// import { fetchPosts, likePost } from 'root/actions/blog/post'
import urls from '/constants/blog/urls' import urls from '/constants/blog/urls'
// import mixinUrls from '/constants/ecommerceMixinBlog/urls' import * as apis from '/api/blogApi'
import iconPaths from '/constants/blog/iconPaths' import iconPaths from '/constants/blog/iconPaths'
// import { setComments } from 'root/actions/blog/comment' // import { setComments } from 'root/actions/blog/comment'
...@@ -42,7 +41,7 @@ const component = { ...@@ -42,7 +41,7 @@ const component = {
showBlogPosts: { showBlogPosts: {
type: Boolean, type: Boolean,
value: true value: true
} },
}, },
data: { data: {
currentTab: 'post', currentTab: 'post',
...@@ -55,178 +54,22 @@ const component = { ...@@ -55,178 +54,22 @@ const component = {
selectedPostId: '', selectedPostId: '',
currentComment: '', currentComment: '',
isCommenting: false, isCommenting: false,
posts: [ posts: [],
{
postedToWechat: false,
publishedAt: '1 个月前',
settings: {
hideBlogDate: null,
},
relativeUrl: '/blog/96789f79667',
approvedCommentsCount: 0,
pendingCommentsCount: 0,
publicUrl: 'http://site-376580-3028-5672.sxl.cn/blog/96789f79667',
updatedAt: '2018-07-07T02:04:29.037-07:00',
headerImage: {
storageKey: null,
backgroundVariation: null,
noCompression: null,
videoHtml: null,
h: null,
linkTarget: null,
url: '//res.cloudinary.com/hrscywv4p/image/upload/c_limit,fl_progressive,h_1200,q_90,w_2000/wxewyckdmotqycwfb8pk.jpg',
defaultValue: null,
linkInputEnabled: null,
s: null,
descriptionInputEnabled: null,
useImage: null,
videoUrl: null,
linkUrl: null,
textColor: 'light',
w: null,
format: null,
userClassName: null,
type: 'Blog.BackgroundImage',
id: 'f_0e58f04a-374f-47d0-a5f6-93205fa418c9',
storage: null,
sizing: 'cover',
},
state: 'published',
firstContentImage: null,
iconUrl: 'https://res.cloudinary.com/hrscywv4p/image/upload/c_limit,fl_progressive,h_1200,q_90,w_2000/wxewyckdmotqycwfb8pk.jpg',
allTagsList: [
'及',
],
title: '添加博客标题',
pinned: false,
icon: {
storageKey: null,
backgroundVariation: null,
noCompression: null,
videoHtml: null,
h: null,
linkTarget: null,
url: '//res.cloudinary.com/hrscywv4p/image/upload/c_limit,fl_progressive,h_1200,q_90,w_2000/wxewyckdmotqycwfb8pk.jpg',
defaultValue: null,
linkInputEnabled: null,
s: null,
descriptionInputEnabled: null,
useImage: null,
videoUrl: null,
linkUrl: null,
textColor: 'light',
w: null,
format: null,
userClassName: null,
type: 'Blog.BackgroundImage',
id: 'f_0e58f04a-374f-47d0-a5f6-93205fa418c9',
storage: null,
sizing: 'cover',
},
blurb: '',
id: 1622941,
createdAt: '2018-06-27T05:53:29.801-07:00',
likesString: '',
},
{
postedToWechat: false,
publishedAt: '1 个月前',
settings: {
hideBlogDate: null,
},
relativeUrl: '/blog/5671fc1179e',
approvedCommentsCount: 0,
pendingCommentsCount: 0,
publicUrl: 'http://site-376580-3028-5672.sxl.cn/blog/5671fc1179e',
updatedAt: '2018-07-07T02:04:07.562-07:00',
headerImage: {
storageKey: null,
backgroundVariation: null,
noCompression: null,
videoHtml: null,
h: null,
linkTarget: null,
url: '//assets.strikingly.com/static/backgrounds/blur/1.jpg',
defaultValue: null,
linkInputEnabled: null,
s: null,
descriptionInputEnabled: null,
useImage: null,
videoUrl: null,
linkUrl: null,
textColor: 'light',
w: null,
format: null,
userClassName: null,
type: 'Blog.BackgroundImage',
id: 'f_f163553c-fe33-451c-b8b8-4e11c7beffbc',
storage: null,
sizing: 'cover',
},
state: 'published',
firstContentImage: null,
iconUrl: 'https://assets.strikingly.com/static/backgrounds/blur/1.jpg',
allTagsList: [],
title: '添加博客标题',
pinned: false,
icon: {
storageKey: null,
backgroundVariation: null,
noCompression: null,
videoHtml: null,
h: null,
linkTarget: null,
url: '//assets.strikingly.com/static/backgrounds/blur/1.jpg',
defaultValue: null,
linkInputEnabled: null,
s: null,
descriptionInputEnabled: null,
useImage: null,
videoUrl: null,
linkUrl: null,
textColor: 'light',
w: null,
format: null,
userClassName: null,
type: 'Blog.BackgroundImage',
id: 'f_f163553c-fe33-451c-b8b8-4e11c7beffbc',
storage: null,
sizing: 'cover',
},
blurb: '',
id: 1694849,
createdAt: '2018-07-07T02:03:29.403-07:00',
likesString: '',
},
],
isFetchingPosts: false, isFetchingPosts: false,
paginationPosts: { paginationPosts: {},
currentPage: 1, enableComments: false,
previousPage: null,
nextPage: null,
perPage: 8,
totalPages: 1,
totalCount: 2,
},
enableComments: true,
isLikingPost: false, isLikingPost: false,
categories: [ categories: [],
{
name: '及',
count: 1,
id: 28066,
url: 'https://assets.sxlcdn.com/images/ecommerce/ecommerce-default-image.png',
},
],
isFetchingCategories: false, isFetchingCategories: false,
siteId: '11417761', siteId: '11363837',
layout: 'a', layout: 'a',
searchEnable: false, searchEnable: false,
background: '#fff', background: '#fff',
isWhiteBackground: true, isWhiteBackground: true,
companyName: '', companyName: '',
name: '', name: '',
logoUrl: 'https://user-assets.sxlcdn.com/images/796/FrHBzlUnK-Gt018qFnq8-DKwvrc3.jpg?imageMogr2/strip/thumbnail/500x1000>/quality/90!/interlace/1/format/jpg', logoUrl:
'https://user-assets.sxlcdn.com/images/796/FrHBzlUnK-Gt018qFnq8-DKwvrc3.jpg?imageMogr2/strip/thumbnail/500x1000>/quality/90!/interlace/1/format/jpg',
errorMessage: '', errorMessage: '',
nickName: '', nickName: '',
avatarUrl: '', avatarUrl: '',
...@@ -235,29 +78,32 @@ const component = { ...@@ -235,29 +78,32 @@ const component = {
iconType: 'square', iconType: 'square',
id: 'cc7e704a-b47d-4bdf-9c7f-ae40e1de4fd3', id: 'cc7e704a-b47d-4bdf-9c7f-ae40e1de4fd3',
type: 'page', type: 'page',
url: 'https://user-assets.sxlcdn.com/images/376562/Frj2f53OLrwiz2ArUZCnt1SExckY.png?imageMogr2/strip/thumbnail/800x450>/format/png', url:
'https://user-assets.sxlcdn.com/images/376562/Frj2f53OLrwiz2ArUZCnt1SExckY.png?imageMogr2/strip/thumbnail/800x450>/format/png',
name: 'A', name: 'A',
value: '/pages/ecommerce/about/about', value: '/pages/ecommerce/about/about',
linkError: false, linkError: false
}, },
{ {
iconType: 'square', iconType: 'square',
id: 'a8b6d2dd-9e6b-45b3-aa09-04a6609f6ffa', id: 'a8b6d2dd-9e6b-45b3-aa09-04a6609f6ffa',
type: 'page', type: 'page',
url: 'https://user-assets.sxlcdn.com/images/376562/Fsr-ix0nhHO9UJpWMqJxm86l6ZCA.png?imageMogr2/strip/thumbnail/800x450>/format/png', url:
'https://user-assets.sxlcdn.com/images/376562/Fsr-ix0nhHO9UJpWMqJxm86l6ZCA.png?imageMogr2/strip/thumbnail/800x450>/format/png',
name: 'B', name: 'B',
value: '/pages/ecommerce/about/about', value: '/pages/ecommerce/about/about',
linkError: false, linkError: false
}, },
{ {
iconType: 'square', iconType: 'square',
id: 'e36d9873-0bac-48aa-abbf-5029946945d9', id: 'e36d9873-0bac-48aa-abbf-5029946945d9',
type: 'page', type: 'page',
url: 'https://user-assets.sxlcdn.com/images/376562/FmfiJDD_rafmiMhEGUey1352Wze4.jpg?imageMogr2/strip/thumbnail/800x450>/interlace/1/format/jpg', url:
'https://user-assets.sxlcdn.com/images/376562/FmfiJDD_rafmiMhEGUey1352Wze4.jpg?imageMogr2/strip/thumbnail/800x450>/interlace/1/format/jpg',
name: 'C', name: 'C',
value: '/pages/ecommerce/about/about', value: '/pages/ecommerce/about/about',
linkError: false, linkError: false
}, }
], ],
shortcutsFirstLine: [], shortcutsFirstLine: [],
shortcutsSecondLine: [], shortcutsSecondLine: [],
...@@ -265,35 +111,33 @@ const component = { ...@@ -265,35 +111,33 @@ const component = {
banners: [ banners: [
{ {
type: '', type: '',
url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/26.jpg', url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/26.jpg'
}, },
{ {
type: '', type: '',
url: 'https://user-assets.sxlcdn.com/images/376562/Frj2f53OLrwiz2ArUZCnt1SExckY.png?imageMogr2/strip/thumbnail/800x450>/format/png', url:
'https://user-assets.sxlcdn.com/images/376562/Frj2f53OLrwiz2ArUZCnt1SExckY.png?imageMogr2/strip/thumbnail/800x450>/format/png'
}, },
{ {
type: '', type: '',
url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/24.jpg', url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/24.jpg'
}, },
{ {
type: '', type: '',
url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/16.jpg', url: 'https://uploads.sxlcdn.com/static/backgrounds/patterns/16.jpg'
}, }
], ],
tabs: [ tabs: [
{ {
id: 'post', id: 'post',
name: '文章', name: '文章'
}, },
{ {
id: 'category', id: 'category',
name: '分类', name: '分类'
}, }
],
mix: [
'store',
'blog',
], ],
mix: ['store', 'blog'],
navigationBar: {}, navigationBar: {},
showContactBtn: false, showContactBtn: false,
showSlider: true, showSlider: true,
...@@ -308,63 +152,116 @@ const component = { ...@@ -308,63 +152,116 @@ const component = {
animates: [ animates: [
{ {
type: 'translateY', type: 'translateY',
args: [ args: ['100%']
'100%', }
],
},
], ],
option: { option: {
transformOrigin: '50% 50% 0', transformOrigin: '50% 50% 0',
transition: { transition: {
duration: 400, duration: 400,
timingFunction: 'ease', timingFunction: 'ease',
delay: 0, delay: 0
}, }
}
}
]
}
}, },
attached() {
const { siteId } = this.data
this.fetchPosts(siteId, 'all', 1)
this.fetchCategories(siteId)
}, },
], methods: {
fetchPosts(siteId, category, pageNum) {
this.setData({
isFetchingPosts: true
})
let { posts } = this.data
apis.getPosts(siteId, category, pageNum).then(({ data: { data } }) => {
const {
blog: {
enableComments,
id,
miniProgramLogoUrl: logoUrl,
pagination,
blogPosts
}
} = data
posts = uniq(posts.concat(blogPosts))
this.setData({
isFetchingPosts: false,
enableComments,
logoUrl,
posts,
paginationPosts: pagination.blogPosts,
siteId: id
})
})
}, },
fetchCategories(siteId) {
this.setData({
isFetchingCategories: true
})
apis.getCategories(siteId).then(({ data: { data: categories } }) => {
this.setData({
categories: categories.map(it =>
Object.assign(it, { url: `https:${it.url}` })
),
isFetchingCategories: false
})
})
}, },
ready() { loadMorePosts() {
wx.setNavigationBarTitle({ title: this.data.name }) const { paginationPosts, siteId } = this.data
if (paginationPosts.nextPage) {
this.fetchPosts(siteId, 'all', paginationPosts.nextPage)
}
}, },
handleTab(event) { handleTab(event) {
this.setData({ this.setData({
currentTab: event.currentTarget.dataset.id currentTab: event.currentTarget.dataset.id
}) })
}, },
handleSearch() { handleGlobalTab() {
// let { mix } = this.data, this.setData({
url = urls.PAGES.POST_SEARCH selectedPostId: '',
// if (mix && mix.length == 2) { isCommenting: false,
// if (mix[0] == 'store') { currentComment: ''
// switch (mix[1]) { })
// case 'blog':
// url = mixinUrls.PAGES.SEARCH
// break
// default:
// break
// }
// }
// }
wx.navigateTo({ url })
}, },
handlePost(e) { handlePost(e) {
const { id } = e.currentTarget.dataset const { id } = e.currentTarget.dataset
wx.navigateTo({ url: `${urls.PAGES.POST_DETAIL}?postId=${id}` }) var detail = {
}, postId: id
handleProduct(e) { }
this.handlePost(e) this.triggerEvent('PostTap', detail)
}, },
handleCategory(e) { handleCategory(e) {
const { id, name } = e.currentTarget.dataset const { id, name } = e.currentTarget.dataset
wx.navigateTo({ const detail = {
url: `${urls.PAGES.POST_LIST}?category=${name}&categoryId=${id}` id,
}) name
}
this.triggerEvent('CategoryTap', detail)
},
setError: () => {},
fetchPostSharing: () => {},
setComments: () => {},
likePost: () => {}
},
ready() {
wx.setNavigationBarTitle({ title: this.data.name })
},
handleSearch() {
url = urls.PAGES.POST_SEARCH
wx.navigateTo({ url })
}, },
handleProduct(e) {
this.handlePost(e)
},
handlePage(e) { handlePage(e) {
const { id } = e.currentTarget.dataset const { id } = e.currentTarget.dataset
const navigateUrl = [ const navigateUrl = [
...@@ -420,13 +317,6 @@ const component = { ...@@ -420,13 +317,6 @@ const component = {
} }
}, },
loadMorePosts() {
const { paginationPosts, siteId } = this.data
if (paginationPosts.nextPage) {
this.fetchPosts(siteId, 'all', paginationPosts.nextPage)
}
},
handleCard(e) { handleCard(e) {
const { post } = e.currentTarget.dataset const { post } = e.currentTarget.dataset
this.setData({ currentPost: post }) this.setData({ currentPost: post })
...@@ -451,38 +341,7 @@ const component = { ...@@ -451,38 +341,7 @@ const component = {
this.setData({ this.setData({
shareLoading: true shareLoading: true
}) })
// this.fetchPostSharing(siteId, {
// scene: postId.toString(),
// page: 'pages/blog/postDetail/postDetail'
// }).then(res => {
// trackUI(
// 'shareImageBlogPost',
// postId,
// JSON.stringify({
// team_member_id: this.data.teamMemberId || -1,
// key: `shareImageBlogPost${postId}`
// })
// )
// this.setData({
// shareLoading: false
// })
// if (res.success) {
// wx.navigateTo({
// url: `${urls.PAGES.POST_SHARE}?postId=${postId}`
// })
// } else {
// wx.showModal({
// title: '网络错误',
// content: '请稍后重试',
// showCancel: false,
// success: res => {
// if (res.confirm) {
this.closeShareView() this.closeShareView()
// }
// }
// })
// }
// })
}, },
handleLike(e) { handleLike(e) {
const { isLikingPost } = this.data const { isLikingPost } = this.data
...@@ -507,7 +366,6 @@ const component = { ...@@ -507,7 +366,6 @@ const component = {
} }
} }
} }
bindGetUserInfoHandler(e, cb)
}, },
switchOperation(e) { switchOperation(e) {
const { post } = e.currentTarget.dataset const { post } = e.currentTarget.dataset
...@@ -595,15 +453,6 @@ const component = { ...@@ -595,15 +453,6 @@ const component = {
} }
}) })
}, },
handleGlobalTab() {
this.setData({
selectedPostId: '',
isCommenting: false,
currentComment: ''
})
},
onPullDownRefresh() { onPullDownRefresh() {
this.fetchPosts(this.data.siteId, 'all', 1).then(res => this.fetchPosts(this.data.siteId, 'all', 1).then(res =>
wx.stopPullDownRefresh() wx.stopPullDownRefresh()
...@@ -611,84 +460,4 @@ const component = { ...@@ -611,84 +460,4 @@ const component = {
} }
} }
// function mapStateToProps(state) {
// const { list: categories, isFetching: isFetchingCategories } = getCategories(
// state,
// ),
// {
// list: posts,
// isFetching: isFetchingPosts,
// pagination: paginationPosts,
// isLikingPost,
// } = getPosts(state, urls.PAGES.POST_INDEX),
// { mainBackground: background, isWhiteBackground } = getStyle(state),
// { siteId, layout, searchEnable, mix, companyName, name, logoUrl } = getAttr(
// state,
// ),
// { nickName, avatarUrl } = getUserInfo(state),
// { navigationBar } = getNavigationBar(state),
// {
// shortcuts,
// shortcutsFirstLine,
// shortcutsSecondLine,
// shortcutsSecondLineWrapperClass,
// } = getShortcuts(state),
// { errorMessage } = getError(state),
// tabs = [{ id: 'post', name: '文章' }]
// if (categories && categories.length) {
// tabs.push({ id: 'category', name: '分类' })
// }
// const showContactBtn = state.getIn(['globalData', 'attr', 'showContactBtn'])
// let blogLayout = getMixLayout(mix, layout, 'blog')
// let ifInPageDesign = false
// let components = []
// let shortcutsLayout = ''
// if (state.get('components') !== null) {
// components = getComponents(state, 'blog')
// ifInPageDesign = true
// const blogAndCategoryComp = components.find(
// comp => comp.type === 'blogAndCategory',
// )
// const shortcutsComp = components.find(comp => comp.type === 'shortcuts')
// if (blogAndCategoryComp && blogAndCategoryComp.settings) {
// blogLayout = blogAndCategoryComp.settings.layout || 'a'
// }
// if (shortcutsComp && shortcutsComp.settings) {
// shortcutsLayout = shortcutsComp.settings.layout || 'circle'
// }
// }
// // hack temporarily, will be removed in v2
// if (Array.isArray(mix) && mix[0] === 'presentation') {
// ifInPageDesign = false
// }
// const enableComments = state.getIn([
// 'blog',
// 'setting',
// 'settings',
// 'enableComments',
// ])
// const teamMemberId = getTeamMemberId(state)
// return
// }
// function mapDispatchToProps(dispatch) {
// return {
// fetchPosts: (siteId, category, pageNum) =>
// dispatch(fetchPosts(urls.PAGES.POST_INDEX, siteId, category, pageNum)),
// setError: errorMessage => dispatch(setError(errorMessage)),
// fetchPostSharing: (siteId, data) =>
// dispatch(fetchPostSharing(siteId, data)),
// setComments: (postId, setData, successCb, failCb) =>
// dispatch(setComments(postId, setData, successCb, failCb)),
// likePost: (status, postId, nickName, avatarUrl) =>
// dispatch(likePost(status, postId, nickName, avatarUrl)),
// }
// }
// const enhance = (shareView, connect(mapStateToProps, mapDispatchToProps))
Component(component) Component(component)
const BASE_URL = 'https://www.sxl.cn'
export default { export default {
REQUESTS: { REQUESTS: {
// post.js // post.js
GET_POST: (code, productId) => GET_POST: productId =>
`/r/v1/blog_posts/${productId}?code=${code}&v=${new Date().getTime()}`, `${BASE_URL}/r/v1/blog_posts/${productId}?v=${new Date().getTime()}`,
GET_POSTS: (siteId, code, category, pageNum = 1) => category === 'all' GET_POSTS: (siteId, category, pageNum = 1) =>
? `/r/v1/sites/${siteId}/mini_program/blog?limit=8&page=${pageNum}&code=${code}&expand=blogPosts&exclude_content=true&v=${new Date().getTime()}` category === 'all'
: `/r/v1/sites/${siteId}/mini_program/blog?expand=blogPosts&limit=8&page=${pageNum}&tag=${encodeURI(`${category}`)}&code=${code}&exclude_content=true&v=${new Date().getTime()}`, ? `${BASE_URL}/r/v1/sites/${siteId}/mini_program/blog?limit=8&page=${pageNum}&expand=blogPosts&exclude_content=true&v=${new Date().getTime()}`
: `${BASE_URL}/r/v1/sites/${siteId}/mini_program/blog?expand=blogPosts&limit=8&page=${pageNum}&tag=${encodeURI(
`${category}`
)}&exclude_content=true&v=${new Date().getTime()}`,
GET_POSTS_SEARCH: siteId => GET_POSTS_SEARCH: siteId =>
`/r/v1/mini_program/apps/${siteId}/search_blog_posts?v=${new Date().getTime()}`, `${BASE_URL}/r/v1/mini_program/apps/${siteId}/search_blog_posts?v=${new Date().getTime()}`,
// category // category
GET_CATEGORIES: pageId => GET_CATEGORIES: pageId =>
`/r/v1/sites/${pageId}/blog/tags?v=${new Date().getTime()}`, `${BASE_URL}/r/v1/sites/${pageId}/blog/tags?v=${new Date().getTime()}`,
// comment // comment
GET_COMMENTS: (postId, pageNum) => GET_COMMENTS: (postId, pageNum) =>
`/r/v1/mini_program/blog/blog_posts/${postId}/user_comments?per=9999&page=${pageNum}`, `${BASE_URL}/r/v1/mini_program/blog/blog_posts/${postId}/user_comments?per=9999&page=${pageNum}`,
SET_COMMENTS: postId => SET_COMMENTS: postId =>
`/r/v1/mini_program/blog/blog_posts/${postId}/user_comments`, `${BASE_URL}/r/v1/mini_program/blog/blog_posts/${postId}/user_comments`,
// sharing // sharing
GET_SHARING_BASICINFO: siteId => `/r/v1/sites/${siteId}/mp/basic_info`, GET_SHARING_BASICINFO: siteId =>
SET_SHARING_SCENECODE: siteId => `/r/v1/sites/${siteId}/mp/scene_codes`, `${BASE_URL}/r/v1/sites/${siteId}/mp/basic_info`,
SET_SHARING_SCENECODE: siteId =>
`${BASE_URL}/r/v1/sites/${siteId}/mp/scene_codes`,
// setting // setting
GET_SETTINGS: pageId => GET_SETTINGS: pageId =>
`/r/v1/sites/${pageId}/blog_settings?v=${new Date().getTime()}`, `${BASE_URL}/r/v1/sites/${pageId}/blog_settings?v=${new Date().getTime()}`,
SET_LIKE_POST: postId => `/r/v1/mini_program/blog/blog_posts/${postId}/likes`, SET_LIKE_POST: postId =>
DELETE_LIKE_POST: postId => `/r/v1/mini_program/blog/likes/${postId}` `${BASE_URL}/r/v1/mini_program/blog/blog_posts/${postId}/likes`,
DELETE_LIKE_POST: postId =>
`${BASE_URL}/r/v1/mini_program/blog/likes/${postId}`
}, },
METHODS: { METHODS: {
// post // post
...@@ -39,21 +47,21 @@ export default { ...@@ -39,21 +47,21 @@ export default {
GET_SETTINGS: () => 'GET', GET_SETTINGS: () => 'GET',
// sharing // sharing
GET_SHARING_BASICINFO: () => 'GET', GET_SHARING_BASICINFO: () => 'GET',
SET_SHARING_SCENECODE: () => 'POST', SET_SHARING_SCENECODE: () => 'POST'
}, },
PAGES: { PAGES: {
POST_INDEX: '/pages/blog/postIndex/postIndex', POST_INDEX: '/pages/postIndex/postIndex',
POST_DETAIL: '/pages/blog/postDetail/postDetail', POST_DETAIL: '/pages/postDetail/postDetail',
POST_LIST: '/pages/blog/postList/postList', POST_LIST: '/pages/postList/postList',
POST_SEARCH: '/pages/blog/postSearch/postSearch', POST_SEARCH: '/pages/postSearch/postSearch',
MORE: '/pages/blog/more/more', MORE: '/pages/more/more',
ABOUT: '/pages/blog/about/about', ABOUT: '/pages/about/about',
SXL: '/pages/blog/sxl/sxl', SXL: '/pages/sxl/sxl',
POST_SHARE: '/pages/blog/postShare/postShare', POST_SHARE: '/pages/postShare/postShare',
WMP_SHARE: '/pages/blog/wmpShare/wmpShare', WMP_SHARE: '/pages/wmpShare/wmpShare'
}, },
NAVIGATION: { NAVIGATION: {
POST_INDEX: 'pages#blog#more#more', POST_INDEX: 'pages#blog#more#more',
ABOUT: 'pages#blog#about#about', ABOUT: 'pages#blog#about#about'
}, }
} }
const base_path = ''
export default {
//error
SET_ERROR_MESSAGE: base_path + 'set_error_message',
CLEAR_ERROR_MESSAGE: base_path + 'clear_error_message',
//global
SET_GLOBAL_DATA: base_path + 'miniprogram/set_global_data',
CLEAR_GLOBAL_DATA: base_path + 'miniprogram/clear_store_data',
//setting
REFRESH_DATA: 'app/blog/refresh_data',
}
export default { export default {
onLoad(options) {}, canUseSearch: true,
canUseCollection: false,
canUseShare: false,
} }
const preFix = '/assets/common'
export const iconPath = {
ICON_AUTH_WECHAT: `${preFix}/wechat-icon.png`,
ICON_AUTH_BG: `${preFix}/background.png`,
downloadImage: `${preFix}/icon-download-image.png`,
}
export default iconPath
module.exports = {
GET_COMPONENTS: (siteId, type) =>
`/r/v1/mini_program/apps/${siteId}/components?type[]=${type}`,
GET_RELATION_DATA: siteId =>
`/r/v1/mini_program/apps/${siteId}/relation_data`,
FETCH_ANNOUNCEMENTS: siteId =>
`/r/v1/sites/${siteId}/announcement/mp/announcements`,
PAGES: {
ANNOUNCEMENTS: '/pages/common/announcement/announcement',
},
UPLOAD_IMAGE: siteId =>
`/r/v1/mini_program/apps/${siteId}/asset_images_presign`,
}
(function webpackUniversalModuleDefinition(root, factory) {
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd)
define([], factory);
else {
var a = factory();
for (var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(this, function () {
return /******/ (function (modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if (installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/
};
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/
}
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function (value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function (exports, name, getter) {
/******/ if (!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/
});
/******/
}
/******/
};
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function (module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/
};
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 11);
/******/
})
/************************************************************************/
/******/([
/* 0 */
/***/ (function (module, exports, __webpack_require__) {
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
module.exports = {
type: function type(ob) {
return Object.prototype.toString.call(ob).slice(8, -1).toLowerCase();
},
isObject: function isObject(ob, real) {
if (real) {
return this.type(ob) === "object";
} else {
return ob && (typeof ob === 'undefined' ? 'undefined' : _typeof(ob)) === 'object';
}
},
isFormData: function isFormData(val) {
return typeof FormData !== 'undefined' && val instanceof FormData;
},
trim: function trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, '');
},
encode: function encode(val) {
return encodeURIComponent(val).replace(/%40/gi, '@').replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',').replace(/%20/g, '+').replace(/%5B/gi, '[').replace(/%5D/gi, ']');
},
formatParams: function formatParams(data) {
var str = "";
var first = true;
var that = this;
if ((typeof data === 'undefined' ? 'undefined' : _typeof(data)) != "object") {
return data;
}
function _encode(sub, path) {
var encode = that.encode;
var type = that.type(sub);
if (type == "array") {
sub.forEach(function (e, i) {
_encode(e, path + "%5B%5D");
});
} else if (type == "object") {
for (var key in sub) {
if (path) {
_encode(sub[key], path + "%5B" + encode(key) + "%5D");
} else {
_encode(sub[key], encode(key));
}
}
} else {
if (!first) {
str += "&";
}
first = false;
str += path + "=" + encode(sub);
}
}
_encode(data, "");
return str;
},
// Do not overwrite existing attributes
merge: function merge(a, b) {
for (var key in b) {
if (!a.hasOwnProperty(key)) {
a[key] = b[key];
} else if (this.isObject(b[key], 1) && this.isObject(a[key], 1)) {
this.merge(a[key], b[key]);
}
}
return a;
}
};
/***/
}),
/* 1 */
/***/ (function (module, exports, __webpack_require__) {
function KEEP(_, cb) { cb(); }
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/*
* author: wendu
* email: 824783146@qq.com
**/
var util = __webpack_require__(0);
var isBrowser = typeof document !== "undefined";
//EngineWrapper can help generating a http engine quickly through a adapter
function EngineWrapper(adapter) {
var AjaxEngine = function () {
function AjaxEngine() {
_classCallCheck(this, AjaxEngine);
this.requestHeaders = {};
this.readyState = 0;
this.timeout = 0; // 0 stands for no timeout
this.responseURL = "";
this.responseHeaders = {};
}
_createClass(AjaxEngine, [{
key: "_call",
value: function _call(name) {
this[name] && this[name].apply(this, [].splice.call(arguments, 1));
}
}, {
key: "_changeReadyState",
value: function _changeReadyState(state) {
this.readyState = state;
this._call("onreadystatechange");
}
}, {
key: "open",
value: function open(method, url) {
this.method = method;
if (!url) {
url = location.href;
} else {
url = util.trim(url);
if (url.indexOf("http") !== 0) {
// Normalize the request url
if (isBrowser) {
var t = document.createElement("a");
t.href = url;
url = t.href;
}
}
}
this.responseURL = url;
this._changeReadyState(1);
}
}, {
key: "send",
value: function send(arg) {
var _this = this;
arg = arg || null;
if (isBrowser) {
var cookie = document.cookie;
if (cookie) {
this.requestHeaders.cookie = cookie;
}
}
var self = this;
if (adapter) {
var request = {
method: self.method,
url: self.responseURL,
headers: self.requestHeaders || {},
body: arg
};
util.merge(request, self._options || {});
if (request.method === "GET") {
request.body = null;
}
self._changeReadyState(3);
var timer;
self.timeout = self.timeout || 0;
if (self.timeout > 0) {
timer = setTimeout(function () {
if (self.readyState === 3) {
_this._call("onloadend");
self._changeReadyState(0);
self._call("ontimeout");
}
}, self.timeout);
}
request.timeout = self.timeout;
adapter(request, function (response) {
function getAndDelete(key) {
var t = response[key];
delete response[key];
return t;
}
// If the request has already timeout, return
if (self.readyState !== 3) return;
clearTimeout(timer);
// Make sure the type of status is integer
self.status = getAndDelete("statusCode") - 0;
var responseText = getAndDelete("responseText");
var statusMessage = getAndDelete("statusMessage");
// Network error, set the status code 0
if (!self.status) {
self.statusText = responseText;
self._call("onerror", { msg: statusMessage });
} else {
// Parsing the response headers to array in a object, because
// there may be multiple values with the same header name
var responseHeaders = getAndDelete("headers");
var headers = {};
for (var field in responseHeaders) {
var value = responseHeaders[field];
var key = field.toLowerCase();
// Is array
if ((typeof value === "undefined" ? "undefined" : _typeof(value)) === "object") {
headers[key] = value;
} else {
headers[key] = headers[key] || [];
headers[key].push(value);
}
}
var cookies = headers["set-cookie"];
if (isBrowser && cookies) {
cookies.forEach(function (e) {
// Remove the http-Only property of the cookie
// so that JavaScript can operate it.
document.cookie = e.replace(/;\s*httpOnly/ig, "");
});
}
self.responseHeaders = headers;
// Set the fields of engine from response
self.statusText = statusMessage || "";
self.response = self.responseText = responseText;
self._response = response;
self._changeReadyState(4);
self._call("onload");
}
self._call("onloadend");
});
} else {
console.error("Ajax require adapter");
}
}
}, {
key: "setRequestHeader",
value: function setRequestHeader(key, value) {
this.requestHeaders[util.trim(key)] = value;
}
}, {
key: "getResponseHeader",
value: function getResponseHeader(key) {
return (this.responseHeaders[key.toLowerCase()] || "").toString() || null;
}
}, {
key: "getAllResponseHeaders",
value: function getAllResponseHeaders() {
var str = "";
for (var key in this.responseHeaders) {
str += key + ":" + this.getResponseHeader(key) + "\r\n";
}
return str || null;
}
}, {
key: "abort",
value: function abort(msg) {
this._changeReadyState(0);
this._call("onerror", { msg: msg });
this._call("onloadend");
}
}], [{
key: "setAdapter",
value: function setAdapter(requestAdapter) {
adapter = requestAdapter;
}
}]);
return AjaxEngine;
}();
return AjaxEngine;
}
// learn more about keep-loader: https://github.com/wendux/keep-loader
;
module.exports = EngineWrapper;
/***/
}),
/* 2 */
/***/ (function (module, exports, __webpack_require__) {
function KEEP(_, cb) { cb(); }
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var utils = __webpack_require__(0);
var isBrowser = typeof document !== "undefined";
var Fly = function () {
function Fly(engine) {
_classCallCheck(this, Fly);
this.engine = engine || XMLHttpRequest;
this.default = this; //For typeScript
/**
* Add lock/unlock API for interceptor.
*
* Once an request/response interceptor is locked, the incoming request/response
* will be added to a queue before they enter the interceptor, they will not be
* continued until the interceptor is unlocked.
*
* @param [interceptor] either is interceptors.request or interceptors.response
*/
function wrap(interceptor) {
var resolve;
var reject;
function _clear() {
interceptor.p = resolve = reject = null;
}
utils.merge(interceptor, {
lock: function lock() {
if (!resolve) {
interceptor.p = new Promise(function (_resolve, _reject) {
resolve = _resolve;
reject = _reject;
});
}
},
unlock: function unlock() {
if (resolve) {
resolve();
_clear();
}
},
clear: function clear() {
if (reject) {
reject("cancel");
_clear();
}
}
});
}
var interceptors = this.interceptors = {
response: {
use: function use(handler, onerror) {
this.handler = handler;
this.onerror = onerror;
}
},
request: {
use: function use(handler) {
this.handler = handler;
}
}
};
var irq = interceptors.request;
var irp = interceptors.response;
wrap(irp);
wrap(irq);
this.config = {
method: "GET",
baseURL: "",
headers: {},
timeout: 0,
parseJson: true, // Convert response data to JSON object automatically.
withCredentials: false
};
}
_createClass(Fly, [{
key: "request",
value: function request(url, data, options) {
var _this = this;
var engine = new this.engine();
var contentType = "Content-Type";
var contentTypeLowerCase = contentType.toLowerCase();
var interceptors = this.interceptors;
var requestInterceptor = interceptors.request;
var responseInterceptor = interceptors.response;
var requestInterceptorHandler = requestInterceptor.handler;
var promise = new Promise(function (resolve, reject) {
if (utils.isObject(url)) {
options = url;
url = options.url;
}
options = options || {};
options.headers = options.headers || {};
function isPromise(p) {
// some polyfill implementation of Promise may be not standard,
// so, we test by duck-typing
return p && p.then && p.catch;
}
/**
* If the request/response interceptor has been locked,
* the new request/response will enter a queue. otherwise, it will be performed directly.
* @param [promise] if the promise exist, means the interceptor is locked.
* @param [callback]
*/
function enqueueIfLocked(promise, callback) {
if (promise) {
promise.then(function () {
callback();
});
} else {
callback();
}
}
// make the http request
function makeRequest(options) {
data = options.body;
// Normalize the request url
url = utils.trim(options.url);
var baseUrl = utils.trim(options.baseURL || "");
if (!url && isBrowser && !baseUrl) url = location.href;
if (url.indexOf("http") !== 0) {
var isAbsolute = url[0] === "/";
if (!baseUrl && isBrowser) {
var arr = location.pathname.split("/");
arr.pop();
baseUrl = location.protocol + "//" + location.host + (isAbsolute ? "" : arr.join("/"));
}
if (baseUrl[baseUrl.length - 1] !== "/") {
baseUrl += "/";
}
url = baseUrl + (isAbsolute ? url.substr(1) : url);
if (isBrowser) {
// Normalize the url which contains the ".." or ".", such as
// "http://xx.com/aa/bb/../../xx" to "http://xx.com/xx" .
var t = document.createElement("a");
t.href = url;
url = t.href;
}
}
var responseType = utils.trim(options.responseType || "");
var isGet = options.method === "GET";
if (isGet) {
if (data) {
if (utils.type(data) !== "string") {
data = utils.formatParams(data);
}
url += (url.indexOf("?") === -1 ? "?" : "&") + data;
}
}
engine.open(options.method, url);
// try catch for ie >=9
try {
engine.withCredentials = !!options.withCredentials;
engine.timeout = options.timeout || 0;
if (responseType !== "stream") {
engine.responseType = responseType;
}
} catch (e) { }
var customContentType = options.headers[contentType] || options.headers[contentTypeLowerCase];
// default content type
var _contentType = "application/x-www-form-urlencoded";
// If the request data is json object, transforming it to json string,
// and set request content-type to "json". In browser, the data will
// be sent as RequestBody instead of FormData
if (utils.trim((customContentType || "").toLowerCase()) === _contentType) {
data = utils.formatParams(data);
} else if (!utils.isFormData(data) && ["object", "array"].indexOf(utils.type(data)) !== -1) {
_contentType = 'application/json;charset=utf-8';
data = JSON.stringify(data);
}
//If user doesn't set content-type, set default.
if (!customContentType) {
options.headers[contentType] = _contentType;
}
for (var k in options.headers) {
if (k === contentType && utils.isFormData(data)) {
// Delete the content-type, Let the browser set it
delete options.headers[k];
} else {
try {
// In browser environment, some header fields are readonly,
// write will cause the exception .
engine.setRequestHeader(k, options.headers[k]);
} catch (e) { }
}
}
function onresult(handler, data, type) {
enqueueIfLocked(responseInterceptor.p, function () {
if (handler) {
//如果失败,添加请求信息
if (type) {
data.request = options;
}
var ret = handler.call(responseInterceptor, data, Promise);
data = ret === undefined ? data : ret;
}
if (!isPromise(data)) {
data = Promise[type === 0 ? "resolve" : "reject"](data);
}
data.then(function (d) {
resolve(d);
}).catch(function (e) {
reject(e);
});
});
}
function onerror(e) {
e.engine = engine;
onresult(responseInterceptor.onerror, e, -1);
}
function Err(msg, status) {
this.message = msg;
this.status = status;
}
engine.onload = function () {
// The xhr of IE9 has not response field
var response = engine.response || engine.responseText;
if (response && options.parseJson && (engine.getResponseHeader(contentType) || "").indexOf("json") !== -1
// Some third engine implementation may transform the response text to json object automatically,
// so we should test the type of response before transforming it
&& !utils.isObject(response)) {
response = JSON.parse(response);
}
var headers = {};
var items = (engine.getAllResponseHeaders() || "").split("\r\n");
items.pop();
items.forEach(function (e) {
if (!e) return;
var key = e.split(":")[0];
headers[key] = engine.getResponseHeader(key);
});
var status = engine.status;
var statusText = engine.statusText;
var data = { data: response, headers: headers, status: status, statusText: statusText };
// The _response filed of engine is set in adapter which be called in engine-wrapper.js
utils.merge(data, engine._response);
if (status >= 200 && status < 300 || status === 304) {
data.engine = engine;
data.request = options;
onresult(responseInterceptor.handler, data, 0);
} else {
var e = new Err(statusText, status);
e.response = data;
onerror(e);
}
};
engine.onerror = function (e) {
onerror(new Err(e.msg || "Network Error", 0));
};
engine.ontimeout = function () {
onerror(new Err("timeout [ " + engine.timeout + "ms ]", 1));
};
engine._options = options;
setTimeout(function () {
engine.send(isGet ? null : data);
}, 0);
}
enqueueIfLocked(requestInterceptor.p, function () {
utils.merge(options, _this.config);
var headers = options.headers;
headers[contentType] = headers[contentType] || headers[contentTypeLowerCase] || "";
delete headers[contentTypeLowerCase];
options.body = data || options.body;
url = utils.trim(url || "");
options.method = options.method.toUpperCase();
options.url = url;
var ret = options;
if (requestInterceptorHandler) {
ret = requestInterceptorHandler.call(requestInterceptor, options, Promise) || options;
}
if (!isPromise(ret)) {
ret = Promise.resolve(ret);
}
ret.then(function (d) {
//if options continue
if (d === options) {
makeRequest(d);
} else {
resolve(d);
}
}, function (err) {
reject(err);
});
});
});
promise.engine = engine;
return promise;
}
}, {
key: "all",
value: function all(promises) {
return Promise.all(promises);
}
}, {
key: "spread",
value: function spread(callback) {
return function (arr) {
return callback.apply(null, arr);
};
}
}]);
return Fly;
}();
//For typeScript
Fly.default = Fly;
["get", "post", "put", "patch", "head", "delete"].forEach(function (e) {
Fly.prototype[e] = function (url, data, option) {
return this.request(url, data, utils.merge({ method: e }, option));
};
});
["lock", "unlock", "clear"].forEach(function (e) {
Fly.prototype[e] = function () {
this.interceptors.request[e]();
};
});
// Learn more about keep-loader: https://github.com/wendux/keep-loader
;
module.exports = Fly;
/***/
}),
/* 3 */,
/* 4 */,
/* 5 */,
/* 6 */
/***/ (function (module, exports, __webpack_require__) {
"use strict";
//微信小程序适配器
module.exports = function (request, responseCallback) {
var con = {
method: request.method,
url: request.url,
dataType: request.dataType || undefined,
header: request.headers,
data: request.body || {},
success: function success(res) {
responseCallback({
statusCode: res.statusCode,
responseText: res.data,
headers: res.header,
statusMessage: res.errMsg
});
},
fail: function fail(res) {
responseCallback({
statusCode: res.statusCode || 0,
statusMessage: res.errMsg
});
}
};
wx.request(con);
};
/***/
}),
/* 7 */,
/* 8 */,
/* 9 */,
/* 10 */,
/* 11 */
/***/ (function (module, exports, __webpack_require__) {
"use strict";
//微信小程序入口
var Fly = __webpack_require__(2);
var EngineWrapper = __webpack_require__(1);
var adapter = __webpack_require__(6);
var wxEngine = EngineWrapper(adapter);
module.exports = function (engine) {
return new Fly(engine || wxEngine);
};
/***/
})
/******/]);
});
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";function e(){return Qe.apply(null,arguments)}function t(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function n(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function s(e){return void 0===e}function i(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function r(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function a(e,t){var n,s=[];for(n=0;n<e.length;++n)s.push(t(e[n],n));return s}function o(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function u(e,t){for(var n in t)o(t,n)&&(e[n]=t[n]);return o(t,"toString")&&(e.toString=t.toString),o(t,"valueOf")&&(e.valueOf=t.valueOf),e}function l(e,t,n,s){return ge(e,t,n,s,!0).utc()}function d(e){return null==e._pf&&(e._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),e._pf}function h(e){if(null==e._isValid){var t=d(e),n=Xe.call(t.parsedDateParts,function(e){return null!=e}),s=!isNaN(e._d.getTime())&&t.overflow<0&&!t.empty&&!t.invalidMonth&&!t.invalidWeekday&&!t.weekdayMismatch&&!t.nullInput&&!t.invalidFormat&&!t.userInvalidated&&(!t.meridiem||t.meridiem&&n);if(e._strict&&(s=s&&0===t.charsLeftOver&&0===t.unusedTokens.length&&void 0===t.bigHour),null!=Object.isFrozen&&Object.isFrozen(e))return s;e._isValid=s}return e._isValid}function c(e){var t=l(NaN);return null!=e?u(d(t),e):d(t).userInvalidated=!0,t}function f(e,t){var n,i,r;if(s(t._isAMomentObject)||(e._isAMomentObject=t._isAMomentObject),s(t._i)||(e._i=t._i),s(t._f)||(e._f=t._f),s(t._l)||(e._l=t._l),s(t._strict)||(e._strict=t._strict),s(t._tzm)||(e._tzm=t._tzm),s(t._isUTC)||(e._isUTC=t._isUTC),s(t._offset)||(e._offset=t._offset),s(t._pf)||(e._pf=d(t)),s(t._locale)||(e._locale=t._locale),Ke.length>0)for(n=0;n<Ke.length;n++)s(r=t[i=Ke[n]])||(e[i]=r);return e}function m(t){f(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===et&&(et=!0,e.updateOffset(this),et=!1)}function _(e){return e instanceof m||null!=e&&null!=e._isAMomentObject}function y(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function g(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=y(t)),n}function p(e,t,n){var s,i=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),a=0;for(s=0;s<i;s++)(n&&e[s]!==t[s]||!n&&g(e[s])!==g(t[s]))&&a++;return a+r}function w(t){!1===e.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function v(t,n){var s=!0;return u(function(){if(null!=e.deprecationHandler&&e.deprecationHandler(null,t),s){for(var i,r=[],a=0;a<arguments.length;a++){if(i="","object"==typeof arguments[a]){i+="\n["+a+"] ";for(var o in arguments[0])i+=o+": "+arguments[0][o]+", ";i=i.slice(0,-2)}else i=arguments[a];r.push(i)}w(t+"\nArguments: "+Array.prototype.slice.call(r).join("")+"\n"+(new Error).stack),s=!1}return n.apply(this,arguments)},n)}function M(t,n){null!=e.deprecationHandler&&e.deprecationHandler(t,n),tt[t]||(w(n),tt[t]=!0)}function S(e){return e instanceof Function||"[object Function]"===Object.prototype.toString.call(e)}function D(e,t){var s,i=u({},e);for(s in t)o(t,s)&&(n(e[s])&&n(t[s])?(i[s]={},u(i[s],e[s]),u(i[s],t[s])):null!=t[s]?i[s]=t[s]:delete i[s]);for(s in e)o(e,s)&&!o(t,s)&&n(e[s])&&(i[s]=u({},i[s]));return i}function k(e){null!=e&&this.set(e)}function Y(e,t){var n=e.toLowerCase();st[n]=st[n+"s"]=st[t]=e}function O(e){return"string"==typeof e?st[e]||st[e.toLowerCase()]:void 0}function T(e){var t,n,s={};for(n in e)o(e,n)&&(t=O(n))&&(s[t]=e[n]);return s}function x(e,t){it[e]=t}function b(e,t,n){var s=""+Math.abs(e),i=t-s.length;return(e>=0?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+s}function P(e,t,n,s){var i=s;"string"==typeof s&&(i=function(){return this[s]()}),e&&(ut[e]=i),t&&(ut[t[0]]=function(){return b(i.apply(this,arguments),t[1],t[2])}),n&&(ut[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),e)})}function W(e){return e.match(/\[[\s\S]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"")}function H(e,t){return e.isValid()?(t=R(t,e.localeData()),ot[t]=ot[t]||function(e){var t,n,s=e.match(rt);for(t=0,n=s.length;t<n;t++)ut[s[t]]?s[t]=ut[s[t]]:s[t]=W(s[t]);return function(t){var i,r="";for(i=0;i<n;i++)r+=S(s[i])?s[i].call(t,e):s[i];return r}}(t),ot[t](e)):e.localeData().invalidDate()}function R(e,t){function n(e){return t.longDateFormat(e)||e}var s=5;for(at.lastIndex=0;s>=0&&at.test(e);)e=e.replace(at,n),at.lastIndex=0,s-=1;return e}function C(e,t,n){Yt[e]=S(t)?t:function(e,s){return e&&n?n:t}}function F(e,t){return o(Yt,e)?Yt[e](t._strict,t._locale):new RegExp(function(e){return U(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,s,i){return t||n||s||i}))}(e))}function U(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function L(e,t){var n,s=t;for("string"==typeof e&&(e=[e]),i(t)&&(s=function(e,n){n[t]=g(e)}),n=0;n<e.length;n++)Ot[e[n]]=s}function N(e,t){L(e,function(e,n,s,i){s._w=s._w||{},t(e,s._w,s,i)})}function G(e,t,n){null!=t&&o(Ot,e)&&Ot[e](t,n._a,n,e)}function V(e){return E(e)?366:365}function E(e){return e%4==0&&e%100!=0||e%400==0}function I(t,n){return function(s){return null!=s?(j(this,t,s),e.updateOffset(this,n),this):A(this,t)}}function A(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function j(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&E(e.year())&&1===e.month()&&29===e.date()?e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),Z(n,e.month())):e._d["set"+(e._isUTC?"UTC":"")+t](n))}function Z(e,t){if(isNaN(e)||isNaN(t))return NaN;var n=function(e,t){return(e%t+t)%t}(t,12);return e+=(t-n)/12,1===n?E(e)?29:28:31-n%7%2}function z(e,t){var n;if(!e.isValid())return e;if("string"==typeof t)if(/^\d+$/.test(t))t=g(t);else if(t=e.localeData().monthsParse(t),!i(t))return e;return n=Math.min(e.date(),Z(e.year(),t)),e._d["set"+(e._isUTC?"UTC":"")+"Month"](t,n),e}function $(t){return null!=t?(z(this,t),e.updateOffset(this,!0),this):A(this,"Month")}function q(){function e(e,t){return t.length-e.length}var t,n,s=[],i=[],r=[];for(t=0;t<12;t++)n=l([2e3,t]),s.push(this.monthsShort(n,"")),i.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,""));for(s.sort(e),i.sort(e),r.sort(e),t=0;t<12;t++)s[t]=U(s[t]),i[t]=U(i[t]);for(t=0;t<24;t++)r[t]=U(r[t]);this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+s.join("|")+")","i")}function J(e){var t=new Date(Date.UTC.apply(null,arguments));return e<100&&e>=0&&isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e),t}function B(e,t,n){var s=7+t-n;return-((7+J(e,0,s).getUTCDay()-t)%7)+s-1}function Q(e,t,n,s,i){var r,a,o=1+7*(t-1)+(7+n-s)%7+B(e,s,i);return o<=0?a=V(r=e-1)+o:o>V(e)?(r=e+1,a=o-V(e)):(r=e,a=o),{year:r,dayOfYear:a}}function X(e,t,n){var s,i,r=B(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+K(i=e.year()-1,t,n):a>K(e.year(),t,n)?(s=a-K(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function K(e,t,n){var s=B(e,t,n),i=B(e+1,t,n);return(V(e)-s+i)/7}function ee(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],d=[];for(t=0;t<7;t++)n=l([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),d.push(s),d.push(i),d.push(r);for(a.sort(e),o.sort(e),u.sort(e),d.sort(e),t=0;t<7;t++)o[t]=U(o[t]),u[t]=U(u[t]),d[t]=U(d[t]);this._weekdaysRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function te(){return this.hours()%12||12}function ne(e,t){P(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function se(e,t){return t._meridiemParse}function ie(e){return e?e.toLowerCase().replace("_","-"):e}function re(e){var t=null;if(!Xt[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=Jt._abbr;ae(t)}catch(e){}return Xt[e]}function ae(e,t){var n;return e&&(n=s(t)?ue(e):oe(e,t))&&(Jt=n),Jt._abbr}function oe(e,t){if(null!==t){var n=Qt;if(t.abbr=e,null!=Xt[e])M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),n=Xt[e]._config;else if(null!=t.parentLocale){if(null==Xt[t.parentLocale])return Kt[t.parentLocale]||(Kt[t.parentLocale]=[]),Kt[t.parentLocale].push({name:e,config:t}),null;n=Xt[t.parentLocale]._config}return Xt[e]=new k(D(n,t)),Kt[e]&&Kt[e].forEach(function(e){oe(e.name,e.config)}),ae(e),Xt[e]}return delete Xt[e],null}function ue(e){var n;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return Jt;if(!t(e)){if(n=re(e))return n;e=[e]}return function(e){for(var t,n,s,i,r=0;r<e.length;){for(t=(i=ie(e[r]).split("-")).length,n=(n=ie(e[r+1]))?n.split("-"):null;t>0;){if(s=re(i.slice(0,t).join("-")))return s;if(n&&n.length>=t&&p(i,n,!0)>=t-1)break;t--}r++}return null}(e)}function le(e){var t,n=e._a;return n&&-2===d(e).overflow&&(t=n[xt]<0||n[xt]>11?xt:n[bt]<1||n[bt]>Z(n[Tt],n[xt])?bt:n[Pt]<0||n[Pt]>24||24===n[Pt]&&(0!==n[Wt]||0!==n[Ht]||0!==n[Rt])?Pt:n[Wt]<0||n[Wt]>59?Wt:n[Ht]<0||n[Ht]>59?Ht:n[Rt]<0||n[Rt]>999?Rt:-1,d(e)._overflowDayOfYear&&(t<Tt||t>bt)&&(t=bt),d(e)._overflowWeeks&&-1===t&&(t=Ct),d(e)._overflowWeekday&&-1===t&&(t=Ft),d(e).overflow=t),e}function de(e,t,n){return null!=e?e:null!=t?t:n}function he(t){var n,s,i,r,a,o=[];if(!t._d){for(i=function(t){var n=new Date(e.now());return t._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}(t),t._w&&null==t._a[bt]&&null==t._a[xt]&&function(e){var t,n,s,i,r,a,o,u;if(null!=(t=e._w).GG||null!=t.W||null!=t.E)r=1,a=4,n=de(t.GG,e._a[Tt],X(pe(),1,4).year),s=de(t.W,1),((i=de(t.E,1))<1||i>7)&&(u=!0);else{r=e._locale._week.dow,a=e._locale._week.doy;var l=X(pe(),r,a);n=de(t.gg,e._a[Tt],l.year),s=de(t.w,l.week),null!=t.d?((i=t.d)<0||i>6)&&(u=!0):null!=t.e?(i=t.e+r,(t.e<0||t.e>6)&&(u=!0)):i=r}s<1||s>K(n,r,a)?d(e)._overflowWeeks=!0:null!=u?d(e)._overflowWeekday=!0:(o=Q(n,s,i,r,a),e._a[Tt]=o.year,e._dayOfYear=o.dayOfYear)}(t),null!=t._dayOfYear&&(a=de(t._a[Tt],i[Tt]),(t._dayOfYear>V(a)||0===t._dayOfYear)&&(d(t)._overflowDayOfYear=!0),s=J(a,0,t._dayOfYear),t._a[xt]=s.getUTCMonth(),t._a[bt]=s.getUTCDate()),n=0;n<3&&null==t._a[n];++n)t._a[n]=o[n]=i[n];for(;n<7;n++)t._a[n]=o[n]=null==t._a[n]?2===n?1:0:t._a[n];24===t._a[Pt]&&0===t._a[Wt]&&0===t._a[Ht]&&0===t._a[Rt]&&(t._nextDay=!0,t._a[Pt]=0),t._d=(t._useUTC?J:function(e,t,n,s,i,r,a){var o=new Date(e,t,n,s,i,r,a);return e<100&&e>=0&&isFinite(o.getFullYear())&&o.setFullYear(e),o}).apply(null,o),r=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Pt]=24),t._w&&void 0!==t._w.d&&t._w.d!==r&&(d(t).weekdayMismatch=!0)}}function ce(e){var t,n,s,i,r,a,o=e._i,u=en.exec(o)||tn.exec(o);if(u){for(d(e).iso=!0,t=0,n=sn.length;t<n;t++)if(sn[t][1].exec(u[1])){i=sn[t][0],s=!1!==sn[t][2];break}if(null==i)return void(e._isValid=!1);if(u[3]){for(t=0,n=rn.length;t<n;t++)if(rn[t][1].exec(u[3])){r=(u[2]||" ")+rn[t][0];break}if(null==r)return void(e._isValid=!1)}if(!s&&null!=r)return void(e._isValid=!1);if(u[4]){if(!nn.exec(u[4]))return void(e._isValid=!1);a="Z"}e._f=i+(r||"")+(a||""),_e(e)}else e._isValid=!1}function fe(e,t,n,s,i,r){var a=[function(e){var t=parseInt(e,10);{if(t<=49)return 2e3+t;if(t<=999)return 1900+t}return t}(e),Vt.indexOf(t),parseInt(n,10),parseInt(s,10),parseInt(i,10)];return r&&a.push(parseInt(r,10)),a}function me(e){var t=on.exec(function(e){return e.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").trim()}(e._i));if(t){var n=fe(t[4],t[3],t[2],t[5],t[6],t[7]);if(!function(e,t,n){if(e&&jt.indexOf(e)!==new Date(t[0],t[1],t[2]).getDay())return d(n).weekdayMismatch=!0,n._isValid=!1,!1;return!0}(t[1],n,e))return;e._a=n,e._tzm=function(e,t,n){if(e)return un[e];if(t)return 0;var s=parseInt(n,10),i=s%100;return(s-i)/100*60+i}(t[8],t[9],t[10]),e._d=J.apply(null,e._a),e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),d(e).rfc2822=!0}else e._isValid=!1}function _e(t){if(t._f!==e.ISO_8601)if(t._f!==e.RFC_2822){t._a=[],d(t).empty=!0;var n,s,i,r,a,o=""+t._i,u=o.length,l=0;for(i=R(t._f,t._locale).match(rt)||[],n=0;n<i.length;n++)r=i[n],(s=(o.match(F(r,t))||[])[0])&&((a=o.substr(0,o.indexOf(s))).length>0&&d(t).unusedInput.push(a),o=o.slice(o.indexOf(s)+s.length),l+=s.length),ut[r]?(s?d(t).empty=!1:d(t).unusedTokens.push(r),G(r,s,t)):t._strict&&!s&&d(t).unusedTokens.push(r);d(t).charsLeftOver=u-l,o.length>0&&d(t).unusedInput.push(o),t._a[Pt]<=12&&!0===d(t).bigHour&&t._a[Pt]>0&&(d(t).bigHour=void 0),d(t).parsedDateParts=t._a.slice(0),d(t).meridiem=t._meridiem,t._a[Pt]=function(e,t,n){var s;if(null==n)return t;return null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((s=e.isPM(n))&&t<12&&(t+=12),s||12!==t||(t=0),t):t}(t._locale,t._a[Pt],t._meridiem),he(t),le(t)}else me(t);else ce(t)}function ye(o){var l=o._i,y=o._f;return o._locale=o._locale||ue(o._l),null===l||void 0===y&&""===l?c({nullInput:!0}):("string"==typeof l&&(o._i=l=o._locale.preparse(l)),_(l)?new m(le(l)):(r(l)?o._d=l:t(y)?function(e){var t,n,s,i,r;if(0===e._f.length)return d(e).invalidFormat=!0,void(e._d=new Date(NaN));for(i=0;i<e._f.length;i++)r=0,t=f({},e),null!=e._useUTC&&(t._useUTC=e._useUTC),t._f=e._f[i],_e(t),h(t)&&(r+=d(t).charsLeftOver,r+=10*d(t).unusedTokens.length,d(t).score=r,(null==s||r<s)&&(s=r,n=t));u(e,n||t)}(o):y?_e(o):function(o){var u=o._i;s(u)?o._d=new Date(e.now()):r(u)?o._d=new Date(u.valueOf()):"string"==typeof u?function(t){var n=an.exec(t._i);null===n?(ce(t),!1===t._isValid&&(delete t._isValid,me(t),!1===t._isValid&&(delete t._isValid,e.createFromInputFallback(t)))):t._d=new Date(+n[1])}(o):t(u)?(o._a=a(u.slice(0),function(e){return parseInt(e,10)}),he(o)):n(u)?function(e){if(!e._d){var t=T(e._i);e._a=a([t.year,t.month,t.day||t.date,t.hour,t.minute,t.second,t.millisecond],function(e){return e&&parseInt(e,10)}),he(e)}}(o):i(u)?o._d=new Date(u):e.createFromInputFallback(o)}(o),h(o)||(o._d=null),o))}function ge(e,s,i,r,a){var o={};return!0!==i&&!1!==i||(r=i,i=void 0),(n(e)&&function(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;var t;for(t in e)if(e.hasOwnProperty(t))return!1;return!0}(e)||t(e)&&0===e.length)&&(e=void 0),o._isAMomentObject=!0,o._useUTC=o._isUTC=a,o._l=i,o._i=e,o._f=s,o._strict=r,function(e){var t=new m(le(ye(e)));return t._nextDay&&(t.add(1,"d"),t._nextDay=void 0),t}(o)}function pe(e,t,n,s){return ge(e,t,n,s,!1)}function we(e,n){var s,i;if(1===n.length&&t(n[0])&&(n=n[0]),!n.length)return pe();for(s=n[0],i=1;i<n.length;++i)n[i].isValid()&&!n[i][e](s)||(s=n[i]);return s}function ve(e){var t=T(e),n=t.year||0,s=t.quarter||0,i=t.month||0,r=t.week||0,a=t.day||0,o=t.hour||0,u=t.minute||0,l=t.second||0,d=t.millisecond||0;this._isValid=function(e){for(var t in e)if(-1===Ut.call(hn,t)||null!=e[t]&&isNaN(e[t]))return!1;for(var n=!1,s=0;s<hn.length;++s)if(e[hn[s]]){if(n)return!1;parseFloat(e[hn[s]])!==g(e[hn[s]])&&(n=!0)}return!0}(t),this._milliseconds=+d+1e3*l+6e4*u+1e3*o*60*60,this._days=+a+7*r,this._months=+i+3*s+12*n,this._data={},this._locale=ue(),this._bubble()}function Me(e){return e instanceof ve}function Se(e){return e<0?-1*Math.round(-1*e):Math.round(e)}function De(e,t){P(e,0,0,function(){var e=this.utcOffset(),n="+";return e<0&&(e=-e,n="-"),n+b(~~(e/60),2)+t+b(~~e%60,2)})}function ke(e,t){var n=(t||"").match(e);if(null===n)return null;var s=((n[n.length-1]||[])+"").match(cn)||["-",0,0],i=60*s[1]+g(s[2]);return 0===i?0:"+"===s[0]?i:-i}function Ye(t,n){var s,i;return n._isUTC?(s=n.clone(),i=(_(t)||r(t)?t.valueOf():pe(t).valueOf())-s.valueOf(),s._d.setTime(s._d.valueOf()+i),e.updateOffset(s,!1),s):pe(t).local()}function Oe(e){return 15*-Math.round(e._d.getTimezoneOffset()/15)}function Te(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function xe(e,t){var n,s,r,a=e,u=null;return Me(e)?a={ms:e._milliseconds,d:e._days,M:e._months}:i(e)?(a={},t?a[t]=e:a.milliseconds=e):(u=fn.exec(e))?(n="-"===u[1]?-1:1,a={y:0,d:g(u[bt])*n,h:g(u[Pt])*n,m:g(u[Wt])*n,s:g(u[Ht])*n,ms:g(Se(1e3*u[Rt]))*n}):(u=mn.exec(e))?(n="-"===u[1]?-1:(u[1],1),a={y:be(u[2],n),M:be(u[3],n),w:be(u[4],n),d:be(u[5],n),h:be(u[6],n),m:be(u[7],n),s:be(u[8],n)}):null==a?a={}:"object"==typeof a&&("from"in a||"to"in a)&&(r=function(e,t){var n;if(!e.isValid()||!t.isValid())return{milliseconds:0,months:0};t=Ye(t,e),e.isBefore(t)?n=Pe(e,t):((n=Pe(t,e)).milliseconds=-n.milliseconds,n.months=-n.months);return n}(pe(a.from),pe(a.to)),(a={}).ms=r.milliseconds,a.M=r.months),s=new ve(a),Me(e)&&o(e,"_locale")&&(s._locale=e._locale),s}function be(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function Pe(e,t){var n={milliseconds:0,months:0};return n.months=t.month()-e.month()+12*(t.year()-e.year()),e.clone().add(n.months,"M").isAfter(t)&&--n.months,n.milliseconds=+t-+e.clone().add(n.months,"M"),n}function We(e,t){return function(n,s){var i,r;return null===s||isNaN(+s)||(M(t,"moment()."+t+"(period, number) is deprecated. Please use moment()."+t+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),r=n,n=s,s=r),n="string"==typeof n?+n:n,i=xe(n,s),He(this,i,e),this}}function He(t,n,s,i){var r=n._milliseconds,a=Se(n._days),o=Se(n._months);t.isValid()&&(i=null==i||i,o&&z(t,A(t,"Month")+o*s),a&&j(t,"Date",A(t,"Date")+a*s),r&&t._d.setTime(t._d.valueOf()+r*s),i&&e.updateOffset(t,a||o))}function Re(e,t){var n,s=12*(t.year()-e.year())+(t.month()-e.month()),i=e.clone().add(s,"months");return n=t-i<0?(t-i)/(i-e.clone().add(s-1,"months")):(t-i)/(e.clone().add(s+1,"months")-i),-(s+n)||0}function Ce(e){var t;return void 0===e?this._locale._abbr:(null!=(t=ue(e))&&(this._locale=t),this)}function Fe(){return this._locale}function Ue(e,t){P(0,[e,e.length],0,t)}function Le(e,t,n,s,i){var r;return null==e?X(this,s,i).year:(r=K(e,s,i),t>r&&(t=r),function(e,t,n,s,i){var r=Q(e,t,n,s,i),a=J(r.year,0,r.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}.call(this,e,t,n,s,i))}function Ne(e,t){t[Rt]=g(1e3*("0."+e))}function Ge(e){return e}function Ve(e,t,n,s){var i=ue(),r=l().set(s,t);return i[n](r,e)}function Ee(e,t,n){if(i(e)&&(t=e,e=void 0),e=e||"",null!=t)return Ve(e,t,n,"month");var s,r=[];for(s=0;s<12;s++)r[s]=Ve(e,s,n,"month");return r}function Ie(e,t,n,s){"boolean"==typeof e?(i(t)&&(n=t,t=void 0),t=t||""):(n=t=e,e=!1,i(t)&&(n=t,t=void 0),t=t||"");var r=ue(),a=e?r._week.dow:0;if(null!=n)return Ve(t,(n+a)%7,s,"day");var o,u=[];for(o=0;o<7;o++)u[o]=Ve(t,(o+a)%7,s,"day");return u}function Ae(e,t,n,s){var i=xe(t,n);return e._milliseconds+=s*i._milliseconds,e._days+=s*i._days,e._months+=s*i._months,e._bubble()}function je(e){return e<0?Math.floor(e):Math.ceil(e)}function Ze(e){return 4800*e/146097}function ze(e){return 146097*e/4800}function $e(e){return function(){return this.as(e)}}function qe(e){return function(){return this.isValid()?this._data[e]:NaN}}function Je(e){return(e>0)-(e<0)||+e}function Be(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n=An(this._milliseconds)/1e3,s=An(this._days),i=An(this._months);t=y((e=y(n/60))/60),n%=60,e%=60;var r=y(i/12),a=i%=12,o=s,u=t,l=e,d=n?n.toFixed(3).replace(/\.?0+$/,""):"",h=this.asSeconds();if(!h)return"P0D";var c=h<0?"-":"",f=Je(this._months)!==Je(h)?"-":"",m=Je(this._days)!==Je(h)?"-":"",_=Je(this._milliseconds)!==Je(h)?"-":"";return c+"P"+(r?f+r+"Y":"")+(a?f+a+"M":"")+(o?m+o+"D":"")+(u||l||d?"T":"")+(u?_+u+"H":"")+(l?_+l+"M":"")+(d?_+d+"S":"")}var Qe,Xe;Xe=Array.prototype.some?Array.prototype.some:function(e){for(var t=Object(this),n=t.length>>>0,s=0;s<n;s++)if(s in t&&e.call(this,t[s],s,t))return!0;return!1};var Ke=e.momentProperties=[],et=!1,tt={};e.suppressDeprecationWarnings=!1,e.deprecationHandler=null;var nt;nt=Object.keys?Object.keys:function(e){var t,n=[];for(t in e)o(e,t)&&n.push(t);return n};var st={},it={},rt=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,at=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,ot={},ut={},lt=/\d/,dt=/\d\d/,ht=/\d{3}/,ct=/\d{4}/,ft=/[+-]?\d{6}/,mt=/\d\d?/,_t=/\d\d\d\d?/,yt=/\d\d\d\d\d\d?/,gt=/\d{1,3}/,pt=/\d{1,4}/,wt=/[+-]?\d{1,6}/,vt=/\d+/,Mt=/[+-]?\d+/,St=/Z|[+-]\d\d:?\d\d/gi,Dt=/Z|[+-]\d\d(?::?\d\d)?/gi,kt=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,Yt={},Ot={},Tt=0,xt=1,bt=2,Pt=3,Wt=4,Ht=5,Rt=6,Ct=7,Ft=8;P("Y",0,0,function(){var e=this.year();return e<=9999?""+e:"+"+e}),P(0,["YY",2],0,function(){return this.year()%100}),P(0,["YYYY",4],0,"year"),P(0,["YYYYY",5],0,"year"),P(0,["YYYYYY",6,!0],0,"year"),Y("year","y"),x("year",1),C("Y",Mt),C("YY",mt,dt),C("YYYY",pt,ct),C("YYYYY",wt,ft),C("YYYYYY",wt,ft),L(["YYYYY","YYYYYY"],Tt),L("YYYY",function(t,n){n[Tt]=2===t.length?e.parseTwoDigitYear(t):g(t)}),L("YY",function(t,n){n[Tt]=e.parseTwoDigitYear(t)}),L("Y",function(e,t){t[Tt]=parseInt(e,10)}),e.parseTwoDigitYear=function(e){return g(e)+(g(e)>68?1900:2e3)};var Ut,Lt=I("FullYear",!0);Ut=Array.prototype.indexOf?Array.prototype.indexOf:function(e){var t;for(t=0;t<this.length;++t)if(this[t]===e)return t;return-1},P("M",["MM",2],"Mo",function(){return this.month()+1}),P("MMM",0,0,function(e){return this.localeData().monthsShort(this,e)}),P("MMMM",0,0,function(e){return this.localeData().months(this,e)}),Y("month","M"),x("month",8),C("M",mt),C("MM",mt,dt),C("MMM",function(e,t){return t.monthsShortRegex(e)}),C("MMMM",function(e,t){return t.monthsRegex(e)}),L(["M","MM"],function(e,t){t[xt]=g(e)-1}),L(["MMM","MMMM"],function(e,t,n,s){var i=n._locale.monthsParse(e,s,n._strict);null!=i?t[xt]=i:d(n).invalidMonth=e});var Nt=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Gt="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Vt="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Et=kt,It=kt;P("w",["ww",2],"wo","week"),P("W",["WW",2],"Wo","isoWeek"),Y("week","w"),Y("isoWeek","W"),x("week",5),x("isoWeek",5),C("w",mt),C("ww",mt,dt),C("W",mt),C("WW",mt,dt),N(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});P("d",0,"do","day"),P("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),P("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),P("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),P("e",0,0,"weekday"),P("E",0,0,"isoWeekday"),Y("day","d"),Y("weekday","e"),Y("isoWeekday","E"),x("day",11),x("weekday",11),x("isoWeekday",11),C("d",mt),C("e",mt),C("E",mt),C("dd",function(e,t){return t.weekdaysMinRegex(e)}),C("ddd",function(e,t){return t.weekdaysShortRegex(e)}),C("dddd",function(e,t){return t.weekdaysRegex(e)}),N(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:d(n).invalidWeekday=e}),N(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var At="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),jt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Zt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),zt=kt,$t=kt,qt=kt;P("H",["HH",2],0,"hour"),P("h",["hh",2],0,te),P("k",["kk",2],0,function(){return this.hours()||24}),P("hmm",0,0,function(){return""+te.apply(this)+b(this.minutes(),2)}),P("hmmss",0,0,function(){return""+te.apply(this)+b(this.minutes(),2)+b(this.seconds(),2)}),P("Hmm",0,0,function(){return""+this.hours()+b(this.minutes(),2)}),P("Hmmss",0,0,function(){return""+this.hours()+b(this.minutes(),2)+b(this.seconds(),2)}),ne("a",!0),ne("A",!1),Y("hour","h"),x("hour",13),C("a",se),C("A",se),C("H",mt),C("h",mt),C("k",mt),C("HH",mt,dt),C("hh",mt,dt),C("kk",mt,dt),C("hmm",_t),C("hmmss",yt),C("Hmm",_t),C("Hmmss",yt),L(["H","HH"],Pt),L(["k","kk"],function(e,t,n){var s=g(e);t[Pt]=24===s?0:s}),L(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),L(["h","hh"],function(e,t,n){t[Pt]=g(e),d(n).bigHour=!0}),L("hmm",function(e,t,n){var s=e.length-2;t[Pt]=g(e.substr(0,s)),t[Wt]=g(e.substr(s)),d(n).bigHour=!0}),L("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Pt]=g(e.substr(0,s)),t[Wt]=g(e.substr(s,2)),t[Ht]=g(e.substr(i)),d(n).bigHour=!0}),L("Hmm",function(e,t,n){var s=e.length-2;t[Pt]=g(e.substr(0,s)),t[Wt]=g(e.substr(s))}),L("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Pt]=g(e.substr(0,s)),t[Wt]=g(e.substr(s,2)),t[Ht]=g(e.substr(i))});var Jt,Bt=I("Hours",!0),Qt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Gt,monthsShort:Vt,week:{dow:0,doy:6},weekdays:At,weekdaysMin:Zt,weekdaysShort:jt,meridiemParse:/[ap]\.?m?\.?/i},Xt={},Kt={},en=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,tn=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,nn=/Z|[+-]\d\d(?::?\d\d)?/,sn=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],rn=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],an=/^\/?Date\((\-?\d+)/i,on=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,un={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};e.createFromInputFallback=v("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(e){e._d=new Date(e._i+(e._useUTC?" UTC":""))}),e.ISO_8601=function(){},e.RFC_2822=function(){};var ln=v("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=pe.apply(null,arguments);return this.isValid()&&e.isValid()?e<this?this:e:c()}),dn=v("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var e=pe.apply(null,arguments);return this.isValid()&&e.isValid()?e>this?this:e:c()}),hn=["year","quarter","month","week","day","hour","minute","second","millisecond"];De("Z",":"),De("ZZ",""),C("Z",Dt),C("ZZ",Dt),L(["Z","ZZ"],function(e,t,n){n._useUTC=!0,n._tzm=ke(Dt,e)});var cn=/([\+\-]|\d\d)/gi;e.updateOffset=function(){};var fn=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,mn=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;xe.fn=ve.prototype,xe.invalid=function(){return xe(NaN)};var _n=We(1,"add"),yn=We(-1,"subtract");e.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",e.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var gn=v("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){return void 0===e?this.localeData():this.locale(e)});P(0,["gg",2],0,function(){return this.weekYear()%100}),P(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ue("gggg","weekYear"),Ue("ggggg","weekYear"),Ue("GGGG","isoWeekYear"),Ue("GGGGG","isoWeekYear"),Y("weekYear","gg"),Y("isoWeekYear","GG"),x("weekYear",1),x("isoWeekYear",1),C("G",Mt),C("g",Mt),C("GG",mt,dt),C("gg",mt,dt),C("GGGG",pt,ct),C("gggg",pt,ct),C("GGGGG",wt,ft),C("ggggg",wt,ft),N(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,s){t[s.substr(0,2)]=g(e)}),N(["gg","GG"],function(t,n,s,i){n[i]=e.parseTwoDigitYear(t)}),P("Q",0,"Qo","quarter"),Y("quarter","Q"),x("quarter",7),C("Q",lt),L("Q",function(e,t){t[xt]=3*(g(e)-1)}),P("D",["DD",2],"Do","date"),Y("date","D"),x("date",9),C("D",mt),C("DD",mt,dt),C("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),L(["D","DD"],bt),L("Do",function(e,t){t[bt]=g(e.match(mt)[0])});var pn=I("Date",!0);P("DDD",["DDDD",3],"DDDo","dayOfYear"),Y("dayOfYear","DDD"),x("dayOfYear",4),C("DDD",gt),C("DDDD",ht),L(["DDD","DDDD"],function(e,t,n){n._dayOfYear=g(e)}),P("m",["mm",2],0,"minute"),Y("minute","m"),x("minute",14),C("m",mt),C("mm",mt,dt),L(["m","mm"],Wt);var wn=I("Minutes",!1);P("s",["ss",2],0,"second"),Y("second","s"),x("second",15),C("s",mt),C("ss",mt,dt),L(["s","ss"],Ht);var vn=I("Seconds",!1);P("S",0,0,function(){return~~(this.millisecond()/100)}),P(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),P(0,["SSS",3],0,"millisecond"),P(0,["SSSS",4],0,function(){return 10*this.millisecond()}),P(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),P(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),P(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),P(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),P(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),Y("millisecond","ms"),x("millisecond",16),C("S",gt,lt),C("SS",gt,dt),C("SSS",gt,ht);var Mn;for(Mn="SSSS";Mn.length<=9;Mn+="S")C(Mn,vt);for(Mn="S";Mn.length<=9;Mn+="S")L(Mn,Ne);var Sn=I("Milliseconds",!1);P("z",0,0,"zoneAbbr"),P("zz",0,0,"zoneName");var Dn=m.prototype;Dn.add=_n,Dn.calendar=function(t,n){var s=t||pe(),i=Ye(s,this).startOf("day"),r=e.calendarFormat(this,i)||"sameElse",a=n&&(S(n[r])?n[r].call(this,s):n[r]);return this.format(a||this.localeData().calendar(r,this,pe(s)))},Dn.clone=function(){return new m(this)},Dn.diff=function(e,t,n){var s,i,r;if(!this.isValid())return NaN;if(!(s=Ye(e,this)).isValid())return NaN;switch(i=6e4*(s.utcOffset()-this.utcOffset()),t=O(t)){case"year":r=Re(this,s)/12;break;case"month":r=Re(this,s);break;case"quarter":r=Re(this,s)/3;break;case"second":r=(this-s)/1e3;break;case"minute":r=(this-s)/6e4;break;case"hour":r=(this-s)/36e5;break;case"day":r=(this-s-i)/864e5;break;case"week":r=(this-s-i)/6048e5;break;default:r=this-s}return n?r:y(r)},Dn.endOf=function(e){return void 0===(e=O(e))||"millisecond"===e?this:("date"===e&&(e="day"),this.startOf(e).add(1,"isoWeek"===e?"week":e).subtract(1,"ms"))},Dn.format=function(t){t||(t=this.isUtc()?e.defaultFormatUtc:e.defaultFormat);var n=H(this,t);return this.localeData().postformat(n)},Dn.from=function(e,t){return this.isValid()&&(_(e)&&e.isValid()||pe(e).isValid())?xe({to:this,from:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},Dn.fromNow=function(e){return this.from(pe(),e)},Dn.to=function(e,t){return this.isValid()&&(_(e)&&e.isValid()||pe(e).isValid())?xe({from:this,to:e}).locale(this.locale()).humanize(!t):this.localeData().invalidDate()},Dn.toNow=function(e){return this.to(pe(),e)},Dn.get=function(e){return e=O(e),S(this[e])?this[e]():this},Dn.invalidAt=function(){return d(this).overflow},Dn.isAfter=function(e,t){var n=_(e)?e:pe(e);return!(!this.isValid()||!n.isValid())&&("millisecond"===(t=O(s(t)?"millisecond":t))?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(t).valueOf())},Dn.isBefore=function(e,t){var n=_(e)?e:pe(e);return!(!this.isValid()||!n.isValid())&&("millisecond"===(t=O(s(t)?"millisecond":t))?this.valueOf()<n.valueOf():this.clone().endOf(t).valueOf()<n.valueOf())},Dn.isBetween=function(e,t,n,s){return("("===(s=s||"()")[0]?this.isAfter(e,n):!this.isBefore(e,n))&&(")"===s[1]?this.isBefore(t,n):!this.isAfter(t,n))},Dn.isSame=function(e,t){var n,s=_(e)?e:pe(e);return!(!this.isValid()||!s.isValid())&&("millisecond"===(t=O(t||"millisecond"))?this.valueOf()===s.valueOf():(n=s.valueOf(),this.clone().startOf(t).valueOf()<=n&&n<=this.clone().endOf(t).valueOf()))},Dn.isSameOrAfter=function(e,t){return this.isSame(e,t)||this.isAfter(e,t)},Dn.isSameOrBefore=function(e,t){return this.isSame(e,t)||this.isBefore(e,t)},Dn.isValid=function(){return h(this)},Dn.lang=gn,Dn.locale=Ce,Dn.localeData=Fe,Dn.max=dn,Dn.min=ln,Dn.parsingFlags=function(){return u({},d(this))},Dn.set=function(e,t){if("object"==typeof e)for(var n=function(e){var t=[];for(var n in e)t.push({unit:n,priority:it[n]});return t.sort(function(e,t){return e.priority-t.priority}),t}(e=T(e)),s=0;s<n.length;s++)this[n[s].unit](e[n[s].unit]);else if(e=O(e),S(this[e]))return this[e](t);return this},Dn.startOf=function(e){switch(e=O(e)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===e&&this.weekday(0),"isoWeek"===e&&this.isoWeekday(1),"quarter"===e&&this.month(3*Math.floor(this.month()/3)),this},Dn.subtract=yn,Dn.toArray=function(){return[this.year(),this.month(),this.date(),this.hour(),this.minute(),this.second(),this.millisecond()]},Dn.toObject=function(){return{years:this.year(),months:this.month(),date:this.date(),hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()}},Dn.toDate=function(){return new Date(this.valueOf())},Dn.toISOString=function(e){if(!this.isValid())return null;var t=!0!==e,n=t?this.clone().utc():this;return n.year()<0||n.year()>9999?H(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):S(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this._d.valueOf()).toISOString().replace("Z",H(n,"Z")):H(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},Dn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="";this.isLocal()||(e=0===this.utcOffset()?"moment.utc":"moment.parseZone",t="Z");var n="["+e+'("]',s=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",i=t+'[")]';return this.format(n+s+"-MM-DD[T]HH:mm:ss.SSS"+i)},Dn.toJSON=function(){return this.isValid()?this.toISOString():null},Dn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},Dn.unix=function(){return Math.floor(this.valueOf()/1e3)},Dn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},Dn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},Dn.year=Lt,Dn.isLeapYear=function(){return E(this.year())},Dn.weekYear=function(e){return Le.call(this,e,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},Dn.isoWeekYear=function(e){return Le.call(this,e,this.isoWeek(),this.isoWeekday(),1,4)},Dn.quarter=Dn.quarters=function(e){return null==e?Math.ceil((this.month()+1)/3):this.month(3*(e-1)+this.month()%3)},Dn.month=$,Dn.daysInMonth=function(){return Z(this.year(),this.month())},Dn.week=Dn.weeks=function(e){var t=this.localeData().week(this);return null==e?t:this.add(7*(e-t),"d")},Dn.isoWeek=Dn.isoWeeks=function(e){var t=X(this,1,4).week;return null==e?t:this.add(7*(e-t),"d")},Dn.weeksInYear=function(){var e=this.localeData()._week;return K(this.year(),e.dow,e.doy)},Dn.isoWeeksInYear=function(){return K(this.year(),1,4)},Dn.date=pn,Dn.day=Dn.days=function(e){if(!this.isValid())return null!=e?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=e?(e=function(e,t){return"string"!=typeof e?e:isNaN(e)?"number"==typeof(e=t.weekdaysParse(e))?e:null:parseInt(e,10)}(e,this.localeData()),this.add(e-t,"d")):t},Dn.weekday=function(e){if(!this.isValid())return null!=e?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return null==e?t:this.add(e-t,"d")},Dn.isoWeekday=function(e){if(!this.isValid())return null!=e?this:NaN;if(null!=e){var t=function(e,t){return"string"==typeof e?t.weekdaysParse(e)%7||7:isNaN(e)?null:e}(e,this.localeData());return this.day(this.day()%7?t:t-7)}return this.day()||7},Dn.dayOfYear=function(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==e?t:this.add(e-t,"d")},Dn.hour=Dn.hours=Bt,Dn.minute=Dn.minutes=wn,Dn.second=Dn.seconds=vn,Dn.millisecond=Dn.milliseconds=Sn,Dn.utcOffset=function(t,n,s){var i,r=this._offset||0;if(!this.isValid())return null!=t?this:NaN;if(null!=t){if("string"==typeof t){if(null===(t=ke(Dt,t)))return this}else Math.abs(t)<16&&!s&&(t*=60);return!this._isUTC&&n&&(i=Oe(this)),this._offset=t,this._isUTC=!0,null!=i&&this.add(i,"m"),r!==t&&(!n||this._changeInProgress?He(this,xe(t-r,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,e.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:Oe(this)},Dn.utc=function(e){return this.utcOffset(0,e)},Dn.local=function(e){return this._isUTC&&(this.utcOffset(0,e),this._isUTC=!1,e&&this.subtract(Oe(this),"m")),this},Dn.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var e=ke(St,this._i);null!=e?this.utcOffset(e):this.utcOffset(0,!0)}return this},Dn.hasAlignedHourOffset=function(e){return!!this.isValid()&&(e=e?pe(e).utcOffset():0,(this.utcOffset()-e)%60==0)},Dn.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Dn.isLocal=function(){return!!this.isValid()&&!this._isUTC},Dn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Dn.isUtc=Te,Dn.isUTC=Te,Dn.zoneAbbr=function(){return this._isUTC?"UTC":""},Dn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Dn.dates=v("dates accessor is deprecated. Use date instead.",pn),Dn.months=v("months accessor is deprecated. Use month instead",$),Dn.years=v("years accessor is deprecated. Use year instead",Lt),Dn.zone=v("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),Dn.isDSTShifted=v("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={};if(f(e,this),(e=ye(e))._a){var t=e._isUTC?l(e._a):pe(e._a);this._isDSTShifted=this.isValid()&&p(e._a,t.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted});var kn=k.prototype;kn.calendar=function(e,t,n){var s=this._calendar[e]||this._calendar.sameElse;return S(s)?s.call(t,n):s},kn.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.replace(/MMMM|MM|DD|dddd/g,function(e){return e.slice(1)}),this._longDateFormat[e])},kn.invalidDate=function(){return this._invalidDate},kn.ordinal=function(e){return this._ordinal.replace("%d",e)},kn.preparse=Ge,kn.postformat=Ge,kn.relativeTime=function(e,t,n,s){var i=this._relativeTime[n];return S(i)?i(e,t,n,s):i.replace(/%d/i,e)},kn.pastFuture=function(e,t){var n=this._relativeTime[e>0?"future":"past"];return S(n)?n(t):n.replace(/%s/i,t)},kn.set=function(e){var t,n;for(n in e)S(t=e[n])?this[n]=t:this["_"+n]=t;this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},kn.months=function(e,n){return e?t(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||Nt).test(n)?"format":"standalone"][e.month()]:t(this._months)?this._months:this._months.standalone},kn.monthsShort=function(e,n){return e?t(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[Nt.test(n)?"format":"standalone"][e.month()]:t(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},kn.monthsParse=function(e,t,n){var s,i,r;if(this._monthsParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],s=0;s<12;++s)r=l([2e3,s]),this._shortMonthsParse[s]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[s]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===t?-1!==(i=Ut.call(this._shortMonthsParse,a))?i:null:-1!==(i=Ut.call(this._longMonthsParse,a))?i:null:"MMM"===t?-1!==(i=Ut.call(this._shortMonthsParse,a))?i:-1!==(i=Ut.call(this._longMonthsParse,a))?i:null:-1!==(i=Ut.call(this._longMonthsParse,a))?i:-1!==(i=Ut.call(this._shortMonthsParse,a))?i:null}.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),s=0;s<12;s++){if(i=l([2e3,s]),n&&!this._longMonthsParse[s]&&(this._longMonthsParse[s]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[s]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[s]||(r="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[s]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[s].test(e))return s;if(n&&"MMM"===t&&this._shortMonthsParse[s].test(e))return s;if(!n&&this._monthsParse[s].test(e))return s}},kn.monthsRegex=function(e){return this._monthsParseExact?(o(this,"_monthsRegex")||q.call(this),e?this._monthsStrictRegex:this._monthsRegex):(o(this,"_monthsRegex")||(this._monthsRegex=It),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},kn.monthsShortRegex=function(e){return this._monthsParseExact?(o(this,"_monthsRegex")||q.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(o(this,"_monthsShortRegex")||(this._monthsShortRegex=Et),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},kn.week=function(e){return X(e,this._week.dow,this._week.doy).week},kn.firstDayOfYear=function(){return this._week.doy},kn.firstDayOfWeek=function(){return this._week.dow},kn.weekdays=function(e,n){return e?t(this._weekdays)?this._weekdays[e.day()]:this._weekdays[this._weekdays.isFormat.test(n)?"format":"standalone"][e.day()]:t(this._weekdays)?this._weekdays:this._weekdays.standalone},kn.weekdaysMin=function(e){return e?this._weekdaysMin[e.day()]:this._weekdaysMin},kn.weekdaysShort=function(e){return e?this._weekdaysShort[e.day()]:this._weekdaysShort},kn.weekdaysParse=function(e,t,n){var s,i,r;if(this._weekdaysParseExact)return function(e,t,n){var s,i,r,a=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],s=0;s<7;++s)r=l([2e3,1]).day(s),this._minWeekdaysParse[s]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[s]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[s]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(i=Ut.call(this._weekdaysParse,a))?i:null:"ddd"===t?-1!==(i=Ut.call(this._shortWeekdaysParse,a))?i:null:-1!==(i=Ut.call(this._minWeekdaysParse,a))?i:null:"dddd"===t?-1!==(i=Ut.call(this._weekdaysParse,a))?i:-1!==(i=Ut.call(this._shortWeekdaysParse,a))?i:-1!==(i=Ut.call(this._minWeekdaysParse,a))?i:null:"ddd"===t?-1!==(i=Ut.call(this._shortWeekdaysParse,a))?i:-1!==(i=Ut.call(this._weekdaysParse,a))?i:-1!==(i=Ut.call(this._minWeekdaysParse,a))?i:null:-1!==(i=Ut.call(this._minWeekdaysParse,a))?i:-1!==(i=Ut.call(this._weekdaysParse,a))?i:-1!==(i=Ut.call(this._shortWeekdaysParse,a))?i:null}.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),s=0;s<7;s++){if(i=l([2e3,1]).day(s),n&&!this._fullWeekdaysParse[s]&&(this._fullWeekdaysParse[s]=new RegExp("^"+this.weekdays(i,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[s]=new RegExp("^"+this.weekdaysShort(i,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[s]=new RegExp("^"+this.weekdaysMin(i,"").replace(".",".?")+"$","i")),this._weekdaysParse[s]||(r="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[s]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[s].test(e))return s;if(n&&"ddd"===t&&this._shortWeekdaysParse[s].test(e))return s;if(n&&"dd"===t&&this._minWeekdaysParse[s].test(e))return s;if(!n&&this._weekdaysParse[s].test(e))return s}},kn.weekdaysRegex=function(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(o(this,"_weekdaysRegex")||(this._weekdaysRegex=zt),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},kn.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(o(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=$t),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},kn.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(o(this,"_weekdaysRegex")||ee.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(o(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=qt),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},kn.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},kn.meridiem=function(e,t,n){return e>11?n?"pm":"PM":n?"am":"AM"},ae("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===g(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),e.lang=v("moment.lang is deprecated. Use moment.locale instead.",ae),e.langData=v("moment.langData is deprecated. Use moment.localeData instead.",ue);var Yn=Math.abs,On=$e("ms"),Tn=$e("s"),xn=$e("m"),bn=$e("h"),Pn=$e("d"),Wn=$e("w"),Hn=$e("M"),Rn=$e("y"),Cn=qe("milliseconds"),Fn=qe("seconds"),Un=qe("minutes"),Ln=qe("hours"),Nn=qe("days"),Gn=qe("months"),Vn=qe("years"),En=Math.round,In={ss:44,s:45,m:45,h:22,d:26,M:11},An=Math.abs,jn=ve.prototype;return jn.isValid=function(){return this._isValid},jn.abs=function(){var e=this._data;return this._milliseconds=Yn(this._milliseconds),this._days=Yn(this._days),this._months=Yn(this._months),e.milliseconds=Yn(e.milliseconds),e.seconds=Yn(e.seconds),e.minutes=Yn(e.minutes),e.hours=Yn(e.hours),e.months=Yn(e.months),e.years=Yn(e.years),this},jn.add=function(e,t){return Ae(this,e,t,1)},jn.subtract=function(e,t){return Ae(this,e,t,-1)},jn.as=function(e){if(!this.isValid())return NaN;var t,n,s=this._milliseconds;if("month"===(e=O(e))||"year"===e)return t=this._days+s/864e5,n=this._months+Ze(t),"month"===e?n:n/12;switch(t=this._days+Math.round(ze(this._months)),e){case"week":return t/7+s/6048e5;case"day":return t+s/864e5;case"hour":return 24*t+s/36e5;case"minute":return 1440*t+s/6e4;case"second":return 86400*t+s/1e3;case"millisecond":return Math.floor(864e5*t)+s;default:throw new Error("Unknown unit "+e)}},jn.asMilliseconds=On,jn.asSeconds=Tn,jn.asMinutes=xn,jn.asHours=bn,jn.asDays=Pn,jn.asWeeks=Wn,jn.asMonths=Hn,jn.asYears=Rn,jn.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*g(this._months/12):NaN},jn._bubble=function(){var e,t,n,s,i,r=this._milliseconds,a=this._days,o=this._months,u=this._data;return r>=0&&a>=0&&o>=0||r<=0&&a<=0&&o<=0||(r+=864e5*je(ze(o)+a),a=0,o=0),u.milliseconds=r%1e3,e=y(r/1e3),u.seconds=e%60,t=y(e/60),u.minutes=t%60,n=y(t/60),u.hours=n%24,a+=y(n/24),i=y(Ze(a)),o+=i,a-=je(ze(i)),s=y(o/12),o%=12,u.days=a,u.months=o,u.years=s,this},jn.clone=function(){return xe(this)},jn.get=function(e){return e=O(e),this.isValid()?this[e+"s"]():NaN},jn.milliseconds=Cn,jn.seconds=Fn,jn.minutes=Un,jn.hours=Ln,jn.days=Nn,jn.weeks=function(){return y(this.days()/7)},jn.months=Gn,jn.years=Vn,jn.humanize=function(e){if(!this.isValid())return this.localeData().invalidDate();var t=this.localeData(),n=function(e,t,n){var s=xe(e).abs(),i=En(s.as("s")),r=En(s.as("m")),a=En(s.as("h")),o=En(s.as("d")),u=En(s.as("M")),l=En(s.as("y")),d=i<=In.ss&&["s",i]||i<In.s&&["ss",i]||r<=1&&["m"]||r<In.m&&["mm",r]||a<=1&&["h"]||a<In.h&&["hh",a]||o<=1&&["d"]||o<In.d&&["dd",o]||u<=1&&["M"]||u<In.M&&["MM",u]||l<=1&&["y"]||["yy",l];return d[2]=t,d[3]=+e>0,d[4]=n,function(e,t,n,s,i){return i.relativeTime(t||1,!!n,e,s)}.apply(null,d)}(this,!e,t);return e&&(n=t.pastFuture(+this,n)),t.postformat(n)},jn.toISOString=Be,jn.toString=Be,jn.toJSON=Be,jn.locale=Ce,jn.localeData=Fe,jn.toIsoString=v("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Be),jn.lang=gn,P("X",0,0,"unix"),P("x",0,0,"valueOf"),C("x",Mt),C("X",/[+-]?\d+(\.\d{1,3})?/),L("X",function(e,t,n){n._d=new Date(1e3*parseFloat(e,10))}),L("x",function(e,t,n){n._d=new Date(g(e))}),e.version="2.20.1",function(e){Qe=e}(pe),e.fn=Dn,e.min=function(){return we("isBefore",[].slice.call(arguments,0))},e.max=function(){return we("isAfter",[].slice.call(arguments,0))},e.now=function(){return Date.now?Date.now():+new Date},e.utc=l,e.unix=function(e){return pe(1e3*e)},e.months=function(e,t){return Ee(e,t,"months")},e.isDate=r,e.locale=ae,e.invalid=c,e.duration=xe,e.isMoment=_,e.weekdays=function(e,t,n){return Ie(e,t,n,"weekdays")},e.parseZone=function(){return pe.apply(null,arguments).parseZone()},e.localeData=ue,e.isDuration=Me,e.monthsShort=function(e,t){return Ee(e,t,"monthsShort")},e.weekdaysMin=function(e,t,n){return Ie(e,t,n,"weekdaysMin")},e.defineLocale=oe,e.updateLocale=function(e,t){if(null!=t){var n,s,i=Qt;null!=(s=re(e))&&(i=s._config),(n=new k(t=D(i,t))).parentLocale=Xt[e],Xt[e]=n,ae(e)}else null!=Xt[e]&&(null!=Xt[e].parentLocale?Xt[e]=Xt[e].parentLocale:null!=Xt[e]&&delete Xt[e]);return Xt[e]},e.locales=function(){return nt(Xt)},e.weekdaysShort=function(e,t,n){return Ie(e,t,n,"weekdaysShort")},e.normalizeUnits=O,e.relativeTimeRounding=function(e){return void 0===e?En:"function"==typeof e&&(En=e,!0)},e.relativeTimeThreshold=function(e,t){return void 0!==In[e]&&(void 0===t?In[e]:(In[e]=t,"s"===e&&(In.ss=t-1),!0))},e.calendarFormat=function(e,t){var n=e.diff(t,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},e.prototype=Dn,e.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"YYYY-[W]WW",MONTH:"YYYY-MM"},e});
//! moment.js locale configuration
//! locale : Chinese (China) [zh-cn]
//! author : suupic : https://github.com/suupic
//! author : Zeno Zeng : https://github.com/zenozeng
import moment from './moment-core'
var zhCn = moment.defineLocale('zh-cn', {
months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'),
weekdaysMin: '日_一_二_三_四_五_六'.split('_'),
longDateFormat: {
LT: 'HH:mm',
LTS: 'HH:mm:ss',
L: 'YYYY/MM/DD',
LL: 'YYYY年M月D日',
LLL: 'YYYY年M月D日Ah点mm分',
LLLL: 'YYYY年M月D日ddddAh点mm分',
l: 'YYYY/M/D',
ll: 'YYYY年M月D日',
lll: 'YYYY年M月D日 HH:mm',
llll: 'YYYY年M月D日dddd HH:mm'
},
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
meridiemHour: function (hour, meridiem) {
if (hour === 12) {
hour = 0;
}
if (meridiem === '凌晨' || meridiem === '早上' ||
meridiem === '上午') {
return hour;
} else if (meridiem === '下午' || meridiem === '晚上') {
return hour + 12;
} else {
// '中午'
return hour >= 11 ? hour : hour + 12;
}
},
meridiem: function (hour, minute, isLower) {
var hm = hour * 100 + minute;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1130) {
return '上午';
} else if (hm < 1230) {
return '中午';
} else if (hm < 1800) {
return '下午';
} else {
return '晚上';
}
},
calendar: {
sameDay: '[今天]LT',
nextDay: '[明天]LT',
nextWeek: '[下]ddddLT',
lastDay: '[昨天]LT',
lastWeek: '[上]ddddLT',
sameElse: 'L'
},
dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/,
ordinal: function (number, period) {
switch (period) {
case 'd':
case 'D':
case 'DDD':
return number + '日';
case 'M':
return number + '月';
case 'w':
case 'W':
return number + '周';
default:
return number;
}
},
relativeTime: {
future: '%s内',
past: '%s前',
s: '几秒',
ss: '%d 秒',
m: '1 分钟',
mm: '%d 分钟',
h: '1 小时',
hh: '%d 小时',
d: '1 天',
dd: '%d 天',
M: '1 个月',
MM: '%d 个月',
y: '1 年',
yy: '%d 年'
},
week: {
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
dow: 1, // Monday is the first day of the week.
doy: 4 // The week that contains Jan 4th is the first week of the year.
}
});
export default moment
Page({
onLoad() {
wx.switchTab({
url: '/pages/blog/postIndex/postIndex',
})
},
})
import { connect } from 'root/wmp-redux' import urls from '/constants/blog/urls'
import compose from 'ramda/src/compose' import * as apis from '/api/blogApi'
import urls from 'root/constants/blog/urls' import moment from '/lib/moment'
import { import { formatProtocol } from '/utils/utils'
fetchComments, import { getQnUrl } from '/utils/helpers/imageHelper'
setComments, import { html2json } from '/utils/wxParse/html2json'
onExtend,
onReplyExtend,
clearComments,
} from 'root/actions/blog/comment'
import { fetchPosts, likePost } from 'root/actions/blog/post'
import { fetchPostDetail, clearPostDetail } from 'root/actions/blog/postDetail'
import { setError } from 'root/actions/common/error'
import { fetchPostSharing } from 'root/actions/blog/sharing'
import { setGlobalData } from 'root/actions/common/global'
import Feature from 'root/constants/common/feature' import iconPaths from '/constants/blog/iconPaths'
import iconPaths from 'root/constants/blog/iconPaths'
import { getComments } from 'root/selectors/blog/comment' function traverse(o, func) {
import { getPostDetail } from 'root/selectors/blog/postDetail' for (const i in o) {
import { getUserInfo, getAttr } from 'root/selectors/common/global' func.apply(this, [i, o[i]])
import { getError } from 'root/selectors/common/error' if (o[i] !== null && typeof o[i] === 'object') {
import { trackUI } from 'wechat_common/tracker/index.bs' // going one step down in the object tree!!
traverse(o[i], func)
import { getTeamMemberId } from 'root/selectors/presentation/teamMemberSelector' }
}
import wrappedWXTool, { }
bindGetUserInfoHandler,
} from 'wechat_common/utils/wrappedWXTool'
import shareView from 'root/templates/shareView/shareView'
const { const {
iconWechat, iconWechat,
...@@ -45,7 +31,7 @@ const { ...@@ -45,7 +31,7 @@ const {
homeBtn, homeBtn,
iconMomentLike, iconMomentLike,
iconMomentLikeGrey, iconMomentLikeGrey,
iconMomentLikeTotal, iconMomentLikeTotal
} = iconPaths } = iconPaths
const page = { const page = {
...@@ -69,19 +55,133 @@ const page = { ...@@ -69,19 +55,133 @@ const page = {
iconMomentLikeTotal, iconMomentLikeTotal,
shareLoading: false, shareLoading: false,
cachedSections: null, cachedSections: null,
// mapStateToProps
teamMemberId: '11363837',
comments: [],
isFetchingComments: false,
totalCountComments: 1,
paginationComments: {
currentPage: 1,
previousPage: null,
nextPage: null,
perPage: 9999,
totalPages: 0,
totalCount: 0
},
sections: [],
headerTitle: '',
headerTitleString: '',
headerSubTitle: '',
backgroundUrl: '',
publishedAt: '',
textColor: '',
isLikingPost: false,
enableComments: true,
nickName: '',
avatarUrl: '',
canUseSearch: true,
canUseCollection: false,
canUseShare: false,
siteId: '11363837',
isFromSharedProductCard: false,
isFromSharedProductQRCode: false,
postId: '',
showShareVariation: false,
shareAnimation: {
actions: [
{
animates: [
{
type: 'translateY',
args: ['100%']
}
],
option: {
transformOrigin: '50% 50% 0',
transition: {
duration: 400,
timingFunction: 'ease',
delay: 0
}
}
}
]
}
}, },
onLoad(options) { onLoad(options) {
const { postId, scene } = options const { postId, scene } = options
const postIdFromScene = decodeURIComponent(scene) const postIdFromScene = decodeURIComponent(scene)
wx.getExtConfig({
success: res => {
this.fetchPosts(res.extConfig.attr.siteId)
},
})
this.setData({ postId: postId || postIdFromScene }) this.setData({ postId: postId || postIdFromScene })
this.fetchPostDetail(postId || postIdFromScene) this.fetchPostDetail(postId || postIdFromScene)
this.fetchComments(postId || postIdFromScene) this.fetchComments(postId || postIdFromScene)
}, },
fetchPostDetail(postId) {
apis.getPostDetail(postId).then(({ data: { data } }) => {
let currentPost = JSON.parse(JSON.stringify(data)),
{ content = { header: {} }, publishedAt } = currentPost,
{
header = { backgroundImage: {}, title: {} },
sections = []
} = content,
{ backgroundImage = {}, title = {}, subTitle = {} } = header,
{ url: backgroundUrl, textColor = 'light' } = backgroundImage
const headerTitleString = title.value.replace(/<\/?[^>]+(>|$)/g, '')
publishedAt = moment(publishedAt)
.locale('zh-cn')
.fromNow()
backgroundUrl =
backgroundUrl && backgroundUrl !== '!'
? backgroundUrl
: getQnUrl(currentPost.content.header.backgroundImage)
backgroundUrl = formatProtocol(backgroundUrl).replace('.mp4', '.jpg')
const textList = [
'Blog.Quote',
'Quote',
'Blog.Text',
'RichText',
'Blog.Title',
'Title'
]
traverse(currentPost, (key, value) => {
if (value) {
if (textList.includes(value.type)) {
value.value = html2json(value.value)
} else if (value.type === 'Image' || value.type === 'Blog.Image') {
value.imageUrl =
value.url && value.url !== '!' ? value.url : getQnUrl(value)
}
}
})
const headerTitle = currentPost.content.header.title.value
const headerSubTitle = currentPost.content.header.subTitle.value
this.setData({
currentPost: data,
sections,
headerTitle,
headerTitleString,
headerSubTitle,
publishedAt,
backgroundUrl,
textColor
})
})
},
fetchComments(postId, pageNum = 1) {
this.setData({
isFetchingComments: true
})
apis.getComments(postId, pageNum).then(({ data: { data } }) => {
this.setData({
comments: data,
isFetchingComments: false
})
})
},
onUnload() { onUnload() {
this.clearPostDetail() this.clearPostDetail()
this.clearComments() this.clearComments()
...@@ -89,52 +189,35 @@ const page = { ...@@ -89,52 +189,35 @@ const page = {
onHide() { onHide() {
this.closeShareView() this.closeShareView()
}, },
afterStateChange() {
/**
* bug fix for RDT-777
*
* root cause: wx.previewImage will trigger onHide and onShow,
* which leads rerender due to our injected newOnShow method
*
* solution: Use a varible cachedSections to cache sections, and use the cached
* value until page is destroyed.
*/
const { sections, cachedSections } = this.data
if (!cachedSections && sections) {
this.setData({
cachedSections: sections,
})
}
},
bindShare() { bindShare() {
wx.showShareMenu({ wx.showShareMenu({
withShareTicket: true, withShareTicket: true
}) })
}, },
bindMark() { bindMark() {
this.setData({ this.setData({
'currentPost.marked': true, 'currentPost.marked': true
}) })
wx.showToast({ wx.showToast({
title: '已收藏', title: '已收藏',
icon: 'success', icon: 'success'
}) })
}, },
bindUnmark() { bindUnmark() {
this.setData({ this.setData({
'currentPost.marked': false, 'currentPost.marked': false
}) })
wx.showToast({ wx.showToast({
title: '已取消收藏', title: '已取消收藏',
icon: 'success', icon: 'success'
}) })
}, },
bindTextAreaChange(e) { bindTextAreaChange(e) {
this.setData({ this.setData({
currentComment: e.detail.value, currentComment: e.detail.value
}) })
}, },
bindGetUserInfoHandler, // bindGetUserInfoHandler,
onClickSubmit(e) { onClickSubmit(e) {
const { currentComment } = this.data const { currentComment } = this.data
if (!currentComment || !currentComment.trim()) { if (!currentComment || !currentComment.trim()) {
...@@ -153,15 +236,15 @@ const page = { ...@@ -153,15 +236,15 @@ const page = {
} }
const that = this const that = this
this.setData({ this.setData({
submitting: true, submitting: true
}) })
const sendData = { const sendData = {
content: currentComment, content: currentComment,
nickname: nickName, nickname: nickName,
wechat_photo: [avatarUrl], wechat_photo: [avatarUrl],
settings: { settings: {
form_id: formId, form_id: formId
}, }
} }
wrappedWXTool.login({ wrappedWXTool.login({
success: loginRes => { success: loginRes => {
...@@ -172,40 +255,32 @@ const page = { ...@@ -172,40 +255,32 @@ const page = {
() => { () => {
that.setData({ that.setData({
submitting: false, submitting: false,
currentComment: '', currentComment: ''
}) })
trackUI(
'commentBlogPost',
currentPost.id,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `commentBlogPost${currentPost.id}`,
}),
)
wx.showToast({ wx.showToast({
title: '评论成功,审核通过后显示在留言列表', title: '评论成功,审核通过后显示在留言列表',
icon: 'none', icon: 'none',
duration: 2000, duration: 2000
}) })
}, },
() => { () => {
that.setData({ submitting: false }) that.setData({ submitting: false })
}, }
) )
}, },
fail() { fail() {
wx.showModal({ wx.showModal({
content: '请求失败,请重试', content: '请求失败,请重试'
}) })
that.setData({ that.setData({
submitting: false, submitting: false
}) })
}, }
}) })
}, },
showImage(e) { showImage(e) {
wx.previewImage({ wx.previewImage({
urls: [e.target.dataset.imageUrl], urls: [e.target.dataset.imageUrl]
}) })
}, },
loadMoreComment() { loadMoreComment() {
...@@ -226,11 +301,11 @@ const page = { ...@@ -226,11 +301,11 @@ const page = {
gotoHome() { gotoHome() {
wx.switchTab({ wx.switchTab({
url: urls.PAGES.POST_INDEX, url: urls.PAGES.POST_INDEX
}) })
this.setGlobalData({ this.setGlobalData({
isFromSharedProductCard: false, isFromSharedProductCard: false,
isFromSharedProductQRCode: false, isFromSharedProductQRCode: false
}) })
}, },
...@@ -238,35 +313,30 @@ const page = { ...@@ -238,35 +313,30 @@ const page = {
const { currentPost, headerTitleString } = this.data const { currentPost, headerTitleString } = this.data
return { return {
title: headerTitleString, title: headerTitleString,
path: `/pages/blog/postDetail/postDetail?postId=${currentPost.id}`, path: `/pages/blog/postDetail/postDetail?postId=${currentPost.id}`
} }
}, },
sharePicture() { sharePicture() {
const { siteId, currentPost: { id: postId } } = this.data const {
siteId,
currentPost: { id: postId }
} = this.data
if (wx.isWept) { if (wx.isWept) {
this.setError('预览模式下暂不支持,请手机预览') this.setError('预览模式下暂不支持,请手机预览')
} else { } else {
this.setData({ this.setData({
shareLoading: true, shareLoading: true
}) })
this.fetchPostSharing(siteId, { this.fetchPostSharing(siteId, {
scene: postId.toString(), scene: postId.toString(),
page: 'pages/blog/postDetail/postDetail', page: 'pages/blog/postDetail/postDetail'
}).then(res => { }).then(res => {
trackUI(
'shareImageBlogPost',
postId,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `shareImageBlogPost${postId}`,
}),
)
this.setData({ this.setData({
shareLoading: false, shareLoading: false
}) })
if (res.success) { if (res.success) {
wx.navigateTo({ wx.navigateTo({
url: `${urls.PAGES.POST_SHARE}?postId=${postId}`, url: `${urls.PAGES.POST_SHARE}?postId=${postId}`
}) })
} else { } else {
wx.showModal({ wx.showModal({
...@@ -277,7 +347,7 @@ const page = { ...@@ -277,7 +347,7 @@ const page = {
if (res.confirm) { if (res.confirm) {
this.closeShareView() this.closeShareView()
} }
}, }
}) })
} }
}) })
...@@ -291,109 +361,10 @@ const page = { ...@@ -291,109 +361,10 @@ const page = {
if (!isLikingPost) { if (!isLikingPost) {
this.likePost(!isLiked, currentPost.id, nickName, avatarUrl) this.likePost(!isLiked, currentPost.id, nickName, avatarUrl)
if (!isLiked) { if (!isLiked) {
trackUI(
'likeBlogPost',
currentPost.id,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `likeBlogPost${currentPost.id}`,
}),
)
} }
} }
} }
bindGetUserInfoHandler(e, cb)
},
}
function mapStateToProps(state) {
const {
list: comments,
isFetching: isFetchingComments,
pagination: paginationComments,
totalCount: totalCountComments,
} = getComments(state)
const {
currentPost,
isLikingPost,
sections,
headerTitle,
headerTitleString,
headerSubTitle,
publishedAt,
textColor,
backgroundUrl,
} = getPostDetail(state)
const { nickName, avatarUrl } = getUserInfo(state)
const { canUseSearch, canUseCollection, canUseShare } = Feature
const { errorMessage } = getError(state)
const { siteId } = getAttr(state)
const enableComments = state.getIn([
'blog',
'setting',
'settings',
'enableComments',
])
const isFromSharedProductCard = state.getIn([
'globalData',
'isFromSharedProductCard',
])
const isFromSharedProductQRCode = state.getIn([
'globalData',
'isFromSharedProductQRCode',
])
const teamMemberId = getTeamMemberId(state)
return {
comments,
isFetchingComments,
totalCountComments,
paginationComments,
isLikingPost,
teamMemberId,
currentPost,
enableComments,
sections,
headerTitle,
headerTitleString,
headerSubTitle,
backgroundUrl,
publishedAt,
textColor,
nickName,
avatarUrl,
canUseSearch,
canUseCollection,
canUseShare,
errorMessage,
siteId,
isFromSharedProductCard,
isFromSharedProductQRCode,
} }
} }
function mapDispatchToProps(dispatch) { Page(page)
return {
fetchPosts: siteId => dispatch(fetchPosts(urls.PAGES.POST_INDEX, siteId)),
fetchPostDetail: postId => dispatch(fetchPostDetail(postId)),
clearPostDetail: () => dispatch(clearPostDetail()),
clearComments: () => dispatch(clearComments()),
onExtend: currentId => dispatch(onExtend(currentId)),
onReplyExtend: currentId => dispatch(onReplyExtend(currentId)),
fetchComments: (postId, pageNum) =>
dispatch(fetchComments(postId, pageNum)),
setComments: (postId, setData, successCb, failCb) =>
dispatch(setComments(postId, setData, successCb, failCb)),
setError: errorMessage => dispatch(setError(errorMessage)),
fetchPostSharing: (siteId, data) =>
dispatch(fetchPostSharing(siteId, data)),
setGlobalData: data => dispatch(setGlobalData(data)),
likePost: (status, postId, nickName, avatarUrl) =>
dispatch(likePost(status, postId, nickName, avatarUrl)),
}
}
const enhance = compose(shareView, connect(mapStateToProps, mapDispatchToProps))
Page(enhance(page))
<import src="root/utils/wxParse/wxParse.wxml" /> <import src="/utils/wxParse/wxParse.wxml" />
<import src="./postItem/postItem.wxml" /> <import src="./postItem/postItem.wxml" />
<import src="./commentList/commentList.wxml" /> <import src="./commentList/commentList.wxml" />
<import src="root/templates/messageModal/messageModal.wxml" /> <import src="/templates/messageModal/messageModal.wxml" />
<import src="root/templates/shareView/shareView.wxml" /> <import src="/templates/shareView/shareView.wxml" />
<import src="root/templates/loaderPage/loaderPage.wxml" /> <import src="/templates/loaderPage/loaderPage.wxml" />
<view wx:if="{{errorMessage}}"> <view wx:if="{{errorMessage}}">
<template is="message-modal" data="{{message: errorMessage}}" /> <template is="message-modal" data="{{message: errorMessage}}" />
</view> </view>
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
</view> </view>
<view class="post-body"> <view class="post-body">
<!-- bug fix for RDT-777, prevent rerender using a cached value. For details see postDetail.js --> <!-- bug fix for RDT-777, prevent rerender using a cached value. For details see postDetail.js -->
<block wx:for="{{cachedSections}}" wx:for-item="item" wx:key="item.id"> <block wx:for="{{sections}}" wx:for-item="item" wx:key="item.id">
<template wx:if="{{item.component.type == 'Blog.Quote' || item.component.type == 'Quote'}}" is="post-item-quote" data="{{...item.component.value}}" /> <template wx:if="{{item.component.type == 'Blog.Quote' || item.component.type == 'Quote'}}" is="post-item-quote" data="{{...item.component.value}}" />
<template wx:if="{{item.component.type == 'Blog.Text' || item.component.type == 'RichText'}}" is="post-item-text" data="{{...item.component.value}}" /> <template wx:if="{{item.component.type == 'Blog.Text' || item.component.type == 'RichText'}}" is="post-item-text" data="{{...item.component.value}}" />
<template wx:if="{{item.component.type == 'Blog.Title' || item.component.type == 'Title'}}" is="post-item-title" data="{{...item.component.value}}" /> <template wx:if="{{item.component.type == 'Blog.Title' || item.component.type == 'Title'}}" is="post-item-title" data="{{...item.component.value}}" />
......
@import "/styles/main.wxss"; @import "/styles/main.wxss";
@import "/utils/wxParse/wxParse.wxss"; @import "/utils/wxParse/wxParse.wxss";
@import '/pages/blog/postDetail/commentList/commentList.wxss'; @import '/pages/postDetail/commentList/commentList.wxss';
@import '/pages/blog/postDetail/postItem/postItem.wxss'; @import '/pages/postDetail/postItem/postItem.wxss';
@import "/templates/messageModal/messageModal.wxss"; @import "/templates/messageModal/messageModal.wxss";
@import "/templates/shareView/shareView.wxss"; @import "/templates/shareView/shareView.wxss";
......
<import src="root/utils/wxParse/wxParse.wxml" /> <import src="/utils/wxParse/wxParse.wxml" />
<template name="post-item-quote"> <template name="post-item-quote">
<view class="post-item-quote post-item-wrapper"> <view class="post-item-quote post-item-wrapper">
<view class="post-item-quote-content"> <view class="post-item-quote-content">
......
import { connect } from 'root/wmp-redux'
import compose from 'ramda/src/compose'
import { fetchPosts, likePost } from 'root/actions/blog/post'
import urls from 'root/constants/blog/urls'
import iconPaths from 'root/constants/blog/iconPaths'
import { setComments } from 'root/actions/blog/comment'
import { fetchPostSharing } from 'root/actions/blog/sharing'
import { getPosts } from 'root/selectors/blog/post'
import { getAttr, getUserInfo } from 'root/selectors/common/global'
import { getComponents } from 'root/selectors/common/componentsSelector'
import {
login,
bindGetUserInfoHandler,
} from 'wechat_common/utils/wrappedWXTool'
import { getMixLayout } from 'root/utils/helpers/helper'
import shareView from 'root/templates/shareView/shareView'
import { trackUI } from 'wechat_common/tracker/index.bs'
import { getTeamMemberId } from 'root/selectors/presentation/teamMemberSelector'
const { iconComment, iconLike } = iconPaths
const page = {
data: {
currentCategory: 'all',
iconComment,
iconLike,
shareLoading: false,
selectedPostId: '',
currentComment: '',
isCommenting: false,
},
onLoad(options) {
wx.setNavigationBarTitle({
title: options.category,
})
this.setData({
isFetchingPosts: true,
currentCategory: options.category,
})
const { paginationPosts, siteId } = this.data
if (!paginationPosts) {
this.fetchPosts(siteId, options.category, 1)
}
},
handlePost(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({ url: `${urls.PAGES.POST_DETAIL}?postId=${id}` })
},
loadMorePosts() {
const { paginationPosts, siteId, currentCategory } = this.data
if (paginationPosts.nextPage) {
this.fetchPosts(siteId, currentCategory, paginationPosts.nextPage)
}
},
handleCard(e) {
const { post } = e.currentTarget.dataset
this.setData({ currentPost: post })
},
onShareAppMessage() {
const { currentPost = {}, showShareVariation, currentCategory } = this.data
const { title, id } = currentPost
if (id && showShareVariation) {
return {
title,
path: `${urls.PAGES.POST_DETAIL}?postId=${id}`,
}
} else {
return {
path: `${urls.PAGES.POST_LIST}?category=${currentCategory}`,
}
}
},
sharePicture() {
const { siteId, currentPost } = this.data
const { id: postId } = currentPost
if (wx.isWept) {
this.setError('预览模式下暂不支持,请手机预览')
} else {
this.setData({
shareLoading: true,
})
this.fetchPostSharing(siteId, {
scene: postId.toString(),
page: 'pages/blog/postDetail/postDetail',
}).then(res => {
trackUI(
'shareImageBlogPost',
postId,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `shareImageBlogPost${postId}`,
}),
)
this.setData({
shareLoading: false,
})
if (res.success) {
wx.navigateTo({
url: `${urls.PAGES.POST_SHARE}?postId=${postId}`,
})
} else {
wx.showModal({
title: '网络错误',
content: '请稍后重试',
showCancel: false,
success: res => {
if (res.confirm) {
this.closeShareView()
}
},
})
}
})
}
},
handleLike(e) {
const { postId, isLiked } = e.currentTarget.dataset
const cb = userInfo => {
const { nickName, avatarUrl } = userInfo
this.setData({ selectedPostId: '' })
if (!isLiked) {
trackUI(
'likeBlogPost',
postId,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `likeBlogPost${postId}`,
}),
)
}
this.likePost(!isLiked, postId, nickName, avatarUrl)
}
bindGetUserInfoHandler(e, cb)
},
switchOperation(e) {
const { post } = e.currentTarget.dataset
const { selectedPostId } = this.data
this.setData({
isCommenting: false,
currentComment: '',
selectedPostId:
selectedPostId && selectedPostId == post.id ? '' : post.id,
currentPost: post,
})
},
handleComment(e) {
const cb = () => {
this.setData({ isCommenting: true, selectedPostId: '' })
}
bindGetUserInfoHandler(e, cb)
},
handleInputComment(e) {
this.setData({ currentComment: e.detail.value })
},
handleSendComment(e) {
const { currentComment } = this.data
if (!currentComment || !currentComment.trim()) {
this.setError('评论内容不能为空')
} else if (wx.isWept) {
this.setError('预览模式下暂不支持,请手机预览')
} else {
this.setData({ isCommenting: false })
this.createComment(e.detail.formId)
}
},
createComment(formId) {
const { currentPost, currentComment, nickName, avatarUrl } = this.data
const that = this
this.setData({
submitting: true,
})
const sendData = {
content: currentComment,
nickname: nickName,
wechat_photo: [avatarUrl],
settings: {
form_id: formId,
},
}
login({
success: loginRes => {
sendData.code = loginRes.code
that.setComments(
currentPost.id,
sendData,
() => {
that.setData({
submitting: false,
currentComment: '',
})
wx.showToast({
title: '评论成功,审核通过后显示在留言列表',
icon: 'none',
duration: 2000,
})
trackUI(
'commentBlogPost',
currentPost.id,
JSON.stringify({
team_member_id: this.data.teamMemberId || -1,
key: `commentBlogPost${currentPost.id}`,
}),
)
},
() => {
that.setData({ submitting: false })
},
)
},
fail() {
wx.showModal({
content: '请求失败,请重试',
})
that.setData({
submitting: false,
})
},
})
},
handleGlobalTab() {
this.setData({
selectedPostId: '',
isCommenting: false,
currentComment: '',
})
},
onPullDownRefresh() {
const { siteId, currentCategory } = this.data
this.fetchPosts(siteId, currentCategory, 1).then(res =>
wx.stopPullDownRefresh(),
)
},
}
function mapStateToProps(state, options) {
const {
list: posts,
isFetching: isFetchingPosts,
pagination: paginationPosts,
} = getPosts(state, `${urls.PAGES.POST_LIST}_${options.category}`),
{ siteId, layout, mix, companyName, name, logoUrl } = getAttr(state),
enableComments = state.getIn([
'blog',
'setting',
'settings',
'enableComments',
])
const { nickName, avatarUrl } = getUserInfo(state)
let blogLayout = getMixLayout(mix, layout, 'blog')
let components = []
if (state.get('components') !== null) {
components = getComponents(state, 'blog')
const blogAndCategoryComp = components.find(
comp => comp.type === 'blogAndCategory',
)
if (blogAndCategoryComp && blogAndCategoryComp.settings) {
blogLayout = blogAndCategoryComp.settings.layout || 'a'
}
}
const teamMemberId = getTeamMemberId(state)
return {
posts,
isFetchingPosts,
paginationPosts,
enableComments,
teamMemberId,
siteId,
layout: blogLayout,
logoUrl,
companyName,
name,
nickName,
avatarUrl,
}
}
function mapDispatchToProps(dispatch) {
return {
fetchPosts: (siteId, category, pageNum) =>
dispatch(
fetchPosts(
`${urls.PAGES.POST_LIST}_${category}`,
siteId,
category,
pageNum,
),
),
fetchPostSharing: (siteId, data) =>
dispatch(fetchPostSharing(siteId, data)),
setComments: (postId, setData, successCb, failCb) =>
dispatch(setComments(postId, setData, successCb, failCb)),
likePost: (status, postId, nickName, avatarUrl) =>
dispatch(likePost(status, postId, nickName, avatarUrl)),
}
}
const enhance = compose(shareView, connect(mapStateToProps, mapDispatchToProps))
Page(enhance(page))
{
"enablePullDownRefresh": true
}
<import src="root/templates/postCard/postCard.wxml"/>
<import src="root/templates/loaderPage/loaderPage.wxml"/>
<import src="root/templates/loaderBar/loaderBar.wxml"/>
<import src="root/templates/shareView/shareView.wxml"/>
<view class="post-index">
<block wx:if="{{!isFetchingPosts || posts.length > 0}}">
<scroll-view style="height: 100vh; background-color:#fff;" scroll-y="true" lower-threshold="80" bindscrolltolower="loadMorePosts" bindtap="handleGlobalTab">
<view class="posts">
<block wx:for="{{posts}}" wx:key="id" wx:for-item="item">
<template wx:if="{{layout === 'a'}}" is="post-card-a" data="{{...item, handlePost}}"/>
<template wx:if="{{layout === 'b'}}" is="post-card-b" data="{{...item, handlePost}}"/>
<template wx:if="{{layout === 'c'}}" is="post-card-c" data="{{...item, handlePost}}"/>
<template wx:if="{{layout === 'd'}}" is="post-card-moment" data="{{item, index, companyName, logoUrl, name, handleCard, handlePost, handleLike, handleComment, openShareView, selectedPostId, enableComments}}"/>
</block>
</view>
<template
is='loader-bar'
data="{{isLoading: posts.length > 0 && paginationPosts.nextPage, emptyText: posts && posts.length === 0 && '- 暂无文章 -', noMoreText:posts && posts.length !== 0 && paginationPosts.currentPage === paginationPosts.totalPages && '- 无更多文章 -', style:'margin: 0 0 40rpx 0'}}"
/>
</scroll-view>
<form bindsubmit="handleSendComment">
<view class="comment-form" wx:if="{{isCommenting}}">
<input placeholder-class="comment-placeholder" class="comment-input" placeholder="评论" value="{{currentComment}}" bindinput="handleInputComment" cursor-spacing="8" focus="{{true}}"/>
<button class="comment-btn {{currentComment ? '' : 'disabled'}}" formType="submit">发送</button>
</view>
</form>
</block>
<template wx:else is='loader-page'/>
<template is="share-view" data="{{shareLoading, sharePicture, shareAnimation, showShareVariation, closeShareView, openShareView}}"/>
</view>
@import '/components/blog/blog/blog.wxss';
@import "/styles/main.wxss";
import { debounce } from 'wechat_common/utils/utils'
import { connect } from 'root/wmp-redux'
import { afterClearData } from 'root/actions/common/tools'
import {
fetchPostsSearch,
clearPostsSearch,
} from 'root/actions/blog/postSearch'
import urls from 'root/constants/blog/urls.js'
import iconPaths from 'root/constants/blog/iconPaths'
import { getPostsSearch } from 'root/selectors/blog/postSearch'
import { getStyle, getAttr } from 'root/selectors/common/global'
const { iconClear, iconLink, iconNoResult, iconClearWhite } = iconPaths
const page = {
data: {
keywords: '',
isFirst: true,
iconClear,
iconClearWhite,
iconLink,
iconNoResult,
},
handlePost(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({ url: `${urls.PAGES.POST_DETAIL}?postId=${id}` })
},
handleInput(event) {
const value = event.detail.value
this.setData({ isFirst: false, keywords: value })
const { siteId, keywords } = this.data
this.fetchPostsAfterClear(siteId, keywords, 1)
},
handleClear() {
this.setData({ keywords: '' })
this.clearPosts()
},
loadMorePosts() {
let {
paginationPosts: { pages, currPage },
siteId,
keywords,
isFetchingPosts,
} = this.data
if (!isFetchingPosts && currPage < pages) {
this.fetchPosts(siteId, keywords, ++currPage)
}
},
}
function mapStateToProps(state) {
const {
list: posts,
isFetching: isFetchingPosts,
pagination: paginationPosts,
} = getPostsSearch(state, urls.PAGES.POST_SEARCH),
{ mainBackground: background, isWhiteBackground } = getStyle(state),
{ siteId } = getAttr(state)
return {
posts,
isFetchingPosts,
paginationPosts,
siteId,
background,
isWhiteBackground,
}
}
function mapDispatchToProps(dispatch) {
const PAGE = urls.PAGES.POST_SEARCH
return {
clearPosts: () => dispatch(clearPostsSearch(PAGE)),
fetchPosts: (siteId, keywords, pageNum) =>
dispatch(fetchPostsSearch(PAGE, siteId, keywords, pageNum)),
fetchPostsAfterClear: debounce(
(siteId, keywords, pageNum) =>
dispatch(
afterClearData(
clearPostsSearch(PAGE),
fetchPostsSearch(PAGE, siteId, keywords, pageNum),
),
),
600,
),
}
}
const connectedPage = connect(page, mapStateToProps, mapDispatchToProps)
Page(connectedPage)
{
"navigationBarTitleText": "搜索"
}
\ No newline at end of file
<import src="root/templates/searchBar/searchBar.wxml"/>
<import src="root/templates/loaderBar/loaderBar.wxml"/>
<import src="root/templates/loaderPage/loaderPage.wxml"/>
<import src="root/templates/resultPage/resultPage.wxml"/>
<import src="root/templates/postCard/postCard.wxml"/>
<view class="post-search" style="height: 100vh; overflow:hidden;">
<template
is="search-bar"
data="{{background, isWhiteBackground, value:keywords, placeholder:'搜索文章', iconClear, iconClearWhite, handleInput, handleClear}}"
/>
<template wx:if="{{isFirst || !keywords}}" is='result-page'
data="{{height: 'calc(100vh - 112rpx)', text: '结果将会显示在这里'}}"/>
<block wx:if="{{posts.length}}">
<block wx:if="{{keywords}}">
<view class="search-result" style="width:calc(100vw - 80rpx)">
<text class="search-result-text">{{paginationPosts.totalCount}}条关于"{{keywords}}"的结果</text>
</view>
<scroll-view class="posts-content" lower-threshold="60" scroll-y="true" style="height:calc(100vh - 202rpx)"
bindscrolltolower="loadMorePosts">
<block wx:for="{{posts}}" wx:key="{{item.id}}" wx:for-index="index" wx:for-item="item">
<template is="post-card-d" data="{{...item, handlePost, icon: iconLink}}"/>
</block>
<template
is='loader-bar'
data="{{isLoading: isFetchingPosts, noMoreText: !isFetchingPosts&& paginationPosts.currPage === paginationPosts.pages && '- 无更多结果 -', style:'margin: 56rpx 0 40rpx 0'}}"
/>
</scroll-view>
</block>
</block>
<block wx:else>
<template wx:if="{{isFetchingPosts}}" is='loader-page' data="{{height: 'calc(100vh - 112rpx)'}}"/>
<template wx:else is='result-page'
data="{{height: 'calc(100vh - 109rpx)', icon: iconNoResult, text: '未找到相关结果'}}"/>
</block>
</view>
@import '/templates/searchBar/searchBar.wxss';
@import '/templates/loaderBar/loaderBar.wxss';
@import '/templates/loaderPage/loaderPage.wxss';
@import '/templates/resultPage/resultPage.wxss';
@import '/templates/postCard/postCard.wxss';
@import "/styles/main.wxss";
.post-search .search-result {
padding: 28rpx 16rpx 28rpx 40rpx;
width: 100vw;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.post-search .search-result .search-result-text {
height: 40rpx;
font-size: 28rpx;
color: #b6b6b6;
}
import { connect } from 'root/wmp-redux'
import iconPaths from 'root/constants/blog/iconPaths'
import { cutString, saveCanvasImage } from 'root/utils/helpers/helper'
import { getImageInfo } from 'wechat_common/utils/helpers/shareHelper'
import { getSharing } from 'root/selectors/blog/sharing'
import { replaceQnDomain } from 'wechat_common/utils/helpers/imageHelper'
import { formatProtocol } from 'wechat_common/utils/utils'
import urls from 'root/constants/blog/urls'
import { getPosts } from 'root/selectors/blog/post'
import { getPostDetail } from 'root/selectors/blog/postDetail'
const { iconDownload } = iconPaths
const TITLE_PER_LINE_WORDS = 16
const TITILE_MAX_LINES = 2
const CONTENT_PER_LINE_WORDS = 19
const CONTENT_MAX_LINES = 6
const NAME_PER_LINE_WORDS = 9
const NAME_MAX_LINES = 1
const page = {
data: {
iconDownload,
hasDrawn: false,
},
onLoad(option) {
const { postList } = this.data
const { postId } = option
const { blurb, title: headerTitle, iconUrl: backgroundUrl } = postList.find(
item => item.id == postId,
)
const { officialName = '', officialIconUrl, sceneCodeUrl } = this.data
const title = cutString(headerTitle, TITLE_PER_LINE_WORDS * 2)
const content = cutString(blurb, CONTENT_PER_LINE_WORDS * 2)
const name = cutString(officialName, NAME_PER_LINE_WORDS * 2)
const { screenWidth, screenHeight } = wx.getSystemInfoSync()
const promiseList = [backgroundUrl, officialIconUrl].map(url =>
getImageInfo(formatProtocol(replaceQnDomain(url))),
)
const ctx = wx.createCanvasContext('share-image')
Promise.all(promiseList)
.then(([backgroundUrl, officialIconUrl]) => {
ctx.scale(screenWidth / 375, screenWidth / 375)
ctx.setFillStyle('white')
ctx.fillRect(0, 0, 329, 476)
ctx.drawImage(backgroundUrl.path, 0, 0, 329, 189.5)
ctx.setFillStyle('rgba(0, 0, 0, 0.2)')
ctx.fillRect(0, 0, 329, 189.5)
ctx.setFontSize(18)
ctx.setFillStyle('#ffffff')
ctx.setTextAlign('left')
title.slice(0, TITILE_MAX_LINES).forEach((item, index) => {
if (
title.length > TITILE_MAX_LINES &&
index === TITILE_MAX_LINES - 1
) {
item = `${item.slice(0, item.length - 1)}...`
}
ctx.fillText(item, 18, 155 + index * 22)
})
ctx.setFontSize(15)
ctx.setFillStyle('#222222')
content.slice(0, CONTENT_MAX_LINES).forEach((item, index) => {
if (
content.length > CONTENT_MAX_LINES &&
index === CONTENT_MAX_LINES - 1
) {
item = `${item.slice(0, item.length - 1)}...`
}
ctx.fillText(item, 18, 220 + index * 24)
})
const grd = ctx.createLinearGradient(0, 245, 0, 355)
grd.addColorStop(0, 'rgba(255, 255, 255, 0)')
grd.addColorStop(1, 'white')
ctx.setFillStyle(grd)
ctx.fillRect(0, 245, 329, 100)
ctx.save()
ctx.beginPath()
ctx.arc(43, 401, 23, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(officialIconUrl.path, 20, 378, 46, 46)
ctx.restore()
ctx.setFillStyle('#222222')
ctx.setTextAlign('left')
ctx.setFontSize(14)
name.slice(0, NAME_MAX_LINES).forEach((item, index) => {
if (name.length > NAME_MAX_LINES && index === NAME_MAX_LINES - 1) {
item = `${item.slice(0, item.length - 1)}...`
}
ctx.fillText(item, 72, 395)
})
ctx.setFontSize(12)
ctx.setFillStyle('#bbbbbb')
ctx.fillText('扫码查看完整文章', 72, 417)
return getImageInfo(replaceQnDomain(sceneCodeUrl))
})
.then(sceneCodeUrl => {
ctx.drawImage(sceneCodeUrl.path, 201, 350, 110, 110)
ctx.draw()
this.setData({
hasDrawn: true,
})
})
.catch(e => {
ctx.draw()
this.setData({
hasDrawn: true,
})
wx.showModal({
title: '网络错误',
content: '图片生成失败',
showCancel: false,
})
})
},
saveImage() {
saveCanvasImage('share-image')
},
}
function mapStateToProps(state, ownProps) {
const { officialName, officialIconUrl, sceneCodeUrl } = getSharing(state)
const { list: postList } = getPosts(state, urls.PAGES.POST_INDEX)
const { headerTitleString, backgroundUrl } = getPostDetail(state)
return {
officialName,
officialIconUrl,
sceneCodeUrl,
headerTitleString,
backgroundUrl,
postList,
}
}
const connectedPage = connect(page, mapStateToProps, {})
Page(connectedPage)
{
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#292940"
}
<view class="share-container">
<canvas canvas-id="share-image" class="share-canvas" style="display: {{hasDrawn ? 'flex' : 'none' }}"></canvas>
<view class="flex-row flex-col center-align-item" style="width: 658rpx;height: 952rpx; margin-top: 48rpx; background: #fff" wx:if="{{!hasDrawn}}">
<view class="loader"/>
</view>
<view class="share-save" bindtap="saveImage">
<image src="{{iconDownload}}" />
<text>保存图片</text>
</view>
</view>
@import "/styles/main.wxss";
@import "/templates/loader/loader.wxss";
.share-container {
width: 100vw;
height: 100vh;
background: #292940;
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.share-canvas {
width: 658rpx;
height: 952rpx;
margin-top: 48rpx;
background: transparent;
}
.share-save {
display: flex;
width: 656rpx;
height: 96rpx;
background: rgba(255, 255, 255, 0.18);
justify-content: center;
align-items: center;
border: solid 2rpx rgba(255, 255, 255, 0.17);
border-radius: 10rpx;
image {
width: 48rpx;
height: 48rpx;
margin-right: 16rpx;
}
text {
color: rgba(255, 255, 255, 0.8);
font-size: 32rpx;
}
}
...@@ -2,5 +2,9 @@ ...@@ -2,5 +2,9 @@
"publicComponents": { "publicComponents": {
"blog": "components/blog/blog" "blog": "components/blog/blog"
}, },
"pages": {
"postDetail": "pages/postDetail/postDetail",
"postList": "pages/postList/postList"
},
"main": "index.js" "main": "index.js"
} }
\ No newline at end of file
import { extendMethod } from 'root/utils/helpers/helper' function extendMethod(...fns) {
return function (...args) {
fns.forEach(
fn => (typeof fn === 'function' ? fn.bind(this)(...args) : null),
)
}
}
function shareView(app) { function shareView(app) {
app.onLoad = extendMethod(app.onLoad, function() { app.onLoad = extendMethod(app.onLoad, function() {
......
Page()
export const data = {
type: 'product' || 'category' || 'page' || 'usage',
url: '',
value: '' || { id: '', name: '' },
}
export function makePhoneCall(e) {
// if (wx.isWept) {
// this.setError('预览模式下暂不支持拨打电话,请手机预览')
// } else {
// const { phone } = e.currentTarget.dataset
// wx.makePhoneCall({
// phoneNumber: phone,
// })
// }
}
export function handleProduct(e) {
// const { id, name } = e.currentTarget.dataset
// if (id) {
// wx.navigateTo({
// url: ``,
// })
// }
}
export function handleCategory(e) {
// const { id, name } = e.currentTarget.dataset
// if (id) {
// wx.navigateTo({
// url: ``,
// })
// }
}
export function handlePage(e) {
// const { id, name } = e.currentTarget.dataset
// if (id) {
// wx.navigateTo({
// url: ``,
// })
// }
}
<template name="shortcut">
<view class="{{'shortcut shortcut-' + iconType}}">
<image wx:if="{{type == 'product'}}" class="shortcut-image" bindtap="handleProduct" src="{{url}}" data-id="{{value}}"/>
<image wx:elif="{{type == 'category'}}" class="shortcut-image" bindtap="handleCategory" src="{{url}}" data-id="{{value.id}}" data-name="{{value.name}}" />
<block wx:elif="{{type == 'usage'}}">
<button wx:if="{{value == 'livechat'}}" open-type="{{showContactBtn ? 'contact' : ''}}" class="shortcut-button" bindtap="liveChat">
<image src="{{url}}" class="shortcut-image" />
</button>
<button wx:elif="{{value == 'share'}}" open-type="share" class="shortcut-button" bindtap="share">
<image src="{{url}}" class="shortcut-image" />
</button>
<image wx:elif="{{value == 'map'}}" bindtap="navigation" data-coordinate="{{mapCoordinate}}" data-address="{{mapLocation}}" src="{{url}}" class="shortcut-image" />
<image wx:else bindtap="makePhoneCall" data-phone="{{number}}" src="{{url}}" class="shortcut-image" />
</block>
<image wx:elif="{{type == 'page'}}" class="shortcut-image" bindtap="handlePage" src="{{url}}" data-id="{{value}}" />
<image wx:elif="{{type == 'blogPost'}}" class="shortcut-image" bindtap="handlePost" src="{{url}}" data-id="{{value}}" />
<image wx:else src="{{url}}" class="shortcut-image" />
<text class="shortcut-image-text">{{name}}</text>
</view>
</template>
.shortcut {
text-align: center;
}
.shortcut .shortcut-image {
height: 120rpx;
width: 120rpx;
display: block;
}
.shortcut .shortcut-button {
height: 120rpx;
width: 120rpx;
display: block;
padding: 0;
}
.shortcut .shortcut-image-text {
color: #333;
font-size: 15px;
}
.shortcut-container {
text-align: center;
}
.shortcut-square {
border-radius: 3px;
}
.shortcut-circle .shortcut-image,
.shortcut-circle .shortcut-button {
border-radius: 50%;
overflow: hidden;
}
.shortcut-circle button::after {
border: 0;
}
\ No newline at end of file
<import src="root/templates/shortcut/shortcut.wxml"/> <import src="/templates/shortcut/shortcut.wxml"/>
<template name="shortcuts"> <template name="shortcuts">
<view wx:if="{{shortcuts.length !== 0}}" class="shortcuts"> <view wx:if="{{shortcuts.length !== 0}}" class="shortcuts">
......
import {
doAuthorizeGet as doGet,
doPut,
doPost,
doDelete,
poller,
} from 'wechat_common/utils/request'
import { ADMINTOOL_API_URL } from 'root/constants/admintool/urlConstants'
const customHeader = {
headerName: 'Authorization',
}
function getVerifyCode(phone) {
return doPost({
url: ADMINTOOL_API_URL.GET_VERIFY_CODE(),
data: {
phone,
},
})
}
function authorize(phone, verifyCode) {
return doPost({
url: ADMINTOOL_API_URL.LOGIN(),
data: {
phone,
verify_code: verifyCode,
},
})
}
function fetchWmpList() {
return doGet({
url: ADMINTOOL_API_URL.WMPLIST(),
customHeader,
})
}
function fetchUnreadCount() {
return doGet({
url: ADMINTOOL_API_URL.GET_UNREAD_COUNT(),
customHeader,
})
}
function fetchSummaryInfo(appId, storefrontId) {
return doGet({
url: ADMINTOOL_API_URL.SUMMARY_INFO(appId, storefrontId),
customHeader,
})
}
function fetchAnalytics(options) {
return doGet({
url: options,
customHeader,
})
}
function fetchMemberList(appId, orderBy, page) {
return doGet({
url: ADMINTOOL_API_URL.MEMBERLIST(appId, { order_by: orderBy, page }),
customHeader,
})
}
function fetchMemberDetail(appId, memberId) {
return doGet({
url: ADMINTOOL_API_URL.MEMBERLIST(appId, memberId),
customHeader,
})
}
function fetchMiniprogramPaymentOrders(options) {
return doGet({
url: options,
customHeader,
})
}
function fetchOrderDetail(appType, appId, orderNum, orderType) {
const url = ADMINTOOL_API_URL.ORDERDETAIL(appType, appId, orderType, orderNum)
return doGet({
url,
customHeader,
})
}
function updateOrderDetail(appType, appId, orderNum, orderType, remark, status) {
return doPut({
url: ADMINTOOL_API_URL.ORDERDETAIL(appType, appId, orderType, orderNum),
data: {
remark,
status,
},
customHeader,
})
}
function setOrderStatus(appId, orderNum, status, shipping_notes) {
if(status === 'shipped'){
return doPost({
url: ADMINTOOL_API_URL.ORDERDSTATUS(appId, orderNum, status),
data: {
status,
deliveryMemberPhone: shipping_notes,
},
customHeader,
})
}else{
return doPut({
url: ADMINTOOL_API_URL.ORDERDSTATUS(appId, orderNum, status),
data: {status},
customHeader,
})
}
}
function fetchVisitorData(options) {
return new Promise((resolve, reject) => {
poller(options, resolve, reject, customHeader)
})
}
function fetchComments(appId, status, page, storefrontId) {
let options = {}
if (storefrontId) {
options = {
page,
storefrontId,
}
} else {
options = {
page,
}
}
return doGet({
url: ADMINTOOL_API_URL.COMMENTLIST(appId, status, options),
customHeader,
})
}
function approveComment(appId, commentId) {
return doPut({
url: ADMINTOOL_API_URL.APPROVECOMMENT(appId, commentId),
data: {
id: commentId,
},
customHeader,
})
}
function deleteComment(appId, commentId) {
return doDelete({
url: ADMINTOOL_API_URL.DELETECOMMENT(appId, commentId),
data: {
id: commentId,
},
customHeader,
})
}
function replyComment(appId, commentId, content, replyStatus) {
return doPost({
url: ADMINTOOL_API_URL.REPLYCOMMENT(appId),
data: {
id: commentId,
content,
status: replyStatus ? 4 : 3,
},
customHeader,
})
}
function approveAllComments(appId, storefrontId) {
return doPost({
url: ADMINTOOL_API_URL.APPROVEALL(appId, storefrontId),
data: storefrontId
? {
storefrontId,
}
: null,
customHeader,
})
}
function markMemberAsRead(appId, memberId) {
return doPost({
url: ADMINTOOL_API_URL.MARK_MEMBER_AS_READ(appId, memberId),
data: {
id: memberId,
},
customHeader,
})
}
function markOrderAsRead(appId, orderNumber) {
return doPost({
url: ADMINTOOL_API_URL.MARK_ORDER_AS_READ(appId, orderNumber),
data: {
orderNumber,
},
customHeader,
})
}
function markCommentAsRead(appId, commentId) {
return doPost({
url: ADMINTOOL_API_URL.MARK_COMMENT_AS_READ(appId, commentId),
data: {
id: commentId,
},
customHeader,
})
}
function createTicket(appId, content, attachments) {
return doPost({
url: ADMINTOOL_API_URL.CREATE_TICKET(appId),
data: {
id: appId,
content,
attachments,
},
customHeader,
})
}
function upload(options) {
wx.uploadFile({
url: options.url,
filePath: options.filePath,
name: 'file',
formData: {
token: options.token,
},
success: res => {
if (options.success) {
options.success(res)
}
},
})
}
function getUploadInfo(appId) {
return doPost({
url: ADMINTOOL_API_URL.GET_UPLOAD_INFO(appId),
})
}
function fetchOrders({appType, appId, orderType, storefrontId, nextPage, status}) {
let options = { page: nextPage }
if (storefrontId) options.storefrontId = storefrontId
if (status) options.status = status
if (appType === 'restaurant'){
options.type = orderType === 'dinein' ? 'dine_in' : orderType
}
//options.per = 8
return doGet({
url: ADMINTOOL_API_URL.ORDERLIST(appType, appId, orderType, options),
customHeader,
}).then(
res => {
switch(appType) {
case 'restaurant':
res.data.total = res.data.paginationMeta.totalCount || 0
res.data.unread = res.data.unreadCount || 0
break;
case 'showcase':
switch (orderType) {
case 'sales':
res.data.orders = res.data.sales
res.data.total = res.data.totalSalesOrders || 0
res.data.unread = res.data.salesOrdersUnread || 0
break;
case 'recharge':
res.data.orders = res.data.recharges
res.data.total = res.data.totalRechargeOrders || 0
res.data.unread = res.data.rechargeOrdersUnread || 0
break;
case 'groupon':
res.data.orders = res.data.grouponOrders
res.data.total = res.data.paginationMeta.totalCount || 0
res.data.unread = res.data.grouponOrdersUnread || 0
break;
default:
break;
}
break;
default:
break;
}
return res
}
)
}
function fetchDocuments() {
return doGet({
url: ADMINTOOL_API_URL.DOCUMENTLIST(),
customHeader,
})
}
function fetchDocumentUnreadCount() {
return doGet({
url: ADMINTOOL_API_URL.DOCUMENT_UNREAD_COUNT(),
customHeader,
})
}
function fetchDocumentContent(id) {
return doGet({
url: ADMINTOOL_API_URL.DOCUMENT_CONTENT(id),
customHeader,
})
}
const admintoolApi = {
fetchWmpList,
fetchSummaryInfo,
getVerifyCode,
authorize,
fetchComments,
approveComment,
fetchUnreadCount,
fetchAnalytics,
fetchMiniprogramPaymentOrders,
fetchVisitorData,
fetchMemberList,
fetchMemberDetail,
fetchOrderDetail,
deleteComment,
approveAllComments,
replyComment,
markMemberAsRead,
markOrderAsRead,
markCommentAsRead,
createTicket,
upload,
getUploadInfo,
fetchOrders,
updateOrderDetail,
fetchDocuments,
fetchDocumentUnreadCount,
fetchDocumentContent,
setOrderStatus,
}
export default admintoolApi
import { doGet, doPut } from 'wechat_common/utils/request'
import UrlConstants from 'root/constants/ecommerce/urlConstants'
export function getAdvertisePopup(siteId, code) {
const url = UrlConstants.ECOMMERCE.GET_ADVERTISE_POPUP(siteId, code)
return doGet({ url })
}
export function hideAdvertisePopup(siteId, code) {
const url = UrlConstants.ECOMMERCE.UPDATE_ADVERTISE_POPUP(siteId)
return doPut({
url,
data: { code },
})
}
import { doGet } from 'wechat_common/utils/request'
import { FETCH_ANNOUNCEMENTS } from 'root/constants/common/urlConstants'
export function fetchAnnouncements(siteId) {
return doGet({
url: FETCH_ANNOUNCEMENTS(siteId),
})
}
import { doGet, doPost, doPut, doDelete } from 'wechat_common/utils/request'
import { bindSiteId, bindCode } from 'wechat_common/utils/context'
import Urls from 'root/constants/blog/urls'
import compose from 'ramda/src/compose'
import {
bindRetry,
bindShowToast,
} from 'wechat_common/networkErrorManager'
const getPostDetail = compose(bindCode, bindSiteId)(
(siteId, code, { params: { postId }, ...options }) =>
doGet(
bindRetry({
url: Urls.REQUESTS.GET_POST(code, postId),
...options,
}),
),
)
const getPosts = compose(bindCode, bindSiteId)(
(siteId, code, { params: { category, pageNum }, ...options }) =>
doGet(
bindRetry({
url: Urls.REQUESTS.GET_POSTS(siteId, code, category, pageNum),
...options,
}),
),
)
const likePost = compose(bindCode, bindSiteId)(
(
siteId,
code,
{ params: { status, postId, nickname, wechat_photo }, ...options },
) => {
const params = {
url: status
? Urls.REQUESTS.SET_LIKE_POST(postId)
: Urls.REQUESTS.DELETE_LIKE_POST(postId),
data: { code, nickname, wechat_photo },
...options,
}
if (status) {
doPost(bindShowToast(params))
} else {
doDelete(bindShowToast(params))
}
},
)
export default {
likePost,
getPosts,
getPostDetail,
}
import { doGet, doPost, doPut, poller } from 'wechat_common/utils/request'
import { bindSiteId, bindCode } from 'wechat_common/utils/context'
import UrlConstants from 'root/constants/ecommerce/urlConstants'
import compose from 'ramda/src/compose'
import { bindRefresh, bindRetry, bindShowToast } from 'wechat_common/networkErrorManager'
export function getCategories(options) {
return doGet(bindRetry(options))
}
export function getProducts(url) {
return doGet(bindRefresh({ url }))
}
export const getFlashSaleProducts = bindSiteId((siteId, status, pageNum) => {
const url = UrlConstants.ECOMMERCE.GET_FLASH_SALE_PRODUCTS(
siteId,
status,
pageNum,
)
return doGet(bindRetry({ url }))
})
export const getGroupBuyProducts = bindSiteId(
(siteId, status, pageNum, code) => {
const url = UrlConstants.ECOMMERCE.GET_GROUP_BUY_PRODUCTS(
siteId,
status,
pageNum,
code,
)
return doGet(bindRetry({ url }))
},
)
export function getGroupBuyDetail(options) {
return doGet(bindRetry(options))
}
export function getProduct(options) {
return doGet(bindRetry(options))
}
export function getShare(options) {
return doPost(bindShowToast(options))
}
export function getSettings(options) {
return doGet(bindRetry(options))
}
export function convertCoupon(options) {
return doPost(bindShowToast(options))
}
export function getOptimal(options) {
return doPost(bindShowToast(options))
}
export const getCoupons = compose(bindCode, bindSiteId)((siteId, code) =>
doGet(bindRetry({ url: UrlConstants.ECOMMERCE.GET_COUPON_LIST_BY_SITE(siteId, code) })),
)
export const getUserCoupons = compose(bindCode, bindSiteId)(
(siteId, code, status, page, size) =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.GET_COUPON_LIST_BY_USER(
siteId,
code,
status,
page,
size,
),
})),
)
export function search(options) {
return doPost(bindRetry(options))
}
export const getMembershipCard = compose(bindCode, bindSiteId)((siteId, code) =>
doGet(bindRetry({ url: UrlConstants.ECOMMERCE.GET_MEMBERSHIP_CARD(siteId, code) })),
)
export function getSignature(url) {
return doGet(bindRetry({ url }))
}
export function getAffiliateSettings(url) {
return doGet(bindRetry({ url }))
}
export function getAffiliateMpCode(url) {
return doGet(bindRetry({ url }))
}
export function fetchCommissionDetail(url) {
return doGet(bindRetry({ url }))
}
export function fetchCommissionHistory(url) {
return doGet(bindRetry({ url }))
}
export function fetchCommissionOrder(url) {
return doGet(bindRetry({ url }))
}
export function applyAffiliate(options) {
return doPost(bindShowToast(options))
}
export function updateAffiliate(options) {
return doPut(bindShowToast(options))
}
export function createWithdrawal(options) {
return doPost(bindShowToast(options))
}
export function createOrder(options) {
doPost(bindShowToast({
url: options.url,
data: options.data,
success(res) {
if (res.status === 200) {
const pollUrl = `/r/v1/tasks/${res.data.type}/${res.data.id}.jsm?v=2`
poller(pollUrl, options.success, options.fail)
} else if (options.success) {
options.success(res)
}
},
fail() {
if (options.fail) {
options.fail()
}
},
}))
}
export function createGroupBuyOrder(options) {
doPost(bindShowToast({
url: options.url,
data: options.data,
success(res) {
if (res.status === 200) {
const pollUrl = `/r/v1/tasks/${res.data.type}/${res.data.id}.jsm?v=2`
poller(pollUrl, options.success, options.fail)
} else if (options.success) {
options.success(res)
}
},
fail(err) {
if (options.fail) {
options.fail(err)
}
},
}))
}
export function getOrders(options) {
doGet(bindRetry(options))
}
export function updateOrder(options) {
doPut(bindShowToast(options))
}
export function getSliders(options) {
doGet(bindRetry(options))
}
export function receviceCoupons(options) {
doPost(bindShowToast(options))
}
const getOrderById = compose(bindCode, bindSiteId)(
(siteId, code, { params: { orderId }, ...options }) => {
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.GET_ORDERS_BY_ID({ siteId, code, orderId }),
...options,
}))
},
)
export function upload(options) {
wx.uploadFile({
url: options.url,
filePath: options.filePath,
name: 'file',
formData: {
token: options.token,
},
success: res => {
if (options.success) {
options.success(res)
}
},
})
}
export function middleWareFunction(options) {
return doPost(bindShowToast(options))
}
export function fetchComments(options) {
return doGet(bindRetry(options))
}
export function fetchPointsRule(url) {
return doGet(bindRetry({ url }))
}
export const fetchPointRecords = compose(bindCode, bindSiteId)(
(siteId, code, page) =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.FETCH_POINT_RECORDS(siteId, code, page),
})),
)
export const fetchNewCouponList = compose(bindCode, bindSiteId)(
(siteId, code) =>
new Promise((resolve, reject) => {
poller(
UrlConstants.ECOMMERCE.FETCH_NEW_COUPON_LIST(siteId, code),
resolve,
reject,
)
}),
)
const cancelOrder = compose(bindCode, bindSiteId)(
(siteId, code, { params: { orderId }, ...options }) =>
doPut(bindShowToast({
url: UrlConstants.ECOMMERCE.CANCEL_ORDER(siteId, orderId, code),
...options,
})),
)
const getUserInfo = compose(bindCode, bindSiteId)((siteId, code, options) =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.GET_USER_INFO(siteId, code),
...options,
})),
)
export const fetchRechargeSetting = bindSiteId(siteId =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.FETCH_RECHARGE_SETTING(siteId),
})),
)
export const getStoreValueCardInfo = compose(bindCode, bindSiteId)(
(siteId, code) =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.GET_STORE_VALUE_CARD_INFO(siteId, code),
})),
)
export const recharge = compose(bindCode, bindSiteId)(
(siteId, code, rechargeRuleId) =>
doPost(bindShowToast({
url: UrlConstants.ECOMMERCE.RECHARGE(siteId),
data: {
code,
rechargeRuleId,
},
})).then(res => {
if (res.status === 200) {
const pollUrl = `/r/v1/tasks/${res.data.type}/${res.data.id}.jsm?v=2`
return new Promise(resolve => {
poller(pollUrl, response => {
resolve(response)
})
})
} else {
return Promise.reject(res)
}
}),
)
export const fetchRechargeRecords = compose(bindCode, bindSiteId)(
(siteId, code, page) =>
doGet(bindRetry({
url: UrlConstants.ECOMMERCE.FETCH_RECHARGE_RECORDS(siteId, code, page),
})),
)
export default {
getCategories,
getProducts,
getProduct,
getShare,
getSettings,
convertCoupon,
search,
getMembershipCard,
getSignature,
createOrder,
createGroupBuyOrder,
getOrders,
updateOrder,
getSliders,
getFlashSaleProducts,
getGroupBuyProducts,
getGroupBuyDetail,
getCoupons,
getOptimal,
receviceCoupons,
getOrderById,
upload,
middleWareFunction,
fetchComments,
fetchPointsRule,
fetchPointRecords,
fetchNewCouponList,
cancelOrder,
getUserInfo,
getStoreValueCardInfo,
fetchRechargeSetting,
recharge,
fetchRechargeRecords,
}
import { doGet, doPost, doPut, poller } from 'wechat_common/utils/request'
import URLS from 'root/constants/giftcard/urls'
export function getProducts(url) {
return doGet({ url })
}
export function getProduct(url) {
return doGet({ url })
}
export function getCards(url) {
return doGet({ url })
}
export function createOrder(options) {
return new Promise((resolve, reject) => {
doPost({
url: options.url,
data: options.data,
success(res) {
if (res.status === 200) {
const { type, id } = res.data
const pollUrl = URLS.POLL_ORDER(type, id)
poller(pollUrl, resolve, reject)
} else {
resolve(res)
}
},
fail() {
reject()
},
})
})
}
export function getPackets(url) {
return doGet({ url })
}
export function getPacket(url) {
return doGet({ url })
}
export function updatePacket(options) {
return doPut(options)
}
export function getSettings(url) {
return doGet({ url })
}
import { doGet, doPost, doPut } from 'wechat_common/utils/request'
import { PRESENTATION_API } from 'root/constants/presentation/urlConstants'
import compose from 'ramda/src/compose'
import { bindSiteId, bindCode } from 'wechat_common/utils/context'
import { bindRetry, bindShowToast } from 'wechat_common/networkErrorManager'
function fetchSettings(options) {
return doGet(bindRetry(options))
}
function fetchPortfolioSettings(options) {
return doGet(bindRetry(options))
}
function getProducts(url) {
return doGet(bindRetry({ url }))
}
function getCategories(options) {
return doGet(bindRetry(options))
}
function getProduct(options) {
return doGet(bindRetry(options))
}
function getCode(options) {
return doPost(bindRetry(options))
}
function verify(options) {
return doPost(bindRetry(options))
}
function registerTeamMember(options) {
return doPost(bindRetry(options))
}
export const fetchGroups = compose(bindSiteId, bindCode)((code, siteId) =>
doGet({
url: PRESENTATION_API.FETCH_GROUP_LIST(siteId, code),
}),
)
export const fetchGroupSummaryDataById = compose(bindSiteId, bindCode)(
(code, siteId, teamId) =>
doGet({
url: PRESENTATION_API.FETCH_GROUP_SUMMARY_DATA_BY_DI(
siteId,
code,
teamId,
),
}),
)
function createFeedback(options) {
return doPost(options)
}
export const fetchGroupMembers = compose(bindSiteId, bindCode)(
(code, siteId, groupId, page, rankingType) =>
doGet({
url: PRESENTATION_API.FETCH_GROUP_MEMBERS(
siteId,
code,
groupId,
page,
rankingType,
),
}),
)
export const bindCustomerRelationship = compose(bindSiteId, bindCode)(
(code, siteId, teamMemberId) =>
doPost(
bindRetry({
url: PRESENTATION_API.BIND_CUSTOMER_RELATIONSHIP(siteId),
data: {
code,
teamMemberId,
},
}),
),
)
export const getTeamMemberInfoById = compose(bindSiteId, bindCode)(
(code, siteId, teamMemberId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_TEAM_MEMBER_CARD_INFO(
siteId,
code,
teamMemberId,
),
}),
),
)
export const fetchTeamMemberCards = compose(bindSiteId, bindCode)(
(code, siteId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_TEAM_MEMBER_CARDS(siteId, code),
}),
),
)
export const updateCustomerRelationship = compose(bindSiteId, bindCode)(
(code, siteId, currentTeamMemberId, targetTeamMemberId) =>
doPost(
bindShowToast({
url: PRESENTATION_API.UPDATE_CUSTOMER_RELATIONSHIP(siteId),
data: {
memberId: currentTeamMemberId,
targetTeamMemberId,
code,
},
}),
),
)
export const fetchRadarUnreadCount = compose(bindSiteId, bindCode)(
(code, siteId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_RADAR_UNREAD_COUNT(siteId, code),
}),
),
)
export const fetchGroupChartAnalytics = compose(bindSiteId, bindCode)(
(code, siteId, teamId, startDate, endDate) =>
doGet({
url: PRESENTATION_API.FETCH_GROUP_CHART_ANALYTICS(
siteId,
code,
teamId,
startDate,
endDate,
),
}),
)
const fetchGroupAnalyticsReports = compose(bindSiteId, bindCode)(
(code, siteId, teamId, startDate, endDate) =>
doGet({
url: PRESENTATION_API.FETCH_GROUP_ANALYTICS_REPORTS(
siteId,
code,
teamId,
startDate,
endDate,
),
}),
)
const fetchMemberList = compose(bindSiteId, bindCode)(
(code, siteId, value, memberId, groupId, needFakeData) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_MEMBER_LIST(
code,
siteId,
value,
memberId,
groupId,
needFakeData,
),
}),
),
)
const fetchVisitorDetail = compose(bindCode, bindSiteId)(
(siteId, code, memberId, startDate, endDate, teamMemberId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_VISITOR_DETAIL(
siteId,
code,
memberId,
startDate,
endDate,
teamMemberId,
),
}),
),
)
const fetchVisitorInfo = compose(bindSiteId)((siteId, memberId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_VISITOR_INFO(siteId, memberId),
}),
),
)
const fetchVisitorAnalytics = compose(bindSiteId)((siteId, memberId, options) =>
doGet({
url: PRESENTATION_API.FETCH_VISITOR_ANALYTICS(siteId, memberId, options),
}),
)
const fetchVisitorGeneralReport = compose(bindSiteId)(
(siteId, memberId, options) =>
doGet({
url: PRESENTATION_API.FETCH_VISITOR_GENERAL_REPORT(
siteId,
memberId,
options,
),
}),
)
const updateVisitorInfo = compose(bindSiteId)(
(siteId, code, memberId, contactInfo) =>
doPut(
bindShowToast({
url: PRESENTATION_API.UPDATE_VISITOR_INFO(siteId, memberId),
data: {
code,
contactInfo,
},
}),
),
)
const fetchContactForms = compose(bindSiteId, bindCode)((code, siteId) =>
doGet(
bindRetry({
url: PRESENTATION_API.FETCH_CONTACT_FORMS(siteId, code),
}),
),
)
const updateContactFormsReadMark = compose(bindSiteId, bindCode)(
(code, siteId, messageId) =>
doPost({
url: PRESENTATION_API.UPDATE_CONTACT_FORMS_READ_MARK(siteId),
data: {
code,
contact_form_ids: [messageId],
},
}),
)
function createContactForm(options) {
return doPost(options)
}
function fetchFormSettings(options) {
return doGet(bindRetry(options))
}
function getShare(options) {
return doPost(bindRetry(options))
}
const updateMemberCardInfo = compose(bindSiteId, bindCode)(
(code, siteId, value, memberId) =>
doPut(
bindShowToast({
url: PRESENTATION_API.UPDATE_MEMBER_CARD_INFO(siteId, memberId),
data: Object.assign({}, {code}, value),
}),
),
)
const updateTeamMember = compose(bindCode, bindSiteId)(
(siteId, code, memberId, data) =>
doPut(
bindShowToast({
url: PRESENTATION_API.UPDATE_TEAM_MEMBER(siteId, memberId, code),
data,
}),
),
)
const updateMemberLikes = compose(bindSiteId, bindCode)(
(code, siteId, likeState, teamMemberId) =>
doPut(
bindShowToast({
url: PRESENTATION_API.UPDATE_MEMBER_LIKES(siteId),
data: {
code,
likeState,
teamMemberId,
},
}),
),
)
const getChannelList = compose(bindCode, bindSiteId)(
(siteId, code, memberId, groupId) => {
const url = PRESENTATION_API.GET_CHANNEL_LIST(
siteId,
code,
memberId,
groupId,
)
return doGet(
bindRetry({
url,
}),
)
},
)
const getChannelDetail = compose(bindCode, bindSiteId)(
(siteId, code, channelId, memberId, groupId, serviceNotification) => {
const url = PRESENTATION_API.GET_CHANNEL_DETAIL(
siteId,
code,
channelId,
memberId,
groupId,
serviceNotification,
)
return doGet({
url,
})
},
)
const getChannelUnreadCount = compose(bindCode, bindSiteId)(
(siteId, code, channelId) => {
const url = PRESENTATION_API.GET_CHANNEL_UNREAD(siteId, code, channelId)
return doGet({
url,
})
},
)
const postChannelDetail = compose(bindCode, bindSiteId)(
(siteId, code, channelId, data) => {
const url = PRESENTATION_API.POST_CHANNEL_DETAIL(siteId, code)
data.channelId = channelId
return doPost({
url,
data,
})
},
)
const getBroadcasts = compose(bindCode, bindSiteId)((siteId, code) => {
const url = PRESENTATION_API.GET_BROADCASTS(siteId, code)
return doGet(
bindRetry({
url,
}),
)
})
const getSystemNotifications = compose(bindSiteId)(
(siteId, teamMemberId, startDate, endDate) => {
const url = PRESENTATION_API.GET_SYSTEM_NOTIFICATIONS(
siteId,
teamMemberId,
startDate,
endDate,
)
return doGet(
bindRetry({
url,
}),
)
},
)
const createBroadcast = compose(bindCode, bindSiteId)((siteId, code, data) => {
const url = PRESENTATION_API.CREATE_BROADCAST(siteId, code)
return doPost({
url,
data,
})
})
const getChannelListUnread = compose(bindCode, bindSiteId)((siteId, code) => {
const url = PRESENTATION_API.GET_CHANNEL_LIST_UNREAD(siteId, code)
return doGet(
bindRetry({
url,
}),
)
})
const fetchTrackEvents = compose(bindCode, bindSiteId)(
(siteId, code, startDate, endDate) => {
const url = PRESENTATION_API.FETCH_TRACK_EVENTS(
siteId,
code,
startDate,
endDate,
)
return doGet(
bindRetry({
url,
}),
)
},
)
const fetchProductDetail = compose(bindSiteId)((siteId, productId) => {
const url = PRESENTATION_API.GET_PRODUCT_DETAIL(siteId, productId)
return doGet({
url,
})
})
const fetchAnalyticsReport = compose(bindCode, bindSiteId)(
(siteId, code, teamMemberId, startDate, endDate, interval) => {
const url = PRESENTATION_API.FETCH_ANALYTICS_REPORTS(
siteId,
code,
teamMemberId,
startDate,
endDate,
interval,
)
return doGet({
url,
})
},
)
const getServiceMessageInfo = compose(bindSiteId)(
(siteId, clientId, teamMemberId) => {
const url = PRESENTATION_API.GET_SERVICE_MESSAGE_INFO(
siteId,
clientId,
teamMemberId,
)
return doGet({
url,
})
},
)
const fetchProductShare = compose(bindSiteId)(
(siteId, productId, page, affiliateOpenId) => {
const url = PRESENTATION_API.GET_PRODUCT_SHARE(
siteId,
productId,
page,
affiliateOpenId,
)
return doPost({
url,
})
},
)
const fetchAppList = code => {
const url = PRESENTATION_API.GET_APP_LIST(code)
return doGet(bindRetry({ url }))
}
const postFormId = compose(bindCode, bindSiteId)((siteId, code, formId) => {
const url = PRESENTATION_API.POST_VALID_FORM_ID(siteId)
const data = {
code,
formId,
}
return doPost({
url,
data,
})
})
const sendServiceMessage = compose(bindCode, bindSiteId)(
(siteId, code, clientid, type) => {
const url = PRESENTATION_API.POST_SEND_SERVICE(siteId)
const data = {
code,
stCoreClientId: Number(clientid),
notificationType: type,
}
return doPost({
url,
data,
})
},
)
const putServiceCount = compose(bindSiteId)((siteId, memberId) => {
const url = PRESENTATION_API.PUT_SERVICE_NOTIFICATION_COUNT(siteId)
const data = {
member_id: memberId,
}
return doPut({
url,
data,
})
})
const getServiceNotifyPopup = compose(bindCode, bindSiteId)((siteId, code) => {
const url = PRESENTATION_API.FETCH_SERVICE_POPUP_STATUS(siteId, code)
return doGet({
url,
})
})
const presentationApi = {
fetchAnalyticsReport,
fetchAppList,
fetchSettings,
getProducts,
fetchPortfolioSettings,
getCategories,
getProduct,
getCode,
verify,
registerTeamMember,
fetchGroupMembers,
fetchGroups,
createFeedback,
createContactForm,
fetchFormSettings,
fetchMemberList,
fetchVisitorDetail,
updateVisitorInfo,
fetchVisitorInfo,
fetchVisitorAnalytics,
fetchVisitorGeneralReport,
fetchContactForms,
updateContactFormsReadMark,
updateMemberCardInfo,
updateTeamMember,
updateMemberLikes,
getChannelList,
getChannelDetail,
getChannelUnreadCount,
postChannelDetail,
getChannelListUnread,
getBroadcasts,
createBroadcast,
fetchTrackEvents,
fetchProductDetail,
fetchProductShare,
getShare,
getSystemNotifications,
fetchRadarUnreadCount,
fetchGroupChartAnalytics,
fetchGroupAnalyticsReports,
fetchGroupSummaryDataById,
postFormId,
getServiceMessageInfo,
sendServiceMessage,
putServiceCount,
getServiceNotifyPopup,
}
export default presentationApi
import EmojiObj from './emojimap'
const emoji = EmojiObj.emojiList.emoji
const prefix = EmojiObj.emojiSxlUrl
/* eslint-disable max-statements */
function generateRichTextNode(text) {
let tempStr = text
const richTextNode = []
let leftBracketIndex = tempStr.indexOf('[')
let rightBracketIndex = 0
// 没有emoji
if (leftBracketIndex === -1) {
richTextNode.push({
type: 'text',
text: tempStr,
})
return richTextNode
}
while (tempStr.length !== 0) {
// 最前面是文本
if (leftBracketIndex !== 0) {
// 最后全是文字
if (leftBracketIndex === -1) {
richTextNode.push({
type: 'text',
text: tempStr.slice(0, tempStr.length),
})
tempStr = ''
} else {
richTextNode.push({
type: 'text',
text: tempStr.slice(0, leftBracketIndex),
})
tempStr = tempStr.substring(leftBracketIndex, tempStr.length + 1)
}
} else {
// 前面是[
rightBracketIndex = tempStr.indexOf(']')
const emojiName = tempStr.slice(0, rightBracketIndex + 1)
if (emoji[emojiName]) {
richTextNode.push({
name: 'img',
attrs: {
width: '20rpx',
height: '20rpx',
src: emoji[emojiName].img,
},
})
} else {
richTextNode.push({
type: 'text',
text: `${emojiName}`,
})
}
tempStr = tempStr.substring(rightBracketIndex + 1, tempStr.length)
}
leftBracketIndex = tempStr.indexOf('[')
if (tempStr.indexOf(']') === -1) {
leftBracketIndex = -1
}
}
return richTextNode
}
/* eslint-enable max-statements */
function generateImageNode(fileStr) {
let file = {}
try {
file = JSON.parse(fileStr)
} catch (e) {
console.info('JSON parse error')
}
let width = 0
let height = 0
if (file.w > 150) {
width = 100
height = file.h / (file.w / 100)
} else {
width = file.w
height = file.h
}
const richTextNode = []
richTextNode.push({
name: 'img',
attrs: {
width: `${width}rpx`,
height: `${height}rpx`,
src: file.url,
},
})
return richTextNode
}
function generateBigEmojiImageFile(messageStr) {
let content = {}
try {
content = JSON.parse(messageStr)
} catch (e) {
console.info('JSON parse error')
}
const file = { w: content.w, h: content.h, url: '' }
file.url = `${prefix}/${content.catalog}/${content.name}`
return JSON.stringify(file)
}
export const convertChatItem = item => {
if (item.messageType === 'welcome_message') {
return {
...item,
customMessage: item.customMessageChanged
? item.customMessageChanged
: JSON.parse(item.customMessage),
messageType: 'welcome_message',
}
} else if (item.messageType === 'service_notification') {
return {
...item,
messageType: 'service_notification',
}
}
if (item.message) {
return {
...item,
messageType: 'text',
nodes: generateRichTextNode(item.message),
}
} else if (item.messageType === 'image') {
return {
...item,
messageType: 'image',
nodes: generateImageNode(item.customMessage),
}
} else if (item.messageType === 'big_emoji') {
return {
...item,
messageType: 'image',
nodes: generateImageNode(generateBigEmojiImageFile(item.customMessage)),
}
} else {
return {
...item,
messageType: 'text',
}
}
}
const emojiSxlUrl = `https://user-assets.sxlcdn.com/livechat/emoji`
const iconEmojiDelete =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAk1BMVEUAAADAwMC3t7e/v7/Gxsa/v7+/v7/AwMC/v7+/v7+/v7++vr7CwsK/v7+/v7+/v7++vr6/v7+/v7+/v7+/v7+/v7/AwMCbm5u/v7+/v7+/v7+/v7/AwMC/v7+/v7+/v7+/v7+/v7+/v7++vr7AwMC9vb2/v7+/v7/CwsLAwMDHx8fExMTIyMjLy8vJycnKysrPz8+3r5/kAAAAKHRSTlMA8Q39A/vcIwf34B0P1uNJIuzPjHFqFwVFmls4J761rMiFoVJ6XTNYKpTiKQAAAylJREFUeF7tmEl3ozAQhNt0ywYM3rfETuJMltEC2P//1w3IVg555olmHjfVlcMnAVUtFfgVFBQUFBQUFBREXAAR8BVnEUNZzF8YwTYdsZQ/Pcc8CsGmNEpyZMoqn/MYzxoFomIIhTDFujtjD3OlEFEyVENqzHUN1HUfx0QiKjliKJEVIorioxuF4LQ0AkW53mddf7Asmh5eNaLQeUfGLtUN4w8wtbaULlshiFZVw3gF7g8Jr4UQ5ZMfQkAvl5pRnWO+teYGhT53YMBbWTP0OAMCrqaJQjmLwKunhmGWkx4MiEZSyZEPQvDeMGRy7MOAbCTRCyHYFqqx+Rxi6KHIC7FhYrCW/gKCQSAuTBRWGwaDCSE4CmmNDtRhJhFxIS5M2o1uweQZmw7iC5N2o58O9EMhyDabjAdxYdJudIINytXiTiGY5tfrKuJBXJjo8a6FsUiMuIynQDfGuEqScgvEgLgwaTU6wVEJFEVDsYxCoOBBXJjI5LvdIOeyoaRTiGGaNowqZ7wuFybYYnT3vlJ928t9H3o2AeoOIdhUHqNbykzb9R/y6jfDDyH40g3jYo3uo6CUEh2jM4TgYE8mv4z+kJIWeFORToDn+FUhEI1/ohNMxtIy5NgyGJBspBDlMvKnYtzMV1SIxo6CYXeiZG69P8g3mbhvIqrGlQyIG1Xi8ukobYzb32UkOu93hrihi6i3QB5G48aD9aKl8B2vlPyAvYehZyc4WZqjMLMLFR5aKdHN5+kC4nvClGcA4Kdwe0ISbMs7g35yTB6B+swTs1wAtUCSpLBZcnt3lTBJTew1GXX+eGrVj69X5/OGkhu5Aeo54y+rGOjh489Py3drmn//z2nlBR5TPKcV5rnr7SEF9nvGuct3HW2uMVzxz8KifAcaDOICRhVboMEg7kiB5m/f+4nyQ34CRtmAGWAnLmD637V2I2Vn7I+8AXPiF0sHiWjyGDrf43W663WPv7wBdW8kqlXUp5Eonn9BfAGTRYxuZX7vVmJGSyQQTcJpiUxLS+QJGKW4fRfavovX3Clmc6f1mtlBXuSQHaSjLAdoU4fvhYdvuIOCgoKCgoL+AXU1fW2kbFGNAAAAAElFTkSuQmCC'
const albumArr = []
const emojiList = {
emoji: {
'[大笑]': { file: 'emoji_0.png' },
'[可爱]': { file: 'emoji_01.png' },
'[色]': { file: 'emoji_02.png' },
'[嘘]': { file: 'emoji_03.png' },
'[亲]': { file: 'emoji_04.png' },
'[呆]': { file: 'emoji_05.png' },
'[口水]': { file: 'emoji_06.png' },
'[汗]': { file: 'emoji_145.png' },
'[呲牙]': { file: 'emoji_07.png' },
'[鬼脸]': { file: 'emoji_08.png' },
'[害羞]': { file: 'emoji_09.png' },
'[偷笑]': { file: 'emoji_10.png' },
'[调皮]': { file: 'emoji_11.png' },
'[可怜]': { file: 'emoji_12.png' },
'[敲]': { file: 'emoji_13.png' },
'[惊讶]': { file: 'emoji_14.png' },
'[流感]': { file: 'emoji_15.png' },
'[委屈]': { file: 'emoji_16.png' },
'[流泪]': { file: 'emoji_17.png' },
'[嚎哭]': { file: 'emoji_18.png' },
'[惊恐]': { file: 'emoji_19.png' },
'[怒]': { file: 'emoji_20.png' },
'[酷]': { file: 'emoji_21.png' },
'[不说]': { file: 'emoji_22.png' },
'[鄙视]': { file: 'emoji_23.png' },
'[阿弥陀佛]': { file: 'emoji_24.png' },
'[奸笑]': { file: 'emoji_25.png' },
'[睡着]': { file: 'emoji_26.png' },
'[口罩]': { file: 'emoji_27.png' },
'[努力]': { file: 'emoji_28.png' },
'[抠鼻孔]': { file: 'emoji_29.png' },
'[疑问]': { file: 'emoji_30.png' },
'[怒骂]': { file: 'emoji_31.png' },
'[晕]': { file: 'emoji_32.png' },
'[呕吐]': { file: 'emoji_33.png' },
'[拜一拜]': { file: 'emoji_160.png' },
'[惊喜]': { file: 'emoji_161.png' },
'[流汗]': { file: 'emoji_162.png' },
'[卖萌]': { file: 'emoji_163.png' },
'[默契眨眼]': { file: 'emoji_164.png' },
'[烧香拜佛]': { file: 'emoji_165.png' },
'[晚安]': { file: 'emoji_166.png' },
'[强]': { file: 'emoji_34.png' },
'[弱]': { file: 'emoji_35.png' },
'[OK]': { file: 'emoji_36.png' },
'[拳头]': { file: 'emoji_37.png' },
'[胜利]': { file: 'emoji_38.png' },
'[鼓掌]': { file: 'emoji_39.png' },
'[握手]': { file: 'emoji_200.png' },
'[发怒]': { file: 'emoji_40.png' },
'[骷髅]': { file: 'emoji_41.png' },
'[便便]': { file: 'emoji_42.png' },
'[火]': { file: 'emoji_43.png' },
'[溜]': { file: 'emoji_44.png' },
'[爱心]': { file: 'emoji_45.png' },
'[心碎]': { file: 'emoji_46.png' },
'[钟情]': { file: 'emoji_47.png' },
'[唇]': { file: 'emoji_48.png' },
'[戒指]': { file: 'emoji_49.png' },
'[钻石]': { file: 'emoji_50.png' },
'[太阳]': { file: 'emoji_51.png' },
'[有时晴]': { file: 'emoji_52.png' },
'[多云]': { file: 'emoji_53.png' },
'[雷]': { file: 'emoji_54.png' },
'[雨]': { file: 'emoji_55.png' },
'[雪花]': { file: 'emoji_56.png' },
'[爱人]': { file: 'emoji_57.png' },
'[帽子]': { file: 'emoji_58.png' },
'[皇冠]': { file: 'emoji_59.png' },
'[篮球]': { file: 'emoji_60.png' },
'[足球]': { file: 'emoji_61.png' },
'[垒球]': { file: 'emoji_62.png' },
'[网球]': { file: 'emoji_63.png' },
'[台球]': { file: 'emoji_64.png' },
'[咖啡]': { file: 'emoji_65.png' },
'[啤酒]': { file: 'emoji_66.png' },
'[干杯]': { file: 'emoji_67.png' },
'[柠檬汁]': { file: 'emoji_68.png' },
'[餐具]': { file: 'emoji_69.png' },
'[汉堡]': { file: 'emoji_70.png' },
'[鸡腿]': { file: 'emoji_71.png' },
'[面条]': { file: 'emoji_72.png' },
'[冰淇淋]': { file: 'emoji_73.png' },
'[沙冰]': { file: 'emoji_74.png' },
'[生日蛋糕]': { file: 'emoji_75.png' },
'[蛋糕]': { file: 'emoji_76.png' },
'[糖果]': { file: 'emoji_77.png' },
'[葡萄]': { file: 'emoji_78.png' },
'[西瓜]': { file: 'emoji_79.png' },
'[光碟]': { file: 'emoji_80.png' },
'[手机]': { file: 'emoji_81.png' },
'[电话]': { file: 'emoji_82.png' },
'[电视]': { file: 'emoji_83.png' },
'[声音开启]': { file: 'emoji_84.png' },
'[声音关闭]': { file: 'emoji_85.png' },
'[铃铛]': { file: 'emoji_86.png' },
'[锁头]': { file: 'emoji_87.png' },
'[放大镜]': { file: 'emoji_88.png' },
'[灯泡]': { file: 'emoji_89.png' },
'[锤头]': { file: 'emoji_90.png' },
'[烟]': { file: 'emoji_91.png' },
'[炸弹]': { file: 'emoji_92.png' },
'[枪]': { file: 'emoji_93.png' },
'[刀]': { file: 'emoji_94.png' },
'[药]': { file: 'emoji_95.png' },
'[打针]': { file: 'emoji_96.png' },
'[钱袋]': { file: 'emoji_97.png' },
'[钞票]': { file: 'emoji_98.png' },
'[银行卡]': { file: 'emoji_99.png' },
'[手柄]': { file: 'emoji_100.png' },
'[麻将]': { file: 'emoji_101.png' },
'[调色板]': { file: 'emoji_102.png' },
'[电影]': { file: 'emoji_103.png' },
'[麦克风]': { file: 'emoji_104.png' },
'[耳机]': { file: 'emoji_105.png' },
'[音乐]': { file: 'emoji_106.png' },
'[吉他]': { file: 'emoji_107.png' },
'[火箭]': { file: 'emoji_108.png' },
'[飞机]': { file: 'emoji_109.png' },
'[火车]': { file: 'emoji_110.png' },
'[公交]': { file: 'emoji_111.png' },
'[轿车]': { file: 'emoji_112.png' },
'[出租车]': { file: 'emoji_113.png' },
'[警车]': { file: 'emoji_114.png' },
'[自行车]': { file: 'emoji_115.png' },
},
}
for (const emoji in emojiList) {
const emojiItem = emojiList[emoji]
for (const key in emojiItem) {
const item = emojiItem[key]
item.img = `${emojiSxlUrl}/${emoji}/${item.file}`
}
}
emojiList.jgz = {}
for (let i = 1; i <= 31; i++) {
const key = `jgz${i >= 10 ? i : `0${i}`}`
emojiList.jgz[key] = { file: `${key}.gif` }
}
// 内容
for (const emoji in emojiList) {
const emojiItem = emojiList[emoji]
for (const key in emojiItem) {
const item = emojiItem[key]
item.img = `${emojiSxlUrl}/${emoji}/${item.file}`
}
// 封面
albumArr.push({
album: emoji,
img: emojiItem[Object.keys(emojiItem)[0]].img,
})
}
// 添加删除按钮
emojiList.emoji['[删除]'] = {}
emojiList.emoji['[删除]'].img = iconEmojiDelete
export default {
emojiList,
albumArr,
emojiSxlUrl,
}
import { dispatch } from 'root/wmp-redux'
import { fetchSettings } from 'root/reducers/ecommerce/settings'
import {
fetchProducts,
fetchFlashSaleProducts,
fetchGroupBuyProducts,
} from 'root/actions/ecommerce/productActions'
import { forceUpdate } from 'root/reducers/ecommerce/global'
import { getMembershipCard } from 'root/actions/ecommerce/membership/cardActions'
export function refreshData(siteId, code, doNotFetchProducts) {
if (!doNotFetchProducts) {
dispatch(fetchProducts(siteId, 'all', 1))
}
dispatch(fetchFlashSaleProducts('all', 1))
code && dispatch(fetchGroupBuyProducts('all', 1, code))
dispatch(fetchSettings(siteId))
dispatch(getMembershipCard())
}
import { PAGES } from 'root/constants/admintool/urlConstants'
export function handleRequestSuccess() {
wx.hideLoading()
}
export function handleRequestError(err) {
wx.hideLoading()
if (err.data.code === 200000) {
wx.showModal({
title: '身份验证失败',
content: '请重新登录',
showCancel: false,
success: () => {
wx.setStorageSync('admin_tool_token', null)
wx.reLaunch({
url: PAGES.LOGIN,
})
},
})
} else if (err.data.code === 200001) {
wx.showModal({
title: '手机号不存在',
content: '请确认您输入的手机号已在员工授权管理中录入',
showCancel: false,
success: () => {
wx.setStorageSync('admin_tool_token', null)
wx.reLaunch({
url: PAGES.LOGIN,
})
},
})
} else if (err.data.code === 200002) {
wx.showModal({
title: '重新登录',
content: '权限发生变更,已被强行登出。请重新登录,或联系管理员。',
showCancel: false,
success: () => {
wx.setStorageSync('admin_tool_token', null)
wx.reLaunch({
url: PAGES.LOGIN,
})
},
})
} else {
wx.showModal({
title: '获取失败',
content: '请重新获取',
showCancel: false,
})
}
}
import {formatProtocol as _formatProtocol} from './tools'
export function formatProtocol(url) {
if (!url) {
return ''
}
return _formatProtocol(url)
}
export function convertPostLikes(post, nickname){
const { isLiked, likes = [] } = post
if(isLiked){
let likedIndex = -1
let isFirst = true
for(let i = 0; i < likes.length; i++){
if(isFirst && likes[i] === nickname){
likedIndex = i
isFirst = false
}
}
post.likes = likes.filter((item, index) => index !== likedIndex)
} else {
(post.likes || []).push(nickname)
}
post.isLiked = !post.isLiked
return post
}
\ No newline at end of file
export const targetMap = {
broadcast: {
className: 'broadcast',
title: '新建群发',
topHint: '您将给所有客户发送该条消息',
field: 'message',
maxLength: -1,
submitText: '发送',
},
welcome: {
className: 'welcome',
title: '设置欢迎语',
placeholder: '请输入欢迎语',
topHint: '当访客进入名片时在聊天窗口显示',
field: 'welcomeMessage',
switchField: 'openHotKey',
maxLength: 200,
},
autoReply: {
className: 'auto-reply',
title: '设置自动回复',
topHint: '当客户发送新消息时,自动回复:',
field: 'autoReply',
maxLength: 200,
},
}
import curry from 'ramda/src/curry'
export function getCacheTime(duration) {
return new Date().getTime() + duration
}
export const commonAnimation = wx.createAnimation({
duration: 400,
timingFunction: 'ease',
})
export const parseScene = scene => {
const params = {}
decodeURIComponent(scene)
.split('&')
.forEach((item, index) => {
const itemArr = item.split('=')
if (itemArr.length === 2) {
params[itemArr[0]] = itemArr[1]
} else {
params[index] = itemArr[0]
}
})
return params
}
// blog ecommerce presentation
export const baseFilterRelationData = curry(
(baseType, data, relationData = {}) => {
const filterData = data.filter(item => {
const type = item.category || baseType
if (item.type === 'blogPost' || item.type === 'product') {
if (
relationData[`${type}ProductIdList`] &&
relationData[`${type}ProductIdList`].find(
value => value.toString() === item.value.toString(),
)
) {
return false
}
} else if (item.type === 'category') {
if (
relationData[`${type}CategoryIdList`] &&
relationData[`${type}CategoryIdList`].find(
value => value.toString() === item.value.id.toString(),
)
) {
return false
}
}
return true
})
return filterData
},
)
// hexToRgba('#fff', 0.1) => rgba(255, 255, 255, 0.1)
export const hexToRgba = function(hex, alpha = 1) {
let c
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
c = hex.substring(1).split('')
if (c.length === 3) {
c = [c[0], c[0], c[1], c[1], c[2], c[2]]
}
c = `0x${c.join('')}`
return `rgba(${(c >> 16) & 255}, ${(c >> 8) & 255}, ${c & 255}, ${alpha})`
}
return c
}
// 1600 => 1.6k
export const KFormatter = num =>
num > 999 ? `${(num / 1000).toFixed(1)}k` : num
import { iconPath } from 'root/constants/presentation/iconPath'
import {
getRoundImage,
replaceWithDownloadFileDomain,
} from 'root/utils/helpers/presentationHelper'
import { showModal } from 'wechat_common/utils/wrappedWXTool'
export const getScoreText = score => {
const map = {
1: '很差',
2: '不太满意',
3: '一般',
4: '比较满意',
5: '非常满意',
}
return map[score]
}
const getImageInfo = url =>
new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: res => {
resolve(res)
},
fail: e => {
reject(e)
},
})
})
const rpxToPx = size => {
const { windowWidth } = wx.getSystemInfoSync()
return windowWidth * size / 750
}
function drawCloseIcon() {
const ctx = wx.createCanvasContext('closeIcon')
ctx.setFillStyle('#ffffff')
ctx.drawImage(
iconPath.ICON_CANVAS_CLOSE,
rpxToPx(710) - rpxToPx(104),
15,
30,
30,
)
ctx.draw()
}
export const drawContactCard = (
teamMember,
logoUrl,
isWhiteBackground,
companyName,
) =>
new Promise((resolve, reject) => {
const ctx = wx.createCanvasContext('contactCard')
const { profile } = teamMember
// bg
ctx.setFillStyle('#ffffff')
ctx.fillRect(0, 0, rpxToPx(710), rpxToPx(950))
const { avatarUrl, roundAvatarUrl } = profile
const qrCodeUrl = teamMember.qrCode
const imageUrlList = [qrCodeUrl, roundAvatarUrl]
const promiseList = imageUrlList.map(url => getImageInfo(url))
Promise.all(promiseList)
.catch(() => {
showModal({
content: '图片加载失败,请稍后重试',
showCancel: false,
})
reject()
})
.then(([verifyQrCodeInfo, roundAvatarInfo]) => {
const verifyQrCodePath = verifyQrCodeInfo.path
const roundAvatarPath = roundAvatarInfo.path
const cardOrigin = {
x: rpxToPx(43),
y: rpxToPx(220),
}
const avatarData = {
w: rpxToPx(200),
h: rpxToPx(200),
r: rpxToPx(100), // 头像半径
}
const drawImage = (imagePath, beginX, beginY, width, height) => {
ctx.drawImage(imagePath, beginX, beginY, width, height)
}
// 画用户头像
drawImage(roundAvatarPath, 20, 30, avatarData.w, avatarData.h)
// 画用户信息
drawProfile()
// 画下方二维码
drawBottomBar()
ctx.draw()
resolve()
function drawProfile() {
const profileOrigin = {
m: rpxToPx(353), // 中点
l: rpxToPx(43), // 左起点
r: rpxToPx(650), // 右终点
y: rpxToPx(300), // 高度起点
}
const iconData = {
w: rpxToPx(36),
h: rpxToPx(36),
}
drawProfileBackground()
ctx.setFontSize(rpxToPx(30))
drawPhone()
if (profile.wechatAccount) {
drawWechatAccount()
}
if (profile.email) {
drawEmail()
}
function drawProfileBackground() {
// name
ctx.setFontSize(rpxToPx(40))
ctx.setFillStyle('#000000')
ctx.fillText(profile.name, 140, 70)
drawPosition()
if (companyName) {
drawCompany()
}
}
function drawPosition() {
// position 职位背景,icon,职位名称
ctx.setFontSize(rpxToPx(30))
const positionMetrics = ctx.measureText(profile.position).width
ctx.setFillStyle(
profile.color !== '#ffffff' ? profile.color : '#F4F4F4',
)
ctx.fillRect(140, 82, positionMetrics + 8, 24)
// position
ctx.setFillStyle(
profile.color === '#ffffff' ? '#000000' : '#ffffff',
)
ctx.fillText(profile.position, 144, 100)
}
function drawCompany() {
// company
ctx.setFillStyle('#000000')
const companyMetrics = ctx.measureText(companyName).width
ctx.fillText(companyName, 22, profileOrigin.y + rpxToPx(22))
// icon
drawImage(
iconPath.ICON_VERIFY,
companyMetrics + 24,
profileOrigin.y - 2,
15,
15,
)
}
function drawPhone() {
ctx.drawImage(
iconPath.ICON_CANVAS_PHONE,
profileOrigin.l,
profileOrigin.y + rpxToPx(158) - rpxToPx(20),
iconData.w,
iconData.h,
)
ctx.setFillStyle('#999999')
ctx.fillText(
'手机',
profileOrigin.l + iconData.w + rpxToPx(10),
profileOrigin.y + rpxToPx(165),
)
const phoneMetrics = ctx.measureText(profile.phone).width
ctx.setFillStyle('#666666')
ctx.fillText(
profile.phone,
profileOrigin.r - phoneMetrics,
profileOrigin.y + rpxToPx(165),
)
ctx.setStrokeStyle('#e8e8e8')
ctx.setLineWidth(0.5)
ctx.moveTo(profileOrigin.l, profileOrigin.y + rpxToPx(190))
ctx.lineTo(profileOrigin.r, profileOrigin.y + rpxToPx(190))
ctx.stroke()
}
function drawWechatAccount() {
ctx.drawImage(
iconPath.ICON_CANVAS_WECHAT,
profileOrigin.l,
profileOrigin.y + rpxToPx(82) - rpxToPx(20),
iconData.w,
iconData.h,
)
ctx.setFillStyle('#999999')
ctx.fillText(
'微信',
profileOrigin.l + iconData.w + rpxToPx(10),
profileOrigin.y + rpxToPx(89),
)
const wechatMetrics = ctx.measureText(profile.wechatAccount).width
ctx.setFillStyle('#666666')
ctx.fillText(
profile.wechatAccount,
profileOrigin.r - wechatMetrics,
profileOrigin.y + rpxToPx(89),
)
ctx.setStrokeStyle('#e8e8e8')
ctx.setLineWidth(0.5)
ctx.moveTo(profileOrigin.l, profileOrigin.y + rpxToPx(116))
ctx.lineTo(profileOrigin.r, profileOrigin.y + rpxToPx(116))
ctx.stroke()
}
function drawEmail() {
ctx.drawImage(
iconPath.ICON_CANVAS_EMAIL,
profileOrigin.l,
profileOrigin.y + rpxToPx(230) - rpxToPx(20),
iconData.w,
iconData.h,
)
ctx.setFillStyle('#999999')
ctx.fillText(
'邮箱',
profileOrigin.l + iconData.w + rpxToPx(10),
profileOrigin.y + rpxToPx(237),
)
const emailMetrics = ctx.measureText(profile.email).width
ctx.setFillStyle('#666666')
ctx.fillText(
profile.email,
profileOrigin.r - emailMetrics,
profileOrigin.y + rpxToPx(237),
)
}
}
function drawBottomBar() {
ctx.setFontSize(rpxToPx(32))
ctx.setFillStyle('#636972')
ctx.fillText('长按识别二维码', rpxToPx(67), rpxToPx(689))
ctx.fillText('进入小程序了解更多', rpxToPx(67), rpxToPx(744))
ctx.drawImage(
verifyQrCodePath,
rpxToPx(416),
rpxToPx(590),
rpxToPx(219),
rpxToPx(219),
)
ctx.drawImage(
roundAvatarPath,
rpxToPx(416) + rpxToPx(64),
rpxToPx(590) + rpxToPx(64),
rpxToPx(92),
rpxToPx(92),
)
}
drawCloseIcon()
})
.catch(() => {
showModal({
content: '图片渲染失败',
showCancel: false,
})
reject()
})
})
import Accounting from 'accounting'
import curry from 'ramda/src/curry'
import moment from 'wechat_common/lib/moment'
const format = {
code: 'CNY',
symbol: '¥',
decimal: '.',
thousand: '',
precision: 2,
name: 'Chinese Yuan',
}
const AMOUNT_UNIT = 100
const PHONE_REGEX = /^[+\d-\(\)]+$/
const MOBILE_REGEX = /^1[0-9]{10}$/
const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,10}(\s*;\s*[\w-\.]+@([\w-]+\.)+[\w-]{2,10})*(\s*;\s*)?$/
function checkMobile(mobile) {
return MOBILE_REGEX.test(mobile)
}
function checkEmail(email) {
return EMAIL_REGEX.test(email)
}
function getFormattedPrice(price) {
return Accounting.formatMoney(price, format)
}
function getShippingFeeNum(cartData, settings) {
const shippingRegionsData = settings.shippingRegions.default
? settings.shippingRegions.default
: settings.shippingRegions.cn
const { feePerOrder, feePerAdditionalItem } = shippingRegionsData
const items = cartData.items
if (!items.some(item => item.product.shippingInfo)) {
return 0
}
const additionalFee = items.reduce((total, next) => {
const fee = next.product.shippingInfo
? feePerAdditionalItem * next.orderItem.quantity
: 0
return total + fee
}, 0)
return feePerOrder - feePerAdditionalItem + additionalFee
}
function getTotalPriceNum(cartData) {
let totalPrice = 0
cartData.items.forEach(
item => (totalPrice += item.orderItem.quantity * item.orderItem.price),
)
return totalPrice
}
function getTotalPriceNumWithoutFlashSale(cartData) {
let totalPrice = 0
cartData.items.forEach(item => {
const canUseCoupon = !(
item.product.flashSale && !item.product.flashSale.coupon
)
if (canUseCoupon) {
totalPrice += item.orderItem.quantity * item.orderItem.price
}
})
return totalPrice
}
function getDiscountNum(cartData, coupon, settings) {
let number = 0
if (coupon.category) {
if (coupon.option.condition.productId) {
const found = cartData.items.find(
item =>
Number(item.productId) === Number(coupon.option.condition.productId),
)
if (found) {
const canUseCoupon = !(
found.product.flashSale && !found.product.flashSale.coupon
)
if (canUseCoupon) {
number =
found.orderItem.price *
found.orderItem.quantity *
coupon.option.amount /
100
}
}
} else {
switch (coupon.category) {
case 'free_shipping':
number = getShippingFeeNum(cartData, settings)
return number
case 'flat':
number = coupon.option.amount
break
case 'percentage':
number =
getTotalPriceNumWithoutFlashSale(cartData) *
coupon.option.amount /
100
break
// no default
}
}
}
if (number >= getTotalPriceNum(cartData)) {
number = getTotalPriceNum(cartData)
}
return number
}
function getPriceScope(prices) {
if (!prices.length) {
return '¥--'
}
let maxPrice = Math.max(...prices)
let minPrice = Math.min(...prices)
maxPrice =
maxPrice % 1 === 0 ? maxPrice : getFormattedPrice(maxPrice).slice(1) // remove the currency symbol
minPrice =
minPrice % 1 === 0 ? minPrice : getFormattedPrice(minPrice).slice(1)
if (maxPrice > minPrice) {
return ${minPrice} - ${maxPrice}`
}
return ${minPrice}`
}
function getMinPrice(prices) {
if (!prices.length) {
return '¥--'
}
const minPrice = Math.min(...prices)
if (minPrice >= 10000) {
return `¥${Number(Math.floor(minPrice / 1000) / 10).toFixed(1)}万`
} else {
return minPrice % 1 === 0 ? ${minPrice}` : getFormattedPrice(minPrice)
}
}
function getItemsNum(cartData) {
let total = 0
cartData.items.forEach(item => (total += item.orderItem.quantity))
return total
}
function getPointsDiscountAmount(cartData) {
if (!cartData.points) {
return 0
}
const { isUsePointsDeductible, pointsDeductibleAmount } = cartData.points
if (isUsePointsDeductible && pointsDeductibleAmount) {
return pointsDeductibleAmount * AMOUNT_UNIT
} else {
return 0
}
}
function getCouponBaseAmount(cartData, couponSaveMoney, settings) {
let amount = 0
amount = getTotalPriceNum(cartData) - couponSaveMoney
if (amount < 0) {
amount = 0
}
return amount
}
function getFinalAmount(shippingFeeBaseAmount) {
let finalAmount = 0
if (shippingFeeBaseAmount < 0) {
finalAmount = 0
} else {
finalAmount = shippingFeeBaseAmount
}
return finalAmount / 100
}
function getPointsBaseAmount(
shippingFeeBaseAmount,
discountAmount,
isOpenDeduct,
) {
let pointsBaseAmount = 0
const expandedDiscountAmount = discountAmount * AMOUNT_UNIT
if (isOpenDeduct) {
pointsBaseAmount = shippingFeeBaseAmount - expandedDiscountAmount
} else {
pointsBaseAmount = shippingFeeBaseAmount
}
if (pointsBaseAmount < 0) {
pointsBaseAmount = 0
}
return pointsBaseAmount
}
function getShippingFeeBaseAmount(
cartData,
settings,
couponBaseAmount,
isNotNeedExpressFee,
) {
let shippingFeeBaseAmount = 0
const { coupon } = cartData
const totalShippingFeeAmount = getShippingFeeNum(cartData, settings)
if ((coupon && coupon.category === 'free_shipping') || isNotNeedExpressFee) {
shippingFeeBaseAmount = couponBaseAmount
} else {
shippingFeeBaseAmount = couponBaseAmount + totalShippingFeeAmount
}
return shippingFeeBaseAmount
}
function canUseCoupon(cartData, coupon) {
if (coupon.category) {
if (coupon.option.condition.orderOver) {
return getTotalPriceNum(cartData) >= coupon.option.condition.orderOver
} else if (coupon.option.condition.productId) {
return cartData.items.find(
item =>
Number(item.productId) === Number(coupon.option.condition.productId),
)
}
return true
}
}
function leftItemsNum(cartData, variation, product) {
if (variation.quantity === -1) {
return -1
}
const variationId = variation.id
const foundItem = cartData.items.find(
item => Number(item.orderItem.id) === Number(variationId),
)
let totalQuantity = variation.quantity
// flash sale not started or expired, normal quantity should excludes flash quantity
// make sure flash quantiy is enough
const isFlashSaleStartedAndNotExpired =
product &&
product.flashSale &&
!product.flashSale.started &&
!product.flashSale.expired
if (isFlashSaleStartedAndNotExpired) {
const foundFlashVariation = product.flashSale.settings.find(
item => item.lineItemId === variationId,
)
if (foundFlashVariation) {
totalQuantity -= foundFlashVariation.flashQuantity
}
}
// group buy not started or expired, normal quantity should excludes group buy quantity
// make sure group buy quantiy is enough
const isGroupBuyStartedAndNotExpired =
product &&
product.groupBuy &&
!product.groupBuy.started &&
!product.groupBuy.expired
if (isGroupBuyStartedAndNotExpired) {
const foundGroupBuyVariation = product.groupBuy.settings.find(
item => item.lineItemId === variationId,
)
if (foundGroupBuyVariation) {
totalQuantity -= foundGroupBuyVariation.groupBuyQuantity
}
}
if (foundItem) {
return totalQuantity - foundItem.orderItem.quantity
}
return totalQuantity
}
function selectedItemsNumForSameProduct(cartData, variation) {
const { productId } = variation
const foundItems =
cartData.items.filter(
item => Number(item.productId) === Number(productId),
) || []
return foundItems.reduce((acc, next) => acc + next.orderItem.quantity, 0)
}
const sortWithOrderList = curry((orderList, data) => {
data.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex - bIndex
})
return data
})
function sortWithOrder(orderList = {}) {
return function(a, b) {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex - bIndex
}
}
function checkShippingInfo(data, key) {
const shippingInfo = data.shippingInfo
const errors = {}
if (!shippingInfo.firstName) {
errors.firstName = '姓名不能为空'
}
if (!PHONE_REGEX.test(shippingInfo.phone)) {
errors.phone = '手机格式不正确'
}
if (shippingInfo.email && !EMAIL_REGEX.test(shippingInfo.email)) {
errors.email = '邮箱格式不正确'
}
if (data.needShippingInfo) {
const { provinceIndex, provinceArray } = data
if (
provinceArray[provinceIndex].name !== '海外' &&
(data.provinceIndex === -1 || data.cityIndex === -1)
) {
errors.district = '地区不能为空'
}
if (!shippingInfo.line1) {
errors.line1 = '请填写详细地址'
}
}
return !key ? errors[Object.keys(errors)[0]] : errors[key]
}
function getTime(date) {
const time = new Date(date)
return `${time.getFullYear()}${time.getMonth() +
1}${time.getDate()}${time.getHours()}:${time.getMinutes()}`
}
function formatProtocol(url) {
if (!url) {
return 'https://assets.sxlcdn.com/images/ecommerce/ecommerce-default-image.png'
}
const _url = url.replace('http:', '').replace('https:', '')
return `https:${_url}`
}
function getNeedShippingInfo(products = []) {
return products.some(product => product.shippingInfo)
}
function formatProductData(list, cartData) {
list.forEach(product => {
product.priceScope = getPriceScope(
product.variations.map(variation => variation.price / 100),
)
product.outOfStock = !product.variations.some(
variation => leftItemsNum(cartData, variation) !== 0,
)
if (product.picture.length > 0) {
product.picture[0].thumbnailUrl = formatProtocol(
product.picture[0].thumbnailUrl,
)
} else {
product.picture = [
{
thumbnailUrl: formatProtocol(),
},
]
}
})
return list
}
function getCacheTime(duration) {
return new Date().getTime() + duration
}
function formatFactory(method, key = 'url') {
let assertError = ''
if (typeof method !== 'function') {
assertError = new TypeError(
`method pass into formatFactory must be a function, but get ${typeof method}`,
)
}
return function(obj) {
if (assertError) {
throw assertError
}
return Object.assign({}, obj, {
[key]: method(obj[key]),
})
}
}
function caculateShortcuts(shortcuts) {
let shortcutsFirstLine
let shortcutsSecondLine
let shortcutsSecondLineWrapperClass
if (shortcuts && shortcuts.length > 5) {
const center = Math.ceil(shortcuts.length / 2)
shortcutsFirstLine = shortcuts.slice(0, center)
shortcutsSecondLine = shortcuts.slice(center)
const firstLineLength = shortcutsFirstLine.length
const secondLineLength = shortcutsSecondLine.length
if (firstLineLength !== secondLineLength) {
if (secondLineLength === 3) {
shortcutsSecondLineWrapperClass = 'shortcut-placeholder-junior'
} else if (secondLineLength === 4) {
shortcutsSecondLineWrapperClass = 'shortcut-placeholder-senior'
}
} else {
shortcutsSecondLineWrapperClass = ' '
}
} else {
shortcutsFirstLine = []
shortcutsSecondLine = []
shortcutsSecondLineWrapperClass = ' '
}
return [
shortcutsFirstLine,
shortcutsSecondLine,
shortcutsSecondLineWrapperClass,
]
}
function caculateShortcutsGroup(shortcuts, type) {
if (type === 'circle' || type === 'square') {
if (shortcuts && shortcuts.length > 5) {
const center = Math.ceil(shortcuts.length / 2)
return [shortcuts.slice(0, center), shortcuts.slice(center)]
}
} else if (type === 'frame') {
if (shortcuts) {
if (shortcuts.length > 4 && shortcuts.length < 9) {
const center = Math.ceil(shortcuts.length / 2)
return [shortcuts.slice(0, center), shortcuts.slice(center)]
} else if (shortcuts.length >= 9) {
const middle = Math.ceil(shortcuts.length / 3)
return [
shortcuts.slice(0, middle),
shortcuts.slice(middle, middle + 3),
shortcuts.slice(middle + 3),
]
}
}
}
return []
}
const trimMoney = money => {
if (money && typeof money === 'number' && !isNaN(money)) {
const moneyString = `${money}`
return Number(moneyString.substring(0, moneyString.length - 2))
}
return money
}
const trimMoneyWithPercentage = money => {
if (money && typeof money === 'number' && !isNaN(money)) {
return (100 - money) / 10
}
return money
}
const formatCouponForNewTemplate = coupon => {
const { category } = coupon
const preMappedCoupon = formatCouponDate(formatCoupon(coupon))
// TODO: bad style
return Object.assign({}, preMappedCoupon, {
couponName: mapCouponName(category),
condition: mapCondition(preMappedCoupon),
})
}
const mapCouponName = category => {
switch (category) {
case 'flat':
return '代金券'
case 'free_shipping':
return '包邮券'
case 'percentage':
return '折扣券'
default:
return '优惠券'
}
}
const mapCondition = coupon => {
if (coupon.option.condition.orderOver) {
return `满${coupon.option.condition.orderOver}可用`
} else if (coupon.option.condition.productId) {
return '特定商品可用'
} else {
return '所有订单可用'
}
}
const formatCoupon = coupon => {
const { option, category } = coupon
let couponName, conditionDescription, amount, sendMethod
if (category === 'flat') {
couponName = option.couponName || '抵扣券'
amount = trimMoney(option.amount)
} else if (category === 'free_shipping') {
couponName = option.couponName || '免邮优惠'
amount = '免邮'
} else {
couponName = option.couponName || '打折券'
amount = trimMoneyWithPercentage(option.amount)
}
if (option.condition.orderOver) {
conditionDescription = `消费满${trimMoney(
option.condition.orderOver,
)}元可使用`
} else if (option.condition.productId) {
conditionDescription = '特定商品可使用'
} else if (option.condition.productCategoryId) {
conditionDescription = '特定商品分类可使用'
} else {
conditionDescription = '适用所有商品'
}
const condition = Object.assign({}, option.condition, {
orderOver: trimMoney(option.condition.orderOver),
conditionDescription,
})
if (coupon.isReceivedWhenOrderPlaced) {
sendMethod = 'isReceivedWhenOrderPlaced'
} else if (coupon.isReceivedWhenBeingMember) {
sendMethod = 'isReceivedWhenBeingMember'
}
const optionResult = Object.assign({}, option, {
couponName,
amount,
condition,
})
return Object.assign({}, coupon, {
option: optionResult,
sendMethod,
})
}
const formatDate = date => {
if (!date) {
return ''
}
return moment(date).format('YYYY.MM.DD')
}
const formatCouponDate = coupon => {
const { startsAt, endsAt } = coupon
const startFormat = formatDate(startsAt)
const endFormat = formatDate(endsAt)
return Object.assign({}, coupon, {
startsAt: startFormat,
endsAt: endFormat,
})
}
const caculateSaveMoney = (currentShipping, couponId) => {
let couponSaveMoney = 0
let couponSaveMoneyToShow = ''
const discountInfo = currentShipping.validCoupon[couponId]
if (!currentShipping.optimalDiscountCoupon && !couponId) {
couponSaveMoneyToShow = '无可用优惠券'
} else if (discountInfo === 'free_shipping') {
couponSaveMoneyToShow = '免邮'
} else if (typeof discountInfo === 'number' && !isNaN(discountInfo)) {
couponSaveMoneyToShow = `- ¥${discountInfo / 100}`
couponSaveMoney = discountInfo
} else {
couponSaveMoneyToShow = '请手动选择优惠券'
}
return {
couponSaveMoney,
couponSaveMoneyToShow,
}
}
const commonError = () => {
wx.showModal({
title: '网络错误',
content: '请刷新重试',
})
}
const ERROR_MSG = {
expired: '优惠券已过期',
'coupon not find': '优惠券未找到',
'user not vip': '仅 VIP 用户可以领取',
'has been used': '优惠券已被领取',
'has been received': '优惠券仅可被兑换一次',
'count not enough': '优惠券不足,无法领取',
'not available to receive': '优惠券使用后方可继续兑换',
}
const showCouponStatus = state => {
const { isFetching } = state.getIn(['coupon', 'user']).toJS()
if (
isFetching &&
isFetching !== 'success' &&
wx.showLoading &&
wx.hideLoading
) {
wx.showLoading()
} else if (wx.hideLoading) {
wx.hideLoading()
}
}
const splitDate = date => {
if (!date) {
return ''
}
return new Date(date)
.toLocaleString()
.substr(0, 10)
.split('/')
.join('-')
}
const formatNumber = (number, precise) => Number(number).toFixed(precise)
const parseScene = scene => {
const params = {}
decodeURIComponent(scene)
.split('&')
.forEach((item, index) => {
const itemArr = item.split('=')
if (itemArr.length === 2) {
params[itemArr[0]] = itemArr[1]
} else {
params[index] = itemArr[0]
}
})
return params
}
const isEmptyDimension = dimension => {
if (
dimension === undefined ||
dimension.name === '' ||
dimension.options === undefined ||
dimension.options === null ||
dimension.options.length === 0
) {
return true
} else {
return false
}
}
const hasMutipleDimensions = dimensions => {
if (!dimensions) {
return false
}
const { dimension1, dimension2 } = dimensions
return !isEmptyDimension(dimension1) && !isEmptyDimension(dimension2)
}
/**
* according to the user's current consumption amount,
* calculate how many points and points can be deducted.
* Parameter:
* points: 2000
* confirmAmount: 600
* costBonusUnit: 100
* reduceMoney: 200
* max-reduce-amount => (2000 * 200 / 100)
*
* Method:
* calculatePointsDiscountData function
*
* conditions:
* 1. the current confirm amount of the user is greater than
* the max deductible amount of the current points of the user
*
* 2. the current confirm amount of the user is less than
* the max deductible amount of the current points of the user
*
* Result:
* usePoints: the user can use the current consumption of points
* pointsDeductibleAmount: points can be used to offset the money
*
*/
export function calculateCanbeUsedPoints(
amount,
costBonusUnit,
reduceMoneyUnit,
) {
return Math.ceil(amount * costBonusUnit / reduceMoneyUnit)
}
export function calculateMaxDeductibleAmount(
totalPoints,
reduceMoneyUnit,
costBonusUnit,
) {
return Math.floor(totalPoints * reduceMoneyUnit / costBonusUnit)
}
export function calculateCanbeDeductibleAmount(
maxCanbeUsedPoints,
costBonusUnit,
reduceMoneyUnit,
) {
return Math.ceil(maxCanbeUsedPoints * reduceMoneyUnit / costBonusUnit)
}
export function calculatePointsDiscountData(
costBonusUnit,
reduceMoney,
costAmount,
totalPoints,
) {
let usePoints = 0
let pointsDeductibleAmount = 0
const maxDeductibleAmount = calculateMaxDeductibleAmount(
totalPoints,
reduceMoney,
costBonusUnit,
)
if (maxDeductibleAmount - costAmount >= 0) {
usePoints = calculateCanbeUsedPoints(costAmount, costBonusUnit, reduceMoney)
pointsDeductibleAmount = costAmount
} else {
const maxCanbeUsedPoints = calculateCanbeUsedPoints(
maxDeductibleAmount,
costBonusUnit,
reduceMoney,
)
if (totalPoints - maxCanbeUsedPoints >= 0) {
pointsDeductibleAmount = calculateCanbeDeductibleAmount(
maxCanbeUsedPoints,
costBonusUnit,
reduceMoney,
)
usePoints = maxCanbeUsedPoints
} else {
pointsDeductibleAmount = maxDeductibleAmount
usePoints = calculateCanbeUsedPoints(
maxDeductibleAmount,
costBonusUnit,
reduceMoney,
)
}
}
return {
usePoints,
expandedPointsDeductibleAmount: parseFloat(
pointsDeductibleAmount / AMOUNT_UNIT,
).toFixed(2),
}
}
export function getRestTime(time, interval, unit) {
return moment(time)
.add(interval, unit)
.from(new Date())
}
export {
checkMobile,
checkEmail,
getFormattedPrice,
getPriceScope,
getMinPrice,
getItemsNum,
getDiscountNum,
getTotalPriceNum,
getFinalAmount,
getCouponBaseAmount,
getPointsBaseAmount,
getShippingFeeBaseAmount,
getShippingFeeNum,
canUseCoupon,
leftItemsNum,
selectedItemsNumForSameProduct,
sortWithOrderList,
checkShippingInfo,
getTime,
formatProtocol,
getNeedShippingInfo,
formatProductData,
getCacheTime,
formatFactory,
sortWithOrder,
caculateShortcuts,
caculateShortcutsGroup,
formatCoupon,
formatCouponDate,
formatCouponForNewTemplate,
caculateSaveMoney,
commonError,
ERROR_MSG,
splitDate,
showCouponStatus,
formatNumber,
parseScene,
isEmptyDimension,
hasMutipleDimensions,
}
import { formatProtocol } from 'root/utils/helpers/ecommerceHelper'
import moment from 'wechat_common/lib/moment'
import compose from 'ramda/src/compose'
export function getQnUrl(image) {
if (image.url && image.url !== '!') {
return image.url
}
return `https://user-assets.sxlcdn.com/${image.storageKey}`
}
export function formattedPrice(price) {
if (price > 0) {
return Number(price / 100).toFixed(2)
} else {
return 0
}
}
export function getAmount(cart) {
let result = 0
for (const id in cart) {
result += cart[id].quantity * cart[id].price
}
return formattedPrice(result)
}
export function getQuantity(cart) {
let result = 0
for (const id in cart) {
result += cart[id].quantity
}
return result
}
export function getOrderItems(cart) {
const result = []
for (const id in cart) {
result.push({
id,
quantity: cart[id].quantity,
})
}
return result
}
export function formatPack(target) {
function addPacketName(packet) {
const { cards } = packet
const quantity = cards.length
if (quantity === 1) {
packet.name = `${cards[0].title}`
} else if (quantity > 1) {
packet.name = `${cards[0].title}等多份`
} else {
packet.name = ''
}
return packet
}
function fotmatPacketBackground(packet) {
packet.background = formatProtocol(getQnUrl(packet.background))
return packet
}
function _formatTime(time) {
return moment(time).format('YYYY年MM月DD日 HH:mm')
}
function formatPacketTime(packet) {
packet.created = _formatTime(packet.created)
packet.updated = _formatTime(packet.updated)
return packet
}
function formattedPackPrice(packet) {
packet.price = formattedPrice(packet.price)
return packet
}
return compose(
addPacketName,
fotmatPacketBackground,
formatPacketTime,
formattedPackPrice,
)(target)
}
export function getCardList(packet) {
const { cards } = packet
return cards.map(card => {
const newCard = {
cardId: card.cardId,
}
return newCard
})
}
import { getQnUrl } from 'wechat_common/utils/helpers/imageHelper'
export function getMixLayout(mix, layout, type, isMain) {
const index = isMain ? 0 : 1
if(Array.isArray(mix) && Array.isArray(layout) && mix[index] === type){
return layout[index]
}
return layout
}
// Chinese character、full Angle symbol takes up two widths
// English character、half - Angle symbol takes up one widths
export function cutString(str, width) {
const re = /[^\x00-\xff]/g
const result = []
let cnlen = 0
if (str.match(re)) {
cnlen = str.match(re).length
}
let strlen = Number(str.length) + cnlen
while (strlen > width) {
let tmplen = 0
let index = 0
for (let i = 0; i < str.length; i++) {
if (str[i].match(re)) {
tmplen += 2
} else {
tmplen++
}
if (tmplen > width) {
index = i
break
}
}
result.push(str.substring(0, index))
str = str.substring(index)
if (str.match(re)) {
cnlen = str.match(re).length
}
strlen = Number(str.length) + cnlen
}
result.push(str)
return result
}
// save the canvas as a image
export function saveCanvasImage(canvasId) {
wx.canvasToTempFilePath({
canvasId: canvasId,
success(res) {
const filePath = res.tempFilePath
wx.getSetting({
success(res) {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
wx.saveImageToPhotosAlbum({
filePath,
success(result) {
wx.showToast({
title: '保存成功',
icon: 'success',
duration: 2000,
})
},
fail(result) {
console.log(result)
},
})
},
fail() {
wx.showModal({
title: '提示',
content: '保存图片需要进行授权操作,请前往小程序设置页完成授权。',
showCancel: false,
})
},
})
} else {
wx.saveImageToPhotosAlbum({
filePath,
success(result) {
wx.showToast({
title: '保存成功',
icon: 'success',
duration: 2000,
})
},
})
}
},
})
},
})
}
// canvas draw image need use this api to get image local path
export const getImageInfo = function(url) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: url,
success: res => resolve(res),
fail: e => reject(e),
})
})
}
export function parseUploadImage(upload) {
const uploadImage = []
const previewImage = []
if (upload) {
upload.forEach(image => {
uploadImage.push({
productId: image.productId,
hash: image.hash,
image: getQnUrl(image),
})
previewImage.push({
productId: image.productId,
url: getQnUrl(image)
})
})
}
return {
uploadImage,
previewImage,
}
}
export function extendMethod(...fns) {
return function(...args) {
fns.forEach(
fn => (typeof fn === 'function' ? fn.bind(this)(...args) : null),
)
}
}
import wrappedWXTool from 'wechat_common/utils/wrappedWXTool'
import { setGlobalData } from 'root/reducers/ecommerce/global'
import { dispatch } from 'root/wmp-redux'
export default function login(cb) {
wrappedWXTool.login({
success(loginRes) {
const { code } = loginRes
dispatch(
setGlobalData({
code,
}),
)
if (cb && typeof cb === 'function') {
cb(loginRes)
}
wrappedWXTool.wxGetuserInfo().then(getInfoRes => {
dispatch(
setGlobalData({
userInfo: getInfoRes.userInfo,
}),
)
})
},
})
}
import curry from 'ramda/src/curry'
import { html2json } from 'wechat_common/wxParse/html2json'
import { getProductList } from 'root/selectors/presentation/productSelector'
import { getStatusByPath } from 'wechat_common/selectors/entityStatusSelector'
import { iconPath } from 'root/constants/presentation/iconPath'
import moment from 'wechat_common/lib/moment'
const DAYS = [1, 7, 30, 90]
const DATE_TYPE = 'YYYY-MM-DD'
const RENDER_COLOR = ['#B6628E', '#CE605D', '#66B67A', '#DCBD73', '#5E77AF']
export function getCacheTime(duration) {
return new Date().getTime() + duration
}
export function getQnUrl(image, options) {
if (!image) {
return null
}
if (image.url && image.url !== '!') {
return image.url
}
if (image.storageKey && image.storageKey.charAt(0) === '/') {
image.storageKey = image.storageKey.slice(1)
}
const _options = options
? options
: `imageMogr2/strip/thumbnail/1200x9000>/format/${image.format}`
if (NODE_ENV !== 'production') {
return `https://okzg4jwn8.qnssl.com/${image.storageKey}?${_options}`
}
return `https://user-assets.sxlcdn.com/${image.storageKey}?${_options}`
}
export function formatProtocol(url) {
let _url = url
if (!url) {
return 'https://assets.sxlcdn.com/images/ecommerce/ecommerce-default-image.png'
}
if (typeof url === 'object') {
_url = getQnUrl(url)
}
_url = _url.replace('http:', '').replace('https:', '')
return `https:${_url}`
}
export function formatFactory(method, key = 'url') {
let assertError = ''
if (typeof method !== 'function') {
assertError = new TypeError(
`method pass into formatFactory must be a function, but get ${typeof method}`,
)
}
return function(obj) {
if (assertError) {
throw assertError
}
return Object.assign({}, obj, {
[key]: method(obj[key]),
})
}
}
export function caculateShortcuts(shortcuts) {
let shortcutsFirstLine
let shortcutsSecondLine
let shortcutsSecondLineWrapperClass
if (shortcuts && shortcuts.length > 5) {
const center = Math.ceil(shortcuts.length / 2)
shortcutsFirstLine = shortcuts.slice(0, center)
shortcutsSecondLine = shortcuts.slice(center)
const firstLineLength = shortcutsFirstLine.length
const secondLineLength = shortcutsSecondLine.length
if (firstLineLength !== secondLineLength) {
if (secondLineLength === 3) {
shortcutsSecondLineWrapperClass = 'shortcut-placeholder-junior'
} else if (secondLineLength === 4) {
shortcutsSecondLineWrapperClass = 'shortcut-placeholder-senior'
}
} else {
shortcutsSecondLineWrapperClass = ' '
}
} else {
shortcutsFirstLine = []
shortcutsSecondLine = []
shortcutsSecondLineWrapperClass = ' '
}
return [
shortcutsFirstLine,
shortcutsSecondLine,
shortcutsSecondLineWrapperClass,
]
}
function strNumDiscode(str) {
str = str.replace(/&forall;/g, '∀')
str = str.replace(/&part;/g, '∂')
str = str.replace(/&exists;/g, '∃')
str = str.replace(/&empty;/g, '∅')
str = str.replace(/&nabla;/g, '∇')
str = str.replace(/&isin;/g, '∈')
str = str.replace(/&notin;/g, '∉')
str = str.replace(/&ni;/g, '∋')
str = str.replace(/&prod;/g, '∏')
str = str.replace(/&sum;/g, '∑')
str = str.replace(/&minus;/g, '−')
str = str.replace(/&lowast;/g, '∗')
str = str.replace(/&radic;/g, '√')
str = str.replace(/&prop;/g, '∝')
str = str.replace(/&infin;/g, '∞')
str = str.replace(/&ang;/g, '∠')
str = str.replace(/&and;/g, '∧')
str = str.replace(/&or;/g, '∨')
str = str.replace(/&cap;/g, '∩')
str = str.replace(/&cap;/g, '∪')
str = str.replace(/&int;/g, '∫')
str = str.replace(/&there4;/g, '∴')
str = str.replace(/&sim;/g, '∼')
str = str.replace(/&cong;/g, '≅')
str = str.replace(/&asymp;/g, '≈')
str = str.replace(/&ne;/g, '≠')
str = str.replace(/&le;/g, '≤')
str = str.replace(/&ge;/g, '≥')
str = str.replace(/&sub;/g, '⊂')
str = str.replace(/&sup;/g, '⊃')
str = str.replace(/&nsub;/g, '⊄')
str = str.replace(/&sube;/g, '⊆')
str = str.replace(/&supe;/g, '⊇')
str = str.replace(/&oplus;/g, '⊕')
str = str.replace(/&otimes;/g, '⊗')
str = str.replace(/&perp;/g, '⊥')
str = str.replace(/&sdot;/g, '⋅')
return str
}
// HTML 支持的希腊字母
function strGreeceDiscode(str) {
str = str.replace(/&Alpha;/g, 'Α')
str = str.replace(/&Beta;/g, 'Β')
str = str.replace(/&Gamma;/g, 'Γ')
str = str.replace(/&Delta;/g, 'Δ')
str = str.replace(/&Epsilon;/g, 'Ε')
str = str.replace(/&Zeta;/g, 'Ζ')
str = str.replace(/&Eta;/g, 'Η')
str = str.replace(/&Theta;/g, 'Θ')
str = str.replace(/&Iota;/g, 'Ι')
str = str.replace(/&Kappa;/g, 'Κ')
str = str.replace(/&Lambda;/g, 'Λ')
str = str.replace(/&Mu;/g, 'Μ')
str = str.replace(/&Nu;/g, 'Ν')
str = str.replace(/&Xi;/g, 'Ν')
str = str.replace(/&Omicron;/g, 'Ο')
str = str.replace(/&Pi;/g, 'Π')
str = str.replace(/&Rho;/g, 'Ρ')
str = str.replace(/&Sigma;/g, 'Σ')
str = str.replace(/&Tau;/g, 'Τ')
str = str.replace(/&Upsilon;/g, 'Υ')
str = str.replace(/&Phi;/g, 'Φ')
str = str.replace(/&Chi;/g, 'Χ')
str = str.replace(/&Psi;/g, 'Ψ')
str = str.replace(/&Omega;/g, 'Ω')
str = str.replace(/&alpha;/g, 'α')
str = str.replace(/&beta;/g, 'β')
str = str.replace(/&gamma;/g, 'γ')
str = str.replace(/&delta;/g, 'δ')
str = str.replace(/&epsilon;/g, 'ε')
str = str.replace(/&zeta;/g, 'ζ')
str = str.replace(/&eta;/g, 'η')
str = str.replace(/&theta;/g, 'θ')
str = str.replace(/&iota;/g, 'ι')
str = str.replace(/&kappa;/g, 'κ')
str = str.replace(/&lambda;/g, 'λ')
str = str.replace(/&mu;/g, 'μ')
str = str.replace(/&nu;/g, 'ν')
str = str.replace(/&xi;/g, 'ξ')
str = str.replace(/&omicron;/g, 'ο')
str = str.replace(/&pi;/g, 'π')
str = str.replace(/&rho;/g, 'ρ')
str = str.replace(/&sigmaf;/g, 'ς')
str = str.replace(/&sigma;/g, 'σ')
str = str.replace(/&tau;/g, 'τ')
str = str.replace(/&upsilon;/g, 'υ')
str = str.replace(/&phi;/g, 'φ')
str = str.replace(/&chi;/g, 'χ')
str = str.replace(/&psi;/g, 'ψ')
str = str.replace(/&omega;/g, 'ω')
str = str.replace(/&thetasym;/g, 'ϑ')
str = str.replace(/&upsih;/g, 'ϒ')
str = str.replace(/&piv;/g, 'ϖ')
str = str.replace(/&middot;/g, '·')
return str
}
function strcharacterDiscode(str) {
// 加入常用解析
str = str.replace(/&nbsp;&nbsp;/g, ' ')
str = str.replace(/&yen;/g, '¥')
str = str.replace(/&quot;/g, "'")
str = str.replace(/&amp;/g, '&')
// str = str.replace(/&lt;/g, '‹');
// str = str.replace(/&gt;/g, '›');
str = str.replace(/&lt;/g, '<')
str = str.replace(/&gt;/g, '>')
str = str.replace(/&#8226;/g, '•')
return str
}
// HTML 支持的其他实体
function strOtherDiscode(str) {
str = str.replace(/&OElig;/g, 'Œ')
str = str.replace(/&oelig;/g, 'œ')
str = str.replace(/&Scaron;/g, 'Š')
str = str.replace(/&scaron;/g, 'š')
str = str.replace(/&Yuml;/g, 'Ÿ')
str = str.replace(/&fnof;/g, 'ƒ')
str = str.replace(/&circ;/g, 'ˆ')
str = str.replace(/&tilde;/g, '˜')
str = str.replace(/&ensp;/g, '')
str = str.replace(/&emsp;/g, '')
str = str.replace(/&thinsp;/g, '')
str = str.replace(/&zwnj;/g, '')
str = str.replace(/&zwj;/g, '')
str = str.replace(/&lrm;/g, '')
str = str.replace(/&rlm;/g, '')
str = str.replace(/&ndash;/g, '–')
str = str.replace(/&mdash;/g, '—')
str = str.replace(/&lsquo;/g, '‘')
str = str.replace(/&rsquo;/g, '’')
str = str.replace(/&sbquo;/g, '‚')
str = str.replace(/&ldquo;/g, '“')
str = str.replace(/&rdquo;/g, '”')
str = str.replace(/&bdquo;/g, '„')
str = str.replace(/&dagger;/g, '†')
str = str.replace(/&Dagger;/g, '‡')
str = str.replace(/&bull;/g, '•')
str = str.replace(/&hellip;/g, '…')
str = str.replace(/&permil;/g, '‰')
str = str.replace(/&prime;/g, '′')
str = str.replace(/&Prime;/g, '″')
str = str.replace(/&lsaquo;/g, '‹')
str = str.replace(/&rsaquo;/g, '›')
str = str.replace(/&oline;/g, '‾')
str = str.replace(/&euro;/g, '€')
str = str.replace(/&trade;/g, '™')
str = str.replace(/&larr;/g, '←')
str = str.replace(/&uarr;/g, '↑')
str = str.replace(/&rarr;/g, '→')
str = str.replace(/&darr;/g, '↓')
str = str.replace(/&harr;/g, '↔')
str = str.replace(/&crarr;/g, '↵')
str = str.replace(/&lceil;/g, '⌈')
str = str.replace(/&rceil;/g, '⌉')
str = str.replace(/&lfloor;/g, '⌊')
str = str.replace(/&rfloor;/g, '⌋')
str = str.replace(/&loz;/g, '◊')
str = str.replace(/&spades;/g, '♠')
str = str.replace(/&clubs;/g, '♣')
str = str.replace(/&hearts;/g, '♥')
str = str.replace(/&diams;/g, '♦')
str = str.replace(/&#39;/g, "'")
return str
}
export function generateEditorContentHTML(items) {
return items
.map(item => {
if (item.type === 'Image') {
return `<img
src="${getQnUrl(item)}"
width="${item.w}"
height="${item.h}" />`
} else if (item.type === 'RichText') {
return `<p>${item.value}</p>`
} else if (item.type === 'Video') {
const attrs = Object.entries(item)
.map(([key, value]) => `${key}="${value}"`)
.join(' ')
return `<video ${attrs}></video>`
}
})
.join('')
}
export function parseString(str) {
str = strNumDiscode(str)
str = strGreeceDiscode(str)
str = strcharacterDiscode(str)
str = strOtherDiscode(str)
return str
}
export function sortWithOrder(orderList = {}) {
return function(a, b) {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex - bIndex
}
}
export const sortWithOrderList = curry((orderList, data) => {
data.sort((a, b) => {
const aIndex = orderList[a.id] || -a.id
const bIndex = orderList[b.id] || -b.id
return aIndex - bIndex
})
return data
})
export const checkProtocol = product => {
if (product.picture.length > 0) {
product.picture[0].thumbnailUrl = formatProtocol(
product.picture[0].thumbnailUrl,
)
} else {
product.picture = [
{
thumbnailUrl: formatProtocol(),
},
]
}
return product
}
export const matchNameInProduct = (productList, name) =>
productList.filter(product => (product.categories || []).includes(name))
export const convertProductDetailData = product => {
if (product.detailEnabled && product.detail) {
const { items } = product.detail
if (items) {
const parsedHtml = generateEditorContentHTML(items)
const contentTemp = parsedHtml.replace(
/&nbsp;/g,
'<em class="dump">z</em>',
)
const content = html2json(parseString(contentTemp))
product.detail.content = content
product.detailNodes = content.nodes
}
}
return product
}
export const getFirstProduct = products => {
const state = wx.store.getState()
const { ids } = getStatusByPath(state, ['product', 'category', 'all'])
const productList = getProductList(state, ids)
if (productList && productList.length > 0) {
return productList[0]
} else {
return null
}
}
// there is no route in the return of getCurrentPages in wept
// so using pageName to predicate current page
export const isInProductsPage = () =>
getCurrentPages().pop().data.pageName === 'productIndex'
// there is no route in the return of getCurrentPages in wept
// so using pageName to predicate current page
export const isInProductDetailPage = () =>
getCurrentPages().pop().data.pageName === 'productDetail'
export function getTeamMemberAvatar(image) {
if (!image) {
return `${iconPath.DEFAULT_AVATAR}&random=${Math.random()}`
}
return getQnUrl(image, `imageView2/1/w/300/h/400/format/${image.format}`)
}
export function getRoundTeamMemberAvatar(image) {
if (!image) {
return `${iconPath.DEFAULT_ROUND_AVATAR}&random=${Math.random()}`
}
return getQnUrl(
image,
`imageView2/1/w/300/h/300/format/${image.format}|roundPic/radius/!50p`,
)
}
export function getRoundImage(imageUrl) {
return `${imageUrl}|roundPic/radius/!50p`
}
export function replaceWithDownloadFileDomain(url) {
return url && url.replace('nzr2ybsda.qnssl.com', 'user-assets.sxlcdn.com')
}
export const getPeriodTimeByNumber = days => {
const startDate = moment()
.add(-days, 'days')
.format(DATE_TYPE)
const endDate = moment().format(DATE_TYPE)
return {
startDate,
endDate,
}
}
export const formatChartsData = charts => {
let expandedCharts = []
if (charts.length > 0) {
expandedCharts = charts.map((item, index) => {
item.data = parseFloat(item.data) || 0
item.stroke = true
item.color = RENDER_COLOR[index]
return item
})
}
return expandedCharts
}
export const formatGroupChartData = chartsData => {
const { groupArticleAnalytics, groupProductAnalytics } = chartsData
const articleCharts = formatChartsData(groupArticleAnalytics)
const productCharts = formatChartsData(groupProductAnalytics)
return {
groupArticleAnalytics: articleCharts,
groupProductAnalytics: productCharts,
}
}
export const filterEnableChartList = chartsList => {
const enableRenderChartList = []
if (chartsList.length) {
chartsList.forEach(item => {
if (item.series && item.series.length > 0) {
enableRenderChartList.push(item)
}
})
}
return enableRenderChartList
}
export const getDaysByTabStatus = tabStatus => {
let days
switch (tabStatus) {
case 'yesterday':
days = DAYS[0]
break
case '7days':
days = DAYS[1]
break
case '30days':
days = DAYS[2]
break
case '90days':
days = DAYS[3]
break
default:
}
return days
}
const resetItem = (item, typeMap) => {
item.label = item.label ? item.label : ''
if (item.label.match(/【.*】/)) {
item.label = item.label.match(/【.*】/)[0].replace(/【|】/g, '')
}
let path = item.subtype
if (item.type === 'ui' && item.subtype === 'sharePage') {
try {
const pathParam = JSON.parse(item.extraParam)
path = pathParam.path
for (const key in typeMap) {
const regEx = new RegExp(key)
if (regEx.test(path)) {
if (typeMap[key].isShare) {
return Object.assign(
{},
item,
{ isHiddenRadar: false },
typeMap[key],
)
} else {
return Object.assign(
{},
item,
{ isHiddenRadar: true },
typeMap[key],
)
}
}
}
} catch (err) {
throw new Error('pathParam parse error')
}
}
for (const key in typeMap) {
const regEx = new RegExp(key)
if (regEx.test(path)) {
return Object.assign(
{},
item,
{
isHiddenRadar: false,
isStayTime: typeMap[key].isStayTime,
},
typeMap[key],
)
}
}
return Object.assign({}, item, {
name: item.label,
isHiddenRadar: true,
})
}
export const formatRadar = item => {
// fix label value is null or `点赞了 【博客】`
item.label = item.label ? item.label : ''
const radarUiTypeShow = [
'chat',
'leaveContact',
'submitContactForm',
'likeBlogPost',
'likeContactCard',
'shareContactCard',
'sharePostPage',
'shareProductPage',
'shareImageBlogPost',
'shareImageProduct',
'saveAsImage',
'saveToContact',
'copyWechatId',
'viewQrcode',
'copyEmail',
'enterApp',
'leaveApp',
]
if (item.label.match(/【.*】/)) {
item.label = item.label.match(/【.*】/)[0].replace(/【|】/g, '')
}
const radarPvTypeShow = {
'productDetail/productDetail': {
pageName: '产品',
name: `【${item.label}】`,
highLight: true,
showTimes: true,
isShare: true,
isStayTime: true,
},
'product/product': {
pageName: '产品分类',
name: `【${item.label}】`,
highLight: true,
showTimes: true,
},
'postList/postList': {
pageName: '文章分类',
name: `【${item.label}】`,
highLight: true,
showTimes: true,
},
'postDetail/postDetail': {
pageName: '文章',
name: `【${item.label}】`,
highLight: true,
showTimes: true,
isShare: true,
isStayTime: true,
},
'presentation/index/index': {
pageName: '',
name: `首页`,
highLight: true,
showTimes: true,
isShare: false,
},
'contact/contact': {
isShare: true,
pageName: '您的',
name: `名片`,
highLight: true,
showTimes: true,
isHiddenRadar: true,
},
}
// fix the pv include blog's and product's catgary and detail
if (item.type === 'pv') {
item = resetItem(item, radarPvTypeShow)
} else if (item.type === 'ui') {
if (radarUiTypeShow.indexOf(item.subtype) > -1) {
item.name = `【${item.label}】`
item.isHiddenRadar = false
} else if (item.subtype === 'sharePage') {
item = resetItem(item, radarPvTypeShow)
} else {
item.isHiddenRadar = true
}
}
// the personalized push text
if (item.subtype === 'chat') {
item.welcomeText = item.repeatTimes >= 2 ? '' : '快去回复吧'
} else if (item.subtype === 'submitContactForm') {
item.welcomeText = item.repeatTimes >= 2 ? '' : '快去看看他说了什么吧'
} else if (item.subtype === 'likeContactCard') {
item.welcomeText = item.repeatTimes >= 2 ? '' : '似乎对您有好感,快联系ta吧'
} else if (item.subtype === 'viewContactCard') {
item.showTimes = true
item.welcomeText =
item.repeatTimes >= 2 ? '看来成交在望,快联系ta吧!' : '初识在此刻'
} else if (item.subtype === 'shareContactCard') {
item.showTimes = true
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧!'
: '您的人脉正在扩散!'
} else if (item.subtype === 'saveAsImage') {
item.welcomeText =
item.repeatTimes >= 2 ? '' : '近期可能会联系您,密切关注哦'
} else if (item.subtype === 'copyWechatId') {
item.welcomeText = item.repeatTimes >= 2 ? '' : '可能会加您好友,请留意哦'
} else if (item.subtype === 'shareImageProduct') {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧'
: '看来还挺感兴趣的,快联系ta吧'
} else if (item.subtype === 'copyEmail') {
item.welcomeText =
item.repeatTimes >= 2 ? '' : '可能会发送邮件给您,请留意哦'
} else if (item.subtype === 'sharePage') {
let path
try {
const pathParam = JSON.parse(item.extraParam)
path = pathParam.path
} catch (err) {
throw new Error('pathParam parse error')
}
if (path.includes('contact/contact')) {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧!'
: '您的人脉正在扩散!'
}
} else if (item.subtype === 'likeBlogPost') {
item.welcomeText = item.repeatTimes >= 2 ? '' : '看来很认可呢,要不要联系下'
} else if (item.type === 'pv') {
if (item.subtype.includes('postList?category')) {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧'
: '看来对这类文章还挺感兴趣的,快联系ta介绍更多吧'
} else if (item.subtype.includes('product?category')) {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧'
: '看来对这类产品还挺感兴趣的,快联系ta介绍更多吧'
} else if (item.subtype.includes('postId')) {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧'
: '看来对公司还挺感兴趣的,快联系ta吧'
} else if (item.subtype.includes('productId')) {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧'
: '看来还挺感兴趣的,快联系ta吧'
} else if (item.subtype.includes('presentation/index/index')) {
item.isStayTime = true
item.welcomeText =
item.repeatTimes >= 2
? '看来对公司还挺感兴趣的,快联系ta吧'
: '这是个重要情报哦'
} else if (item.subtype.includes('contact/contact')) {
item.isStayTime = true
item.welcomeText =
item.repeatTimes >= 2 ? '看来成交在望,快联系ta吧' : '初识在此刻'
} else if (item.subtype.includes('businessCard/businessCard')) {
item.highLight = false
item.pageName = '您的'
item.name = '名片夹'
item.showTimes = true
}
}
if (item.subtype === 'leaveContact') {
const str1 = item.label.split(',')[0]
const str2 = str1.split('留下了电话:')
item.phone = str2[1]
item.name = str2[0]
item.nickname = str2[0]
}
if (item.subtype === 'shareProductPage') {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧!'
: '看来还挺感兴趣的,快联系ta吧'
}
if (item.subtype === 'sharePostPage') {
item.welcomeText =
item.repeatTimes >= 2
? '看来成交在望,快联系ta吧!'
: '看来还挺感兴趣的,快联系ta吧'
}
const hourTime = parseInt(item.dwellTime / 3600)
? `${parseInt(item.dwellTime / 3600)}小时`
: ``
const minuteTime = parseInt((item.dwellTime % 3600) / 60)
? `${parseInt((item.dwellTime % 3600) / 60)}分钟`
: ``
const secondTime = `${
(item.dwellTime % 3600) % 60 && item.type === 'pv'
? (item.dwellTime % 3600) % 60
: (item.dwellTime % 3600) % 60 + 1
}秒`
item.dwellTime = hourTime + minuteTime + secondTime
return item
}
import {doGet, dePostWithPromise} from 'wechat_common/utils/request'
export default function REQUEST(options, bindType) {
switch (options.method) {
case "GET":
return bindType ? doGet(bindType(options)) : doGet(options)
case "POST":
return bindType ? dePostWithPromise(bindType(options)) : dePostWithPromise(options)
default :
return bindType ? doGet(bindType(options)) : doGet(options)
}
}
const lastStateContainer = {}
function _observable(store, keyPath, callback) {
return store.subscribe(() => {
const newState = store.getState().getIn(keyPath)
const keyPathStr = keyPath.join('')
if (lastStateContainer[keyPathStr] !== newState) {
const _lastState = lastStateContainer[keyPathStr]
lastStateContainer[keyPathStr] = newState
callback(newState, _lastState)
}
})
}
export default function createObserveble(store, keyPath) {
let subscribeId = 0
return {
subscribe: callback => {
subscribeId = _observable(store, keyPath, callback)
},
unSubscribe: () => {
store.unsubscribe(subscribeId)
},
}
}
import moment from 'wechat_common/lib/moment'
import { getQnUrl } from 'wechat_common/utils/helpers/imageHelper'
export function reducerHelper(state, action, handlers) {
const handler = handlers[action.type]
return handler ? handler(state, action) : state
}
export function formatFactory(method, key = 'url') {
let assertError = ''
if (typeof method !== 'function') {
assertError = new TypeError(
`method pass into formatFactory must be a function, but get ${typeof method}`,
)
}
return function(obj) {
if (assertError) {
throw assertError
}
return Object.assign({}, obj, {
[key]: method(obj[key]),
})
}
}
export function formatProtocol(url) {
const _url = url.replace('http:', '').replace('https:', '')
return `https:${_url}`
}
// 判断十六进制色值是浅色还是深色 return true: 浅色,false:深色
const isLightFunc = hexColor => {
const color = hexColor.toLowerCase()
const sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt(`0x${color.slice(i, i + 2)}`, 0))
}
if (
sColorChange[0] * 0.299 +
sColorChange[1] * 0.578 +
sColorChange[2] * 0.114 >=
192
) {
return true
} else {
return false
}
}
export function isWhite(color) {
if (
!color ||
color === '' ||
color === '#fff' ||
color === '#ffffff' ||
color === 'white'
) {
return true
} else if (color === 'black') {
return false
}
return isLightFunc(color)
}
export function getDateDiff(dateTimeStamp) {
const monthCount = moment().diff(dateTimeStamp, 'months')
const weekCount = moment().diff(dateTimeStamp, 'weeks')
const dayCount = moment().diff(dateTimeStamp, 'days')
const hourCount = moment().diff(dateTimeStamp, 'hours')
const minCount = moment().diff(dateTimeStamp, 'minutes')
let result = ''
if (monthCount >= 1) {
result = `${String(parseInt(monthCount, 0))}月前`
} else if (weekCount >= 1) {
result = `${String(parseInt(weekCount, 0))}周前`
} else if (dayCount >= 1) {
result = `${String(parseInt(dayCount, 0))}天前`
} else if (hourCount >= 1) {
result = `${String(parseInt(hourCount, 0))}小时前`
} else if (minCount >= 1) {
result = `${String(parseInt(minCount, 0))}分钟前`
} else {
result = '刚刚'
}
return result
}
export function validatePhoneNumber(phoneNumer) {
return /^1[34578]\d{9}$/.test(phoneNumer)
}
export function formatAmount(amount) {
return (Number(amount) / 100).toFixed(2)
}
export function formatGrouponStatus(status) {
switch (status) {
case 'refund_pending':
return { name: '待退款', color: '#fb7d2b', type: 'effective' }
case 'enabled':
return { name: '未使用', color: '#93b719', type: 'effective' }
case 'used':
return { name: '已核销', color: '#9298a0', type: 'used' }
case 'refunded':
return { name: '已退款', color: '#9298a0', type: 'refunded' }
case 'expired':
return { name: '已过期', color: '#9298a0', type: 'expired' }
case 'disabled':
return { name: '已失效', color: '#e64751', type: 'disabled' }
default:
return ''
}
}
// parse the comment pictures and split comment to show or hide.
export function updateComment(comments) {
let lessCommentImg = []
let moreCommentImg = []
let lessComments = []
let holdPictures = []
let newComments = []
if (comments) {
comments.forEach(value => {
if (value.content && value.content.length > 82) {
value.shortContent = `${value.content.substring(0, 82)}...`
}
value.createdAt = moment(value.createdAt)
.locale('zh-cn')
.fromNow()
if (value.pictures && value.pictures.length > 0) {
value.pictures.map(img => {
if (img.image) {
holdPictures.push(img.image)
} else {
holdPictures.push(getQnUrl(img))
}
})
value.pictures = holdPictures
holdPictures = []
} else {
value.pictures = []
}
if (value.pictures && value.pictures.length > 3) {
lessCommentImg = value.pictures.slice(0, 2)
moreCommentImg = value.pictures.slice(2, 3)
} else if (value.pictures && value.pictures.length <= 3) {
lessCommentImg = value.pictures
}
value.lessCommentImg = lessCommentImg || []
value.moreCommentImg = moreCommentImg || []
})
newComments = [...comments]
lessComments = newComments.slice(0, 1)
}
return {
comments: newComments,
lessComments,
}
}
export function compare(property) {
return (a, b) => {
const value1 = a[property]
const value2 = b[property]
return value2 - value1
}
}
export function simpleString(str = '', length = 5) {
return str.length > length ? `${str.substr(0, length)}...` : str
}
/*
* return the main region of a detailed address
* @param {string} addr detailed address
* @return {string}
*/
export const getRegionFromAddress = addr => {
const regEx = /^(.+[省|自治区])?(.+[地区|自治州|市])?(.+[县|区])?/
const rslt = (addr || '').match(regEx)
return rslt || ['']
}
/*
* get the type of the input value
* @param {string} addr detailed address
* @return {string}
*/
export const getType = param => {
if (Array.isArray(param)) {
return 'array'
}
if (param instanceof Date) {
return 'date'
}
if (param === null) {
return 'null'
}
if (param instanceof RegExp) {
return 'regExp'
}
if (param instanceof Error) {
return 'error'
}
return typeof param
}
/*
* compare the first input value to another,
* return a boolean value if they are as same as each other
* @param {Object} a the first object which would be used to compare to another
* @return {Object} b another object
*/
export const isCongruence = (a, b) => {
const typeFirst = getType(a)
if (typeFirst !== getType(b)) {
return false
}
const TYPE_METHODS_MAP = {
array: (x, y) => {
if (x === y) {
return true
}
if (x.length !== y.length) {
return false
}
const ifEqual = (pre, nex) => {
const compareFirst = isCongruence(pre[0], nex[0])
if (pre.length <= 1) {
return compareFirst
}
return compareFirst && TYPE_METHODS_MAP.array(pre.slice(1), nex.slice(1))
}
return ifEqual(x, y)
},
function: (x, y) => {
if (x === y) {
return true
}
return String(x) === String(y)
},
object: (x, y) => {
if (x === y) {
return true
}
const xKeys = Object.keys(x)
const yKeys = Object.keys(y)
if (xKeys.length !== yKeys.length) {
return false
}
const ifKeysEqual = TYPE_METHODS_MAP.array(xKeys, yKeys)
const ifEqual = keys => {
if (keys.length <= 0) {
return true
}
const compareFirst = isCongruence(x[keys[0]], y[keys[0]])
if (keys.length <= 1) {
return compareFirst
}
return compareFirst && ifEqual(keys.slice(1))
}
return ifKeysEqual && ifEqual(xKeys)
},
otherwise: (x, y) => x === y,
}
return (TYPE_METHODS_MAP[typeFirst] || TYPE_METHODS_MAP.otherwise)(a, b)
}
/**
* This middleware can only work in wmp
* @returns {undefined}
*/
export function errorModalMiddleware({ getState }) {
return next => action => {
next(action)
const state = getState()
const { message } = state.getIn(['error']).toJS()
if (message) {
wx.showModal({
content: message,
showCancel: false,
})
}
}
}
export function debounce(fn, time = 100) {
let handle
handle = 0
return function () {
let args, context
context = this
args = arguments
clearTimeout(handle)
return (handle = setTimeout(() => fn.apply(context, args), time))
}
}
export function formatProtocol(url) {
if (!url) {
return 'https://assets.sxlcdn.com/images/ecommerce/ecommerce-default-image.png'
}
const _url = url.replace('http:', '').replace('https:', '')
return `https:${_url}`
}
export function formatAmount(amount) {
// e.g. turn 1234 into 1.2k
const result = amount / 1000
if (result > 1) {
return `${Math.round(result * 10) / 10}k`
} else {
return amount
}
}
export function customNavigateTo(data) {
setTimeout(() => {
wx.navigateTo(data)
}, 50)
}
/*
* charts for WeChat small app v1.0
*
* https://github.com/xiaolin3303/wx-charts
* 2016-11-28
*
* Designed and built with all the love of Web
*/
const config = {
yAxisWidth: 15,
yAxisSplit: 5,
xAxisHeight: 15,
xAxisLineHeight: 15,
legendHeight: 15,
yAxisTitleWidth: 15,
padding: 12,
columePadding: 3,
fontSize: 10,
dataPointShape: ['diamond', 'circle', 'triangle', 'rect'],
colors: ['#7cb5ec', '#f7a35c', '#434348', '#90ed7d', '#f15c80', '#8085e9'],
pieChartLinePadding: 25,
pieChartTextPadding: 15,
xAxisTextPadding: 3,
titleColor: '#333333',
titleFontSize: 20,
subtitleColor: '#999999',
subtitleFontSize: 15,
toolTipPadding: 3,
toolTipBackground: '#000000',
toolTipOpacity: 0.7,
toolTipLineHeight: 14,
radarGridCount: 3,
radarLabelTextMargin: 15,
}
// Object.assign polyfill
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
function assign(target, varArgs) {
if (target == null) {
// TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object')
}
const to = Object(target)
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index]
if (nextSource != null) {
// Skip over if undefined or null
for (const nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey]
}
}
}
}
return to
}
const util = {
toFixed: function toFixed(num, limit) {
limit = limit || 2
if (this.isFloat(num)) {
num = num.toFixed(limit)
}
return num
},
isFloat: function isFloat(num) {
return num % 1 !== 0
},
approximatelyEqual: function approximatelyEqual(num1, num2) {
return Math.abs(num1 - num2) < 1e-10
},
isSameSign: function isSameSign(num1, num2) {
return (
(Math.abs(num1) === num1 && Math.abs(num2) === num2) ||
(Math.abs(num1) !== num1 && Math.abs(num2) !== num2)
)
},
isSameXCoordinateArea: function isSameXCoordinateArea(p1, p2) {
return this.isSameSign(p1.x, p2.x)
},
isCollision: function isCollision(obj1, obj2) {
obj1.end = {}
obj1.end.x = obj1.start.x + obj1.width
obj1.end.y = obj1.start.y - obj1.height
obj2.end = {}
obj2.end.x = obj2.start.x + obj2.width
obj2.end.y = obj2.start.y - obj2.height
const flag =
obj2.start.x > obj1.end.x ||
obj2.end.x < obj1.start.x ||
obj2.end.y > obj1.start.y ||
obj2.start.y < obj1.end.y
return !flag
},
}
function findRange(num, type, limit) {
if (isNaN(num)) {
throw new Error('[wxCharts] unvalid series data!')
}
limit = limit || 10
type = type ? type : 'upper'
let multiple = 1
while (limit < 1) {
limit *= 10
multiple *= 10
}
if (type === 'upper') {
num = Math.ceil(num * multiple)
} else {
num = Math.floor(num * multiple)
}
while (num % limit !== 0) {
if (type === 'upper') {
num++
} else {
num--
}
}
return num / multiple
}
function calValidDistance(distance, chartData, config, opts) {
const dataChartAreaWidth =
opts.width - config.padding - chartData.xAxisPoints[0]
const dataChartWidth = chartData.eachSpacing * opts.categories.length
let validDistance = distance
if (distance >= 0) {
validDistance = 0
} else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) {
validDistance = dataChartAreaWidth - dataChartWidth
}
return validDistance
}
function isInAngleRange(angle, startAngle, endAngle) {
function adjust(angle) {
while (angle < 0) {
angle += 2 * Math.PI
}
while (angle > 2 * Math.PI) {
angle -= 2 * Math.PI
}
return angle
}
angle = adjust(angle)
startAngle = adjust(startAngle)
endAngle = adjust(endAngle)
if (startAngle > endAngle) {
endAngle += 2 * Math.PI
if (angle < startAngle) {
angle += 2 * Math.PI
}
}
return angle >= startAngle && angle <= endAngle
}
function calRotateTranslate(x, y, h) {
const xv = x
const yv = h - y
let transX = xv + (h - yv - xv) / Math.sqrt(2)
transX *= -1
const transY = (h - yv) * (Math.sqrt(2) - 1) - (h - yv - xv) / Math.sqrt(2)
return {
transX,
transY,
}
}
function createCurveControlPoints(points, i) {
function isNotMiddlePoint(points, i) {
if (points[i - 1] && points[i + 1]) {
return (
points[i].y >= Math.max(points[i - 1].y, points[i + 1].y) ||
points[i].y <= Math.min(points[i - 1].y, points[i + 1].y)
)
} else {
return false
}
}
const a = 0.2
const b = 0.2
let pAx = null
let pAy = null
let pBx = null
let pBy = null
if (i < 1) {
pAx = points[0].x + (points[1].x - points[0].x) * a
pAy = points[0].y + (points[1].y - points[0].y) * a
} else {
pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a
pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a
}
if (i > points.length - 3) {
const last = points.length - 1
pBx = points[last].x - (points[last].x - points[last - 1].x) * b
pBy = points[last].y - (points[last].y - points[last - 1].y) * b
} else {
pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b
pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b
}
// fix issue https://github.com/xiaolin3303/wx-charts/issues/79
if (isNotMiddlePoint(points, i + 1)) {
pBy = points[i + 1].y
}
if (isNotMiddlePoint(points, i)) {
pAy = points[i].y
}
return {
ctrA: { x: pAx, y: pAy },
ctrB: { x: pBx, y: pBy },
}
}
function convertCoordinateOrigin(x, y, center) {
return {
x: center.x + x,
y: center.y - y,
}
}
function avoidCollision(obj, target) {
if (target) {
// is collision test
while (util.isCollision(obj, target)) {
if (obj.start.x > 0) {
obj.start.y--
} else if (obj.start.x < 0) {
obj.start.y++
} else if (obj.start.y > 0) {
obj.start.y++
} else {
obj.start.y--
}
}
}
return obj
}
function fillSeriesColor(series, config) {
let index = 0
return series.map(item => {
if (!item.color) {
item.color = config.colors[index]
index = (index + 1) % config.colors.length
}
return item
})
}
function getDataRange(minData, maxData) {
let limit = 0
const range = maxData - minData
if (range >= 10000) {
limit = 1000
} else if (range >= 1000) {
limit = 100
} else if (range >= 100) {
limit = 10
} else if (range >= 10) {
limit = 5
} else if (range >= 1) {
limit = 1
} else if (range >= 0.1) {
limit = 0.1
} else {
limit = 0.01
}
return {
minRange: findRange(minData, 'lower', limit),
maxRange: findRange(maxData, 'upper', limit),
}
}
function measureText(text) {
const fontSize =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10
// wx canvas 未实现measureText方法, 此处自行实现
text = String(text)
var text = text.split('')
let width = 0
text.forEach(item => {
if (/[a-zA-Z]/.test(item)) {
width += 7
} else if (/[0-9]/.test(item)) {
width += 5.5
} else if (/\./.test(item)) {
width += 2.7
} else if (/-/.test(item)) {
width += 3.25
} else if (/[\u4e00-\u9fa5]/.test(item)) {
width += 10
} else if (/\(|\)/.test(item)) {
width += 3.73
} else if (/\s/.test(item)) {
width += 2.5
} else if (/%/.test(item)) {
width += 8
} else {
width += 10
}
})
return width * fontSize / 10
}
function dataCombine(series) {
return series.reduce((a, b) => (a.data ? a.data : a).concat(b.data), [])
}
function getSeriesDataItem(series, index) {
const data = []
series.forEach(item => {
if (item.data[index] !== null && typeof item.data[index] !== 'undefined') {
const seriesItem = {}
seriesItem.color = item.color
seriesItem.name = item.name
seriesItem.data = item.format
? item.format(item.data[index])
: item.data[index]
data.push(seriesItem)
}
})
return data
}
function getMaxTextListLength(list) {
const lengthList = list.map(item => measureText(item))
return Math.max.apply(null, lengthList)
}
function getRadarCoordinateSeries(length) {
const eachAngle = 2 * Math.PI / length
const CoordinateSeries = []
for (let i = 0; i < length; i++) {
CoordinateSeries.push(eachAngle * i)
}
return CoordinateSeries.map(item => -1 * item + Math.PI / 2)
}
function getToolTipData(seriesData, calPoints, index, categories) {
const option =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}
const textList = seriesData.map(item => ({
text: option.format
? option.format(item, categories[index])
: `${item.name}: ${item.data}`,
color: item.color,
}))
const validCalPoints = []
const offset = {
x: 0,
y: 0,
}
calPoints.forEach(points => {
if (typeof points[index] !== 'undefined' && points[index] !== null) {
validCalPoints.push(points[index])
}
})
validCalPoints.forEach(item => {
offset.x = Math.round(item.x)
offset.y += item.y
})
offset.y /= validCalPoints.length
return { textList, offset }
}
function findCurrentIndex(currentPoints, xAxisPoints, opts, config) {
const offset =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0
let currentIndex = -1
if (isInExactChartArea(currentPoints, opts, config)) {
xAxisPoints.forEach((item, index) => {
if (currentPoints.x + offset > item) {
currentIndex = index
}
})
}
return currentIndex
}
function isInExactChartArea(currentPoints, opts, config) {
return (
currentPoints.x < opts.width - config.padding &&
currentPoints.x >
config.padding + config.yAxisWidth + config.yAxisTitleWidth &&
currentPoints.y > config.padding &&
currentPoints.y <
opts.height - config.legendHeight - config.xAxisHeight - config.padding
)
}
function findRadarChartCurrentIndex(currentPoints, radarData, count) {
const eachAngleArea = 2 * Math.PI / count
let currentIndex = -1
if (
isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)
) {
const fixAngle = function fixAngle(angle) {
if (angle < 0) {
angle += 2 * Math.PI
}
if (angle > 2 * Math.PI) {
angle -= 2 * Math.PI
}
return angle
}
let angle = Math.atan2(
radarData.center.y - currentPoints.y,
currentPoints.x - radarData.center.x,
)
angle = -1 * angle
if (angle < 0) {
angle += 2 * Math.PI
}
const angleList = radarData.angleList.map(item => {
item = fixAngle(-1 * item)
return item
})
angleList.forEach((item, index) => {
const rangeStart = fixAngle(item - eachAngleArea / 2)
let rangeEnd = fixAngle(item + eachAngleArea / 2)
if (rangeEnd < rangeStart) {
rangeEnd += 2 * Math.PI
}
if (
(angle >= rangeStart && angle <= rangeEnd) ||
(angle + 2 * Math.PI >= rangeStart && angle + 2 * Math.PI <= rangeEnd)
) {
currentIndex = index
}
})
}
return currentIndex
}
function findPieChartCurrentIndex(currentPoints, pieData) {
let currentIndex = -1
if (isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {
let angle = Math.atan2(
pieData.center.y - currentPoints.y,
currentPoints.x - pieData.center.x,
)
angle = -angle
for (let i = 0, len = pieData.series.length; i < len; i++) {
const item = pieData.series[i]
if (
isInAngleRange(
angle,
item._start_,
item._start_ + item._proportion_ * 2 * Math.PI,
)
) {
currentIndex = i
break
}
}
}
return currentIndex
}
function isInExactPieChartArea(currentPoints, center, radius) {
return (
Math.pow(currentPoints.x - center.x, 2) +
Math.pow(currentPoints.y - center.y, 2) <=
Math.pow(radius, 2)
)
}
function splitPoints(points) {
const newPoints = []
let items = []
points.forEach((item, index) => {
if (item !== null) {
items.push(item)
} else {
if (items.length) {
newPoints.push(items)
}
items = []
}
})
if (items.length) {
newPoints.push(items)
}
return newPoints
}
function calLegendData(series, opts, config) {
if (opts.legend === false) {
return {
legendList: [],
legendHeight: 0,
}
}
const padding = 5
const marginTop = 8
const shapeWidth = 15
const legendList = []
let widthCount = 0
let currentRow = []
series.forEach(item => {
const itemWidth =
3 * padding + shapeWidth + measureText(item.name || 'undefined')
if (widthCount + itemWidth > opts.width) {
legendList.push(currentRow)
widthCount = itemWidth
currentRow = [item]
} else {
widthCount += itemWidth
currentRow.push(item)
}
})
if (currentRow.length) {
legendList.push(currentRow)
}
return {
legendList,
legendHeight: legendList.length * (config.fontSize + marginTop) + padding,
}
}
function calCategoriesData(categories, opts, config) {
const result = {
angle: 0,
xAxisHeight: config.xAxisHeight,
}
let _getXAxisPoints = getXAxisPoints(categories, opts, config),
eachSpacing = _getXAxisPoints.eachSpacing
// get max length of categories text
const categoriesTextLenth = categories.map(item => measureText(item))
const maxTextLength = Math.max.apply(this, categoriesTextLenth)
if (
!opts.xAxis.noRotate &&
maxTextLength + 2 * config.xAxisTextPadding > eachSpacing
) {
result.angle = 45 * Math.PI / 180
result.xAxisHeight =
2 * config.xAxisTextPadding + maxTextLength * Math.sin(result.angle)
}
return result
}
function getRadarDataPoints(angleList, center, radius, series, opts) {
const process =
arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1
const radarOption = opts.extra.radar || {}
radarOption.max = radarOption.max || 0
const maxData = Math.max(
radarOption.max,
Math.max.apply(null, dataCombine(series)),
)
const data = []
series.forEach(each => {
const listItem = {}
listItem.color = each.color
listItem.data = []
each.data.forEach((item, index) => {
const tmp = {}
tmp.angle = angleList[index]
tmp.proportion = item / maxData
tmp.position = convertCoordinateOrigin(
radius * tmp.proportion * process * Math.cos(tmp.angle),
radius * tmp.proportion * process * Math.sin(tmp.angle),
center,
)
listItem.data.push(tmp)
})
data.push(listItem)
})
return data
}
function getPieDataPoints(series) {
const process =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1
let count = 0
let _start_ = 0
series.forEach(item => {
item.data = item.data === null ? 0 : item.data
count += item.data
})
series.forEach(item => {
item.data = item.data === null ? 0 : item.data
item._proportion_ = item.data / count * process
})
series.forEach(item => {
item._start_ = _start_
_start_ += 2 * item._proportion_ * Math.PI
})
return series
}
function getPieTextMaxLength(series) {
series = getPieDataPoints(series)
let maxLength = 0
series.forEach(item => {
const text = item.format
? item.format(Number(item._proportion_.toFixed(2)))
: `${util.toFixed(item._proportion_ * 100)}%`
maxLength = Math.max(maxLength, measureText(text))
})
return maxLength
}
function fixColumeData(points, eachSpacing, columnLen, index, config, opts) {
return points.map(item => {
if (item === null) {
return null
}
item.width = (eachSpacing - 2 * config.columePadding) / columnLen
if (
opts.extra.column &&
opts.extra.column.width &&
Number(opts.extra.column.width) > 0
) {
// customer column width
item.width = Math.min(item.width, Number(opts.extra.column.width))
} else {
// default width should less tran 25px
// don't ask me why, I don't know
item.width = Math.min(item.width, 25)
}
item.x += (index + 0.5 - columnLen / 2) * item.width
return item
})
}
function getXAxisPoints(categories, opts, config) {
const yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth
const spacingValid = opts.justified
? opts.width
: opts.width - 2 * config.padding - yAxisTotalWidth
const dataCount = opts.enableScroll
? Math.min(5, categories.length)
: categories.length
const eachSpacing = spacingValid / dataCount
const xAxisPoints = []
const startX = opts.justified
? config.padding
: config.padding + yAxisTotalWidth
const endX = opts.width - config.padding
categories.forEach((item, index) => {
xAxisPoints.push(startX + index * eachSpacing)
})
if (opts.enableScroll === true) {
xAxisPoints.push(startX + categories.length * eachSpacing)
} else {
xAxisPoints.push(endX)
}
return {
xAxisPoints,
startX,
endX,
eachSpacing,
}
}
function getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
) {
const process =
arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1
const points = []
const validHeight =
opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight
data.forEach((item, index) => {
if (item === null) {
points.push(null)
} else {
const point = {}
point.x = xAxisPoints[index] + Math.round(eachSpacing / 2)
let height = validHeight * (item - minRange) / (maxRange - minRange)
height *= process
point.y =
opts.height -
config.xAxisHeight -
config.legendHeight -
Math.round(height) -
config.padding
points.push(point)
}
})
return points
}
function getYAxisTextList(series, opts, config) {
let data = dataCombine(series)
// remove null from data
data = data.filter(item => item !== null)
let minData = Math.min.apply(this, data)
let maxData = Math.max.apply(this, data)
if (typeof opts.yAxis.min === 'number') {
minData = Math.min(opts.yAxis.min, minData)
}
if (typeof opts.yAxis.max === 'number') {
maxData = Math.max(opts.yAxis.max, maxData)
}
// fix issue https://github.com/xiaolin3303/wx-charts/issues/9
if (minData === maxData) {
const rangeSpan = maxData || 1
// when all the data is 0 , set minData = 0
if (!maxData) {
minData = 0
maxData = 1
} else {
minData -= rangeSpan
maxData += rangeSpan
}
}
const dataRange = getDataRange(minData, maxData)
const minRange = dataRange.minRange
const maxRange = dataRange.maxRange
const range = []
const eachRange = (maxRange - minRange) / config.yAxisSplit
for (let i = 0; i <= config.yAxisSplit; i++) {
range.push(minRange + eachRange * i)
}
return range.reverse()
}
function calYAxisData(series, opts, config) {
const ranges = getYAxisTextList(series, opts, config)
let yAxisWidth = config.yAxisWidth
const rangesFormat = ranges.map(item => {
item = util.toFixed(item, 2)
item = opts.yAxis.format ? opts.yAxis.format(Number(item)) : item
yAxisWidth = Math.max(yAxisWidth, measureText(item) + 5)
return item
})
if (opts.yAxis.disabled === true) {
yAxisWidth = 0
}
return { rangesFormat, ranges, yAxisWidth }
}
function drawPointShape(points, color, shape, context) {
context.beginPath()
context.setStrokeStyle('#ffffff')
context.setLineWidth(1)
context.setFillStyle(color)
if (shape === 'diamond') {
points.forEach((item, index) => {
if (item !== null) {
context.moveTo(item.x, item.y - 4.5)
context.lineTo(item.x - 4.5, item.y)
context.lineTo(item.x, item.y + 4.5)
context.lineTo(item.x + 4.5, item.y)
context.lineTo(item.x, item.y - 4.5)
}
})
} else if (shape === 'circle') {
points.forEach((item, index) => {
if (item !== null) {
context.moveTo(item.x + 3.5, item.y)
context.arc(item.x, item.y, 4, 0, 2 * Math.PI, false)
}
})
} else if (shape === 'rect') {
points.forEach((item, index) => {
if (item !== null) {
context.moveTo(item.x - 3.5, item.y - 3.5)
context.rect(item.x - 3.5, item.y - 3.5, 7, 7)
}
})
} else if (shape === 'triangle') {
points.forEach((item, index) => {
if (item !== null) {
context.moveTo(item.x, item.y - 4.5)
context.lineTo(item.x - 4.5, item.y + 4.5)
context.lineTo(item.x + 4.5, item.y + 4.5)
context.lineTo(item.x, item.y - 4.5)
}
})
}
context.closePath()
context.fill()
context.stroke()
}
function drawRingTitle(opts, config, context) {
const titlefontSize = opts.title.fontSize || config.titleFontSize
const subtitlefontSize = opts.subtitle.fontSize || config.subtitleFontSize
const title = opts.title.name || ''
const subtitle = opts.subtitle.name || ''
const titleFontColor = opts.title.color || config.titleColor
const subtitleFontColor = opts.subtitle.color || config.subtitleColor
const titleHeight = title ? titlefontSize : 0
const subtitleHeight = subtitle ? subtitlefontSize : 0
const margin = 5
if (subtitle) {
const textWidth = measureText(subtitle, subtitlefontSize)
const startX = (opts.width - textWidth) / 2 + (opts.subtitle.offsetX || 0)
let startY = (opts.height - config.legendHeight + subtitlefontSize) / 2
if (title) {
startY -= (titleHeight + margin) / 2
}
context.beginPath()
context.setFontSize(subtitlefontSize)
context.setFillStyle(subtitleFontColor)
context.fillText(subtitle, startX, startY)
context.stroke()
context.closePath()
}
if (title) {
const _textWidth = measureText(title, titlefontSize)
const _startX = (opts.width - _textWidth) / 2 + (opts.title.offsetX || 0)
let _startY = (opts.height - config.legendHeight + titlefontSize) / 2
if (subtitle) {
_startY += (subtitleHeight + margin) / 2
}
context.beginPath()
context.setFontSize(titlefontSize)
context.setFillStyle(titleFontColor)
context.fillText(title, _startX, _startY)
context.stroke()
context.closePath()
}
}
function drawPointText(points, series, config, context) {
// 绘制数据文案
const data = series.data
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle('#666666')
points.forEach((item, index) => {
if (item !== null) {
const formatVal = series.format ? series.format(data[index]) : data[index]
context.fillText(
formatVal,
item.x - measureText(formatVal) / 2,
item.y - 2,
)
}
})
context.closePath()
context.stroke()
}
function drawRadarLabel(
angleList,
radius,
centerPosition,
opts,
config,
context,
) {
const radarOption = opts.extra.radar || {}
radius += config.radarLabelTextMargin
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle(radarOption.labelColor || '#666666')
angleList.forEach((angle, index) => {
const pos = {
x: radius * Math.cos(angle),
y: radius * Math.sin(angle),
}
const posRelativeCanvas = convertCoordinateOrigin(
pos.x,
pos.y,
centerPosition,
)
let startX = posRelativeCanvas.x
const startY = posRelativeCanvas.y
if (util.approximatelyEqual(pos.x, 0)) {
startX -= measureText(opts.categories[index] || '') / 2
} else if (pos.x < 0) {
startX -= measureText(opts.categories[index] || '')
}
context.fillText(
opts.categories[index] || '',
startX,
startY + config.fontSize / 2,
)
})
context.stroke()
context.closePath()
}
function drawPieText(series, opts, config, context, radius, center) {
const lineRadius = radius + config.pieChartLinePadding
const textObjectCollection = []
let lastTextObject = null
const seriesConvert = series.map(item => {
const arc =
2 * Math.PI - (item._start_ + 2 * Math.PI * item._proportion_ / 2)
const text = item.format
? item.format(Number(item._proportion_.toFixed(2)))
: `${util.toFixed(item._proportion_ * 100)}%`
const color = item.color
return { arc, text, color }
})
seriesConvert.forEach(item => {
// line end
const orginX1 = Math.cos(item.arc) * lineRadius
const orginY1 = Math.sin(item.arc) * lineRadius
// line start
const orginX2 = Math.cos(item.arc) * radius
const orginY2 = Math.sin(item.arc) * radius
// text start
let orginX3 =
orginX1 >= 0
? orginX1 + config.pieChartTextPadding
: orginX1 - config.pieChartTextPadding
const orginY3 = orginY1
const textWidth = measureText(item.text)
let startY = orginY3
if (
lastTextObject &&
util.isSameXCoordinateArea(lastTextObject.start, { x: orginX3 })
) {
if (orginX3 > 0) {
startY = Math.min(orginY3, lastTextObject.start.y)
} else if (orginX1 < 0) {
startY = Math.max(orginY3, lastTextObject.start.y)
} else if (orginY3 > 0) {
startY = Math.max(orginY3, lastTextObject.start.y)
} else {
startY = Math.min(orginY3, lastTextObject.start.y)
}
}
if (orginX3 < 0) {
orginX3 -= textWidth
}
const textObject = {
lineStart: {
x: orginX2,
y: orginY2,
},
lineEnd: {
x: orginX1,
y: orginY1,
},
start: {
x: orginX3,
y: startY,
},
width: textWidth,
height: config.fontSize,
text: item.text,
color: item.color,
}
lastTextObject = avoidCollision(textObject, lastTextObject)
textObjectCollection.push(lastTextObject)
})
textObjectCollection.forEach(item => {
const lineStartPoistion = convertCoordinateOrigin(
item.lineStart.x,
item.lineStart.y,
center,
)
const lineEndPoistion = convertCoordinateOrigin(
item.lineEnd.x,
item.lineEnd.y,
center,
)
const textPosition = convertCoordinateOrigin(
item.start.x,
item.start.y,
center,
)
context.setLineWidth(1)
context.setFontSize(config.fontSize)
context.beginPath()
context.setStrokeStyle(item.color)
context.setFillStyle(item.color)
context.moveTo(lineStartPoistion.x, lineStartPoistion.y)
const curveStartX =
item.start.x < 0 ? textPosition.x + item.width : textPosition.x
const textStartX =
item.start.x < 0 ? textPosition.x - 10 : textPosition.x + 10
context.quadraticCurveTo(
lineEndPoistion.x,
lineEndPoistion.y,
curveStartX,
textPosition.y,
)
context.moveTo(lineStartPoistion.x, lineStartPoistion.y)
context.stroke()
context.closePath()
context.beginPath()
context.moveTo(textPosition.x + item.width, textPosition.y)
context.arc(curveStartX, textPosition.y, 2, 0, 2 * Math.PI)
context.closePath()
context.fill()
context.beginPath()
context.setFontSize(opts.fontSize)
context.setFillStyle(item.color || '#666666')
context.fillText(item.text, textStartX - 6, textPosition.y + 5)
context.closePath()
context.stroke()
context.closePath()
})
}
function drawToolTipSplitLine(offsetX, opts, config, context) {
const startY = config.padding
const endY =
opts.height - config.padding - config.xAxisHeight - config.legendHeight
context.beginPath()
context.setStrokeStyle('#cccccc')
context.setLineWidth(1)
context.moveTo(offsetX, startY)
context.lineTo(offsetX, endY)
context.stroke()
context.closePath()
}
function drawToolTip(textList, offset, opts, config, context) {
const legendWidth = 4
const legendMarginRight = 5
const arrowWidth = 8
let isOverRightBorder = false
offset = assign(
{
x: 0,
y: 0,
},
offset,
)
offset.y -= 8
const textWidth = textList.map(item => measureText(item.text))
const toolTipWidth =
legendWidth +
legendMarginRight +
4 * config.toolTipPadding +
Math.max.apply(null, textWidth)
const toolTipHeight =
2 * config.toolTipPadding + textList.length * config.toolTipLineHeight
// if beyond the right border
if (
offset.x - Math.abs(opts._scrollDistance_) + arrowWidth + toolTipWidth >
opts.width
) {
isOverRightBorder = true
}
// draw background rect
context.beginPath()
// hacker tooltip grd
const grd = context.createLinearGradient(0, 150, 0, 50)
grd.addColorStop(0, config.toolTipBackground)
grd.addColorStop(1, config.toolTipBackground)
context.setFillStyle(grd)
// context.setFillStyle(
// opts.tooltip.option.background || config.toolTipBackground,
// )
context.setGlobalAlpha(config.toolTipOpacity)
if (isOverRightBorder) {
context.moveTo(offset.x, offset.y + 10)
context.lineTo(offset.x - arrowWidth, offset.y + 10 - 5)
context.lineTo(offset.x - arrowWidth, offset.y + 10 + 5)
context.moveTo(offset.x, offset.y + 10)
context.fillRect(
offset.x - toolTipWidth - arrowWidth,
offset.y,
toolTipWidth,
toolTipHeight,
)
} else {
context.moveTo(offset.x, offset.y + 10)
context.lineTo(offset.x + arrowWidth, offset.y + 10 - 5)
context.lineTo(offset.x + arrowWidth, offset.y + 10 + 5)
context.moveTo(offset.x, offset.y + 10)
context.fillRect(
offset.x + arrowWidth,
offset.y,
toolTipWidth,
toolTipHeight,
)
}
context.closePath()
context.fill()
context.setGlobalAlpha(1)
// draw legend
textList.forEach((item, index) => {
context.beginPath()
const tooltipTextGrd = context.createLinearGradient(0, 150, 0, 50)
tooltipTextGrd.addColorStop(0, item.color)
tooltipTextGrd.addColorStop(1, item.color)
context.setFillStyle(tooltipTextGrd)
let startX = offset.x + arrowWidth + 2 * config.toolTipPadding
const startY =
offset.y +
(config.toolTipLineHeight - config.fontSize) / 2 +
config.toolTipLineHeight * index +
config.toolTipPadding
if (isOverRightBorder) {
startX = offset.x - toolTipWidth - arrowWidth + 2 * config.toolTipPadding
}
context.fillRect(startX, startY, legendWidth, config.fontSize)
context.closePath()
})
// draw text list
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle('#ffffff')
textList.forEach((item, index) => {
let startX =
offset.x +
arrowWidth +
2 * config.toolTipPadding +
legendWidth +
legendMarginRight
if (isOverRightBorder) {
startX =
offset.x -
toolTipWidth -
arrowWidth +
2 * config.toolTipPadding +
Number(legendWidth) +
legendMarginRight
}
const startY =
offset.y +
(config.toolTipLineHeight - config.fontSize) / 2 +
config.toolTipLineHeight * index +
config.toolTipPadding
context.fillText(item.text, startX, startY + config.fontSize)
})
context.stroke()
context.closePath()
}
function drawYAxisTitle(title, opts, config, context) {
const startX =
config.xAxisHeight +
(opts.height - config.xAxisHeight - measureText(title)) / 2
context.save()
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle(opts.yAxis.titleFontColor || '#333333')
context.translate(0, opts.height)
context.rotate(-90 * Math.PI / 180)
context.fillText(title, startX, config.padding + 0.5 * config.fontSize)
context.stroke()
context.closePath()
context.restore()
}
function drawColumnDataPoints(series, opts, config, context) {
const process =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1
let _calYAxisData = calYAxisData(series, opts, config),
ranges = _calYAxisData.ranges
let _getXAxisPoints = getXAxisPoints(opts.categories, opts, config),
xAxisPoints = _getXAxisPoints.xAxisPoints,
eachSpacing = _getXAxisPoints.eachSpacing
const minRange = ranges.pop()
const maxRange = ranges.shift()
context.save()
if (
opts._scrollDistance_ &&
opts._scrollDistance_ !== 0 &&
opts.enableScroll === true
) {
context.translate(opts._scrollDistance_, 0)
}
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
let points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
points = fixColumeData(
points,
eachSpacing,
series.length,
seriesIndex,
config,
opts,
)
// 绘制柱状数据图
context.beginPath()
context.setFillStyle(eachSeries.color)
points.forEach((item, index) => {
if (item !== null) {
const startX = item.x - item.width / 2 + 1
const height =
opts.height -
item.y -
config.padding -
config.xAxisHeight -
config.legendHeight
context.moveTo(startX, item.y)
context.rect(startX, item.y, item.width - 2, height)
}
})
context.closePath()
context.fill()
})
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
let points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
points = fixColumeData(
points,
eachSpacing,
series.length,
seriesIndex,
config,
opts,
)
if (opts.dataLabel !== false && process === 1) {
drawPointText(points, eachSeries, config, context)
}
})
context.restore()
return {
xAxisPoints,
eachSpacing,
}
}
function drawAreaDataPoints(series, opts, config, context) {
const process =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1
let _calYAxisData2 = calYAxisData(series, opts, config),
ranges = _calYAxisData2.ranges
let _getXAxisPoints2 = getXAxisPoints(opts.categories, opts, config),
xAxisPoints = _getXAxisPoints2.xAxisPoints,
eachSpacing = _getXAxisPoints2.eachSpacing
const minRange = ranges.pop()
const maxRange = ranges.shift()
const endY =
opts.height - config.padding - config.xAxisHeight - config.legendHeight
const calPoints = []
context.save()
if (
opts._scrollDistance_ &&
opts._scrollDistance_ !== 0 &&
opts.enableScroll === true
) {
context.translate(opts._scrollDistance_, 0)
}
if (
opts.tooltip &&
opts.tooltip.textList &&
opts.tooltip.textList.length &&
process === 1
) {
drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context)
}
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
const points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
calPoints.push(points)
const splitPointList = splitPoints(points)
splitPointList.forEach(points => {
// 绘制区域数据
context.beginPath()
context.setStrokeStyle(eachSeries.color)
// 添加线性渐变
if (eachSeries.gradient) {
const grd = context.createLinearGradient(0, 150, 0, 50)
grd.addColorStop(0, eachSeries.color)
grd.addColorStop(1, eachSeries.endColor)
context.setFillStyle(grd)
} else {
context.setFillStyle(eachSeries.color)
}
context.setGlobalAlpha(eachSeries.globalAlpha || 0.6)
context.setLineWidth(2)
if (points.length > 1) {
const firstPoint = points[0]
const lastPoint = points[points.length - 1]
context.moveTo(firstPoint.x, firstPoint.y)
if (opts.extra.lineStyle === 'curve') {
points.forEach((item, index) => {
if (index > 0) {
const ctrlPoint = createCurveControlPoints(points, index - 1)
context.bezierCurveTo(
ctrlPoint.ctrA.x,
ctrlPoint.ctrA.y,
ctrlPoint.ctrB.x,
ctrlPoint.ctrB.y,
item.x,
item.y,
)
}
})
} else {
points.forEach((item, index) => {
if (index > 0) {
context.lineTo(item.x, item.y)
}
})
}
if (eachSeries.outlineColor) {
context.setGlobalAlpha(1)
context.setStrokeStyle(eachSeries.outlineColor)
context.stroke()
context.setGlobalAlpha(eachSeries.globalAlpha || 0.6)
context.setStrokeStyle(eachSeries.color)
}
context.lineTo(lastPoint.x, endY)
context.lineTo(firstPoint.x, endY)
context.lineTo(firstPoint.x, firstPoint.y)
} else {
const item = points[0]
context.moveTo(item.x - eachSpacing / 2, item.y)
context.lineTo(item.x + eachSpacing / 2, item.y)
context.lineTo(item.x + eachSpacing / 2, endY)
context.lineTo(item.x - eachSpacing / 2, endY)
context.moveTo(item.x - eachSpacing / 2, item.y)
}
context.closePath()
context.fill()
context.setGlobalAlpha(1)
// hacker grd
const grd = context.createLinearGradient(0, 150, 0, 50)
grd.addColorStop(0, opts.background)
grd.addColorStop(1, opts.background)
context.setFillStyle(grd)
})
if (opts.dataPointShape !== false) {
const shape =
config.dataPointShape[seriesIndex % config.dataPointShape.length]
drawPointShape(points, eachSeries.color, shape, context)
}
})
if (opts.dataLabel !== false && process === 1) {
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
const points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
drawPointText(points, eachSeries, config, context)
})
}
context.restore()
return {
xAxisPoints,
calPoints,
eachSpacing,
}
}
function drawLineDataPoints(series, opts, config, context) {
const process =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1
let _calYAxisData3 = calYAxisData(series, opts, config),
ranges = _calYAxisData3.ranges
let _getXAxisPoints3 = getXAxisPoints(opts.categories, opts, config),
xAxisPoints = _getXAxisPoints3.xAxisPoints,
eachSpacing = _getXAxisPoints3.eachSpacing
const minRange = ranges.pop()
const maxRange = ranges.shift()
const calPoints = []
context.save()
if (
opts._scrollDistance_ &&
opts._scrollDistance_ !== 0 &&
opts.enableScroll === true
) {
context.translate(opts._scrollDistance_, 0)
}
if (
opts.tooltip &&
opts.tooltip.textList &&
opts.tooltip.textList.length &&
process === 1
) {
drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context)
}
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
const points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
calPoints.push(points)
const splitPointList = splitPoints(points)
splitPointList.forEach((points, index) => {
context.beginPath()
context.setStrokeStyle(eachSeries.color)
context.setLineWidth(2)
if (points.length === 1) {
context.moveTo(points[0].x, points[0].y)
context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI)
} else {
context.moveTo(points[0].x, points[0].y)
if (opts.extra.lineStyle === 'curve') {
points.forEach((item, index) => {
if (index > 0) {
const ctrlPoint = createCurveControlPoints(points, index - 1)
context.bezierCurveTo(
ctrlPoint.ctrA.x,
ctrlPoint.ctrA.y,
ctrlPoint.ctrB.x,
ctrlPoint.ctrB.y,
item.x,
item.y,
)
}
})
} else {
points.forEach((item, index) => {
if (index > 0) {
context.lineTo(item.x, item.y)
}
})
}
context.moveTo(points[0].x, points[0].y)
}
context.closePath()
context.stroke()
})
if (opts.dataPointShape !== false) {
const shape =
config.dataPointShape[seriesIndex % config.dataPointShape.length]
drawPointShape(points, eachSeries.color, shape, context)
}
})
if (opts.dataLabel !== false && process === 1) {
series.forEach((eachSeries, seriesIndex) => {
const data = eachSeries.data
const points = getDataPoints(
data,
minRange,
maxRange,
xAxisPoints,
eachSpacing,
opts,
config,
process,
)
drawPointText(points, eachSeries, config, context)
})
}
context.restore()
return {
xAxisPoints,
calPoints,
eachSpacing,
}
}
function drawToolTipBridge(opts, config, context, process) {
context.save()
if (
opts._scrollDistance_ &&
opts._scrollDistance_ !== 0 &&
opts.enableScroll === true
) {
context.translate(opts._scrollDistance_, 0)
}
if (
opts.tooltip &&
opts.tooltip.textList &&
opts.tooltip.textList.length &&
process === 1
) {
drawToolTip(
opts.tooltip.textList,
opts.tooltip.offset,
opts,
config,
context,
)
}
context.restore()
}
function drawXAxis(categories, opts, config, context) {
let _getXAxisPoints4 = getXAxisPoints(categories, opts, config),
xAxisPoints = _getXAxisPoints4.xAxisPoints,
startX = _getXAxisPoints4.startX,
endX = _getXAxisPoints4.endX,
eachSpacing = _getXAxisPoints4.eachSpacing
const startY =
opts.height - config.padding - config.xAxisHeight - config.legendHeight
const endY = startY + config.xAxisLineHeight
context.save()
if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
context.translate(opts._scrollDistance_, 0)
}
context.beginPath()
context.setStrokeStyle(opts.xAxis.gridColor || '#cccccc')
if (opts.xAxis.disableGrid !== true) {
if (opts.xAxis.type === 'calibration') {
xAxisPoints.forEach((item, index) => {
if (index > 0) {
context.moveTo(item - eachSpacing / 2, startY)
context.lineTo(item - eachSpacing / 2, startY + 4)
}
})
} else {
xAxisPoints.forEach((item, index) => {
context.moveTo(item, startY)
context.lineTo(item, endY)
})
}
}
context.closePath()
context.stroke()
// 对X轴列表做抽稀处理
const validWidth =
opts.width - 2 * config.padding - config.yAxisWidth - config.yAxisTitleWidth
const maxXAxisListLength = Math.min(
categories.length,
Math.ceil(validWidth / config.fontSize / 1.5),
)
const ratio = Math.ceil(
categories.length /
(opts.xAxis.maxPoints && opts.xAxis.maxPoints < maxXAxisListLength - 1
? opts.xAxis.maxPoints
: maxXAxisListLength),
)
categories = categories.map(
(item, index) =>
index % ratio !== 0 && index !== categories.length - 1 ? '' : item,
)
if (opts.xAxis.noRotate || config._xAxisTextAngle_ === 0) {
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle(opts.xAxis.fontColor || '#666666')
categories.forEach((item, index) => {
const offset =
eachSpacing / 2 -
measureText(item) / 2 -
(index === categories.length - 1 ? 4 : 0)
context.fillText(
item,
xAxisPoints[index] + offset,
startY + config.fontSize + 5,
)
})
context.closePath()
context.stroke()
} else {
categories.forEach((item, index) => {
context.save()
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle(opts.xAxis.fontColor || '#666666')
const textWidth = measureText(item)
const offset = eachSpacing / 2 - textWidth
let _calRotateTranslate = calRotateTranslate(
xAxisPoints[index] + eachSpacing / 2,
startY + config.fontSize / 2 + 5,
opts.height,
),
transX = _calRotateTranslate.transX,
transY = _calRotateTranslate.transY
context.rotate(-1 * config._xAxisTextAngle_)
context.translate(transX, transY)
// 不绘制x轴
if (!opts.xAxis.disabled) {
context.fillText(
item,
xAxisPoints[index] + offset,
startY + config.fontSize + 5,
)
}
context.closePath()
context.stroke()
context.restore()
})
}
context.restore()
}
function drawYAxisGrid(opts, config, context) {
const spacingValid =
opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight
const eachSpacing = Math.floor(spacingValid / config.yAxisSplit)
const yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth
const startX = config.padding + yAxisTotalWidth
const endX = opts.width - config.padding
const points = []
for (let i = 0; i < config.yAxisSplit; i++) {
points.push(config.padding + eachSpacing * i)
}
points.push(config.padding + eachSpacing * config.yAxisSplit + 2)
context.beginPath()
context.setStrokeStyle(opts.yAxis.gridColor || '#cccccc')
context.setLineWidth(1)
points.forEach((item, index) => {
context.moveTo(startX, item)
context.lineTo(endX, item)
})
context.closePath()
context.stroke()
// to draw the yAxis the first line
context.beginPath()
context.setStrokeStyle(opts.yAxis.gridColor || '#cccccc')
context.moveTo(startX, points[5])
context.lineTo(endX, points[5])
context.closePath()
context.stroke()
}
function drawYAxis(series, opts, config, context) {
if (opts.yAxis.disabled === true) {
return
}
let _calYAxisData4 = calYAxisData(series, opts, config),
rangesFormat = _calYAxisData4.rangesFormat
const yAxisTotalWidth = config.yAxisWidth + config.yAxisTitleWidth
const spacingValid =
opts.height - 2 * config.padding - config.xAxisHeight - config.legendHeight
const eachSpacing = Math.floor(spacingValid / config.yAxisSplit)
const startX = config.padding + yAxisTotalWidth
const endX = opts.width - config.padding
const endY =
opts.height - config.padding - config.xAxisHeight - config.legendHeight
// set YAxis background
context.setFillStyle(opts.background || '#ffffff')
if (opts._scrollDistance_ < 0) {
context.fillRect(0, 0, startX, endY + config.xAxisHeight + 5)
}
context.fillRect(endX, 0, opts.width, endY + config.xAxisHeight + 5)
const points = []
for (let i = 0; i <= config.yAxisSplit; i++) {
points.push(config.padding + eachSpacing * i)
}
context.stroke()
context.beginPath()
context.setFontSize(config.fontSize)
context.setFillStyle(opts.yAxis.fontColor || '#666666')
rangesFormat.forEach((item, index) => {
const pos = points[index] ? points[index] : endY
context.fillText(
item,
config.padding + config.yAxisTitleWidth,
pos + config.fontSize / 2,
)
})
context.closePath()
context.stroke()
if (opts.yAxis.title) {
drawYAxisTitle(opts.yAxis.title, opts, config, context)
}
}
function drawLegend(series, opts, config, context) {
if (!opts.legend) {
return
}
// each legend shape width 15px
// the spacing between shape and text in each legend is the `padding`
// each legend spacing is the `padding`
// legend margin top `config.padding`
let _calLegendData = calLegendData(series, opts, config),
legendList = _calLegendData.legendList
const padding = 5
const marginTop = 8
const shapeWidth = 15
legendList.forEach((itemList, listIndex) => {
let width = 0
itemList.forEach(item => {
item.name = item.name || 'undefined'
width += 3 * padding + measureText(item.name) + shapeWidth
})
let startX = (opts.width - width) / 2 + padding
const startY =
opts.height -
config.padding -
config.legendHeight +
listIndex * (config.fontSize + marginTop) +
padding +
marginTop
context.setFontSize(config.fontSize)
itemList.forEach(item => {
switch (opts.type) {
case 'line':
context.beginPath()
context.setLineWidth(1)
context.setStrokeStyle(item.color)
context.moveTo(startX - 2, startY + 5)
context.lineTo(startX + 17, startY + 5)
context.stroke()
context.closePath()
context.beginPath()
context.setLineWidth(1)
context.setStrokeStyle('#ffffff')
context.setFillStyle(item.color)
context.moveTo(startX + 7.5, startY + 5)
context.arc(startX + 7.5, startY + 5, 4, 0, 2 * Math.PI)
context.fill()
context.stroke()
context.closePath()
break
case 'pie':
case 'ring':
context.beginPath()
context.setFillStyle(item.color)
context.moveTo(startX + 7.5, startY + 5)
context.arc(startX + 7.5, startY + 5, 7, 0, 2 * Math.PI)
context.closePath()
context.fill()
break
default:
context.beginPath()
context.setFillStyle(item.color)
context.moveTo(startX, startY)
context.rect(startX, startY, 15, 10)
context.closePath()
context.fill()
}
startX += padding + shapeWidth
context.beginPath()
context.setFillStyle(opts.extra.legendTextColor || '#333333')
context.fillText(item.name, startX, startY + 9)
context.closePath()
context.stroke()
startX += measureText(item.name) + 2 * padding
})
})
}
function drawPieDataPoints(series, opts, config, context) {
const process =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1
const pieOption = opts.extra.pie || {}
series = getPieDataPoints(series, process)
const centerPosition = {
x: opts.width / 2,
y: (opts.height - config.legendHeight) / 2,
}
let radius = Math.min(
centerPosition.x -
config.pieChartLinePadding -
config.pieChartTextPadding -
config._pieTextMaxLength_,
centerPosition.y - config.pieChartLinePadding - config.pieChartTextPadding,
)
if (opts.dataLabel) {
radius -= 10
} else {
radius -= 2 * config.padding
}
series = series.map(eachSeries => {
eachSeries._start_ += (pieOption.offsetAngle || 0) * Math.PI / 180
return eachSeries
})
series.forEach(eachSeries => {
context.beginPath()
context.setLineWidth(2)
context.setStrokeStyle('#ffffff')
context.setFillStyle(eachSeries.color)
context.moveTo(centerPosition.x, centerPosition.y)
context.arc(
centerPosition.x,
centerPosition.y,
radius,
eachSeries._start_,
eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI,
)
context.closePath()
context.fill()
if (opts.disablePieStroke !== true) {
context.stroke()
}
})
if (opts.type === 'ring') {
let innerPieWidth = radius * 0.6
if (typeof opts.extra.ringWidth === 'number' && opts.extra.ringWidth > 0) {
innerPieWidth = Math.max(0, radius - opts.extra.ringWidth)
}
context.beginPath()
context.setFillStyle(opts.background || '#ffffff')
context.moveTo(centerPosition.x, centerPosition.y)
context.arc(
centerPosition.x,
centerPosition.y,
innerPieWidth,
0,
2 * Math.PI,
)
context.closePath()
context.fill()
}
if (opts.dataLabel !== false && process === 1) {
// fix https://github.com/xiaolin3303/wx-charts/issues/132
let valid = false
for (let i = 0, len = series.length; i < len; i++) {
if (series[i].data > 0) {
valid = true
break
}
}
if (valid) {
drawPieText(series, opts, config, context, radius, centerPosition)
}
}
if (process === 1 && opts.type === 'ring') {
drawRingTitle(opts, config, context)
}
return {
center: centerPosition,
radius,
series,
}
}
function drawRadarDataPoints(series, opts, config, context) {
const process =
arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1
const radarOption = opts.extra.radar || {}
const coordinateAngle = getRadarCoordinateSeries(opts.categories.length)
const centerPosition = {
x: opts.width / 2,
y: (opts.height - config.legendHeight) / 2,
}
let radius = Math.min(
centerPosition.x -
(getMaxTextListLength(opts.categories) + config.radarLabelTextMargin),
centerPosition.y - config.radarLabelTextMargin,
)
radius -= config.padding
// draw grid
context.beginPath()
context.setLineWidth(1)
context.setStrokeStyle(radarOption.gridColor || '#cccccc')
coordinateAngle.forEach(angle => {
const pos = convertCoordinateOrigin(
radius * Math.cos(angle),
radius * Math.sin(angle),
centerPosition,
)
context.moveTo(centerPosition.x, centerPosition.y)
context.lineTo(pos.x, pos.y)
})
context.stroke()
context.closePath()
// draw split line grid
const _loop = function _loop(i) {
let startPos = {}
context.beginPath()
context.setLineWidth(1)
context.setStrokeStyle(radarOption.gridColor || '#cccccc')
coordinateAngle.forEach((angle, index) => {
const pos = convertCoordinateOrigin(
radius / config.radarGridCount * i * Math.cos(angle),
radius / config.radarGridCount * i * Math.sin(angle),
centerPosition,
)
if (index === 0) {
startPos = pos
context.moveTo(pos.x, pos.y)
} else {
context.lineTo(pos.x, pos.y)
}
})
context.lineTo(startPos.x, startPos.y)
context.stroke()
context.closePath()
}
for (let i = 1; i <= config.radarGridCount; i++) {
_loop(i)
}
const radarDataPoints = getRadarDataPoints(
coordinateAngle,
centerPosition,
radius,
series,
opts,
process,
)
radarDataPoints.forEach((eachSeries, seriesIndex) => {
// 绘制区域数据
context.beginPath()
context.setFillStyle(eachSeries.color)
context.setGlobalAlpha(0.6)
eachSeries.data.forEach((item, index) => {
if (index === 0) {
context.moveTo(item.position.x, item.position.y)
} else {
context.lineTo(item.position.x, item.position.y)
}
})
context.closePath()
context.fill()
context.setGlobalAlpha(1)
if (opts.dataPointShape !== false) {
const shape =
config.dataPointShape[seriesIndex % config.dataPointShape.length]
const points = eachSeries.data.map(item => item.position)
drawPointShape(points, eachSeries.color, shape, context)
}
})
// draw label text
drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context)
return {
center: centerPosition,
radius,
angleList: coordinateAngle,
}
}
function drawCanvas(opts, context) {
context.draw()
}
const Timing = {
easeIn: function easeIn(pos) {
return Math.pow(pos, 3)
},
easeOut: function easeOut(pos) {
return Math.pow(pos - 1, 3) + 1
},
easeInOut: function easeInOut(pos) {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 3)
} else {
return 0.5 * (Math.pow(pos - 2, 3) + 2)
}
},
linear: function linear(pos) {
return pos
},
}
function Animation(opts) {
this.isStop = false
opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration
opts.timing = opts.timing || 'linear'
const delay = 17
const createAnimationFrame = function createAnimationFrame() {
if (typeof requestAnimationFrame !== 'undefined') {
return requestAnimationFrame
} else if (typeof setTimeout !== 'undefined') {
return function(step, delay) {
setTimeout(() => {
const timeStamp = Number(new Date())
step(timeStamp)
}, delay)
}
} else {
return function(step) {
step(null)
}
}
}
const animationFrame = createAnimationFrame()
let startTimeStamp = null
var _step = function step(timestamp) {
if (timestamp === null || this.isStop === true) {
opts.onProcess && opts.onProcess(1)
opts.onAnimationFinish && opts.onAnimationFinish()
return
}
if (startTimeStamp === null) {
startTimeStamp = timestamp
}
if (timestamp - startTimeStamp < opts.duration) {
let process = (timestamp - startTimeStamp) / opts.duration
const timingFunction = Timing[opts.timing]
process = timingFunction(process)
opts.onProcess && opts.onProcess(process)
animationFrame(_step, delay)
} else {
opts.onProcess && opts.onProcess(1)
opts.onAnimationFinish && opts.onAnimationFinish()
}
}
_step = _step.bind(this)
animationFrame(_step, delay)
}
// stop animation immediately
// and tigger onAnimationFinish
Animation.prototype.stop = function() {
this.isStop = true
}
function drawCharts(type, opts, config, context) {
// context.setFillStyle(opts.background)
// context.fillRect(0, 0, opts.width, opts.height)
const _this = this
let series = opts.series
const categories = opts.categories
series = fillSeriesColor(series, config)
let _calLegendData = calLegendData(series, opts, config),
legendHeight = _calLegendData.legendHeight
config.legendHeight = legendHeight
let _calYAxisData = calYAxisData(series, opts, config),
yAxisWidth = _calYAxisData.yAxisWidth
config.yAxisWidth = yAxisWidth
if (categories && categories.length) {
let _calCategoriesData = calCategoriesData(categories, opts, config),
xAxisHeight = _calCategoriesData.xAxisHeight,
angle = _calCategoriesData.angle
config.xAxisHeight = xAxisHeight
config._xAxisTextAngle_ = angle
}
if (type === 'pie' || type === 'ring') {
config._pieTextMaxLength_ =
opts.dataLabel === false ? 0 : getPieTextMaxLength(series)
}
const duration = opts.animation ? 1000 : 0
this.animationInstance && this.animationInstance.stop()
switch (type) {
case 'line':
this.animationInstance = new Animation({
timing: 'easeIn',
duration,
onProcess: function onProcess(process) {
drawYAxisGrid(opts, config, context)
let _drawLineDataPoints = drawLineDataPoints(
series,
opts,
config,
context,
process,
),
xAxisPoints = _drawLineDataPoints.xAxisPoints,
calPoints = _drawLineDataPoints.calPoints,
eachSpacing = _drawLineDataPoints.eachSpacing
_this.chartData.xAxisPoints = xAxisPoints
_this.chartData.calPoints = calPoints
_this.chartData.eachSpacing = eachSpacing
// drawXAxis(categories, opts, config, context)
drawLegend(opts.series, opts, config, context)
drawYAxis(series, opts, config, context)
drawToolTipBridge(opts, config, context, process)
drawCanvas(opts, context)
},
onAnimationFinish: function onAnimationFinish() {
_this.event.trigger('renderComplete')
},
})
break
case 'column':
this.animationInstance = new Animation({
timing: 'easeIn',
duration,
onProcess: function onProcess(process) {
drawYAxisGrid(opts, config, context)
let _drawColumnDataPoints = drawColumnDataPoints(
series,
opts,
config,
context,
process,
),
xAxisPoints = _drawColumnDataPoints.xAxisPoints,
eachSpacing = _drawColumnDataPoints.eachSpacing
_this.chartData.xAxisPoints = xAxisPoints
_this.chartData.eachSpacing = eachSpacing
drawXAxis(categories, opts, config, context)
drawLegend(opts.series, opts, config, context)
drawYAxis(series, opts, config, context)
drawCanvas(opts, context)
},
onAnimationFinish: function onAnimationFinish() {
_this.event.trigger('renderComplete')
},
})
break
case 'area':
this.animationInstance = new Animation({
timing: 'easeIn',
duration,
onProcess: function onProcess(process) {
drawYAxisGrid(opts, config, context)
let _drawAreaDataPoints = drawAreaDataPoints(
series,
opts,
config,
context,
process,
),
xAxisPoints = _drawAreaDataPoints.xAxisPoints,
calPoints = _drawAreaDataPoints.calPoints,
eachSpacing = _drawAreaDataPoints.eachSpacing
_this.chartData.xAxisPoints = xAxisPoints
_this.chartData.calPoints = calPoints
_this.chartData.eachSpacing = eachSpacing
drawXAxis(categories, opts, config, context)
drawLegend(opts.series, opts, config, context)
drawYAxis(series, opts, config, context)
drawToolTipBridge(opts, config, context, process)
drawCanvas(opts, context)
},
onAnimationFinish: function onAnimationFinish() {
_this.event.trigger('renderComplete')
},
})
break
case 'ring':
case 'pie':
this.animationInstance = new Animation({
timing: 'easeInOut',
duration,
onProcess: function onProcess(process) {
_this.chartData.pieData = drawPieDataPoints(
series,
opts,
config,
context,
process,
)
drawLegend(opts.series, opts, config, context)
drawCanvas(opts, context)
},
onAnimationFinish: function onAnimationFinish() {
_this.event.trigger('renderComplete')
},
})
break
case 'radar':
this.animationInstance = new Animation({
timing: 'easeInOut',
duration,
onProcess: function onProcess(process) {
_this.chartData.radarData = drawRadarDataPoints(
series,
opts,
config,
context,
process,
)
drawLegend(opts.series, opts, config, context)
drawCanvas(opts, context)
},
onAnimationFinish: function onAnimationFinish() {
_this.event.trigger('renderComplete')
},
})
break
}
}
// simple event implement
function Event() {
this.events = {}
}
Event.prototype.addEventListener = function(type, listener) {
this.events[type] = this.events[type] || []
this.events[type].push(listener)
}
Event.prototype.trigger = function() {
for (
var _len = arguments.length, args = Array(_len), _key = 0;
_key < _len;
_key++
) {
args[_key] = arguments[_key]
}
const type = args[0]
const params = args.slice(1)
if (this.events[type]) {
this.events[type].forEach(listener => {
try {
listener(...params)
} catch (e) {
console.error(e)
}
})
}
}
const Charts = function Charts(opts) {
opts.title = opts.title || {}
opts.subtitle = opts.subtitle || {}
opts.yAxis = opts.yAxis || {}
opts.xAxis = opts.xAxis || {}
opts.extra = opts.extra || {}
opts.legend = opts.legend !== false
opts.animation = opts.animation !== false
const config$$1 = assign({}, config)
config$$1.yAxisTitleWidth =
opts.yAxis.disabled !== true && opts.yAxis.title
? config$$1.yAxisTitleWidth
: 0
config$$1.pieChartLinePadding =
opts.dataLabel === false ? 0 : config$$1.pieChartLinePadding
config$$1.pieChartTextPadding =
opts.dataLabel === false ? 0 : config$$1.pieChartTextPadding
this.opts = opts
this.config = config$$1
this.context = wx.createCanvasContext(opts.canvasId)
// this.context.setFillStyle(grd)
// this.context.fillRect(10, 10, 150, 80)
// store calcuated chart data
// such as chart point coordinate
this.chartData = {}
this.event = new Event()
this.scrollOption = {
currentOffset: 0,
startTouchX: 0,
distance: 0,
}
drawCharts.call(this, opts.type, opts, config$$1, this.context)
}
Charts.prototype.updateData = function() {
const data =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}
this.opts.series = data.series || this.opts.series
this.opts.categories = data.categories || this.opts.categories
this.opts.height = data.height || this.opts.height
this.opts.title = assign({}, this.opts.title, data.title || {})
this.opts.subtitle = assign({}, this.opts.subtitle, data.subtitle || {})
drawCharts.call(this, this.opts.type, this.opts, this.config, this.context)
}
Charts.prototype.stopAnimation = function() {
this.animationInstance && this.animationInstance.stop()
}
Charts.prototype.addEventListener = function(type, listener) {
this.event.addEventListener(type, listener)
}
Charts.prototype.getCurrentDataIndex = function(e) {
const touches = e.touches && e.touches.length ? e.touches : e.changedTouches
if (touches && touches.length) {
let _touches$ = touches[0],
x = _touches$.x,
y = _touches$.y
if (this.opts.type === 'pie' || this.opts.type === 'ring') {
return findPieChartCurrentIndex({ x, y }, this.chartData.pieData)
} else if (this.opts.type === 'radar') {
return findRadarChartCurrentIndex(
{ x, y },
this.chartData.radarData,
this.opts.categories.length,
)
} else {
return findCurrentIndex(
{ x, y },
this.chartData.xAxisPoints,
this.opts,
this.config,
Math.abs(this.scrollOption.currentOffset),
)
}
}
return -1
}
Charts.prototype.showToolTip = function(e) {
const option =
arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
if (this.opts.type === 'line' || this.opts.type === 'area') {
const index = this.getCurrentDataIndex(e)
const currentOffset = this.scrollOption.currentOffset
const opts = assign({}, this.opts, {
_scrollDistance_: currentOffset,
animation: false,
})
if (index > -1) {
const seriesData = getSeriesDataItem(this.opts.series, index)
if (seriesData.length !== 0) {
let _getToolTipData = getToolTipData(
seriesData,
this.chartData.calPoints,
index,
this.opts.categories,
option,
),
textList = _getToolTipData.textList,
offset = _getToolTipData.offset
opts.tooltip = {
textList,
offset,
option,
}
}
}
drawCharts.call(this, opts.type, opts, this.config, this.context)
}
}
Charts.prototype.scrollStart = function(e) {
if (e.touches[0] && this.opts.enableScroll === true) {
this.scrollOption.startTouchX = e.touches[0].x
}
}
Charts.prototype.scroll = function(e) {
// TODO throtting...
if (e.touches[0] && this.opts.enableScroll === true) {
let _distance = e.touches[0].x - this.scrollOption.startTouchX
const currentOffset = this.scrollOption.currentOffset
const validDistance = calValidDistance(
currentOffset + _distance,
this.chartData,
this.config,
this.opts,
)
this.scrollOption.distance = _distance = validDistance - currentOffset
const opts = assign({}, this.opts, {
_scrollDistance_: currentOffset + _distance,
animation: false,
})
drawCharts.call(this, opts.type, opts, this.config, this.context)
}
}
Charts.prototype.scrollEnd = function(e) {
if (this.opts.enableScroll === true) {
let _scrollOption = this.scrollOption,
currentOffset = _scrollOption.currentOffset,
distance = _scrollOption.distance
this.scrollOption.currentOffset = currentOffset + distance
this.scrollOption.distance = 0
}
}
module.exports = Charts
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