跳至主要內容

曲师商城

sixkey大约 19 分钟项目文档项目

曲师商城

1、项目需求分析

用户需求分析

  • 注册和登录功能:
    • 用户可以通过填写注册表单完成账号注册。
    • 用户可以使用注册的账号和密码进行登录。
    • 提供忘记密码功能,用户可以通过邮箱或手机验证码重置密码。
  • 商品浏览和搜索功能:
    • 用户可以浏览商品列表,查看商品的基本信息、价格、图片等。
    • 用户可以通过关键词搜索商品,系统返回相关商品的结果。
  • 购物车管理功能:
    • 用户可以将感兴趣的商品添加到购物车。
    • 用户可以修改购物车中商品的数量。
    • 用户可以删除购物车中的商品。
    • 用户可以清空购物车。
  • 下单和支付功能:
    • 用户可以选择购物车中的商品完成下单。
    • 用户需要填写配送地址和选择支付方式。
    • 系统生成订单并显示订单详情。
    • 用户进行支付操作,可以选择在线支付或货到付款。
  • 查看订单状态功能:
    • 用户可以查看订单的物流信息和状态,了解订单的进展情况。
    • 用户可以查看订单的详细信息,如商品清单、收货地址等。
  • 取消订单功能:
    • 用户可以在一定条件下取消未发货的订单,如退货期未过等。
  • 评价和回复功能:
    • 用户可以对购买的商品进行评价,并选择评分和文字评论。
    • 商家可以回复用户的评价。
  • 优惠券和积分功能:
    • 用户可以领取优惠券和积分,用于抵扣购物金额或兑换商品。
  • 促销活动功能:
    • 商家可以发布促销活动,如限时折扣、满减等。
    • 用户可以参与促销活动,享受相应的优惠。
  • 客服支持功能:
    • 提供在线客服功能,解答用户的问题和处理售后问题。
  • 搜索和推荐功能:
    • 用户可以通过关键词搜索商品,系统根据用户的浏览和购买记录推荐相关商品。
  • 移动端适配功能:
    • 商城需要适配不同的移动设备,提供良好的移动端用户体验。

商家入驻代办

商家需求分析

  • 商户入驻功能:
    • 商家可以申请入驻商城,并在平台上销售自己的商品。
    • 商家需要提供相关的营业执照、品牌授权等信息进行认证。
  • 商品管理功能:
    • 商家可以发布、修改、删除自己的商品。
    • 商家可以设置商品的基本信息、价格、库存等。
    • 商家可以管理商品分类和品牌信息。
  • 订单管理功能:
    • 商家可以查看自己店铺的订单列表,包括待处理订单和已完成订单。
    • 商家可以接单、发货、退款等操作。
  • 物流管理功能:
    • 商家可以更新自己店铺的物流信息,如发货状态、快递单号等。

2、技术选型

针对曲师商城项目,考虑以下技术选型:

  1. 前端开发框架:Vue主流的前端框架。
  2. 后端开发语言:Java常用的后端开发语言。
  3. 数据库:MySQL常见的关系型型数据库。
  4. 服务器端框架:Spring Boot、SpringCloud常用的后端框架,简化开发流程和提高开发效率。
  5. 缓存技术:Redis常用的缓存技术,用于提高系统性能和减轻数据库压力。
  6. 消息队列:RabbitMQ常用的消息队列技术,用于处理异步任务和解耦系统模块之间的通信。
  7. 搜索引擎:Elasticsearch用于全文搜索和数据分析的搜索引擎技术。
  8. 版本控制工具:Git作为代码版本控制工具,用于团管理代码的版本。
  9. 容器化技术:Docker用于将应用打包成容器,实现快速部署和可移植性。
  10. 云服务:根据实际需求选择合适的云服务提供商(如AWS、阿里云、腾讯云等),用于部署和托管商城系统。

3、项目设计

用户模块

系统架构设计:
  • 前端采用React框架,使用Redux进行状态管理。
  • 后端采用Node.js和Express框架,使用MongoDB作为数据库。
  • 使用Nginx作为反向代理服务器,负载均衡和静态资源处理。
  • 使用Redis作为缓存技术,提高系统性能。
  • 使用RabbitMQ作为消息队列,处理异步任务和解耦系统模块之间的通信。
(1)功能模块设计:
  • 用户模块:包括用户注册、登录、个人信息管理等功能。
  • 商品模块:包括商品的添加、修改、删除、查询等功能。
  • 订单模块:包括下单、支付、查看订单状态等功能。
  • 物流模块:用于商家更新订单的物流信息。
  • 评价模块:用户可以对购买的商品进行评价,商家可以回复评价。
  • 优惠券和积分模块:用户可以领取优惠券和积分,用于抵扣购物金额或兑换商品。
  • 促销活动模块:商家可以发布促销活动,如限时折扣、满减等。
  • 客服支持模块:提供在线客服功能,解答用户的问题和处理售后问题。
  • 搜索和推荐模块:用户可以通过关键词搜索商品,系统会根据用户的浏览和购买记录推荐相关商品。
  • 数据统计和分析模块:统计商城的访问量、销售额等数据,为商家提供数据分析和决策支持。
(2)数据库设计:
  • 用户表:存储用户的基本信息,如用户名、密码、邮箱等。
  • 商品表:存储商品的基本信息,如商品名称、价格、库存等。
  • 分类表:存储商品的分类信息,如服装、电子产品等。
  • 品牌表:存储商品的品牌信息,如Nike、Apple等。
  • 订单表:存储订单的基本信息,如订单号、下单时间、支付状态等。
  • 物流表:存储订单的物流信息,如物流公司、运单号等。
  • 评价表:存储用户对商品的评价信息,如评分、评论内容等。
  • 优惠券表:存储优惠券的基本信息,如优惠码、面额等。
  • 积分表:存储用户的积分信息,如积分余额、获取途径等。
(3)接口设计:
  • 用户接口:包括注册接口、登录接口、个人信息接口等。
  • 商品接口:包括添加商品接口、修改商品接口、删除商品接口、查询商品接口等。
  • 订单接口:包括下单接口、支付接口、查看订单接口等。
  • 物流接口:用于商家更新订单的物流信息接口。
  • 评价接口:包括添加评价接口、回复评价接口等。
  • 优惠券和积分接口:包括领取优惠券接口、使用优惠券接口、积分获取接口等。
  • 促销活动接口:包括发布促销活动接口、参与促销活动接口等。
  • 客服支持接口:提供在线客服功能的相关接口。
  • 搜索和推荐接口:包括商品搜索接口、推荐商品接口等。

商家模块

  1. 商家注册与认证流程:明确商家注册的步骤和要求,如填写公司信息、上传证件等。同时,设计一个认证流程,确保商家的身份和资质真实有效。
  2. 商品管理功能:细化商品管理的界面和操作流程,包括添加商品、编辑商品信息、删除商品等功能。可以考虑提供批量操作和导入导出功能,方便商家进行大量商品的管理。
  3. 订单管理功能:细化订单管理的界面和操作流程,包括查看订单详情、接单、发货、退款等功能。可以考虑提供订单筛选和排序功能,方便商家快速找到需要处理的订单。
  4. 物流管理功能:细化物流管理的界面和操作流程,包括查询物流状态、更新物流信息等功能。可以考虑提供物流公司选择和运单号生成功能,方便商家与物流公司进行交互。
  5. 评价管理功能:细化评价管理的界面和操作流程,包括查看买家的评价、回复评价等功能。可以考虑提供评价分类和搜索功能,方便商家查找和管理评价信息。
  6. 数据统计与分析功能:细化数据统计与分析的界面和报表展示,包括销售额统计、订单量统计、用户评价统计等功能。可以考虑提供图表展示和数据导出功能,方便商家进行数据分析和决策。
  7. 营销活动支持功能:细化营销活动的设置和管理流程,包括优惠券、满减活动等。可以考虑提供活动规则设置、活动发布和推广等功能,方便商家进行营销活动的管理和执行。
  8. 客服支持功能:细化客服支持的界面和沟通方式,包括在线聊天工具、电话支持等。可以考虑提供智能客服机器人和问题解答库,提高客服效率和质量。

技术积累

后端

一、gateway路由路径匹配

匹配多个路径

      routes:
        - id: service-user
          uri: lb://service-user
          predicates:
            - Path= /common/**,/admin/**  #匹配多个路径

二、redis缓存

1、缓存更新策略

内存淘汰超时剔除主动更新
说明不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。给缓存数据添加TTL时间,到期后自动删除缓存。下次查询时更新缓存。编写业务逻辑,在修改数据库的同时,更新缓存。
一致性差一般好
维护成本无低高

业务场景

  • 低一致性需求:使用内存淘汰机制。
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案。

三个问题需要考虑:

(1)删除缓存还是更新缓存?

  • 更新缓存:每次更新数据库都更新缓存,无效写操作较多
  • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存(建议)

(2)如何保证缓存与数据库的操作的同时成功或失败?

  • 单体系统:将缓存与数据库操作放在一个事务
  • 分布式系统:利用TCC等分布式事务方案

(3)先操作缓存还是先操作数据库?

  • 先删除缓存,再操作数据库
  • 先操作数据库,再删除缓存(建议)

本项目使用主动更新 + 删除缓存 + 先操作数据库,再删除缓存

2、缓存三大问题解决方案

(1)、缓存穿透

(2)、缓存击穿

(3)、缓存雪崩

缓存穿透

简单理解:缓存穿透是指客户端请求的数据在缓存中和数据库都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

8f30241f4a924a734157ba22a51bc72f.png

解决方案

(1)缓存空对象

  • 优点:实现简单,维护方便
  • 缺点:
    • 额外的内存消耗
    • 可能造成短期的数据不一致
2ed2ad626e4cf23bb5de7a205d98b513.png
2ed2ad626e4cf23bb5de7a205d98b513.png

(2)布隆过滤器

  • 优点:内存占用较少,没有多余key
  • 缺点:
    • 实现复杂
    • 存在误判可能
3765c356f0617e06ec5e94b65e44dca0.png
3765c356f0617e06ec5e94b65e44dca0.png

本项目采用缓存空对象解决

代码片段

//如果数据中不存在则缓存空对象返回,两分钟后过期
                redisTemplate.opsForValue().set(GoodsConstant.GOODS + userId,"",RedisConstant.CACHE_NULL_EXPIRE, TimeUnit.MINUTES);

其他解决方案:

  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好热点参数的限流
缓存雪崩

**简单理解:缓存雪崩**是指同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

840e066d60bc1a94ddadea5a3cc1a817.png

解决方案

  • 给不同的key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

本项目使用给不同的key的TTL添加随机值

缓存击穿

简单理解:缓存击穿问题也叫热点key问题,即使也给被高并发访问并且缓存重建页较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

31e844250335c6063d24e88449660556.png

解决方案

  • 互斥锁
  • 逻辑过期
解决方案优点缺点
互斥锁(1)没有额外的内存消耗。(2)保证一致性。(3)实现简单(1)线程需要等待,性能受影响。(2)可能有死锁风险
逻辑过期线程不需要等待,性能较好。(1)有额外的内存消耗。(2)不保证一致性。(3)实现复杂

互斥锁

61b82942c04d801283e35bb2dee7a490.png

一下是一个简单的使用互斥锁解决的流程

397072438d11e859412b812ed4856a3a.png

获取互斥锁方法

/**
     * 获取锁方法
     * @param key
     */
    private boolean tryLock(String key,Integer userId){
        //获取锁,要对锁设置一个过期时间,防止某些异常导致死锁
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, userId, RedisConstant.CACHE_LOCK_EXPIRE, TimeUnit.SECONDS);
        //如果为true则说明获取到了锁,若返回false则说明前面已经有线程获取到了锁
        return BooleanUtil.isTrue(flag);
    }

释放互斥锁方法

/**
     * 释放锁
     * @param key
     */
    private void unLock(String key){
        //直接删除锁的键即可
        redisTemplate.delete(key);
    }

核心代码

@Override
    public List<GoodsDto> getList(Integer userId) {
        //先去redis中查询是否有缓存的数据
        List<GoodsDto> GoodsDtoList = (List<GoodsDto>) redisTemplate.opsForValue().get(GoodsConstant.GOODS + userId);
        if(!CollectionUtils.isEmpty(GoodsDtoList)){
            log.info("命中了redis中的商品缓存数据,成功进入缓存---------------");
            return GoodsDtoList;
        }else{
            //未命中redis中的缓存数据
            String lockKey = RedisConstant.GOODS_LOCK + userId;
            //尝试获取锁
            try {
                boolean isLock = this.tryLock(lockKey, userId);
                if(!isLock){
                    //未获取到锁,休眠一段时间等待前一个线程重建缓存
                    Thread.sleep(RedisConstant.THREAD_SLEEP);
                    //重新获取,递归
                    return getList(userId);
                }
                //获取到了锁
                //再去redis中查询是否有缓存的数据
                List<GoodsDto> GoodsList = (List<GoodsDto>) redisTemplate.opsForValue().get(GoodsConstant.GOODS + userId);
                if(!CollectionUtils.isEmpty(GoodsList)){
                    log.info("命中了双重检测redis中的商品缓存数据,成功进入缓存---------------");
                    return GoodsList;
                }
                //根据商户id获取商户所属的店铺
                QueryWrapper<Store> wrapper = new QueryWrapper<>();
                wrapper.eq("user_id",userId);
                List<Store> stores = storeMapper.selectList(wrapper);
                if(CollectionUtils.isEmpty(stores)){
                    //如果数据中不存在则缓存空对象返回来解决redis缓存穿透,两分钟后过期
                    List<UserDto> dtoList = new ArrayList<>();
                    dtoList.add(new UserDto());
                    redisTemplate.opsForValue().set(GoodsConstant.GOODS + userId,dtoList,RedisConstant.CACHE_NULL_EXPIRE, TimeUnit.MINUTES);
                }else{
                    //进行缓存重建
                    List<GoodsDto> goodsDtoList = this.getGoodsDtoList(stores,userId);
                    return goodsDtoList;
                }
            }catch (Exception e){
                throw new ServiceException(ResponseEnum.ERROR);
            }finally {
                //释放锁
                this.unLock(lockKey);
            }
        }
        return null;
    }

逻辑过期

4a8016d4ea953c618d68b72cb2a3fa99.png

三、防止重复提交

使用redis + lua脚本保证原子性操作

lua脚本

if(redis.call('get', KEYS[1]) == ARGV[1]) then
return redis.call('del', KEYS[1])
else return 0 end

核心代码

private static final DefaultRedisScript<Boolean> REDIS_SCRIPT;

    /**
     * 静态代码块中初始化lua脚本
     */
    static {
        REDIS_SCRIPT = new DefaultRedisScript<Boolean>();
        //读取项目中的lua文件
        REDIS_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        REDIS_SCRIPT.setResultType(Boolean.class);
    }

    @Override
    public void saveOrderInfo(OrderDto orderDto,String orderId) {
        //TODO: 需要做防止重复提交和锁定库存操作
        if(StringUtils.isEmpty(orderId)){
            throw new ServiceException(ResponseEnum.ORDER_ID_EXCEPTION);
        }
        //lua脚本保证原子性操作
        //拿着orderId到redis中查询
        //如果redis中有相同orderId,则说明是正常提交订单,把redis中的orderId删除,
        // 这个过程要保证原子性操作,由lua脚本保证。
        Boolean flag = (Boolean) redisTemplate.execute(REDIS_SCRIPT, Arrays.asList(RedisConstant.ORDER_ID + orderId), orderId);
        //如果redis中没有相同orderId,则说明是重复提交订单,不再往下进行
        if(!flag){
            //返回false则说明是重复提交
            throw new ServiceException(ResponseEnum.DO_NOT_REPLACE_SUBMIT);
        } 
    }
}

(四)、分布式锁实现库存锁定,解决商品超卖问题

使用redisson实现分布式锁解决

maven依赖

<dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.11.2</version>
    </dependency>

redisson配置类

@Data
@Configuration
@ConfigurationProperties("spring.redis")
public class RedissonConfig {

    private String host;

    private String addresses;

    private String password;

    private String port;

    private int timeout = 3000;
    private int connectionPoolSize = 64;
    private int connectionMinimumIdleSize=10;
    private int pingConnectionInterval = 60000;
    private static String ADDRESS_PREFIX = "redis://";

    /**
     * 自动装配
     *
     */
    @Bean
    RedissonClient redissonSingle() {
        Config config = new Config();
        //  判断redis 的host是否为空
        if(StringUtils.isEmpty(host)){
            throw new RuntimeException("host is  empty");
        }
        //  配置host,port等参数
        SingleServerConfig serverConfig = config.useSingleServer()
                //redis://127.0.0.1:7181
                .setAddress(ADDRESS_PREFIX + this.host + ":" + port)
                .setTimeout(this.timeout)
                .setPingConnectionInterval(pingConnectionInterval)
                .setConnectionPoolSize(this.connectionPoolSize)
                .setConnectionMinimumIdleSize(this.connectionMinimumIdleSize);
        //  判断进入redis 是否密码
        if(!StringUtils.isEmpty(this.password)) {
            serverConfig.setPassword(this.password);
        }
        // RedissonClient redisson = Redisson.create(config);
        return Redisson.create(config);
    }
}

核心代码

前端

一、后台:由ant design vue开发

(一)、页面过渡效果

在路由文件index.js中引入具体的动画效果,推荐Animate.css | A cross-browser library of CSS animations.open in new window Animate.css动画

安装Animate.css

npm install animate.css --save

引入到路由的index.js文件中

const router = createRouter({
  history,
  routes: [
    {
      //登录页面
      path: "/login",
      name: "Login",
      component: () => import("@/views/login/Login.vue"),
      meta: {
        title: "登录页",
          //这里的这个动画直接去Animate.css中复制即可
        transition: "animate__bounceInRight",
      },
    },
    {
      //后台
      path: "/",
      name: "Layout",
      component: () => import("@/components/layout/Home.vue"),
      redirect: "/index",
      children: [
        {
          //首页
          path: "/index",
          name: "Index",
          component: () => import("@/views/index/Index.vue"),
          meta: {
            title: "首页",
              //这里的这个动画直接去Animate.css中复制即可
            transition: "animate__fadeInLeftBig",
          },
        },
        {
          //首页
          path: "/user",
          name: "User",
          component: () => import("@/views/user/User.vue"),
          meta: {
            title: "用户",
              //这里的这个动画直接去Animate.css中复制即可
            transition: "animate__backInDown",
          },
        },
      ],
    },
    {
      path: "/:pathMatch(.*)*",
      name: "NotFound",
      component: () => import("@/views/404/NotFound.vue"),
    },
  ],
});

在App.vue中修改

<template>
  <router-view #default="{route,Component}">
      <---->一定要加这个前缀animate__animated</---->
    <transition name="fade" :enter-active-class="`animate__animated ${route.meta.transition}`">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<script setup>
//要引入Animate.css的样式
import 'animate.css'
</script>

(二)、ant desgin vue表格渲染时报错

当 data 判断为空,且 horizonScroll 判断为 true 时,ResizeObserver 包裹下的 children 的 width 可能会发生变化,这时候就有可能会触发 ResizeObserver loop completed with undelivered notifications 报错。

所以,如果我们传入非空的值的话,是不是就消除这个报错了呢?我们来实践open in new window 一下。

在 antd 版本大于等于 4.0 的时候

  1. 当初始化 table ,dataSource 赋值空数组时:
react
复制代码<Table columns={[]} dataSource={[]} scroll={{ x: 100 }} />

控制台就会报错:ResizeObserver loop limit exceeded 。

  1. 当初始化 table ,dataSource 赋值 [{}] 时:
react
复制代码<Table columns={[]} dataSource={[{}]} scroll={{ x: 100 }} />

控制台的报错就没了(如果想要更清楚地观察结果,可以在右侧的界面上刷新一下)。

那当 antd 版本小于 4.0 的时候,dataSource 的不同赋值会不会触发这个报错呢?

答案是:不会。

因为 antd 3.x 或以下的版本,没用上 ResizeObserver 这个功能。

二、前台:有Ant design vue开发

(一)、关于如何展示金额小数点问题解决

1、写一个函数,然后使用插值语法调用函数

//展示价格的小数方法
function towNumber(val) {
  return val.toFixed(2);
}

2、比如这个位置展示金额

<span style="font-size: 18px; color: red; font-weight: bold">{{towNumber(item.price)}}</span>

(二)、关于vue3.0 引入 ant-design-vue 报Uncaught TypeError: Cannot read properties of undefined (reading 'value') 的解决办法

其实很简单 就是因为版本的问题

根目录执行下面这个命令就行了

cnpm i --save ant-design-vue@next -S

cnpm 和 npm 都行

(三)、验证手机号正则表达式

//数据校验
var reg_tel =
  /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
function doPhoneLogin() {
  PhoneFormRef.value
    .validate()
    .then(() => {
      //手机号验证
      if (!reg_tel.test(user.value.phone)) {
        message.error("请输入正确的手机号");
      } else {
        message.success("表单验证成功");
        console.log(user.value);
      }
    })
    .catch((error) => {
      console.log("error", error);
    });
}

(四)、验证码倒计时设计

<a-form-item name="code">
                    <a-input-search
                      v-model:value="certification.code"
                      placeholder="请输入验证码"
                      @search="onSearch"
                      style="width: 100%"
                      maxlength="6"
                      minlength="6"
                      size="large"
                      addon-before="验证码"
                    >
                      <template #enterButton>
                        <a-button
                          @click="getCode"
                          style="color: #f56c6c"
                          width="20px"
                          :disabled="disabled"
                        >
                        {{ content }}
                        </a-button>
                      </template>
                    </a-input-search>
                  </a-form-item>
//获取验证码前确认是否是真实手机号
const disabled = ref(false);
const content = ref('获取验证码');
function getCode() {
  Modal.confirm({
    title: "确认此手机号有效?",
    icon: createVNode(ExclamationCircleOutlined),
    content: "确保您所输入的手机号真实有效,否则影响后续操作!",
    okText: "确认",
    cancelText: "取消",
    onOk() {
      var time = 120;
      //禁用获取验证码按钮
      disabled.value = true;
      // 开启定时器
      var timer = setInterval(function () {
        // 判断剩余秒数
        if (time == 0) {
          // 清除定时器和复原按钮
          clearInterval(timer);
          disabled.value = false;
          content.value = "获取验证码";
        } else {
          content.value = `${time}秒后重新获取`;
          time--;
        }
      }, 1000);
    },
    onCancel() {
      message.success("验证码发送失败");
    },
  });
}

(五)、悬浮阴影

<div class="address_style"></div>
.address_style:hover {
  box-shadow: 10px 10px 5px #a0cfff, 10px 10px 5px #fab6b6;
}

.address_style {
  height: 120px;
  background-color: #fff;
  width: 230px;
  opacity: 0.8;
  transition: all 0.5s;
}

(六)、三种路由传参方式

(1)、传字面量

function toDetail(id) {
  router.push({ name: "Detail", query: { id: id } });
}

接收

import { useRoute } from "vue-router";
const route = useRoute();
let id = null;
id = route.query.id;

(2)、传数组

const cartIds = ref([]);
function toSubmitOrder() {
  router.push({ path: "submitorder", query: { id: cartIds.value } });
}

接收

import { useRoute } from "vue-router";
const route = useRoute();
const cartIds = ref([]);
cartIds.value = route.query.id;

(3)、传对象

saveOrder(orderDto.value).then((res) => {
      if (res.code === 200) {
        orderInfoVo.value = res.data;
        message.success("提交订单成功");
        router.push({
          name: "PayFor",
          query: { item: JSON.stringify(orderInfoVo.value) },
        });
      }
    });

接收

import { useRoute } from "vue-router";
const route = useRoute();
const orderInfoVo = JSON.parse(route.query.item);

三、小程序端:由uniapp + uview-plus开发

(一)使用uview-plus模板开发

1、引入uview-plus

在uniapp插件市场搜索uview plus,选择插件后点击右边的“下载插件并导入HBuilderX”

选择创建的项目进行导入即可

2、在项目根目录中的main.js中,引入并使用uview-plus

// main.js
import uviewPlus from '@/uni_modules/uview-plus'
 
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
  const app = createSSRApp(App)
  app.use(uviewPlus)
  return {
    app
  }
}
// #endif

3、在项目根目录的uni.scss中引入uview-plus的全局SCSS主题文件

/* uni.scss */
@import '@/uni_modules/uview-plus/theme.scss';

4、在App.vue的style中引入uview-plus基础样式

<style lang="scss">
    /* 需要给style标签加入lang="scss"属性 */
    @import "@/uni_modules/uview-plus/index.scss";
</style>

5、在使用的页面中使用uview-plus的组件,效果展示

<!-- index.vue -->
<template>
  <view>
    <u-button type="primary" :plain="true" :hairline="true" text="细边"></u-button>
  </view>
</template>

效果展示