Commit 6a8bdd26 by Jason Zhou

less to wxss

parent f9bc81d9
Page({
onLoad() {
wx.switchTab({
url: '/pages/blog/postIndex/postIndex',
})
},
})
<template name="img-content">
<view class="comment-img" style="border:1px solid #f5f5f5;" bindtap="previewCommentImage"
data-event-src="{{pictures}}" data-event-image="{{item.pictures}}">
<image style="width: 180rpx; height:180rpx;" mode="aspectFill" src="{{pictures}}"></image>
</view>
</template>
<template name="comment-list">
<view class="comment-list">
<view class="logo-icon">
<image src="{{item.avatarPhoto[0]}}" mode="aspectFill"></image>
</view>
<view class="comment-text">
<view class="t-header">
<text class="name">{{item.nickname}}</text>
<text class="time">{{item.createdAt}}</text>
</view>
<view class="t-body">
<view class="seller-payment">
<block wx:if="{{item.content.length > 82}}">
<block wx:if="{{item.isExtend}}">
{{item.shortContent}}
<text bindtap="onIsExtend" data-event-id="{{item.id}}">更多</text>
</block>
<block wx:else>
{{item.content}}
<text bindtap="onIsExtend" data-event-id="{{item.id}}">收起</text>
</block>
</block>
<block wx:else>
{{item.content}}
</block>
</view>
<view class="comment-img-box">
<view class="comment-picture">
<view wx:for="{{item.lessCommentImg || []}}" wx:key="{{item}}" wx:for-item="pictures">
<template is="img-content" data="{{pictures, item}}"/>
</view>
<view wx:if="{{item && item.pictures.length > 3}}" class="more-image"
data-event-src="{{item.moreCommentImg[0]}}" data-event-image="{{item.pictures}}"
bindtap="previewCommentImage">
<image style="width: 180rpx; height: 180rpx;" mode="aspectFill"
src="{{item.moreCommentImg}}"></image>
<view style="width: 45rpx; height: 33rpx;" class="overlay">
{{item.pictures.length || 0}}
</view>
</view>
<view wx:if="{{item && item.pictures.length === 2 }}">
<view class="store-img">
<image style="width: 180rpx; height: 180rpx;"></image>
</view>
</view>
</view>
<view class="replay-panel" wx:if="{{item.replyStatus === 'visible'}}">
<view class="store-boss">
<text>作者回复:</text>
<block wx:if="{{item.reply && item.reply.length > 82}}">
<block wx:if="{{item.isReplyExtend}}">
{{item.shortReply}}
<text bindtap="onIsReplyExtend" data-event-id="{{item.id}}">更多</text>
</block>
<block wx:else>
{{item.reply}}
<text bindtap="onIsReplyExtend" data-event-id="{{item.id}}">收起</text>
</block>
</block>
<block wx:else>
{{item.reply}}
</block>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
.comment-list {
margin-bottom: 25px;
display: flex;
}
.comment-list .logo-icon {
display: flex;
align-items: flex-start;
justify-content: flex-start;
margin-right: 20rpx;
flex: 0 0 auto;
}
.comment-list .logo-icon image {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
}
.comment-list .comment-text .t-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 3px;
width: 560rpx;
}
.comment-list .comment-text .t-header .name {
font-size: 15px;
color: #292929;
font-weight: 500;
}
.comment-list .comment-text .t-header .time {
font-size: 12px;
color: #b1b1b1;
}
.comment-list .comment-text .t-body .replay-panel {
border-top: 1px solid #f3f3f3;
margin-top: 3px;
padding-top: 6px;
}
.comment-list .comment-text .t-body .replay-panel .store-boss {
font-size: 15px !important;
line-height: 1.93;
color: #757575 !important;
}
.comment-list .comment-text .t-body .replay-panel .store-boss text {
font-size: 15px !important;
color: #6D8697 !important;
}
.comment-list .comment-text .t-body .seller-payment {
font-size: 15px;
line-height: 1.79;
color: #757575;
display: block;
}
.comment-list .comment-text .t-body .seller-payment text {
font-size: 15px;
color: #6D8697;
}
.comment-list .comment-text .t-body text {
font-size: 13px;
color: #a9aeb2;
display: inline-flex;
justify-content: flex-end;
}
\ No newline at end of file
{
"navigationBarTitleText": ""
}
\ No newline at end of file
<import src="root/utils/wxParse/wxParse.wxml" />
<import src="./postItem/postItem.wxml" />
<import src="./commentList/commentList.wxml" />
<import src="root/templates/messageModal/messageModal.wxml" />
<import src="root/templates/shareView/shareView.wxml" />
<import src="root/templates/loaderPage/loaderPage.wxml" />
<view wx:if="{{errorMessage}}">
<template is="message-modal" data="{{message: errorMessage}}" />
</view>
<!-- 使用loading 占位 -->
<block wx:if="{{!errorMessage && !currentPost.id}}">
<template is='loader-page' />
</block>
<block class="post-detail" wx:if="{{currentPost}}">
<image wx:if="{{isFromSharedProductCard || isFromSharedProductQRCode}}" src="{{homeBtn}}" class="home-btn" bindtap="gotoHome" />
<view class="{{'post-header flex-col' + (backgroundUrl ? '' : ' no-background')}}" style="background-image: url('{{backgroundUrl}}');">
<view class="flex-col normal-flex post-header-wrapper bg-{{textColor}}">
<view class="post-title post-item-wrapper">
<view class="title">
<template wx:if="{{headerTitle.nodes}}" is="wxParse" data="{{...headerTitle}}" />
</view>
<view class="subTitle">
<template wx:if="{{headerSubTitle.nodes}}" is="wxParse" data="{{...headerSubTitle}}" />
</view>
<view class="time">{{ publishedAt }}</view>
</view>
</view>
<view wx:if="{{allTagsList[0]}}" class="tags">{{allTagsList[0]}}</view>
</view>
<view class="post-body">
<!-- 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">
<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.Title' || item.component.type == 'Title'}}" is="post-item-title" data="{{...item.component.value}}" />
<template wx:if="{{item.component.type == 'Blog.Image' || item.component.type == 'Image'}}" is="post-item-image" data="{{...item.component}}" />
<template wx:if="{{item.component.type == 'Blog.Video' || item.component.type == 'Video'}}" is="post-item-video" data="{{...item.component}}" />
<template wx:if="{{item.component.type == 'Blog.Separator' || item.component.type == 'Separator'}}" is="post-item-separator" data="{{...item.component}}" />
</block>
<view class="share-container">
<view class="share-btn" bindtap="openShareView">
<image src="{{iconShare}}" />
<text>分享文章</text>
</view>
<button wx:if="{{currentPost.isLiked}}" class="share-btn" data-is-liked="{{currentPost.isLiked}}" open-type="getUserInfo" bindgetuserinfo="handleLike" style="margin-left: 25rpx; background-color: #708694;">
<image src="{{iconMomentLikeTotal}}" />
<text style="color: white">已赞</text>
</button>
<button wx:else class="share-btn" data-is-liked="{{currentPost.isLiked}}" open-type="getUserInfo" bindgetuserinfo="handleLike" style="margin-left: 25rpx;">
<image src="{{iconMomentLike}}" />
<text>赞一下</text>
</button>
</view>
</view>
<view class="post-footer">
<block wx:if="{{canUserShare && canUseCollection}}">
<view class="flex-row footer-buttons">
<button wx:if="{{!marked}}" class="bookmark-button" bindtap="bindMark">
<image src="{{iconBookmark}}" style="width: 20px; height: 20px;" />
添加至收藏
</button>
<button wx:if="{{marked}}" class="bookmark-button marked" bindtap="bindUnmark">
<image src="{{iconBookmarked}}" style="width: 20px; height: 20px;" />
已添加收藏
</button>
<button open-type="share" class="share-button" bindtap="bindShare">
<image src="{{iconWechat}}" style="width: 20px; height: 20px;" />
分享至微信
</button>
</view>
</block>
<view class="blog-comment">
<view class="text-with-icon">
<image src="{{iconMomentLikeGrey}}" style="width: 40rpx; height: 40rpx; margin-right: 8rpx" />
<text>点赞 ({{currentPost.likes && currentPost.likes.length ? currentPost.likes.length : 0}})</text>
</view>
<block wx:if="{{enableComments}}">
<view class="text-with-icon">
<image src="{{iconComment}}" style="width: 32rpx; height: 32rpx; margin-right: 16rpx" />
<text>评论 ({{comments.length}})</text>
</view>
<form bindsubmit="onClickSubmit" report-submit="true">
<textarea wx:if="{{!showShareVariation}}" placeholder="写点什么吧…" value="{{currentComment}}" bindinput="bindTextAreaChange" class="textarea" />
<input wx:else type="text" placeholder="写点什么吧…" class="input" placeholder-style="position: absolute; top: -90rpx; left: 22rpx; background: transparent" />
<view class="flex-row submit-buttons">
<view class="text-with-icon">
<image src="{{iconLock}}" style="width: 16px; height: 16px;" />
<text class="light-text" style="font-size:14px;">评论需审核后即可显示。</text>
</view>
<button formType="submit" class="submit-button" loading="{{submitting}}" open-type="getUserInfo" bindgetuserinfo="bindGetUserInfoHandler">
提交
</button>
</view>
</form>
</block>
</view>
<block wx:if="{{enableComments}}">
<view class="comment">
<view class="c-header"></view>
<view class="c-body" wx:if="{{comments && comments.length > 0}}">
<block wx:for="{{comments}}" wx:key="item" wx:for-item="item">
<template is="comment-list" data="{{item}}" />
</block>
</view>
<block wx:if="{{isFetchingComments}}">
<view style="text-align: center; height: 20px; margin: 10px 0 50px; position: relative;">
<view class="loader" />
</view>
</block>
<block wx:else>
<view wx:if="{{comments && comments.length === 0}}" class="empty-comment-wrapper" mode="aspectFill">
<!-- <image style="width: 100vw; height: 220px;" src="/assets/blog/comment-empty-bg.png"></image> -->
<view class="add-comment-enter">
<text>暂无评论</text>
</view>
</view>
</block>
</view>
<view style="text-align: center; height: 20px; margin: 10px 0 50px; position: relative;">
<view wx:if="{{comments.length > 0 && paginationMeta.nextPage}}" class="loader" />
<text wx:if="{{comments.length > 6 && !isFetchingComments && paginationMeta.nextPage === null}}" style="line-height: 20px; font-size: 12px; color: #999; padding-bottom: 20px;">
- 无更多评论 -
</text>
</view>
</block>
</view>
</block>
<template is="share-view" data="{{shareLoading, sharePicture, shareAnimation, showShareVariation, closeShareView, openShareView}}" />
<view class="flex-row flex-col center-align-item" style="height: 100vh;" wx:if="{{!currentPost}}">
<view class="loader" />
</view>
@import "/styles/main.wxss";
@import "/utils/wxParse/wxParse.wxss";
@import '/pages/blog/postDetail/commentList/commentList.wxss';
@import '/pages/blog/postDetail/postItem/postItem.wxss';
@import "/templates/messageModal/messageModal.wxss";
@import "/templates/shareView/shareView.wxss";
.wx-view {
letter-spacing: 0.10rpx;
font-size: inherit;
color: inherit;
}
.wxParse-li-text {
overflow: visible;
}
.wxParse-li-circle {
position: relative;
top: -4rpx;
margin-right: 20rpx;
}
.home-btn {
position: fixed;
top: 30rpx;
left: 30rpx;
width: 80rpx;
height: 80rpx;
z-index: 100;
}
.post-detail {
background: #fff;
min-height: 100vh;
}
.post-header {
background-size: cover;
background-position: center;
min-height: 40vh;
}
.post-header .post-header-wrapper {
justify-content: flex-end;
}
.post-header .bg-overlay {
background: rgba(0, 0, 0, 0.4);
}
.post-header .bg-light .post-title .title {
color: #fff;
}
.post-header .bg-light .post-title .title .wxParse-p {
color: #ffffff;
}
.post-header .bg-light .post-title .subTitle {
color: #fff;
}
.post-header .bg-light .post-title .time {
color: #fff;
}
.post-header .bg-dark {
text-shadow: 0 2rpx 24rpx rgba(255, 255, 255, 0.5);
}
.post-header .bg-dark .post-title .title {
color: #161616;
}
.post-header .bg-dark .post-title .title .wxParse-p {
color: #ffffff;
}
.post-header .bg-dark .post-title .subTitle {
color: #161616;
}
.post-header .bg-dark .post-title .time {
color: #161616;
}
.post-header .tags {
z-index: 100;
color: #fff;
font-size: 24rpx;
position: absolute;
top: 40rpx;
left: 40rpx;
line-height: 46rpx;
padding: 0 16rpx;
border-radius: 4rpx;
text-align: center;
background-color: rgba(0, 0, 0, 0.4);
}
.post-header.no-background {
min-height: 20vh;
}
.post-header.no-background .bg-overlay {
background: none;
}
.post-header.no-background .post-title view {
color: #000;
}
.post-header.no-background .post-title .time {
color: #999;
}
.post-title {
font-size: 42rpx;
padding-bottom: 40rpx;
}
.post-title .title {
font-size: 42rpx;
color: #fff;
}
.post-title .subTitle {
font-size: 32rpx;
color: #fff;
margin-top: 10rpx;
}
.post-title .time {
color: #ccc;
font-size: 24rpx;
margin-top: 10rpx;
}
.post-body {
font-size: 32rpx;
line-height: 1.75;
padding-top: 50rpx;
}
.post-item-title {
font-size: 38rpx;
}
.post-footer {
background: #f7f7f7;
}
.post-footer image {
vertical-align: middle;
}
.post-footer .text-with-icon {
margin-bottom: 32rpx;
}
.post-footer .text-with-icon text,
.post-footer .text-with-icon image {
vertical-align: middle;
}
.post-footer .text-with-icon text {
margin-right: 8rpx;
}
.post-footer .footer-buttons,
.post-footer .submit-buttons {
justify-content: space-between;
}
.post-footer .footer-buttons {
background: #fff;
padding: 30rpx 40rpx 60rpx;
}
.post-footer .bookmark-button,
.post-footer .share-button {
display: inline-block;
padding: 0 54rpx;
margin: 0;
border-radius: 200rpx;
flex: 0 0 auto;
}
.post-footer .bookmark-button image,
.post-footer .share-button image {
width: 40rpx;
height: 40rpx;
}
.post-footer .bookmark-button {
background: #fff;
border: 2rpx solid #52616a;
color: #52616a;
}
.post-footer .bookmark-button.marked {
background: #52616a;
color: #fff;
}
.post-footer .share-button {
background: #fff;
color: #3aae3a;
border: 2rpx solid #3aae3a;
}
.post-footer .submit-button {
display: inline-block;
padding: 0 40rpx;
margin: 0;
background: #40404c;
color: #fff;
border-color: #40404c;
}
.post-footer .blog-comment {
padding: 40rpx;
}
.post-footer .blog-comment .textarea {
font-size: 32rpx;
width: 100%;
padding: 30rpx;
background: #fff;
box-sizing: border-box;
box-shadow: inset 0 0 8rpx 0 rgba(0, 0, 0, 0.2);
border-radius: 8rpx;
margin: 30rpx 0;
}
.post-footer .blog-comment .input {
height: 259rpx;
font-size: 32rpx;
padding: 8rpx;
background: #fff;
box-shadow: inset 0 0 8rpx 0 rgba(0, 0, 0, 0.2);
border-radius: 8rpx;
margin: 30rpx 0;
}
.post-footer .comments-list {
margin-top: 60rpx;
}
.post-footer .comments-list .comment-title {
padding: 20rpx 0;
}
.post-footer .comments-list .comment-title .avatar-image {
margin-right: 20rpx;
border-radius: 50rpx;
}
.post-footer .comments-list .comment-title .comment-name {
vertical-align: middle;
display: inline-block;
}
.post-footer .comments-list .comment-text {
line-height: 1.93;
}
.share-container {
display: flex;
justify-content: center;
margin-bottom: 60rpx;
}
.share-container .share-btn {
display: flex;
justify-content: center;
align-items: center;
border-radius: 100rpx;
border: solid 2rpx #708694;
width: 320rpx;
height: 100rpx;
margin-right: 0;
margin-left: 0;
}
.share-container .share-btn image {
width: 40rpx;
height: 40rpx;
}
.share-container .share-btn text {
font-size: 28rpx;
color: #708694;
margin-left: 15rpx;
}
.s-text-color-gray {
color: #999;
}
.s-text-color-black {
color: #222;
}
.s-text-color-brown {
color: #816354;
}
.s-text-color-red {
color: #e40613;
}
.s-text-color-orange {
color: #f1a852;
}
.s-text-color-green {
color: #9cce06;
}
.s-text-color-blue {
color: #0dc5b9;
}
.comment {
padding: 40rpx 36rpx 0;
background: #f7f7f7;
}
.comment .c-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.comment .c-header .count {
height: 80rpx;
color: #636972;
font-size: 28rpx;
display: flex;
align-items: center;
}
.comment .c-header .write-btn {
padding: 16rpx;
border: 2rpx solid #6D8697;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.comment .c-header .write-btn text {
color: #6D8697;
}
.comment .c-body {
margin-top: 18.10rpx;
}
.comment .c-footer {
margin-bottom: 74rpx;
}
.comment .c-footer .check-more-comment {
height: 86rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #6D8697;
border-radius: 12rpx;
color: #6D8697;
font-size: 32rpx;
font-weight: 500;
}
.empty-comment-wrapper {
position: relative;
}
.empty-comment-wrapper image {
display: none;
}
.empty-comment-wrapper .add-comment-enter {
text-align: center;
padding: 50rpx 0 120rpx;
}
.empty-comment-wrapper .add-comment-enter text {
display: block;
font-size: 28rpx;
line-height: 1.64;
color: #999999;
}
.empty-comment-wrapper .add-comment-enter .write-btn {
width: 328rpx;
height: 80rpx;
border-radius: 12rpx;
background-color: #6D8697;
border: solid 2rpx #6D8697;
margin: 30rpx auto;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
}
.comment-img-box .comment-picture {
display: flex;
flex-grow: 30%;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 10rpx;
align-items: flex-start;
overflow: hidden;
}
.comment-img-box .comment-picture .comment-img {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.comment-img-box .comment-picture li:empty {
height: 0;
border: none;
}
.comment-img-box .more-image {
position: relative;
}
.comment-img-box .more-image .overlay {
background: rgba(0, 0, 0, 0.8);
position: absolute;
border-radius: 15rpx;
right: 10rpx;
bottom: 20rpx;
z-index: 500;
color: #ffffff;
font-size: 22rpx;
text-align: center;
line-height: 33rpx;
}
<import src="root/utils/wxParse/wxParse.wxml" />
<template name="post-item-quote">
<view class="post-item-quote post-item-wrapper">
<view class="post-item-quote-content">
<template is="wxParse" data="{{nodes}}" />
</view>
</view>
</template>
<template name="post-item-image">
<view class="post-item-wrapper post-item-image">
<image mode="widthFix" src="{{imageUrl}}" bindtap="showImage" data-image-url="{{imageUrl}}" style="width: {{(w ? (w + 'px') : '100%')}}" />
</view>
</template>
<template name="post-item-video">
<view class="post-item-wrapper post-item-video">
<video src="{{url}}" />
</view>
</template>
<template name="post-item-title">
<view class="post-item-wrapper">
<view class="post-item-title">
<template is="wxParse" data="{{nodes}}" />
</view>
</view>
</template>
<template name="post-item-text">
<view class="post-item-wrapper">
<view class="post-item-text">
<template is="wxParse" data="{{nodes}}" />
</view>
</view>
</template>
<template name="post-item-separator">
<view class="post-item-wrapper post-item-separator">
<view class="separator" />
</view>
</template>
.post-item-wrapper {
padding: 0 40rpx 60rpx;
}
.post-item-image {
text-align: center;
}
.post-item-image image {
max-width: 100%;
}
.post-item-video video {
width: 100%;
height: 418.75rpx;
}
.post-item-quote .post-item-quote-content {
border-left: 4rpx solid #333437;
padding-left: 46rpx;
}
.post-item-separator .separator {
background: #ddd;
height: 4rpx;
width: 260rpx;
margin: 0 auto;
}
.post-item-text {
min-height: 58rpx;
}
.post-item-text,
.post-item-quote-content {
word-break: break-word;
}
\ No newline at end of file
{
"enablePullDownRefresh": true
}
@import "/styles/main.less";
@import '/templates/tabs/tabs.less';
@import '/templates/loaderPage/loaderPage.less';
@import '/templates/loaderBar/loaderBar.less';
@import '/templates/slider/slider.less';
@import '/templates/shortcuts/shortcuts.less';
@import '/templates/postCard/postCard.less';
@import '/templates/categoryCard/categoryCard.less';
@import "/templates/messageModal/messageModal.less";
@import "/templates/shareView/shareView.less";
@light-background: #f4f4f4;
.post-index {
display: flex;
flex-direction: column;
height: 100vh;
text {
font-size: 17px;
color: #fff;
}
scroll-view {
height: 100vh;
flex: 1 1 0%;
}
.posts {
padding: 26rpx;
}
.categories {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
padding: 30rpx 18rpx;
background: @light-background;
}
}
.comment-form {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 15rpx 30rpx;
border: 2rpx solid #e2e4e7;
background-color: #f4f4f4;
.component-placeholder {
font-size: 28rpx;
color: #c6c9cd;
}
.comment-input {
width: 100%;
height: 68rpx;
line-height: 70rpx;
padding-left: 18rpx;
font-size: 28rpx;
color: black;
border: 2rpx solid #e2e4e7;
border-radius: 8rpx;
background-color: white;
}
.comment-btn {
display: inline-block;
height: 70rpx;
width: 150rpx;
line-height: 70rpx;
margin-left: 15rpx;
font-size: 28rpx;
text-align: center;
color: white;
background-color: #62b900;
&.disabled{
background-color: #f6f6f6;
color: #e2e4e7;
}
}
}
<import src="root/templates/postCard/postCard.wxml"/>
<import src="root/templates/categoryCard/categoryCard.wxml"/>
<import src="root/templates/tabs/tabs.wxml"/>
<import src="root/templates/loaderPage/loaderPage.wxml"/>
<import src="root/templates/loaderBar/loaderBar.wxml"/>
<import src="root/templates/slider/slider.wxml"/>
<import src="root/templates/shortcuts/shortcuts.wxml"/>
<import src="root/templates/messageModal/messageModal.wxml"/>
<import src="root/templates/shareView/shareView.wxml"/>
<view class="post-index">
<block wx:if="{{showBlogPosts}}">
<template
is='tabs'
data="{{background, isWhiteBackground, currentTab, hasSearch: searchEnable, iconSearchWhite, iconSearch, tabs, handleTab, handleSearch}}"
/>
</block>
<scroll-view wx:if="{{!isFetchingPosts || posts.length > 0}}"
style="height: {{categories.length ? 'calc(100vh - 80rpx)' : '100vh'}}; box-sizing: border-box; background-color: {{layout === 'd' ? 'white': '#f4f4f4'}}"
scroll-y="true" hidden="{{currentTab == 'category'}}" lower-threshold="80"
bindscrolltolower="loadMorePosts"
bindtap="handleGlobalTab"
>
<block wx:if="{{showSlider}}">
<block wx:if="{{banners && banners.length}}">
<template
is="slider"
data="{{ sliders: banners, dotPosition, hasVideoBanner, handleProduct, handleCategory, handlePage, handleSliderChange }}"
/>
</block>
<block wx:else>
<view wx:if="{{isWept && ifInPageDesign}}" class="component-placeholder" style="height: 300rpx;margin-bottom: 30rpx;">
您还未添加轮播图
</view>
</block>
</block>
<block wx:if="{{showShortcuts}}">
<block wx:if="{{shortcuts && shortcuts.length}}">
<template
is="shortcuts"
data="{{ shortcuts, shortcutsFirstLine, shortcutsSecondLine, shortcutsSecondLineWrapperClass, handleProduct, handleCategory, handlePage, makePhoneCall, showContactBtn }}"
/>
</block>
<block wx:else>
<view wx:if="{{isWept && ifInPageDesign}}" class="component-placeholder" style="height: 224rpx;margin-bottom: 30rpx;">
您还未添加快捷按钮
</view>
</block>
</block>
<block wx:if="{{showBlogPosts}}">
<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'}}"
/>
</block>
</scroll-view>
<template wx:else is='loader-page'/>
<scroll-view style="height: {{categories.length ? 'calc(100vh - 80rpx)' : '100vh'}}; box-sizing: border-box;"
scroll-y="true" hidden="{{currentTab == 'post'}}" bindtap="handleGlobalTab">
<view class="categories">
<block wx:for="{{categories}}" wx:key="id" wx:for-item="item">
<template is="category-card" data="{{...item, handleCategory}}"/>
</block>
</view>
</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>
</view>
<view wx:if="{{errorMessage}}">
<template is="message-modal" data="{{message: errorMessage}}" />
</view>
<template is="share-view" data="{{shareLoading, sharePicture, shareAnimation, showShareVariation, closeShareView, openShareView}}"/>
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;
}
}
button {
font-size: 16px;
}
.flex-row {
display: flex;
flex-direction: row;
align-items: center;
}
.flex-row.flex-start {
align-items: flex-start;
}
.flex-row.space-between {
justify-content: space-between;
}
.flex-row.center-align-item {
justify-content: center;
}
.flex-col {
display: flex;
flex-direction: column;
}
.left-align {
text-align: left;
}
.right-align {
text-align: right;
}
.auto-flex {
flex: 0 0 auto;
}
.normal-flex {
flex: 1 1 0%;
}
.gray-text {
color: #666;
}
.light-text {
color: #999;
}
scroll-view {
overflow: scroll;
}
page {
background: #f4f4f4;
line-height: 1.2;
font-family: -apple-system-font, 'Helvetica Neue', Helvetica, 'Microsoft YaHei', sans-serif;
}
scroll-view::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
@-webkit-keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load3 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
\ No newline at end of file
.loader {
font-size: 10px;
margin: 0;
text-indent: -9999em;
width: 20px;
height: 20px;
display: inline-block;
border-radius: 50%;
background: #797979;
background: -moz-linear-gradient(left, #797979 10%, rgba(121, 121, 121, 0) 42%);
background: -webkit-linear-gradient(left, #797979 10%, rgba(121, 121, 121, 0) 42%);
background: -o-linear-gradient(left, #797979 10%, rgba(121, 121, 121, 0) 42%);
background: -ms-linear-gradient(left, #797979 10%, rgba(121, 121, 121, 0) 42%);
background: linear-gradient(to right, #797979 10%, rgba(121, 121, 121, 0) 42%);
position: relative;
-webkit-animation: load3 1.4s infinite linear;
animation: load3 1.4s infinite linear;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.loader:before {
width: 50%;
height: 50%;
background: #797979;
border-radius: 100% 0 0 0;
position: absolute;
top: 0;
left: 0;
content: '';
}
.loader:after {
background: #f4f4f4;
width: 75%;
height: 75%;
border-radius: 50%;
content: '';
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
\ No newline at end of file
Page()
export const data = {
height: '100vh',
icon: '',
text: '',
}
<template name="result-page">
<view class="result-page" style="height: {{height || '100vh'}}">
<image wx:if="{{icon}}" class="content-icon" src="{{icon}}" mode="aspectFit"/>
<text class="content-text">{{text}}</text>
</view>
</template>
.result-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.result-page .content-icon {
width: 86rpx;
height: 86rpx;
display: block;
margin: 0 auto 26rpx auto;
}
.result-page .content-text {
height: 45rpx;
line-height: 45rpx;
font-size: 32rpx;
text-align: center;
color: #b6b6b6;
}
\ No newline at end of file
Page()
export const data = {
background: '#ffffff',
isWhiteBackground: true,
placeholder: '',
value: '',
iconClear: '',
iconClearWhite: '',
}
export function handleInput(event) {}
export function handleClear() {}
<template name="search-bar">
<view class="search-bar" style="{{'background-color:' + background}}">
<block wx:if="{{isWhiteBackground}}">
<input class="search-input" placeholder="{{placeholder || '搜索'}}"
placeholder-style="color:rgba(0, 0, 0, 0.5)"
focus="{{true}}" bindinput="handleInput"
style="{{'color:black; background-color:'+ background}}"
value="{{value}}"/>
</block>
<block wx:else>
<input class="search-input" placeholder="{{placeholder || '搜索'}}"
placeholder-style="color:rgba(255, 255, 255, 0.5)"
focus="{{true}}" bindinput="handleInput"
style="{{'color:white; background-color:'+ background}}"
value="{{value}}"/>
</block>
<block wx:if="{{value}}">
<image class="clear-icon" src="{{isWhiteBackground ? iconClear: iconClearWhite}}" mode="aspectFit"
bindtap="handleClear"/>
</block>
</view>
</template>
<template name="search-bar-new">
<view class="search-bar-new" style="background-color:{{background}}">
<view class="search-wrapper">
<image class="search-icon" src="{{iconSearch}}" mode="aspectFit"/>
<input class="search-input"
placeholder="{{value ? '' : placeholder || '搜索'}}"
focus="{{true}}"
bindinput="handleInput"
value="{{value}}"
style="color: black; background-color: #f6f6f6"
placeholder-style="color:rgba(0, 0, 0, 0.6)"
/>
<image wx:if="{{value}}" class="clear-icon" src="{{iconClear}}" mode="aspectFit" bindtap="handleClear"/>
</view>
</view>
</template>
.search-bar {
display: flex;
justify-content: space-between;
padding: 18rpx 16rpx 18rpx 24rpx;
background-color: #ffffff;
box-shadow: 0 2rpx 4rpx 0 rgba(0, 0, 0, 0.12);
}
.search-bar .search-input {
width: 100%;
line-height: 45rpx;
padding: 15rpx 0 24rpx 16rpx;
border-radius: 6rpx;
font-size: 32rpx;
font-weight: normal;
color: #212121;
}
.search-bar .search-input::placeholder {
color: #BDC1C5;
}
.search-bar .clear-icon {
width: 45rpx;
height: 45rpx;
padding: 15rpx 24rpx 24rpx 16rpx;
}
.search-bar-new {
padding: 0 30rpx 24rpx;
background-color: #ffffff;
box-shadow: 0 2rpx 4rpx 0 rgba(0, 0, 0, 0.12);
}
.search-bar-new .search-wrapper {
background: #f6f6f6;
border-radius: 4px;
overflow: hidden;
}
.search-bar-new .search-wrapper .search-input {
height: 64rpx;
font-size: 24rpx;
padding: 0 70rpx;
color: rgba(255, 255, 255, 0.4);
background-color: #f6f6f6;
}
.search-bar-new .search-wrapper .search-input::placeholder {
color: #BDC1C5;
}
.search-bar-new .search-wrapper .search-icon {
position: absolute;
width: 32rpx;
height: 32rpx;
top: 16rpx;
left: 50rpx;
}
.search-bar-new .search-wrapper .clear-icon {
position: absolute;
width: 32rpx;
height: 32rpx;
padding: 15rpx 25px;
top: 0;
right: 0;
}
\ No newline at end of file
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,
}
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)
}
},
})
}
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 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 { 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),
)
}
}
export function getQnUrl(image) {
function isUnsplash(url) {
return url.indexOf('unsplash') !== -1
}
if (!image.storageKey) {
return ''
}
if (isUnsplash(image.storageKey)) {
return `${image.storageKey}?h=1500&w=2000&q=40&fit=clip&fm=jpg`
}
return `https://user-assets.sxlcdn.com/${
image.storageKey
}?imageMogr2/strip/thumbnail/1200x9000>/format/${image.format}`
}
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,
}),
)
})
},
})
}
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 default {
onLoad(options) {},
}
/**
*
* htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
// Empty Elements - HTML 5
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
// Block Elements - HTML 5
var block = makeMap("a,address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
// Inline Elements - HTML 5
var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
// Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
// Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
// Special Elements (can contain anything)
var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block");
function HTMLParser(html, handler) {
var index, chars, match, stack = [], last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true;
// Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf("<!--") == 0) {
index = html.indexOf("-->");
if (index >= 0) {
if (handler.comment)
handler.comment(html.substring(4, index));
html = html.substring(index + 3);
chars = false;
}
// end tag
} else if (html.indexOf("</") == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
}
// start tag
} else if (html.indexOf("<") == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf("<");
var text = ''
while (index === 0) {
text += "<";
html = html.substring(1);
index = html.indexOf("<");
}
text += index < 0 ? html : html.substring(0, index);
html = index < 0 ? "" : html.substring(index);
if (handler.chars)
handler.chars(text);
}
} else {
html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2");
if (handler.chars)
handler.chars(text);
return "";
});
parseEndTag("", stack.last());
}
if (html == last)
throw "Parse Error: " + html;
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag("", stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
unary = empty[tagName] || !!unary;
if (!unary)
stack.push(tagName);
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] :
arguments[3] ? arguments[3] :
arguments[4] ? arguments[4] :
fillAttrs[name] ? name : "";
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName)
var pos = 0;
// Find the closest opened tag of the same type
else {
tagName = tagName.toLowerCase();
for (var pos = stack.length - 1; pos >= 0; pos--)
if (stack[pos] == tagName)
break;
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--)
if (handler.end)
handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
}
}
};
function makeMap(str) {
var obj = {}, items = str.split(",");
for (var i = 0; i < items.length; i++)
obj[items[i]] = true;
return obj;
}
module.exports = HTMLParser;
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