实现分享功能(自定义分享时的描述、名称、小图片)

帖子主要解决的是使用share.js分享时无法自定义图片的问题,最后通过引入了微信的SDK解决了这个问题(下面放了share.js和微信sdk的官方地址)。如果没有不引入微信SDK、加域名也是可以实现分享的,但是无法实现小图片和分享描述自定义。lz开发时的环境:Vue + share.js + 微信SDK + 域名 + Java。所以要求开发的时候需要有微信公众号,里面需要提供AppId和Secret。lz使用Java的原因是因为前端相对经验不足,对接微信时出现了各种各样的问题,所以我提供了一个接口给他获取微信相关的access_token等其他信息(实际可以不使用java,前端直接访问微信sdk)。最终实现的分享效果如图和界面图

  • 分享效果图
    在这里插入图片描述



  • 界面效果图
    在这里插入图片描述

相关资源链接:
github上的share.js地址:overtrue/share.js
微信分享sdk地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
参考链接:微信分享SDK接入——Java
微信分享功能(分享给好友/分享到朋友圈-java版本)

后端代码

后端代码分享:后端主要内容有三个类Sign,WeiXinEntity、WeiXinUtil,代码如下,lz特地加了很多的注解,可以自己看看。其中需要修改的地方只有两个将AppId和Secret换成自己微信工作号上获取的即可,至于跟微信相关的开发操作我把它放在代码后面
在这里插入图片描述

  • 类关系图
    在这里插入图片描述

  • Sign.class

package com.luntek.certificate.utils.wxshare;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

class Sign {
    
    
    public static void main(String[] args) {
    
    
        String jsapi_ticket = "jsapi_ticket";

        // 注意 URL 一定要动态获取,不能 hardcode
        String url = "http://example.com";
        Map<String, String> ret = sign(jsapi_ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
    
    
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    ;

    /**
     * 获取微信上返回的签名信息
     *
     * @param jsapi_ticket 指定算法生成的
     * @return 包含微信客户端需要的信息
     * @author Czw
     * @date 2020/9/4 0004 下午 5:45
     */
    public static Map<String, String> sign(String jsapi_ticket, String url) {
    
    
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        //注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        System.err.println("string1=" + string1);
        try {
    
    
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes(StandardCharsets.UTF_8));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("app_id", WeiXinUtil.AppId);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
    
    
        Formatter formatter = new Formatter();
        for (byte b : hash) {
    
    
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
    
    
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
    
    
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

  • WeiXinUtil
package com.luntek.certificate.utils.wxshare;


import net.sf.json.JSONObject;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @description: 微信工具类,获取access_token、ticket
 * @author: Czw
 * @create: 2020-09-03 10:38
 **/
public class WeiXinUtil {
    
    
    //获取access_token填写client_credential,不需要修改
    public static String grant_type = "client_credential";
    //微信公众号上获取的用户唯一凭证,换成自己的
    public static String AppId = "XXXXXXXXX";
    //第三方用户唯一凭证密钥,即appsecret,换成自己的
    public static String secret = "XXXXXXXX";

    /**
     * 获取需要分享的链接信息
     *
     * @param url 被分享的网址链接中需要没有中文字符,必须是公网链接
     * @return WeiXinEntity 返回微信分享需要的参数信息对象
     * @author Czw
     * @date 2020/9/4 0004 下午 5:39
     */
    public static WeiXinEntity getWinXinEntity(String url) {
    
    
        WeiXinEntity wx = new WeiXinEntity();
        String access_token = getAccessToken();
        String ticket = getTicket(access_token);
        Map<String, String> ret = Sign.sign(ticket, url);
        wx.setTicket(ret.get("jsapi_ticket"));
        wx.setSignature(ret.get("signature"));
        wx.setNonceStr(ret.get("nonceStr"));
        wx.setTimestamp(ret.get("timestamp"));
        wx.setAppId(ret.get("app_id"));
        wx.setAccessToken(access_token);
        return wx;
    }

    /**
     * 从微信公众号平台上获取accessToken
     *
     * @return accessToken
     * @author Czw
     * @date 2020/9/4 0004 下午 5:42
     */
    private static String getAccessToken() {
    
    
        String access_token = "";
        //这个url链接地址和参数皆不能变
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=" + grant_type + "&appid=" + AppId + "&secret=" + secret;  //访问链接

        try {
    
    
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, StandardCharsets.UTF_8);
            JSONObject demoJson = JSONObject.fromObject(message);
            access_token = demoJson.getString("access_token");
            is.close();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return access_token;
    }

    /**
     * 根据微信公众号获取的access_token获取标记ticket
     *
     * @param access_token 微信公众号获取获取的token
     * @return ticket 标记
     */
    private static String getTicket(String access_token) {
    
    
        String ticket = null;
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi";//这个url链接和参数不能变
        try {
    
    
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, StandardCharsets.UTF_8);
            JSONObject demoJson = JSONObject.fromObject(message);
            ticket = demoJson.getString("ticket");
            is.close();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return ticket;
    }
}

  • WeiXinEntity
package com.luntek.certificate.utils.wxshare;


import lombok.Data;

/**
 * 微信分享实体类
 *
 * @author: Czw
 * @create: 2020-09-03 10:39
 **/
@Data
public class WeiXinEntity {
    
    

    private String appId;
    private String accessToken;
    private String ticket;
    private String nonceStr;
    private String timestamp;
    private String str;
    private String signature;
}

  • WxShareController
package com.luntek.certificate.api;

import com.github.pagehelper.util.StringUtil;
import com.luntek.certificate.enums.CommonExEnum;
import com.luntek.certificate.exception.BusinessException;
import com.luntek.certificate.utils.wxshare.WeiXinEntity;
import com.luntek.certificate.utils.wxshare.WeiXinUtil;
import io.swagger.annotations.Api;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 微信分享对应接口
 *
 * @author Czw
 * @Date 2020/2/25 0025 下午 12:54
 */
@Validated
@Api(tags = "微信分享controller")
@RestController
@RequestMapping("/wxShare")
public class WxShareController {
    
    


    /**
     * 修改文章是否开启状态
     *
     * @param shareUrl 被分享的网址链接中需要没有中文字符,必须是公网链接
     * @return ResponseResult
     * @auther Czw
     * @date 2020/5/9 0009 上午 10:13
     */
    @GetMapping("/getShareMessage")
    public WeiXinEntity  changeState(@RequestParam("weixinShareUrl") String shareUrl) {
    
    
        if (StringUtil.isEmpty(shareUrl)) {
    
    
            throw new BusinessException(CommonExEnum.SHARE_URL_EMPTY);
        }
        WeiXinEntity weiXinEntity = WeiXinUtil.getWinXinEntity(shareUrl);
        return weiXinEntity;
    }


}

相关的操作步骤:

再发一次文档链接:微信SDK地址

大致步骤:

首先要有一个公众号和已经备案好的域名!自行准备

1. 获取appId和secret、放入MP_verify_0HU5eN6Tzfwovxxx.txt文件到项目根目录下、添加项目访问域名地址、配置IP白名单地址

  • 获取appId和secret
    先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”,所谓的安全域名就是要分享的网页所在的域名,填写完域名后在“基本配置”可以看到开发者ID(AppID)和开发者密码(AppSecret,也就是secret)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    扫描二维码关注公众号,回复: 11732933 查看本文章
  • 下载txt文件(MP_verify_0HU5eN6Tzfwovxxx.txt),放到项目根目录下。lz放在ng的HTML下,如果没有使用ng可以放在项目的index同级下

在这里插入图片描述

  • 添加项目访问域名地址,lz下面添加的方式是错误的,需要在前面加上www
    在这里插入图片描述
  • 配置IP白名单地址
    在这里插入图片描述

2. 复制签名函数
上面的Sign类为获取签名
3. 获取access_token、ticket
建立一个WeinXinUtil类,里面的getAccessToken方法和getTicket方法分别获取access_token、ticket
4. 获取需要分享的url,并进行签名
url的获取要特别注意,如果是使用的histry路由则url中会有#需要去除掉,如果不是此路由方式则无需处理。lz需要分享的url由前端作为参数传递过来,所以相对简单。微信在分享时会在原来的url上加上一些&from=singlemessage、&from=…的,所以url必须要动态获取,网上有些用ajax,在网页通过“location.href.split(‘#’)“ 获取url,在用ajax传给后台,这种方法可行,但是不推荐,一方面用ajax返回,就要访问分享的逻辑,这样后台分享的逻辑增加复杂度,带来不便,是代码不易于维护,可读性低!另一方面分享是返回页面,而ajax是返回json,又增加了复杂度。所以,也可以用HttpRequest的方法,不管微信加多少后缀,都可以获取到完整的当前url
例如,以下代码仅供参考:

public String share(HttpServletRequest request) {
    
    
    ...

    String strUrl = "http://www.xxxxx.com"       //换成安全域名
                    + request.getContextPath()   //项目名称  
                    + request.getServletPath()   //请求页面或其他地址  
                    + "?" + (request.getQueryString()); //参数  

    ...
}

sign类里面有一个签名的方法public static Map<String, String> sign(String jsapi_ticket, String url)

传入ticket和url即可。也就是WeinXinUtil的getWinXinEntity方法,并将返回的map的信息读取存入WinXinEntity 中。在调试时,把sign返回的map打印出来,主要看看生成的signature。然后,把jsapi_ticket、noncestr、timestamp、url 复制到微信提供的”微信 JS 接口签名校验工具“:校验前面是否正确的微信官方工具,比较代码签名生成的signature与校验工具生成的签名signature是否一致,如果一致,说明前面的步骤都是正确的,如果不一致,仔细检查!
在这里插入图片描述
在这里插入图片描述

5. 集成进java web
我们把微信分享分解成3个工具类,现在在处理分享的controller,只要两句话就可以调用微信分享,一句获取url,一句获取WinXinEntity,代码为WxShareController中的代码



前端

需要使用share.js(具体使用自行百度)安装sdk (npm install --save weixin-js-sdk

<!-- 论坛文章/提问内容详情 -->
<template>
  <div v-wechat-title="$route.meta.title=post.title" class="post-wrapper">
    <transition name="el-fade-in-linear">
      <div class="post-content">
        <div class="suspended-bar">
          <el-badge :value="post.likeCount" class="icon-badge" type="primary">
            <div class="icon-background" @click="handleLike">
              <svg-icon icon-class="like" class="icon" :class="{
     
     'is-like':post.like}"/>
            </div>
          </el-badge>
          <div class="icon-background" @click="handleScrollTo">
            <svg-icon icon-class="comment" class="icon"/>
          </div>
          <div v-if="post.type !== '2'" class="icon-background" @click="handleCollection">
            <svg-icon icon-class="collection" class="icon" :class="{
     
     'is-like':post.collect}"/>
          </div>
          <div class="icon-background" @click="handleReset">
            <svg-icon icon-class="report" class="icon"/>
          </div>
          <!--        <el-tooltip effect="dark" content="返回" placement="bottom">-->
          <!--          <div class="icon-background" @click="$router.go(-1)"><svg-icon icon-class="back" class="icon" /></div>-->
          <!--        </el-tooltip>-->
        </div>
        <transition name="el-fade-in-linear">
          <div class="post-detail">
            <div v-show="!isLoading">
              <div class="article-top">
                <div class="avatar-wrapper">
                  <img :src="post.icon" class="el-avatar--circle author-avatar" alt=""
                       @click="handleRedirectUserCenter(post.createBy)"/>
                </div>
                <div class="author-article-info">
                  <div class="author-name" @click="handleRedirectUserCenter(post.createBy)">{
   
   {
                    post.nickName }}
                  </div>
                  <div class="article-time">{
   
   { post.createTime | parseTime('{y}年{m}月{d}日') }} · {
   
   {
                    post.sortName }} · 阅读 {
   
   { post.lookCount }}
                  </div>
                </div>
              </div>
              <!--封面图片-->
              <img v-if="post.thumbnailPath && post.thumbnailPath.length > 0"
                   :src="post.thumbnailPath" class="article-thumb">
              <div class="article-content">
                <h2 class="post_title">{
   
   { post.title }}</h2>
                <div v-html="post.content"></div>
                <el-tag v-for="(item,index) in labelList" :key="index" class="article-tag"
                        type="info">{
   
   { item }}
                </el-tag>
                <share v-if="post.title" :config="config"/>
                <!--                <forum-post-share v-if="post.title"/>-->
              </div>
            </div>
          </div>
        </transition>
        <forum-comment-list id="anchor" :article="post" @commentFinish="commentFinish"/>
      </div>
    </transition>
    <div class="action-box">
      <div class="icon-wrapper split" @click="handleLike">
        <svg-icon icon-class="like" class="icon" :class="{
     
     'is-like':post.like}"/>
        {
   
   { post.likeCount }}
      </div>
      <div v-if="post.type !== '2'" class="icon-wrapper split" @click="handleCollection">
        <svg-icon icon-class="collection" class="icon" :class="{
     
     'is-like':post.collect}"/>
        收藏
      </div>
      <div class="icon-wrapper" @click="handleReset">
        <svg-icon icon-class="report" class="icon"/>
        举报
      </div>
    </div>
    <forum-report :report-dialog-visible.sync="reportDialogVisible"
                  :reported-id="$route.params.id"/>
    <back-to-top
      class="back-to-top"
      :custom-style="myBackToTopStyle"
      :visibility-height="300"
      transition-name="fade"
    />
  </div>
</template>

<script>
  import ForumCommentList from '../Forum/ForumCommentList';
  import {
     
     
    addPostCollect,
    getArticleInfo,
    getWeixinShare,
    likeOrUnlikePost
  } from '../../api/forum';
  import {
     
      parseTime } from '@/utils';
  import ForumReport from './ForumReport';
  import {
     
      getToken } from '../../utils/auth';
  import BackToTop from '../BackToTop/index';
  // import ForumPostShare from './ForumPostShare';
  import wx from 'weixin-js-sdk';


  export default {
     
     
    name: 'ForumPost',
    components: {
     
     
      // ForumPostShare,
      ForumReport,
      ForumCommentList,
      BackToTop
    },
    filters: {
     
     
      parseTime
    },
    data() {
     
     
      return {
     
     
        post: {
     
     },
        temp: {
     
     
          reportedId: null,
          message: null,
          detail: null,
        },
        labelList: [],
        isLoading: true,
        reportDialogVisible: false,
        reportTheme: ['政治相关', '内容涉黄', '内容抄袭', '内容侵权', '侮辱谩骂', '其他问题'],
        config: {
     
     
          title: '朗迅芯云学院',
          image: '',
          sites: ['qzone', 'qq', 'weibo', 'wechat'],
          url: '',
          wechatQrcodeTitle: '微信扫一扫:分享',
          wechatQrcodeHelper: '<p>微信扫一下二维码</p><p>点右上角省略号便可分享至朋友圈</p>'
        },
        myBackToTopStyle: {
     
     
          right: '40px',
          bottom: '40px',
          width: '40px',
          height: '40px',
          'border-radius': '50%',
          'line-height': '38px',
          background: '#FF9607',
          color: '#fff',
          zIndex: 800
        }
      };
    },
    created() {
     
     
      this.isLoading = true;
      this.getPost();
    },
    mounted() {
     
     
      if (this.$route.params.anchor) {
     
     
        setTimeout(() => {
     
     
          this.handleScrollTo();
        }, 800);
      }
      this.shareWeixin();
    },
    methods: {
     
     
      handleRedirectUserCenter(id) {
     
     
        this.$router.push({
     
     
          name: 'ForumUser',
          params: {
     
      userId: id }
        });
      },
      getPost() {
     
     
        getArticleInfo(this.$route.params.id)
          .then(res => {
     
     
            this.config.title = res.data.title;
            this.config.image = res.data.thumbnailPath;
            this.config.url = window.location.href;
            this.post = res.data;
            this.$emit('returnSortId', this.post.sortId);
            this.labelList = res.data.labels.split(',');
            this.isLoading = false;
          });
      },
      judgeLogin() {
     
     
        if (!getToken()) {
     
     
          this.$message({
     
     
            type: 'error',
            message: '请登录后操作!'
          });
          this.$router.push({
     
     
            path: '/login',
            query: {
     
      path: `forum/post/${
       
       this.$route.params.id}` }
          });
          return true;
        }
      },
      handleChooseTheme(index) {
     
     
        this.$set(this.temp, 'theme', this.reportTheme[index]);
        this.temp.theme = this.reportTheme[index];
      },
      handleLike() {
     
     
        if (this.judgeLogin()) {
     
     
          return;
        }
        if (this.post.like === true) {
     
     
          this.post.likeCount--;
          this.post.like = false;
        } else {
     
     
          this.post.likeCount++;
          this.post.like = true;
        }
        likeOrUnlikePost({
     
      postId: this.$route.params.id });
      },
      commentFinish() {
     
     
        this.getPost();
      },
      handleScrollTo() {
     
     
        document.getElementById('anchor')
          .scrollIntoView();
      },
      handleReset() {
     
     
        this.reportDialogVisible = true;
      },
      handleCollection() {
     
     
        if (this.judgeLogin()) {
     
     
          return;
        }
        this.post.collect = this.post.collect !== true;
        addPostCollect(this.$route.params.id);
      },
      shareWeixin() {
     
     
        getWeixinShare(window.location.href)
          .then(res => {
     
     
            // debugger
            this.infoList = res.data;
            wx.config({
     
     
              debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
              appId: this.infoList.appId, // 必填,公众号的唯一标识
              timestamp: this.infoList.timestamp, // 必填,生成签名的时间戳
              nonceStr: this.infoList.nonceStr, // 必填,生成签名的随机串
              signature: this.infoList.signature, // 必填,签名
              jsApiList: [
                'onMenuShareTimeline',
                'onMenuShareAppMessage'
              ] // 必填,需要使用的JS接口列表
            });

            wx.ready(() => {
     
     
              getArticleInfo(this.$route.params.id).then(response=>{
     
     
                wx.onMenuShareAppMessage({
     
     
                  title: response.data.title,
                  desc: '朗讯,用“芯”创造美好未来!',
                  link: window.location.href + '?from=groupmessage',
                  imgUrl: response.data.thumbnailPath,
                  success: function success(res) {
     
     
                    console.log('已分享');
                  },
                  cancel: function cancel(res) {
     
     
                    console.log('已取消');
                  },
                  fail: function fail(res) {
     
     
                    alert(JSON.stringify(res));
                  }
                });
                // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
                wx.onMenuShareTimeline({
     
     
                  title: response.data.title,
                  desc: '朗讯,用“芯”创造美好未来!',
                  link: window.location.href + '?from=groupmessage',
                  imgUrl: response.data.thumbnailPath,
                  // success: function success(res) {
     
     
                  //   console.log('已分享');
                  // },
                  // cancel: function cancel(res) {
     
     
                  //   console.log('已取消');
                  // },
                  // fail: function fail(res) {
     
     
                  //   alert(JSON.stringify(res));
                  // }
                });
              })
            });
            wx.error((res) => {
     
     
              alert('微信验证失败');
            });
          });
      }
    }
  };
</script>

<style lang="scss" scoped>
  @import '~@/assets/style/global';

  .post-wrapper {
     
     
    width: 100%;
    flex: 1;
    list-style: none;
    min-height: 750px;

    .post-content {
     
     
      background-color: white;
      box-shadow: 0 2px 4px rgba(26, 26, 26, .1);

      .post-detail {
     
     
        min-height: 500px;

        .article-top {
     
     
          display: flex;
          padding: 24px 0 12px 24px;

          .avatar-wrapper {
     
     
            width: 55px;
            height: 55px;

            .author-avatar {
     
     
              width: 100%;
              cursor: pointer;
            }
          }

          .author-article-info {
     
     
            position: relative;
            flex: 1;
            padding-left: 18px;

            .author-name {
     
     
              font-weight: bold;
              padding: 4px 0;
              cursor: pointer;
              font-size: 1rem;
            }

            .article-time {
     
     
              bottom: 4px;
              font-size: 14px;
              color: #909090;
            }
          }
        }

        .article-thumb {
     
     
          width: 100%;
          padding: 12px 24px;
        }

        .article-content {
     
     
          position: relative;
          max-width: 680px;
          width: 100%;
          padding: 12px 12px;
          word-wrap: break-word;

          /deep/ img, p, span {
     
     
            width: 100%;
          }

          /deep/ pre {
     
     
            overflow-x: auto;
          }

          .article-tag {
     
     
            width: auto;
            margin: 4px 7px 12px 7px;
          }
        }
      }

      .suspended-bar {
     
     
        position: fixed;
        margin-left: -60px;
        margin-top: 250px;

        .icon-background {
     
     
          width: 34px;
          height: 34px;
          margin-bottom: 6px;
          border-radius: 50%;
          background-color: white;
          text-align: center;
          vertical-align: center;
          box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .04);
          cursor: pointer;

          .icon {
     
     
            font-size: 18px;
            margin-top: 8px;
            color: #aaaaaa;

            &.is-like {
     
     
              color: $forum-blue;
            }

            &.is-like:hover {
     
     
              color: $forum-blue;
            }

            &:hover {
     
     
              color: #777777;
            }
          }
        }
      }
    }

    .action-box {
     
     
      @media screen and (min-width: 960px) {
     
     
        display: none;
      }
      position: fixed;
      z-index: 1;
      bottom: 0;
      width: 100%;
      height: 48px;
      display: flex;
      border-top: 1px solid #ebebeb;
      border-bottom: 1px solid #ebebeb;
      background: #fff;

      .icon-wrapper {
     
     
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        height: 100%;
        cursor: pointer;
        user-select: none;

        &.split {
     
     
          &:after {
     
     
            content: "";
            position: absolute;
            top: 50%;
            right: 0;
            margin-top: -1rem;
            width: 1px;
            height: 2rem;
            background-color: #ebebeb;
          }
        }

        .icon {
     
     
          font-size: 19px;
          margin-right: 5px;
          color: #aaaaaa;

          &.is-like {
     
     
            color: $forum-blue;
          }
        }
      }
    }

    .help-btn-wrapper {
     
     
      display: flex;
      flex-wrap: wrap;
      justify-content: space-around;

      .btn {
     
     
        margin: 0 0 5px 0;
      }
    }

    .back-to-top {
     
     
      @media (max-width: 400px) {
     
     
        display: none;
      }
    }

    .post_title {
     
     
      font-weight: bold;
      margin: 0px 0px 30px 0px;
    }
  }
</style>

  • 前端核心代码截图,大家自己凑合着用吧
    在这里插入图片描述

有什么问题可以私聊我,或者加我QQ:1142937352
写了好几个小时,点个赞再走吧。也许不一定和你想做的一模一样,但肯定有你想要的地方
余生还长,切勿惆怅

猜你喜欢

转载自blog.csdn.net/qq_42910468/article/details/108417491
今日推荐