vue入门,从启动项目开始,做完得物App的用户登录(前端!)

vue入门,做完一个简单的得物的应用

  • 创建工程
  • 添加路由
  • 路径别名
  • 静态页面的开发
  • axios封装
  • 跳转详情页
  • 商品详情页的开发
  • 第一部分(数据的获取)
  • 第二部分(轮播图的制作)
  • 渲染SKU模块
  • SKU动态功能他来了:smile: :stuck_out_tongue_winking_eye::sparkling_heart:
  • SKU模块的展示和隐藏
  • 选择产品的规格
  • 动态渲染尺寸、价格
  • 登录页面的开发
  • 登录引导页的开发
  • 登录页的开发
  • 注册页的开发
  • 订单模块的开发
  • 实现订单静态页面
  • 结果页开发
  • 环境部署
  • vue打包
  • 终于 更新完了!!!
  • 除了没有写静态页面,我愿称之为最保姆级的指导
  • 本次项目通过实现得物APP 的用户登录/注册,商品列表,商品详情页,商品的下单和支付功能、最后将项目打包后,完成一次完整的项目开发。
    通过这次项目,可以学习到的是:

    1. 工程如何搭建,其中包括如何引用项目需要的基础包,如何约定规范的工程项目
    2. 路由的配置
    3. 开发公共组件
    4. 使用axios调用后端接口
    5. 寻找、使用第三方库
    6. 打包、发布、环境部署
      要是感兴趣的话,可以继续看,我会把所有的更新完
      本次项目的功能主要包括三大部分
      1.APP账号的注册和登录
      2.商品列表的浏览和商品详情的浏览
      3.商品的购买和支付

    注册
    新用户进入APP可以选择初始页面中的“用户注册”按钮进行注册页面,使用手机号进行注册


    登录
    老用户进入APP后,点击“用户登录”—>登录页面—>输入账号密码即可

    商品列表
    登录成功之后即可进入商品列表的页面去浏览商品
    商品详情
    点击商品列表中的商品,进入商品详情页查看 商品详情

    下单
    进入商品详情页,点击详情页下方的“立即购买”,弹出商品尺寸选择框,选择尺寸后进入订单确认页面,点击“提交订单” 即可付钱

    支付
    支付后会跳转到支付成功和支付失败页面

    创建工程

    在vscode 中启动项目

    1. 打开项目
      在terminal(Mac)或者CMD(Windows)中创建好项目后,可以直接在vscode中打开项目,(注意:此时尚未执行npm install、npm run dev)
    2. 打开终端
    3. 在终端执行命令
      执行依赖下载命令和启动项目命令

      安装依赖
      进入项目的根目录
    cd dewu-web
    

    下载基础的依赖到本地

    每次添加新的依赖或者拉了别个的代码以后,都要执行以下这个命令

    npm install
    或者
    yarn

    启动工程

    npm run dev
    或者yarn dev

    添加路由

    安装路由
    脚手架vite 默认是不安装router的,需要我们自己动手安装的,在项目根目录下 执行

    npm install vue-router@next
    或者yarn add vue-router@next

    路由配置

    1. 创建路由文件
      在src 目录下新建router文件夹 并在里面添加index.js文件
    // src/router/index.js
    import { createRouter, createWebHistory } from "vue-router";
    
    const routes = [
      // 待添加路由
    ];
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    });
    
    export default router;
    
    1. 加载路由
      在main.js文件引入路由
    // main.js
    import { createApp } from "vue";
    import App from "./App.vue";
    import router from "./router/index";
    
    createApp(App).use(router).mount("#app");
    

    路径别名

    开发中我们其实会经常遇到引入的文件路径很深,或者文件名很长的情况,例如下面的路径

    import Home from “…/…/components/nav/Index.vue”;

    这时候我们希望 有一种类似于代码自动提示的东西,帮助我们自动引入路径,这种功能是有的,但是需要手动配置

    1. 配置路径别名
      路径别名可以让我们告别…/操作
      在根目录下的vite.config.js文件中添加如下代码
    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    //  不要忘记引入path库
    import path from "path";
    
    // https://vitejs.dev/config/
    export default defineConfig({
     resolve: {
       //   路径别名选项
       alias: [
         {
           find: "@", // 当在你的路径中找到@ 就用下面replacement路径替换
           replacement: path.resolve(__dirname, "src") // 拼接根路径
         }
       ]
     },
     plugins: [vue()]
    });
    

    重新启动项目后,文章开头的路径可以这样引入

    import Home from “@/components/nav/Index.vue”;

    到这里我们其实没有实现路径的自动提示功能
    2. 配置路径的自动提示
    在根目录下面新建jsconfig.json 填入下面的代码

    {
      "compilerOptions": {
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"] // 匹配src目录下的所有目录
        }
      },
      "exclude": ["node_modules", "dist"]
    }
    

    这样子我们重启vscode以后,再去尝试,引入路径,会实现代码的自动提示功能
    每次引入文件的时候,都要以@开头
    到这里其实 路径别名的设置和使用已经完成了

    静态页面的开发

    1. 新建目录
      需要新建如下目录结构所示的目录

      在index.vue添加基础的代码
    src
     |__pages
       |__products
         |__index.vue
    

    在index.vue添加基础的代码

    <template>商品列表</template>
    
    <script setup></script>
    
    <style scoped></style>
    

    添加页面到路由
    在src/router/index.js 文件下 添加路由到路由列表

    import { createRouter, createWebHistory } from "vue-router";
    
    const routes = [
      {
        path: "/products",
        name: "products",
        alias: "/",
        component: () => import("@/pages/products/index.vue")
      }
    ];
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    });
    
    export default router;
    

    alias是路由别名,相当于给路由起个外号,如下两个方式否可以访问products页面

    http://localhost:3000/products
    http://localhost:3000/

    这时候其实是访问不到products 页面的,因为没有添加router-view
    在App.vue中添加router-view标签,来显示对应的路由组件

    <template>
      <!-- 添加router-view -->
      <router-view />
    </template>
    
    <script setup></script>
    
    <style>
      body {
        /* 去除body的默认margin */
        margin: 0;
      }
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        /* 删除margin-top */
      }
    </style>
    

    既然可以 配置好了,那么就可以开发静态页面了

    注意: 在这里其实商品列表的商品只需要开发一个商品小格子即可,其他的商品在后面的章节中,可以根据从后端请求来得数据进行循环渲染。


    这是目前所要建的文件

    axios封装

    Axios官方的解释就是:Axios是一个基于promise的HTTP 库,可以用在浏览器的node.js中
    它的作用就是用来调用后端接口获取数据的

    1. 安装Axios
      在终端输入下面的命令

    npm install axios@next
    或者yarn add axios@next
    @next可以保证安装的依赖是最新的版本

    1. 封装接口调用函数

    axios支持基础的get、post请求,如

    import axios from "axios";
    // get请求
    const res = await axios.get(url, {
      // 参数对象
    });
    // post请求
    const res = await axios.post(url, {
      // 参数对象
    });
    

    但是这种调用的方式不够通用,所以在项目中,我们会将请求封装成一个方法来统一调用
    在src目录下新建Utils文件夹,并加入http.js文件
    在文件中加入如下的代码

    import axios from "axios";
    
    axios.defaults.baseURL = import.meta.env.VITE_BASE_URL;
    //这段代码的意思就是从项目根目录中的.env文件中获取VITE_BASE_URL字段的值,这个字段要以VITE_开头,否则无法识别
    //.env文件用于配置通用字段,方便以后同意更改,在目录下新建.env文件要
    //>VITE_BASE_URL=https://www.fastmock.site/mock/c3af16c01eaad121c58feccb492a088c/f8
    axios.defaults.timeout = 5000; // 请求过期时间5s
    
    const requests = async ({
      url,
      method = "get", // 请求方法 get、post 默认为get
      params = {}, // get请求参数
      data = {} // post请求参数
    } = {}) => {
      method = method.toLocaleLowerCase();
      const res = await axios.request({ method, url, params, data });
      return res.data;
    };
    
    export default requests;
    
    1. 调用接口
      axios封装好以后,接下来就可以调用接口来获取商品的列表了
      为了可以让项目更加的整洁,我们需要将请求接口的函数统一管理起来
      在src目录下新建api文件夹,该文件夹就是用来统一管理api的,又因为我们的接口可能有很多种,比如用户。商品。登录等。所以每一个.js文件就是对接口的一种归类
      比如我们要调用获取商品列表的接口,所以这个调用接口的办法应该放在product.js文件内
      新建product.js文件,并键入如下的代码
    // /src/api/product.js
    import requests from "@/utils/http";
    
    async function queryProducts({ url } = {}) {
      const result = await requests({
        url: url,
        method: "GET"
      });
      return result && result.data;
    }
    
    export { queryProducts };
    

    最后只需要在.vue文件内调用queryproductsj就可以获取到数据了

    // src/pages/product/index.vue
    
    <script setup>
      import { onMounted } from "@vue/runtime-core";
      import { queryProducts } from "@/api/product";
    
      onMounted(async () => {
        const res = await queryProducts({ url: "/get/products" });
        console.log(res);
      });
    </script>
    

    如果不出意外的好,就可以在控制台看到打印的数据了,要是没有的话,要好好检查一下代码哦

    在axios封装的时候,做一些更具体的说明,真的是 超级详细哦

    1. 安装axios的时候,使用这个命令yarn add axios@next
    2. 封装 axios 在src目录下新建utils目录,并在该目录下新建http.js文件,完成axios 的封装装,封装http的baseUrl为VITE_BASE_URL=https://www.fastmock.site/mock/c3af16c01eaad121c58feccb492a088c/f8 // 该链接在根目录下新建.env文件,并将该链接黏贴进去
    3. 实现商品api 。 在src目录下新建api目录,在改目录下新建product.js文件。在该文件中写请求商品的API
    4. 调用api 在/src/products/index.vue文件中使用请求商品的api实现数据的请求
    5. 使用api 数据替换死数据、这样我们就可以用从接口请求过来的数据去替换之前在data.js中的数据来实现页面的渲染。

    跳转详情页

    根据下面的目录结构,新建商品的详情页面

    1. 注册路由
      在src/router/index.js文件夹里面注册详情页面的路由
    const routes = [
     {
       path: "/products",
       name: "products",
       alias: "/",
       component: () => import("@/pages/products/index.vue")
     },
     // 注册详情页路由
     {
       path: "/product-detail",
       name: "product-detail",
       component: () => import("@/pages/product-detail/index.vue")
     }
    ];
    
    1. 跳转详情页
      在列表页面添加跳转方法,点击列表中的每个格子,跳转到详情页
    <script setup>
      import { onMounted, ref } from "vue";
      import { queryProducts } from "@/api/product";
      import { useRouter } from "vue-router";
    
      const products = ref([]);
      const router = useRouter();
    
      onMounted(async () => {
        products.value = await queryProducts({ url: "/get/products" });
      });
    
      function getImgUrl(imgUrl) {
        return imgUrl.split(";")[0];
      }
    
      function goDetail(id) {
        router.push({ path: "/product-detail", query: { productId: id } });
      }
    </script>
    

    详细的步骤

    1. 添加商品详情页 。 在src/pages目录下新建product-detail目录,并在该目录下新建Index.vue填充基础的代码
    <template>商品详情页面</template>  <script setup></script>   <style lang="scss" scoped></style>
    
    1. 注册商品详情页路由 在router/index.js 文件中的路由列表中添加商品详情页的路由
    2. 页面跳转。 在商品 列表中添加点击事件,保证点击每一个格子后可以跳转到商品的详情页,跳转商品详情页的时候,需要传递参数,参数为商品id,可以命名为productId

    商品详情页的开发

    第一部分(数据的获取)

    当页面跳转到详情页的时候,首先要做的就是根据商品的id 获取数据
    之前我们使用路由,将商品的Id传递到本页面,那么在商品详情页面,就需要用路由去获取商品的id,然后用商品的id去查询商品的详情的信息

    1. 获取商品的id
      商品id是通过useRouter()函数 通过参数的形式传递过来的,如果要获取参数,就要用useRoute()函数
    <script setup>
      import {{onMounted}} from "vue";
      import {{useRoute}} from "vue-router";
      const route = useRoute();
      onMounted(async()=>{
      // 这里的productId要与router.push(path:"",query:{productId:xxx})中的productId一致
      const {productId}=route.query;
      console.log(productId);
    });
    </script>
    
    1. 根据商品id 查询商品
      (1)写查询函数
      在product.js文件中写查询商品详情的函数

      
       async function getPorduct({ id = "" }) {
       const res = await requests({
       url: "/product/get",
       params: { id }
      });
      return res && res.data;
      }
      // 不要忘记导出哦
      
      export { queryProducts, getPorduct };
      
    2. @description 获取商品详情
    3. @param {*} productId 商品id
    4. @returns {} 商品对象

    (2)调用查询函数
    在生活详情页调用刚写好的查询函数

    <script setup>
      import { onMounted } from "@vue/runtime-core";
      import { useRoute } from "vue-router";
      import { ref } from "vue";
      import { getPorduct } from "@/api/product";
    
      const route = useRoute();
      // 定义商品对象
      const product = ref({});
    
      onMounted(async () => {
        const { productId } = route.query;
        // 调用查询函数,并赋值
        product.value = await getPorduct({ id: productId });
      });
    </script>
    

    此时我们就已经得到了一个商品对象,接下来只需要将得到的数据渲染在页面中即可,除了商品详情之外,其他的两个接口也是需要自己写查询函数,并且调用,获取数据,原理其实都是差不多的,可以参考查询商品的调用的方式

    详细的教程:

    1. 完成接口的书写,在product.js文件中 完成下面两个接口的书写
    1. 调用接口渲染页面
      在商品详情页面调用写好的函数,获取数据,并渲染页面

    第二部分(轮播图的制作)

    轮播图可以使用element-ui的carousel(走马灯)组件来实现

    1. 安装element-ui
      在项目根目录下执行下面的命令

    npm install element-plus或者yarn add element-plus

    安装完毕以后,需要全局引入element-ui组件

    1. 全局引入
      在main.js文件中引入element-ui的组件库以及样式
    import { createApp } from "vue";
    import App from "./App.vue";
    import router from "./router/index";
    // 引入组件库
    import ElementPlus from "element-plus";
    // 引入样式
    import "element-plus/lib/theme-chalk/index.css";
    
    // 使用组件库
    createApp(App).use(router).use(ElementPlus).mount("#app");
    

    经过以上的操作,就可以在任何页面中使用element-ui组件了
    3. 初识 element-ui
    这里可以参考官网,毕竟官网才是最权威的
    element-ui官网
    图一
    图一主要的内容就是一些初始化及个性的配置,这一点我们暂时是不需要去关注的,因为到目前为止,我发已经做好初始的配置,我们的重点是下面
    点开一个组件,可以看见文档中有组件的基本的用法,以及对应用法的基本的示例,在最下面对应属性的说明

    4. 使用Carousel(走马灯)组件
    在左侧栏里找到carousel(走马灯)组件,并点开“基础用法”下面的显示代码,复制里面的组件代码

    <el-carousel height="150px">
      <el-carousel-item v-for="item in 4" :key="item">
        <h3 class="small">{{ item }}</h3>
      </el-carousel-item>
    </el-carousel>
    

    接下来将页面拉到下方,根据如下图所示的属性表进行修改

    首先将走马灯的高度修改的跟设计稿的高度是一样的,这里可以用height属性
    然后将走马灯的内容进行替换

    <!-- 将h3标签修改 -->
    <h3 class="small">{{ item }}</h3>
    <!-- 替换为img标签,因为我们的走马灯现实的是图片 -->
    <img src="" />
    

    最后将走马灯中的v-for循环的数据改成接口中获得的数据
    接口获取到的product中的productImages属性就是轮播图的图片数据,但是因为这个图片数据是字符串,并且每个图片地址之间用分号(;)隔开,所以需要处理一下

    const productImages = computed(
      () =>
        (product.value.productImages && product.value.productImages.split(";")) ||
        []
    );
    

    另外我们还需要加一个属性就是indicator-position(指示器的位置)

    <el-carousel height="175px" indicator-position="outside"> </el-carousel>
    

    渲染SKU模块

    这里要写的其实是这个页面

    1. 静态页面的书写,这里就不做赘述,这个大家可以自己动手去试试,虽然有点麻烦,但是很简单的,不要怕麻烦,多去尝试
      由于这个项目是比较大的,所以这个模块的静态页面最好是写成一个组件。
      因为这个组件是只有在详情页面才会使用的,所以写在product-detail目录下,新建一个sku.vue文件
      在商品的详情页,引入sku组件,并在页面中添加
    <!-- index.vue -->
    <template>
      <!-- 省略无关代码 -->
      <sku />
    </template>
    
    <script setup>
      // 引入sku组件
      import sku from "./components/sku.vue";
    </script>
    
    <style lang="scss" scoped></style>
    
    1. 完成接口函数
      这个函数的接口是:

      在product.js文件中完成接口函数的书写
    // 省略无关代码
    /**
     * @description 获取商品SKU
     * @returns {}
     */
    async function getProductSKU() {
      const res = await requests({ url: "/product/sku" });
      return res && res.data;
    }
    // 导出
    export { getProductSKU };
    
    1. sku模块的渲染
      在sku.vue中调用接口函数,获取到数据,渲染在页面中
      我们得到的数据只能渲染页面中的每个小格子和顶部的图片,顶部的价格以及结算按钮的价格是根据用户点击后的结果来渲染的
    <!-- 省略无关代码 -->
    <!-- 顶部商品图片 -->
    <img :src="sku.productImage" class="img" />
    <!-- 商品尺寸、价格列表 -->
    <li v-for="(product, index) in sku.products" :key="product.id">
      <div class="item">
        <span class="size">{{ product.size }}</span>
        <span class="price">¥{{ product.price }}</span>
      </div>
    </li>
    
    <script setup>
      import { getProductSKU } from "@/api/product";
      import { onMounted, ref } from "vue";
    
      const sku = ref({});
    
      onMounted(async () => {
        // 获取sku数据
        sku.value = await getProductSKU();
      });
    </script>
    

    获取到的数据,可以按照如下来查看


    效果如下

    SKU动态功能他来了😄 😜💖

    忙活了这么多天,终于可以继续更新啦,哈哈,我来了

    SKU模块的展示和隐藏

    1. 父组件传递控制显示隐藏的变量给子组件
      SKU模块的展示和隐藏,我们可以用V-if以及props属性来完成
      首先在SKU模块的mock标签上添加v-if 来控制SKU模块的展示和隐藏
    <div class="mock" v-if="isShow"></div>
    

    当isShow变量为true,SKU模块显示,反之隐藏
    该变量我们可以定义在props中,通过父组件传递进来

    <!-- ./src/pages/product-detail/components/sku.vue -->
    <script setup>
      // 引入defineProps
      import { defineProps, ref } from "vue";
      // 定义props
      defineProps({ isShow: { type: Boolean, default: false } });
    </script>
    

    这里的isShow最好给一个默认值,false,让SKU模块默认不显示
    2. 父组件传递方法给子组件
    有了控制显示隐藏的变量还是不够的,还需要一个方法来改变这个变量,这个方法也可以用props的形式传递过来

    <!-- ./src/pages/product-detail/components/sku.vue -->
    <script setup>
      // 引入defineProps
      import { defineProps, ref } from "vue";
      // 定义props
      defineProps({ isShow: { type: Boolean, default: false }, onShow: Function });
    </script>
    
    1. 父组件中定义props变量和方法
      在父组件中定义要传递过来的变量isShow以及方法onShow
    <template>
      <!-- 添加props属性 -->
      <sku :onShow="onShow" :isShow="isShow" />
    </template>
    <script setup>
      import { ref } from "vue";
      // 引入组件
      import sku from "./components/sku.vue";
      // 定义是否显示的变量
      const isShow = ref(false);
      // 定义props方法
      function onShow(show) {
        isShow.value = show;
      }
    </script>
    
    1. 调用props方法
      在子组件中调用传递过来的方法
    <!-- ./src/pages/product-detail/components/sku.vue -->
    <!-- 调用父组件传递过来的方法,并添加参数 -->
    <div class="close" @click="onShow(false)"></div>
    

    在父组件中调用思考一下的onShow 方法,显示SKU模块

    <!-- ./src/pages/product-detail/index.vue -->
    <div class="btn" @click="onShow(true)">立即购买</div>
    
    1. 组件传值
      在父组件中调用子组件的时候,要将变量和方法传递给子组件
    <template>
      <!-- 省略无关代码 -->
      <sku :isShow="isShow" :onShow="onShow" />
    </template>
    

    选择产品的规格

  • 上图2 默认不显示
  • 点击1 中任意的一个格子后,2 显示
    按照测个逻辑,可以试试
    1. 点击选择尺寸
      在尺寸格子上添加点击事件
    <ul class="size-list">
      <li
        v-for="(product, index) in sku.products"
        :key="product.id"
        @click="choosesku(index)"
      >
        <!-- 当当前点击的index和选中的index相同时,给div添加边框 -->
        <div :class="['item', { 'item-choosed': currentIndex === index }]">
          <span class="size">{{ product.size }}</span>
          <span class="price">¥{{ product.price }}</span>
        </div>
      </li>
    </ul>
    

    实现点击事件

    import { ref } from "vue";
    
    const sku = ref({});
    const choosedSku = ref({}); // 选择的sku数据,用于后面显示选中的尺寸及价格
    const currentIndex = ref(-1); // 当前选中的index
    
    function choosesku(index) {
      currentIndex.value = index;
      choosedSku.value = sku.value.products[index];
    }
    

    动态渲染尺寸、价格

    如图所示,我们要实现的几个功能是

  • 选择尺寸之后,3、4处显示
  • 1、2、3、4处尺寸、价格显示
    3,4处的显示和隐藏,只需要判断是否选择了尺寸即可,可以通过计算属性来判断
  • <div class="order" v-if="isChoosedSku"></div>
    
    const isChoosedSku = computed(
      () => choosedSku.value && Object.keys(choosedSku.value).length > 0
    );
    

    1,2,3,4处数据的渲染只需要用choosedSku 这个数据去渲染即可

    <!-- 顶部价格区域 -->
    <div class="price">¥{{ choosedSku.price || "----" }}</div>
    <!-- 已选择的尺寸区域 -->
    <span class="choose-num"
      >{{ isChoosedSku ? `已选择 ${choosedSku.size}` : "请选择商品" }}</span
    >
    <!-- "约 2 天到"、"约 5 天到"模块的价格 -->
    <div class="order" v-if="isChoosedSku">
      <div class="item two-days">
        <span class="price">¥{{ choosedSku.price || 0 }}</span>
        <div class="line"></div>
        <div class="arrive">约2天到</div>
        <div class="send-way">闪电发货</div>
      </div>
      <div class="item five-days">
        <span class="price">¥{{ choosedSku.price || 0 }}</span>
        <div class="line"></div>
        <div class="arrive">约5天到</div>
        <div class="send-way">普通发货</div>
      </div>
    </div>
    

    登录页面的开发

    登录引导页的开发

    这个主要是了解一下业务的流程,以便以后做开发的时候,思路是比较明确的

    接下来。明确一下需要用到的几个页面(位置与流程图中的位置其实是相似的,以便对照理解)


    页面登录和注册页面其实是一样的页面,区别就是有没有“确认密码”这个输入框
    一、跳转判断
    在流程图可以得知,页面跳转的条件就是 localStorage 中是否有user,可以通过下面的代码获取

    const user =window.localStorage.getItem("user");
    //将获取到的user转换成对象
    const userInfo=JSON.parse(user);
    

    将跳转逻辑封装成一个函数

    function goOrder() {
      // 获取登录信息
      const userInfo = JSON.parse(window.localStorage.getItem("user"));
      // 判断登录信息
      if (userInfo && Object.keys(userInfo).length > 0) {
        router.push({ path: "/order" });
      } else {
        router.push({ path: "/guide" });
      }
    }
    

    添加点击事件,并调用goOrder方法

    <!-- src/pages/product-detail/components/sku.vue -->
    <div class="order" v-if="isChoosedSku">
      <!-- 添加点击事件 -->
      <div class="item two-days" @click="goOrder">
        <span class="price">¥{{ choosedSku.price || 0 }}</span>
        <div class="line"></div>
        <div class="arrive">约2天到</div>
        <div class="send-way">闪电发货</div>
      </div>
      <!-- 添加点击事件 -->
      <div class="item five-days" @click="goOrder">
        <span class="price">¥{{ choosedSku.price || 0 }}</span>
        <div class="line"></div>
        <div class="arrive">约5天到</div>
        <div class="send-way">普通发货</div>
      </div>
    </div>
    
    1. 中间引导页的开发

    在pages下面新建guide,新建index.vue文件

    1. 存储购买商品

    因为点击购买以后,其实是有两种可能的

  • 商品详情—>登录引导页面
  • 商品详情—>订单页面
  • 所以说,购买的商品要跨页面存储是比较好的,都存储在store中

    安装vuex

    npm install vuex@next 或者yarn add vuex@next

    创建store,目录可参考我的目录

    在index.js文件下写入如下的代码

    import { createStore } from "vuex";
    import order from "./modules/order";
    
    const store = createStore({
      modules: {
        order
      }
    });
    
    export default store;
    

    在modules/order.js文件下填入如下的代码

    const order = {
      namespaced: true,
      state: {},
      getters: {},
      mutations: {},
      actions: {}
    };
    
    export default order;
    

    这个大家可以自己配置一下,不会的我会找一个文档,大家可以参考一下这个逻辑,大家可以参考一下我的

    !!!要记得在main.js文件里使用store

    // 引入store
    import store from "@/store/index";
    // 使用store
    createApp(App).use(store).use(router).use(ElementPlus).mount("#app");
    

    实现商品的id 存储
    在modules/order.js里面完成商品的id 的存储

    const order = {
     namespaced: true,
     state: {
       cars: [] // 定义购物车,购物车中存储的是商品id
     },
     getters: {},
     mutations: {
       // 添加商品id到购物车
       addCars(state, productId) {
         // 先判断商品id是否存在,避免重复添加
         if (state.cars.findIndex((el) => el === productId) === -1) {
           state.cars.push(productId);
         }
       }
     },
     actions: {}
    };
    
    export default order;
    

    在跳转逻辑中调用store里面的addCars方法

    <script setup>
      import {  defineProps ref } from "vue";
      import { useRouter } from "vue-router";
      // 引入useStore
      import { useStore } from "vuex";
    
      const props = defineProps({
        onShow: Function,
        isShow: { type: Boolean, default: false },
        productId: String,
      });
    
      // 定义store变量
      const store = useStore();
    
      function goOrder() {
        // 添加商品id到购物车
        store.commit("order/addCars", props.productId);
        const userInfo = JSON.parse(window.localStorage.getItem("user"));
        if (userInfo && Object.keys(userInfo).length > 0) {
          router.push({ path: "/order"});
        } else {
          router.push({ path: "/guide" });
        }
      }
    </script>
    

    获取localStorage信息以及判断对象是否为空,可以封装成一个函数

    登录页的开发

    先是实现静态页面的开发,并添加路由

    跳转登录页面
    在登录页面添加跳转的方法到登录页面

    <div class="login-btn" @click="goPage('login')">用户登录</div>
    

    实现跳转的方法

    function goPage(pageName) {
      router.replace({ path: "/login", query: { pageName } });
    }
    

    实现登录页的逻辑
    在登录页,我们要实现的逻辑功能有:

    1. 点击登录,判断提交的信息
  • 判断内容是否为空
  • 判断两次输入的密码是否相同(如果是pageName位regist的话)
  • 判断通过以后调用接口,实现登录,注册功能
  • 根据接口返回的信息给出的提示信息(登录失败,登陆成功)
    1. 提交信息检验
      提交消息的检验的代码如下
    const isRegist = computed(() => pageName === "regist");
    
    // 校验
    function validate() {
      // 判断姓名
      if (!userName.value.trim()) {
        ElMessage.error({ message: "用户名不能为空", center: true });
        return false;
      }
      // 判断密码
      if (!password.value.trim()) {
        ElMessage.error({ message: "密码不能为空", center: true });
        return false;
      }
    
      if (isRegist.value) {
        // 判断确认密码
        if (!confirmPassword) {
          ElMessage.error({ message: "确认密码不能为空", center: true });
          return false;
        }
        // 判断两次密码输入是否相同
        if (confirmPassword !== password) {
          ElMessage.error({ message: "两次密码不一致", center: true });
          return false;
        }
      }
      return true;
    }
    

    这个校验是比较简单的,如果追求更精准的校验,可以使用正则表达式校验输入的内容是否符合规则
    3. 写接口函数
    这个接口是登录接口,所以我们不能写在product.js文件里,需要在api文件夹中新建login.js文件

    import requests from "@/utils/http";
    
    /**
     * @description 登录
     * @param {*} name
     * @param {*} password
     * @returns {}
     */
    async function login({ name, password } = {}) {
      const result = await requests({
        url: "/login",
        data: { name, password },
        method: "POST"
      });
      return result;
    }
    
    export { login };
    
    

    4.接入Element UI 组件库
    登录、注册是否成功,都需要给用户提示消息,告知用户结果
    首先执行下面的命令安装

    npm install element-plus –save

    在main.js文件里全局引用组件库以及样式文件

    import { createApp } from "vue";
    import App from "./App.vue";
    import router from "./router/index";
    // 全局安装组件库
    import ElementPlus from "element-plus";
    // 引入样式文件
    import "element-plus/lib/theme-chalk/index.css";
    import store from "@/store/index";
    createApp(App)
      .use(store)
      .use(router)
      .use(ElementPlus, { size: "small" })
      .mount("#app");
    
    1. 添加登陆的方法
      在ogin/index.vue文件里写登录函数,完成登录的流程
    <div class="login-btn" @click="onLogin">{{ btnText }}</div>
    
    async function onLogin() {
      // 先校验输入内容
      if (!validate()) {
        return;
      }
      // 调用接口登录
      const res = await login({ name: userName.value, password: password.value });
      // 登录成功,展示提示信息并存储用户信息到Storage
      if (res.isSuccess) {
        ElMessage.success({ message: "登录成功", center: true });
        setStorage("user", res);
      } else {
        // 登录失败,展示提示信息
        ElMessage.error({ message: "登录失败", center: true });
      }
    }
    

    到这里其实有的跳转页面还是不能实现的,后面会加上

    注册页的开发

    由于上面所说的,这个页面其实和登录页面 是一样的,我们只需要在上面稍加更正

    1. 参数的传递和获取
      在引导页传递参数给注册页或登录页
    <div class="login-btn" @click="goPage('login')">用户登录</div>
    <div class="register-btn" @click="goPage('regist')">用户注册</div>
    
    function goPage(pageName) {
      router.replace({ path: "/login", query: { pageName } });
    }
    

    获取参数,在登录页或者注册页获取参数

    const { pageName } = route.query;
    

    2.动态渲染页面
    区别就是是否有 确认表单


    动态渲染密码确认表单,可是通过计算属性来实现

    <div class="input-box" v-if="isRegist">
      <div class="pre-text">确认密码</div>
      <input type="text" placeholder="确认密码" v-model="confirmPassword" />
    </div>
    

    动态渲染提交按钮,也可以通过计算属性来实现

    <div class="login-btn" @click="onLogin">{{ btnText }}</div>
    
    const btnText = computed(() => {
      return isRegist.value ? "注册" : "登录";
    });
    

    表单校验
    表单的校验在原先的基础上确认密码表单是否为空以及两次密码是否相同

    function validate() {
      if (!userName.value.trim()) {
        ElMessage.error({ message: "用户名不能为空", center: true });
        return false;
      }
      if (!password.value.trim()) {
        ElMessage.error({ message: "密码不能为空", center: true });
        return false;
      }
      // 如果是注册页面,校验确认密码以及密码是否相同
      if (isRegist.value) {
        if (!confirmPassword) {
          ElMessage.error({ message: "确认密码不能为空", center: true });
          return false;
        }
        if (confirmPassword !== password) {
          ElMessage.error({ message: "两次密码不一致", center: true });
          return false;
        }
      }
      return true;
    }
    

    调用接口注册

    // api/login.js
    
    /**
     * @description 注册
     * @param {*} name
     * @param {*} password
     * @returns
     */
    async function regist({ name, password } = {}) {
      const result = await requests({
        url: "/register",
        method: "post",
        data: { name, password }
      });
      return result;
    }
    

    调用接口实现注册

    async function onLogin() {
      if (!validate()) {
        return;
      }
      let res = {};
      // 重复使用的对象最好抽离出来,避免重复书写
      const data = { name: userName.value, password: password.value };
      // 根据页面判断决定调用哪个接口
      if (isRegist.value) {
        res = await regist(data);
      } else {
        res = await login(data);
      }
      if (res.isSuccess) {
        ElMessage.success({
          message: isRegist.value ? "注册成功" : "登录成功",
          center: true
        });
        setStorage("user", res);
      } else {
        ElMessage.error({
          message: isRegist.value ? "注册失败" : "登录失败",
          center: true
        });
      }
    }
    

    订单模块的开发

    实现订单静态页面

    根据设计稿实现下图所示的订单静态页面,并添加路由到路由列表

    这个页面要从两个地方可以跳转过来
    SKU约两天后和5天后的按钮
    登录成功以后跳转

    1. SKU页面跳转
      SKU 页面需要获取localStorage里面的用户信息,如果有用户信息表示已经登录,没有的话需要跳转到引导页
      下面是sku 页面的一些的代码
    function goOrder() {
      store.commit("order/addCars", props.productId);
      const userInfo = getStorage("user");
      // isObjectEmpty是封装好的对象是否为空的方法
      if (isObjectEmpty(userInfo)) {
        router.push({ path: "/guide" });
      } else {
        router.push({ path: "/order", query: { productId: props.productId } });
      }
    }
    
    1. 登陆成功后跳转的代码
    // 为了数据响应,这里需要做一点修改
    const refPageName = ref("");
    const { pageName } = route.query;
    refPageName.value = pageName;
    
    // 计算属性监听的数据需要改变
    const isRegist = computed(() => refPageName.value === "regist");
    async function onLogin() {
      //  省略无关代码
      if (res.isSuccess) {
        // 如果是登录页面,两秒后跳转到订单页
        // 如果是注册页,两秒后改变参数,显示登录页信息
        if (!isRegist.value) {
          setTimeout(() => {
            router.replace({ path: "/order" });
          }, 2000);
        } else {
          setTimeout(() => {
            refPageName.value = "login";
            router.replace({ query: { pageName: "login" } });
          }, 2000);
        }
      } else {
        ElMessage.error({
          message: isRegist.value ? "注册失败" : "登录失败",
          center: true
        });
      }
    }
    

    请求数据,渲染页面
    页面数据请求的是getproduct接口,这个接口我,们已经完成了,另外需要获取productId,这个数据可以在store中获取

    import { ref, onMounted } from "vue";
    import { useStore } from "vuex";
    import { getPorduct } from "@/api/product";
    
    const store = useStore();
    // 在store中获取购物车中的商品id
    const productId = store.getters["order/getCars"][0];
    const product = ref({});
    
    onMounted(async () => {
      // 请求接口获取商品数据
      product.value = await getPorduct({ id: productId });
    });
    

    提交订单
    提交订单的接口为:

    在api目录下新建order.js文件

    import requests from "@/utils/http";
    import { getStorage } from "@/utils/storage";
    
    /**
     * @description 购买商品
     * @param {*} userId
     * @param {*} productId
     * @returns
     */
    async function order({ productId } = {}) {
      const userId = getStorage("user").userId;
      const result = await requests({
        url: "/buyproducts",
        method: "POST",
        data: { productId, userId }
      });
      return result && result.data;
    }
    
    export { order };
    

    在订单页面调用接口实现订单提交功能

    async function buy() {
     const res = await order({ productId });
     if (res) {
       // 不要忘记引入饿了么组件
       ElMessage.success({ message: "订单提交成功", center: true });
     } else {
       ElMessage.error({ message: "订单提交失败", center: true });
     }
    }
    

    结果页开发

    静态页面是不做赘述的,只讲一下逻辑结构的应用

    1. 清空已购买的商品
      在点击提交订单的时候,需要清除store中存储的商品的id ,但是要确保提交订单成功了,否则是不可以清除的
    async function buy() {
      const res = await order({ productId });
      if (res) {
        // 购买成功,清除购物车中的商品id
        store.commit("order/removeProduct", productId);
        ElMessage.success({ message: "订单提交成功", center: true });
        setTimeout(() => {
          router.push({ path: "/result" });
        }, 2000);
      } else {
        ElMessage.error({ message: "订单提交失败", center: true });
      }
    }
    
    1. 返回列表页面
      返回列表页面都需要返回两次才可以,所以我们需要用到router的go函数
    function goBack() {
      router.go(-3);
    }
    

    环境部署

    需要做的三件事:

    1. 项目打包
    2. Nginx
    3. 部署环境,访问验证

    vue打包

    现在就是把项目部署到线上
    可以先在项目package.json文件

    {
      ...
      "scripts": {
        "dev": "vite",
        "build": "vite build"
      },
      ...
    }
    

    这个文件的script里其实有两个命令,一个是运行工程的命令:dev,另一个就是打包的命令:build
    执行这个命令

    npm run build
    执行完这个我们可以看到项目中多了一个叫dist 的文件夹,这就表示我们打包成功了

    终于 更新完了!!!

    除了没有写静态页面,我愿称之为最保姆级的指导

    来源:weifeng_bushileng

    物联沃分享整理
    物联沃-IOTWORD物联网 » vue入门,从启动项目开始,做完得物App的用户登录(前端!)

    发表评论