关于在学习使用vue3+Element UI+SpringBoot构建前后端分离带后台小程序过程中的技术点记录和梳理

Vue3 ElementUI相关

1. 如何使用Vue3达到自动拦截所有前端请求已实现对请求自动封装如token等后端需要的数据,和自封装axios工具类

// 引入axios
import axios from 'axios';

let baseUrl='http://localhost:8080/'
// 创建axios实例
const httpService = axios.create({
  baseURL:baseUrl,
  // 请求超时时间
  timeout: 3000 // 需自定义
});

//添加请求和响应拦截器
// 添加请求拦截器
httpService.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  config.headers.token=window.sessionStorage.getItem('token');
  console.log("token ready")
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
httpService.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});

/*网络请求部分*/

/*
 *  get请求
 *  url:请求地址
 *  params:参数
 * */
export function get(url, params = {}) {
  return new Promise((resolve, reject) => {
    httpService({
      url: url,
      method: 'get',
      params: params
    }).then(response => {
      resolve(response);
    }).catch(error => {
      reject(error);
    });
  });
}

/*
 *  post请求
 *  url:请求地址
 *  params:参数
 * */
export function post(url, params = {}) {
  return new Promise((resolve, reject) => {
    httpService({
      url: url,
      method: 'post',
      data: params
    }).then(response => {
      console.log(response)
      resolve(response);
    }).catch(error => {
      console.log(error)
      reject(error);
    });
  });
}

/*
 *  文件上传
 *  url:请求地址
 *  params:参数
 * */
export function fileUpload(url, params = {}) {
  return new Promise((resolve, reject) => {
    httpService({
      url: url,
      method: 'post',
      data: params,
      headers: { 'Content-Type': 'multipart/form-data' }
    }).then(response => {
      resolve(response);
    }).catch(error => {
      reject(error);
    });
  });
}


export default {
  get,
  post,
  fileUpload,
  getServerUrl
}
/**
 * 导出自定义方法
 */
export function getServerUrl(){
  return baseUrl;
}

通过interceptors.request.use的配置可以轻松实现对每一个发出去的请求自动封装一些后端所必需的数据以免每次的重复配置

2.使用router对请求鉴权并跳转,用于判断用户是否登录(即是否存在token)

//在router文件夹下新建permission.js文件
import router from '@/router/index'
router.beforeEach((to,from,next)=>{
    //获取token
    let token = window.sessionStorage.getItem("token");
    console.log("to.path="+to.path)
    console.log("token="+token)

    //不需要鉴权的白名单,数组格式
    const whiteList=["/login"]
    if(token){
        //token存在无需登录
        if(to.path=="/login"){
            //如果是login请求
            next("/")
        }else {
            console.log("pass")
            next()
        }
    }else{
        //token不存在 鉴权失败
        if(whiteList.includes(to.path)){
            next();//不需要登录,直接放行
        }else {
            next("/login");//跳转登录页面
        }
    }
})

3.子组件获取父组件传来的值,以及子组件调用父组件内的方法(用于调用对话框Dialog传入id,title等以及dialog完成修改/添加等操作时刷新父页面数据)

子组件

//1.新建component文件夹,并新建dialog.vue,完成dialog的搭建
<script setup>

//接受父组件传来的方法
const emits = defineEmits(['update:modelValue','initBigTypeList']);

const tableData = ref([])

//接受父组件传来的值
const props = defineProps({
  id:{
    type:Number,
    default:-1,
    required:true
  }
})

const handleClose=()=>{
  //调用父组件传来的方法,传值为false 在此处为关闭dialog
  emits("update:modelValue",false);
}

const handleConfirm= ()=>{
  formRef.value.validate(async (valid)=>{
    if(valid){
      let result = await axios.post("admin/bigType/save",form.value);
      let data = result.data;
      if(data.code==0){
        ElMessage.success("add success");
        //调用父组件initBigTypeList方法,在此处为实现修改后页面自动刷新方法
        emits("initBigTypeList");
        handleClose();
      }else {
        ElMessage.error("fail:"+data.message)
      }
    }else{
      console.log("fail")
    }
  })
}

</script>

<template>
  <el-dialog
    model-value="dialogVisble"
    :title="dialogTitle"
  >
    <el-form
        ref="formRef"
        :model="form"
        :rules="rules"
        label-width="150px"
        status-icon
    >
    </el-form>
  </el-dialog>
</template>


父组件

//导入子组件
import Dialog from "@/views/bigType/componetents/dialog.vue";
//使用子组件,dialogVisible默认为false即不可见
//:id="id" 传递id值
//@initBigTypeList="initBigTypeList" 传递initBigTypeList方法

<Dialog v-model="dialogVisible" :id="id"  @initBigTypeList="initBigTypeList"></Dialog>

慢慢更新…请添加图片描述

JAVA SpringBoot相关

1.拦截部分来自前端的请求并鉴权 配置拦截器

注册配置类

package cn.azh.config;

import cn.azh.interceptor.SysInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * web项目配置类
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
      /**
     * 自定义拦截器类
     * @return
     */
    @Bean
    public SysInterceptor sysInterceptor(){
        return new SysInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] patterns = new String[]{"/admin/login","/product/**","/bigType/**","/user/wxlogin","/weixinpay/**"};
        registry.addInterceptor(sysInterceptor())
                .addPathPatterns("/**")
                //不需要拦截的请求 支持数组 支持通配符*
                .excludePathPatterns(patterns);
    }
}

自定义拦截器类
如果验证通过则return true 放行请求,反之抛出异常


import cn.azh.util.JwtUtils;
import cn.azh.util.StringUtil;
import io.jsonwebtoken.Claims;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SysInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getRequestURI();
        System.out.println("path="+path);
        if(handler instanceof HandlerMethod){
            //判断token是否为空
            String token = request.getHeader("token");
            System.out.println("token:"+token);
            if(StringUtil.isEmpty(token)){
                System.out.println("token为空");
                throw new RuntimeException("签名验证不存在");
            }else {
                Claims claims = JwtUtils.validateJWT(token).getClaims();
                if(path.startsWith("/admin")){
                    if(claims == null || !claims.getSubject().equals("admin")||!claims.getId().equals("-1")){
                        throw new RuntimeException("管理员鉴权失败");
                    }else {
                        System.out.println("管理员成功");
                        return true;
                    }
                }else {
                    if(claims==null){
                        //鉴权失败
                        throw new RuntimeException("鉴权失败");
                    }else{
                        System.out.println("鉴权成功");
                        return true;
                    }
                }
            }
        }else {
            return true;
        }
    }
}

2.映射静态资源,使某请求路径可访问到指定磁盘目录


/**
 * web项目配置类
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {

    /**
     * 映射静态资源
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
				//配置请求路径 支持通配符
				registry.addResourceHandler("/image/productParaImgs/**")
				//物理路径
                .addResourceLocations("file:/Users/Desktop/java1234-mall/productParaImgs/");
    }

}

小程序相关

1.微信小程序个人页面实现自动登录,页面样式展示,流程分析,后端代买

流程分析:
1.onShow方法检测到页面打开,检查Storage中是否有token
2.token不存在则提示用户登录,分别调用wx.login拿到获取openid需要的code和用户昵称头像等信息
3.将上述数据封装到loginParam,向后端发起请求
4.后端接收到请求后,拿到code拼接获取openid的url,得到openid
5.根据openid检查是否存在用户和是否更新,保存至数据库
6.使用JWT工具类生成token返回至前端
7.前端接受到token,存入storage

后端请求工具类

/**
 * 后端请求工具类
 * @param {} params 
 */
//定义请求跟路径
const baseUrl = "http://localhost:8080"

//同时并发请求数
let ajaxTimes=0;
export const requestUtil=(params)=>{
  //判断url中是否带有/my 是私有路径 带token 

  let header = {...params.header}

  if(params.url.includes("/my/")){
    header["token"]=wx.getStorageSync('token')
  }
  var start = new Date().getTime();
  ajaxTimes++;
  wx.showLoading({
    title: '加载中...',
    mask:true
  })
  // 模拟网络延迟加载
  // while(true){
  //   if (new Date().getTime()-start>3*100){
  //     break;
  //   }
  // }
  return new Promise((resolve,reject)=>{
    wx.request({
      ...params,
      header,
      url:baseUrl+params.url,
      success:(result)=>{
        resolve(result.data)
      },
      fail:(error)=>{
        reject(error)
      },
      complete:()=>{
        ajaxTimes--;
        if(ajaxTimes==0){
          wx.hideLoading();//关闭加载图标
        }
      }
    })
  })
}


//返回请求根路径baseUrl
export const getBaseUrl=()=>{
  return baseUrl;
}

//wxlogin封装
export const getWxLogin=()=>{
  return new Promise((resolve,reject)=>{
    wx.login({
      timeout:5000,
      success: (res) => {
        resolve(res)
      },
      fail:(err)=>{
        reject(err)
      }
    })
  });
}

//getUserProfile封装
export const getUserProfile=()=>{
  return new Promise((resolve,reject)=>{
    wx.getUserProfile({
      desc: '获取用户信息',
      success: (res) => {
        resolve(res)
      },
      fail:(err)=>{
        reject(err)
      }
    })
  });
}

//promise形式的小程序微信支付封装
export const requestPay=(pay)=>{
  return new Promise((resolve,reject)=>{
    wx.requestPayment({
      ...pay,
      success: (res) => {
        resolve(res)
      },
      fail:(err)=>{
        reject(err)
      }
    })
  });
}

index.js

// pages/my/index.js
import {getBaseUrl,requestPay,getWxLogin,getUserProfile,requestUtil}from '../../utils/requestUtils';
import regeneratorRuntime from'../../lib/runtime/runtime';

Page({

  /**
   * 页面的初始数据
   */
  data: {
  	//存储user对象
    userInfo:{}
  },
  /**
   * 请求后端获取用户token
   * @param {*} loginParam 
   */
  async wxLogin(loginParam){
    const result = await requestUtil({url:"/user/wxlogin",data:loginParam,method:"post"})
    console.log("result",result)

    let token = result.token
    if(result.code === 0){
      wx.setStorageSync('token', token)
    }
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
  //每次页面展示都会自动检测缓存中有没有token来判断是否登录
    const token = wx.getStorageSync('token')
    if(!token){
      wx.showModal({
        title: '提示',
        content: '微信授权登录后才可进入个人中心',
        complete: (res) => {
          if (res.cancel) {
            
          }
      
          if (res.confirm) {
            Promise.all([getWxLogin(),getUserProfile()]).then(res=>{
              console.log(res[0].code)
              console.log(res[1].userInfo.nickName)
              let loginParam = {
                code:res[0].code,
                nickName:res[1].userInfo.nickName,
                avatarUrl:res[1].userInfo.avatarUrl
              }
              console.log(loginParam)
              wx.setStorageSync('userInfo', res[1].userInfo)
              this.wxLogin(loginParam)
              this.setData({
                userInfo:res[1].userInfo
              })
            }) 
          }
        }
      })
    }else{
      //token存在
      const userInfo = wx.getStorageSync('userInfo')
      this.setData({
        userInfo:userInfo
      })
    }
  }
})

index.wxml

<view class="user_info">
  <!-- 用户背景信息开始 -->
  <view class="user_info_bg">
    <view class="user_info_wrap">
    <image src="{{userInfo.avatarUrl}}" mode=""/>
      <view class="user_name">
        {{userInfo.nickName}} 
      </view>
    </view>
    
  </view>
  <!-- 用户背景信息结束 -->

  <!-- 用户操作菜单开始 -->
  <view class="user_menu">
    <!-- 订单管理开始 -->
    <view class="order_wrap">
      <view class="order_title">
        我的订单
      </view>
      <view class="order_content">
        <navigator url="/pages/order/index?type=1">
          <view class="iconfont icon-daifukuan">
            
          </view>
          <view class="order_name">
            待付款
          </view>
        </navigator>

        <navigator url="/pages/order/index?type=2">
          <view class="iconfont icon-daishouhuo">
            
          </view>
          <view class="order_name">
            待收货
          </view>
        </navigator>

        <navigator url="/pages/order/index?type=3">
          <view class="iconfont icon-tuikuan">
            
          </view>
          <view class="order_name">
            退款/退货
          </view>
        </navigator>

        <navigator url="/pages/order/index?type=0">
          <view class="iconfont icon-dingdan">
            
          </view>
          <view class="order_name">
            全部订单
          </view>
        </navigator>
      </view>
    </view>
    <!-- 订单管理结束 -->

    <!-- 收货地址开始 -->
    <view class="address_wrap" bindtap="handleEditAddress">
      收货地址管理
    </view>
    <!-- 收货地址结束 -->


    <!-- 应用相关信息开始 -->
    <view class="app_info_wrap">
      <view class="app_info_item app_info_contact">
        <text>联系客服</text>
        <text>400-111-22212</text>
      </view>

      <view class="app_info_item">
        <button open-type="feedback" class="feedback ">意见反馈</button>
      </view>

      <view class="app_info_item">
        <text>关于我们</text>
      </view>
    </view>
    <!-- 应用相关信息结束 -->
  </view>
  <!-- 用户操作菜单结束 -->
</view>

indx.less

/* pages/my/index.wxss */
page{
  background-color: #F6F6F4;
}
.user_info{
  .user_info_bg{
    position: relative;
    height: 40vh;
    background-color: var(--themeColor);
    .user_info_wrap{
      position: relative;
      top: 30%;
      text-align: center;
      image{
        width: 150rpx;
        height: 150rpx;
        border-radius: 50%;
      }
      .user_name{

      }
    }
  }

  .user_menu{
    margin-top: 15rpx;
    background-color: #FFF;
    .order_wrap{
      margin: 15rpx;
      .order_title{
        padding: 15rpx;
        padding-left: 35rpx;
        border-bottom: 5rpx solid #F6F6F4;
      }
      .order_content{
        display: flex;
        text-align: center;
        padding: 20rpx;
        navigator{
          flex: 1;
          padding: 15rpx 0 ;
          .iconfont{
            font-size: 24px;
          }
          .order_name{
          }
        }
      }
    }

    .address_wrap{
      margin: 15rpx;
      margin-top: 20rpx;
      background-color: #FFF;
      padding:20rpx 0;
      padding-left: 35rpx;
    }
    
    .app_info_wrap{
      margin: 15rpx;
      margin-top: 20rpx;
      background-color: #FFF;
      .app_info_item{
        padding: 20rpx;
        padding-left: 35rpx;
        border-bottom: 5rpx solid #F6F6F4;
      }
      .app_info_contact{
        display: flex;
        justify-content: space-between;
      }
      .feedback{
        margin: 0;
        padding: 0;
        background-color: transparent;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: start;
        align-items: center;
        font-size: 28rpx;
        font-weight: normal;
        flex-grow: 1;
      }
    }

  }
}

后端controller

package cn.azh.controller;

import cn.azh.constant.SystemConstant;
import cn.azh.entity.R;
import cn.azh.properties.WeixinProperties;
import cn.azh.entity.WxUserInfo;
import cn.azh.service.IWxUserInfoService;
import cn.azh.util.HttpClientUtil;
import cn.azh.util.JwtUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信用户controller
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private WeixinProperties weixinProperties;

    @Autowired
    private HttpClientUtil httpClientUtil;

    @Autowired
    private IWxUserInfoService wxUserInfoService;

    @PostMapping("/wxlogin")
    public R wxLogin(@RequestBody WxUserInfo wxUserInfo){
        System.out.println(wxUserInfo.getCode());
        //拼接jscode2sessionUrl获取openId
        String jscode2sessionUrl = weixinProperties.getJscode2sessionUrl()
                + "?appid="+weixinProperties.getAppid()
                +"&secret="+weixinProperties.getSecret()
                +"&js_code="+wxUserInfo.getCode()
                +"&grant_type=authorization_code";

        System.out.println(jscode2sessionUrl);
        String result = httpClientUtil.sendHttpGet(jscode2sessionUrl);
        JSONObject jsonObject = JSON.parseObject(result);
        String openid = jsonObject.get("openid").toString();
        System.out.println(openid);
		//数据库中查询是否存在该用户
        WxUserInfo resultWxUserInfo = wxUserInfoService.getOne(new QueryWrapper<WxUserInfo>().eq("openid", openid));
        if(resultWxUserInfo==null){
            //用户不存在 插入用户
            wxUserInfo.setOpenid(openid);
            wxUserInfo.setRegisterDate(new Date());
            wxUserInfo.setLastLoginDate(new Date());
            wxUserInfoService.save(wxUserInfo);
        }else {
            //用户存在 更新用户
            resultWxUserInfo.setNickName(wxUserInfo.getNickName());
            resultWxUserInfo.setAvatarUrl(wxUserInfo.getAvatarUrl());
            resultWxUserInfo.setLastLoginDate(new Date());
            wxUserInfoService.updateById(resultWxUserInfo);
        }
        //利用jwt生成token返回前端
        String token = JwtUtils.createJWT(openid, wxUserInfo.getNickName(), SystemConstant.JWT_TTL);

        Map<String, Object> map = new HashMap<>();
        map.put("token",token);
        return R.ok(map);

    }
}

JWT工具类

package cn.azh.util;

import cn.azh.constant.SystemConstant;
import cn.azh.entity.CheckResult;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;

public class JwtUtils {

    /**
     * 签发JWT
     * @param id
     * @param subject 可以是JSON数据 尽可能少
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        SecretKey secretKey = generalKey();
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)   // 主题
                .setIssuer("Java1234")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }

    /**
     * 验证JWT
     * @param jwtStr
     * @return
     */
    public static CheckResult validateJWT(String jwtStr) {
        CheckResult checkResult = new CheckResult();
        Claims claims = null;
        try {
            claims = parseJWT(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
            checkResult.setSuccess(false);
        } catch (SignatureException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        } catch (Exception e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        }
        return checkResult;
    }

    /**
     * 生成加密Key
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    /**
     * 解析JWT字符串
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

    public static void main(String[] args) throws InterruptedException {
        //小明失效 10s
        String sc = createJWT("1","小明", 60 * 60 * 1000);
        System.out.println(sc);
        System.out.println(validateJWT(sc).getErrCode());
        System.out.println(validateJWT(sc).getClaims().getId());
        System.out.println(validateJWT(sc).getClaims().getSubject());
        //Thread.sleep(3000);
        System.out.println(validateJWT(sc).getClaims());
        Claims claims = validateJWT(sc).getClaims();
        String sc2 = createJWT(claims.getId(),claims.getSubject(), SystemConstant.JWT_TTL);
        System.out.println(sc2);
    }

}

HttpClientUtil工具类

package cn.azh.util;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.util.PublicSuffixMatcher;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Component
public class HttpClientUtil {

    /**
     * 默认参数设置
     * setConnectTimeout:设置连接超时时间,单位毫秒。
     * setConnectionRequestTimeout:设置从connect Manager获取Connection 超时时间,单位毫秒。
     * setSocketTimeout:请求获取数据的超时时间,单位毫秒。访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 暂时定义15分钟
     */
    private RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(600000).setConnectTimeout(600000).setConnectionRequestTimeout(600000).build();

    /**
     * 静态内部类---作用:单例产生类的实例
     * @author Administrator
     *
     */
    private static class LazyHolder {
        private static final HttpClientUtil INSTANCE = new HttpClientUtil();

    }
    private HttpClientUtil(){}
    public static HttpClientUtil getInstance(){
        return LazyHolder.INSTANCE;
    }

    /**
     * 发送 post请求
     * @param httpUrl 地址
     */
    public String sendHttpPost(String httpUrl) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 post请求
     * @param httpUrl 地址
     * @param params 参数(格式:key1=value1&key2=value2)
     */
    public String sendHttpPost(String httpUrl, String params) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        try {
            //设置参数
            StringEntity stringEntity = new StringEntity(params, "UTF-8");
            stringEntity.setContentType("application/x-www-form-urlencoded");
            httpPost.setEntity(stringEntity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 发送 post请求
     * @param httpUrl 地址
     * @param maps 参数
     */
    public String sendHttpPost(String httpUrl, Map<String, String> maps) {
        HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost
        // 创建参数队列
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        for (String key : maps.keySet()) {
            nameValuePairs.add(new BasicNameValuePair(key, maps.get(key)));
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 发送Post请求
     * @param httpPost
     * @return
     */
    private String sendHttpPost(HttpPost httpPost) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 创建默认的httpClient实例
            httpClient = HttpClients.createDefault();
            httpPost.setConfig(requestConfig);
            // 执行请求
            long execStart = System.currentTimeMillis();
            response = httpClient.execute(httpPost);
            long execEnd = System.currentTimeMillis();
            System.out.println("=================执行post请求耗时:"+(execEnd-execStart)+"ms");
            long getStart = System.currentTimeMillis();
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
            long getEnd = System.currentTimeMillis();
            System.out.println("=================获取响应结果耗时:"+(getEnd-getStart)+"ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭连接,释放资源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 发送 get请求
     * @param httpUrl
     */
    public String sendHttpGet(String httpUrl) {
        HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求
        return sendHttpGet(httpGet);
    }

    /**
     * 发送 get请求Https
     * @param httpUrl
     */
    public String sendHttpsGet(String httpUrl) {
        HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求
        return sendHttpsGet(httpGet);
    }

    /**
     * 发送Get请求
     * @param httpGet
     * @return
     */
    private String sendHttpGet(HttpGet httpGet) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 创建默认的httpClient实例.


            httpClient = HttpClients.createDefault();

            httpGet.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpGet);
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭连接,释放资源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 发送Get请求Https
     * @param httpGet
     * @return
     */
    private String sendHttpsGet(HttpGet httpGet) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 创建默认的httpClient实例.
            PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(new URL(httpGet.getURI().toString()));
            DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
            httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).build();
            httpGet.setConfig(requestConfig);
            // 执行请求
            response = httpClient.execute(httpGet);
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭连接,释放资源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 发送xml数据
     * @param url
     * @param xmlData
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static HttpResponse sendXMLDataByPost(String url, String xmlData)
            throws ClientProtocolException, IOException {
        HttpClient httpClient = HttpClients.createDefault();
        HttpPost httppost = new HttpPost(url);
        StringEntity entity = new StringEntity(xmlData);
        httppost.setEntity(entity);
        httppost.setHeader("Content-Type", "text/xml;charset=UTF-8");
        HttpResponse response = httpClient.execute(httppost);
        return response;
    }

    /**
     * 获得响应HTTP实体内容
     *
     * @param response
     * @return
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    public static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream is = entity.getContent();
            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line = br.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line + "\n");
                line = br.readLine();
            }
            return sb.toString();
        }
        return "";
    }
}

2.微信商城小程序首页轮播图,商品展示逻辑分析及代码展示

流程分析
1.页面打开后,在onLoad函数中加载
//轮播图数组
swiper_list:[],
baseUrl:‘’,
//商品大类数组
bigType_list:[],
//商品大类数组第一行
bigType_list_row1:[],
//商品大类数组第二行
bigType_list_row2:[],
//热卖商品数组
hotProduct_list:[]
2.wxml中使用wx:for遍历生成数据
3.在每个需要跳转到对应商品链接的加上id进行跳转展示url:/pages/product_detail/index?id={{swiper.id}}

index.wxml

<view >
  <!-- 搜索框开始 -->
  <SearchBar></SearchBar>
  <!-- 搜索框结束 -->

  <!-- 轮播图开始 -->
  <view class="index_swiper">
    <swiper autoplay circular indicator-dots >
      <swiper-item 
        wx:for="{{swiper_list}}" 
        wx:for-item="swiper" 
        wx:key="id">
        <navigator url="/pages/product_detail/index?id={{swiper.id}}">
          <image mode="widthFix" src="{{baseUrl+'/image/swiper/'+swiper.swiperPic}}"></image>
        </navigator> 
      </swiper-item>
    </swiper>
  </view>
  <!-- 轮播图结束 -->

  <!-- 商品大类开始 -->
  <view class="indexBigType">
    <view class="bigTypeRow">
      <navigator 
        wx:for="{{bigType_list_row1}}"
        wx:for-item="bigType" 
        wx:key="id"
        bindtap="handleTypeJump"
        data-index="{{index}}"
        >
        <image mode="widthFix" src="{{baseUrl+'/image/bigType/'+bigType.image}}"></image>
      </navigator>
    </view>

    <view class="bigTypeRow">
      <navigator 
        wx:for="{{bigType_list_row2}}"
        wx:for-item="bigType" 
        wx:key="id"
        bindtap="handleTypeJump"
        data-index="{{index+5}}"
        >
        <image mode="widthFix" src="{{baseUrl+'/image/bigType/'+bigType.image}}"></image>
      </navigator>
    </view>
  </view>
  <!-- 商品大类结束 -->

  <!-- 热卖商品开始 -->
  <view class="index_hotProduct">
    <view class="product_title">热卖推荐</view>
    <view class="product_list">
      <view class="product_detil"
          wx:for="{{hotProduct_list}}"
          wx:for-item="hotProduct"
          wx:key="id">
        <navigator
          url="/pages/product_detail/index?id={{hotProduct.id}}">
          <image mode="widthFix" src="{{baseUrl+'/image/product/'+hotProduct.proPic}}"></image>
          <view class="product_name">{{hotProduct.name}}</view>
          <view class="product_price">${{hotProduct.price}}</view>
          <button type="warn" siez="mini">立即购买</button>
        </navigator> 
      </view>
    </view>
  </view>
  <!-- 热卖商品结束 -->
</view>

index.js

//导入request请求工具类
import {getBaseUrl,requestUtil}from '../../utils/requestUtils';
import regeneratorRuntime from'../../lib/runtime/runtime';
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //轮播图数组
    swiper_list:[],
    baseUrl:'',
    //商品大类数组
    bigType_list:[],
    //商品大类数组第一行
    bigType_list_row1:[],
    //商品大类数组第二行
    bigType_list_row2:[],
    //热卖商品数组
    hotProduct_list:[]
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    //设置baseUrl
    this.setData({
      baseUrl:getBaseUrl()
    })
    //发送异步请求获取轮播数据
    //页面加载后获取轮播图,商品大类,热卖商品数组
    this.getSwiperList();
    this.getBigTypeList();
    this.getHotProduct();
  },
  async getSwiperList(){
    const result =  await requestUtil({url:'/product/findSwiper',method:'GET'});
    this.setData({
      swiper_list:result.message
    }) 
  },
  async getBigTypeList(){
    const result = await requestUtil({url:'/bigType/findAll',method:'GET'})
    const bigTypeList = result.message;
    //使用filter函数将商品大类分行赋值到bigTypeList_row1 2中
    const bigTypeList_row1 = bigTypeList.filter((item,index)=>{
      return index<5;
    })
    const bigTypeList_row2 = bigTypeList.filter((item,index)=>{
      return index>=5;
    })
    this.setData({
      bigType_list:bigTypeList,
      bigType_list_row1:bigTypeList_row1,
      bigType_list_row2:bigTypeList_row2
    })
  },
  //获取热卖商品
  async getHotProduct(){
    const result = await requestUtil({url:'/product/findHot',method:'GET'})
    this.setData({
      hotProduct_list:result.message
    })
  },
  handleTypeJump(e){//商品大类点击跳转商品分类页面事件
    //获取index
    const index = e.currentTarget.dataset.index
    console.log(index)
    const app = getApp();
    //在全局数据中设置index 并跳转页面
    app.globalData.index = index;
    wx.switchTab({
      url: '/pages/category/index',
    })
  }
})

index.less

.container {
  width: 100%;
}
.index_swiper swiper {
  width: 750rpx;
  height: 375rpx;
}
.index_swiper swiper swiper-item image {
  width: 100%;
}
.indexBigType {
  padding-top: 10rpx;
  background-color: #F7F7F7;
}
.indexBigType .bigTypeRow {
  display: flex;
}
.indexBigType .bigTypeRow navigator {
  flex: 1;
}
.indexBigType .bigTypeRow navigator image {
  width: 150rpx;
}
.index_hotProduct .product_title {
  font-size: 32rpx;
  font-weight: 600;
  padding: 20rpx;
  color: var(--themeColor);
  background-color: #E0E0E0;
}
.index_hotProduct .product_list {
  display: flex;
  flex-wrap: wrap;
}
.index_hotProduct .product_list .product_detil {
  width: 46%;
  text-align: center;
  margin: 15rpx;
}
.index_hotProduct .product_list .product_detil navigator image {
  background-color: #F5F5F5;
  width: 100%;
}
.index_hotProduct .product_list .product_detil navigator .product_name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.index_hotProduct .product_list .product_detil navigator .product_price {
  color: var(--themeColor);
}

3.微信小程序购物车实现详解,逻辑步骤分析及代码展示

index.wxml

<!-- 收货地址开始 -->
<view class="reveive_address_row">
  <!-- 如果address属性为空(页面加载时会从缓存中获取)则展示获取收货地址按钮 -->
  <view class="address_btn" wx:if="{{!address}}">
    <button bindtap="handleChooseAdress" type="warn" plain >获取收货地址</button>
  </view> 

  <view wx:else class="user_info_row">
    <view class="user_info">
      <view>
        收货人:{{address.userName}},{{address.telNumber}}
      </view>
      <view>
        {{address.provinceName+address.cityName+address.countyName+address.detailInfo}}
      </view>
    </view>
    <view class="change_address_btn">
      <button size="mini" plain>更换地址</button>
    </view>
  </view>
</view>
<!-- 收货地址结束 -->

<!-- 购物车清单开始  -->
<view class="cart_content">
  <view class="cart_main">
  <block wx:if="{{cart.length!=0}}">
    <view class="cart_item"
      wx:for="{{cart}}"
      wx:key="id"
    >
      <!-- 复选框开始 -->
      <view class="cart_chk_wrap">
        <checkbox-group bindtap="handleItemChange" data-id="{{item.id}}">
          <checkbox checked="{{item.checked}}"></checkbox>          
        </checkbox-group>
      </view>
      <!-- 复选框结束 -->

      <!-- 图片开始 -->
      <navigator class="cart_img_wrap" 
        url="/pages/product_detail/index?id={{item.id}}"
      >
        <image mode="widthFix" src="{{baseUrl+'/image/product/'+item.proPic}}"></image>
      </navigator>
      <!-- 图片结束 -->

      <!-- 商品信息开始 -->
      <view class="cart_info_wrap">
        <navigator 
          url="/pages/product_detail/index?id={{item.id}}"
        >
          <view class="goods_name">{{item.name}}</view>
        </navigator>
            
        <view class="goods_price_wrap">
          <view class="goods_price">¥{{item.price}}</view>
          <view class="cart_num_tool">
            <view class="num_edit" bindtap="handleItemNumEdit" data-id="{{item.id}}" data-operation="{{-1}}">
              -
            </view>
            <view class="goods_num">{{item.num}}</view>
            <view class="num_edit" bindtap="handleItemNumEdit" data-id="{{item.id}}" data-operation="{{1}}">
              +
            </view>
          </view>
        </view>
      </view>
      <!-- 商品信息结束 -->
    </view>
  </block>
  <block wx:else>nothing</block>
  </view>
</view>
<!-- 购物车清单结束 -->


<!-- 底部工具栏开始 -->
<view class="footer_tool">
  <!-- 全选开始 -->
  <view class="all_chk_wrap">
    <checkbox-group bindchange="handleItemAllCheck">
      <checkbox checked="{{allChecked}}"><text decode="true">&nbsp;&nbsp;全选</text></checkbox>
    </checkbox-group>
  </view>
  <!-- 全选结束 -->

  <!-- 合计开始 -->
  <view class="price_total_wrap">
    <view class="total_price">
      合计:<text class="price_text">¥ {{totalPrice}}</text>
    </view>
  </view>
  <!-- 合计结束 -->

  <!-- 结算开始 -->
  <view class="order_pay_wrap" bindtap="handlePay">
    结算({{totalNum}})
  </view>
  <!-- 结算结束 -->
</view>
<!-- 底部工具栏结束 -->



index.js

// pages/cart/index.js
import {getBaseUrl,requestUtil}from '../../utils/requestUtils';
import regeneratorRuntime from'../../lib/runtime/runtime';
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //地址对象
    address:{},
    //购物车数组
    cart:[],
    baseUrl:"",
    //全选
    allChecked:false,
    totalPrice:0,
    totalNum:0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.setData({
      baseUrl:getBaseUrl()
    })

  },
  //处理点击获取地址按钮
  handleChooseAdress(){
    //调用wx官方选择地址事件
    wx.chooseAddress({
      success:(result)=>{
        console.log(result)
        //将地址保存至storage中
        wx.setStorageSync('address', result)
      }
    })
  },
  onShow:function(){
    //页面展示后,从缓存中获取地址,购物车(如购物车为空则赋值空数组防止报错)
    const address = wx.getStorageSync('address');
    const cart = wx.getStorageSync('cart')||[];
    this.setData({
      address:address,
    })
    this.setCart(cart);
  },
  //商品选中事件处理
  handleItemChange:function(e){
    //解构 得到id
    const {id}  = e.currentTarget.dataset;
    //解构 得到cart 与let cart = this.data.cart
    let {cart}=this.data;
    //通过findIndex函数遍历得到与目标id相等的数组下标
    let index = cart.findIndex(v=>v.id==id);
    console.log(index)
    //如果该下标对应的元素的是否选中属性为true则赋值为false 反之赋值为true
    cart[index].checked=!(cart[index].checked);
    //更新购物车
    this.setCart(cart);
  },
  //设置工具栏状态 重新计算 全选 总价 总数量 重设置缓存
  setCart(cart){
    let allChecked = true;
    let totalPrice = 0;
    let totalNum = 0;
    //遍历cart数组
    cart.forEach(v=>{
      if(v.checked){
        //如果对应元素的checked属性为true 则计算总数量,总价
        totalNum = totalNum + v.num
        totalPrice = totalPrice + v.num * v.price
      }else{
        //反之 全部选中的属性为false
        allChecked = false
      }
    })
    allChecked = cart.lenght!=0?allChecked:false;
    this.setData({
      cart:cart,
      allChecked:allChecked,
      totalNum:totalNum,
      totalPrice:totalPrice
    })
 
    //cart设置到缓存
    wx.setStorageSync('cart', cart)
  },
  //商品全选事件处理
  handleItemAllCheck(){
    let {cart,allChecked} = this.data;
    allChecked = !allChecked
    cart.forEach(v=>v.checked=allChecked);

    //重新计算 设置缓存
    this.setCart(cart);
  },
  //商品数量加减
  handleItemNumEdit(e){
    const {id}  = e.currentTarget.dataset;
    const {operation}  = e.currentTarget.dataset;
    let {cart}=this.data;
    let index = cart.findIndex(v=>v.id==id);
    if(cart[index].num ===1 && operation===-1){
      wx.showModal({
        title: '系统提示',
        content: '是否要删除商品?',
        complete: (res) => {
          if (res.cancel) {
            return;
          }
          if (res.confirm) {
            cart.splice(index,1)//从指定下标开始删除n个元素
            this.setCart(cart)
          }
        }
      })

    }
    cart[index].num+=operation;//operation+1/operation-1
    this.setCart(cart)
  },
  //点击结算
  handlePay(){
    const {address,totalPrice} = this.data;
    if(!address){
      wx.showToast({
        title: '"没选择收货地址"',
      })
      return;
    }
    if(totalPrice===0){
      wx.showToast({
        title: '"你还没有选购"',
      })
      return;
    } 
    wx.navigateTo({
      url: '/pages/pay/index',
    })
    
  }

})

index.less

page{
  padding-bottom: 70rpx;
}
.reveive_address_row{
  .address_btn{
    padding:20rpx;
    btn{
      width: 60%;
    }
  }
  .user_info_row{
    display: flex;
    padding:20rpx;
    .user_info{
      flex: 5;
    }
    .change_address_btn{
      flex: 3;
      text-align: right;
      button{
        border: 1rpx solid gray;
        font-weight: normal;
      }
    }
  }
}

.cart_content{
  background-color: #F5F5F5;
  .cart_main{
    padding:2rpx 10rpx 10rpx 10rpx;
    .cart_item{
      display: flex;
      margin: 20rpx;
      padding-right: 20rpx;
      background-color: white;
      border-radius: 10px;
      .cart_chk_wrap{
        flex: 1;
        display: flex;
        justify-content: center;
        align-items: center;
        margin: 20rpx;
        checkbox{

        }
      }
      .cart_img_wrap{
        flex: 2;
        display: flex;
        justify-content: center;
        align-items: center;
        margin: 20rpx;
        background-color: #F5F5F5;
        border-radius: 10rpx;
        image{
          width: 80%;
        }
      }
      .cart_info_wrap{
        flex: 4;
        display: flex;
        flex-direction: column;
        justify-content: space-around;
        .goods_name{
          font-weight: bolder;
          display: -webkit-box;
          overflow: hidden;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 2;
        }
        .goods_price_wrap{
          display: flex;
          justify-content: space-between;
          .goods_price{
            color: var(--themeColor);
            font-size: 34rpx;
          }
          .cart_num_tool{
            display: flex;
            .num_edit{
              display: flex;
              justify-content: center;
              align-items: center;
              width: 55rpx;
              height: 55rpx;
              font-weight: bolder;
            }
            .goods_num{
              display: flex;
              justify-content: center;
              align-items: center;
              width: 85rpx;
              background-color: #F5F5F5;
            }
          }
        }
      }
    }
  }
}

.footer_tool{
  display: flex;
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  border-top:1rpx solid #ccc;
  .all_chk_wrap{
    flex:2;
    display: flex;
    justify-content: center;
    align-items: center;
    padding-left: 25rpx;
  }

  .price_total_wrap{
    flex:5;
    display: flex;
    justify-content: center;
    align-items: center;
    .total_price{
      .price_text{
        color: var(--themeColor);
        font-weight: bolder;
        font-size: 34rpx;
        margin-left: 20rpx;
      }
    }
  }

  .order_pay_wrap{
    flex:3;
    display: flex;
    justify-content: center;
    align-items: center;
    background-image: linear-gradient(90deg,#ff740b,#fe6227);
    border-radius: 60rpx;
    color: #fff;
    font-size: 32rpx;
    font-weight: 600;
    margin-top: 10rpx;
    margin-bottom: 10rpx;
    width: 40rpx;
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值