# vue3.0小兔鲜电商
**Repository Path**: yuefengzhao/rabbit-vue3-ts
## Basic Information
- **Project Name**: vue3.0小兔鲜电商
- **Description**: - 电商发展十余年,是个成熟的模式,小兔鲜儿是B2C电商平台,综合品类平台。参考网易严选
- 平台理念:(品质)新鲜、(价格)亲民、(物流)快捷。
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 10
- **Forks**: 2
- **Created**: 2022-07-05
- **Last Updated**: 2025-09-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 项目介绍
## 项目介绍
> 了解下项目的基本功能。
- 电商发展十余年,是个成熟的模式,小兔鲜儿是B2C电商平台,综合品类平台。参考[网易严选](http://you.163.com/)
- 平台理念:(品质)新鲜、(价格)亲民、(物流)快捷。
> 功能模块
- 首页模块:顶部通栏,吸顶导航,网站头部,左侧分类,轮播图,新鲜好物,人气推荐,热门品牌,分类商品推荐,专题推荐,网站底部。
- 一级分类:面包屑,轮播图,全部二级分类,二级分类推荐商品。
- 二级分类:筛选区域,排序功能,商品列表,无限加载。
- 商品详情:商品图片展示,基本信息展示,配送城市选择,SKU选择,库存选择,商品详情展示,商品评价展示,24小时热销,相关专题,加入购物车。
- 购物车
  - 头部购物车:展示商品数量和列表,删除商品,跳转购物车页面。
  - 购物车页面:购物车商品展示,选择商品,修改数量,修改商品规格,价格计算,跳转下单
- 登录模块:表单校验,账户密码登录,手机号登录,第三方登录,绑定手机,完善信息
- 填写订单:订单商品展示,收货地址选择,收货地址修改,支付方式选择,生成订单。
- 进行支付:订单信息展示,跳转支付网关,提示正在支付,等待支付结果,跳转支付成功页面。
- 个人中心
  - 中心首页:展示个人信息,近期收藏商品,近期足迹,猜你喜欢
  - 订单管理:全部订单,待付款,待发货,待收货,待评价,已完成,已取消。立即付款,取消订单,确认收货,删除订单,查看物流。
  - 订单详情:订单状态,订单进度,详细信息。
总结:完成电商支付闭环。
学完小兔鲜 =>   之前负责模块登录/注册?(没啥含金量)  ,  现在,   商品管理   购物车  支付   订单管理
## 配套资源
> 目的:了解真实企业开发都会有哪些配套资源。
开发配套:
- [原型稿](https://app.mockplus.cn/run/prototype/QO7BCWlUKB/IWlj1dabSw/c-f4gj1smb0?ha=1&ps=1)
- [接口文档](./api.html)
- [参照案例](http://erabbit.itheima.net/#/)
```
账号:jfjbwb4477@sandbox.com
密码:111111
```
总结:
- 在学习开发阶段使用本地API文档与vue3.0版本样例。
## 使用技术
> 目的:概述下项目会运用到的技术点。
项目基于vue技术来实现,大概会使用以下技术:
- vue3.0 (使用组合api的方式来开发)
- vite开发
- axios (请求接口)
- vue-router (单页路由) 
- pinia (状态管理)
- normalize.css (初始化样式)
- @vueuse/core (组合api常用工具库) 
- 算法 [Power Set](https://github.com/zhousg/javascript-algorithms/blob/master/src/algorithms/sets/power-set)
- dayjs (日期处理)
- vee-validate (表单校验)
**重点:** 电商常见业务和解决方案,掌握基于vue3.0的组合api开发模式。
**说明:** 由于前台项目,没有合适的UI组件库(没有适用vue3.0的ui库),不会使用组件库。
- 轮播图组件
- 面包屑组件
- 查看更多组件
- 骨架屏组件
- 复选框组件
- 单选框组件
- 对话框组件
- 消息提示组件 函数调用
- 消息确认组件 函数调用
- 分页组件
- 步骤条组件
- 时间线组件
- 标签页组件
- 城市选择组件
总结:基于vue3.0的技术栈,大量的组件封装。
# 项目起步
## 创建项目
**目标**:使用 vite 初始化小兔鲜项目
**核心步骤:**
- 使用 vite 初始化项目
```bash
yarn create vite rabbit-vue3-ts --template vue-ts
```
- 安装依赖包, 需要进入到`rabbit-vue3-ts`项目中
```
yarn
```
- 启动项目
```
yarn dev
```
## 目录调整
**目标:**能够调整项目的目录结构,规范开发环境。
**修改文件**
- `main.ts`不需要修改
- `App.vue`删除无用的内容
```vue
  app组件
```
**删除文件**
- `src/components/HelloWorld.vue` HelloWorld 组件
- `src/assets/logo.png` vue 默认的 logo
**新增文件夹**
- `utils` 用于存放工具相关
- `assets/images` 用于存放图片相关
- `assets/styles` 用于存放样式相关
- `router` 用于存放路由相关
- `store`用于存放数据相关
- `views`用于存放页面级别的组件
- `types`用于存放 ts 的公共类型
## 使用 git 管理项目
**目标:**能够使用 git 管理项目,并且能够将代码上传到码云
**核心步骤:**
- 初始化项目:`git init`
- 将代码添加到暂存区: `git add .`
- 提交代码 `git commit -m '初始化提交'`
- 在[码云](https://gitee.com/)上创建项目`rabbit-vue3-ts`,选择开源
- 设置仓库别名
```bash
git remote add origin https://gitee.com/jepsonpp/rabbit-pc-ts-93.git
```
- 推送到远程仓库
```bash
git push -u origin master
```
## axios 封装
**目的:**基于 axios 封装一个请求工具,调用接口时使用。
- 安装 axios
```bash
yarn add axios
```
- 新建 `src/utils/request.ts` 模块,代码如下
```js
import axios from 'axios'
// 备用接口地址: http://pcapi-xiaotuxian-front-devtest.itheima.net/
const instance = axios.create({
  baseURL: 'http://pcapi-xiaotuxian-front.itheima.net/',
  timeout: 5000
})
// 添加请求拦截器
instance.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    return config
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error)
  }
)
// 添加响应拦截器
instance.interceptors.response.use(
  function (response) {
    return response
  },
  function (error) {
    // 对响应错误做点什么
    return Promise.reject(error)
  }
)
export default instance
```
- 在`App.vue`文件中进行测试
```vue
  app组件
```
## 配置路径别名
**目标:**能够配置@路径别名,方便导入模块
**核心步骤:**
- 在`vite.config.ts`中增加配置
```js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const path = require('path')
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})
```
需要安装node的类型声明文件
```
yarn add @types/node -D
```
- 修改`tsconfig.json`,增加如下配置
```json
{
  "compilerOptions": {
    // ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
```
**注意:修改完 vite.config.ts 文件需要重启**
## 样式处理-less 变量与 mixins
**目标:** 能够使用 less 变量定义项目中常用的颜色,使用 mixins 定义项目中常用的样式
**核心步骤**
- 变量 `src/assets/styles/variables.less`
```less
// 主题
@xtxColor: #27ba9b;
// 辅助
@helpColor: #e26237;
// 成功
@sucColor: #1dc779;
// 警告
@warnColor: #ffb302;
// 价格
@priceColor: #cf4444;
```
- 混入 `src/assets/styles/mixins.less`
```less
// 鼠标经过上移阴影动画
.hoverShadow () {
  transition: all 0.5s;
  &:hover {
    transform: translate3d(0, -3px, 0);
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
  }
}
```
- 在 app.vue 中测试
```less
```
- 需要安装less的依赖包
```
yarn add less -D
```
- 问题:`variables.less`和`mixins.less`两个 less 文件是需要在多个组件中去使用的。但是如果组件中不导入,直接使用会报错。
## 样式处理-自动导入
**目标:** 能够使用`style-resoures-loader`自动导入项目中的`less变量`和`mixins`
**参考文档:**https://cn.vitejs.dev/config/#css-preprocessoroptions
**核心步骤:**
- 修改`vite.config.ts文件`,增加内容
```js
export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        additionalData: `
          @import "@/assets/styles/variables.less";
          @import "@/assets/styles/mixins.less";
        `
      }
    }
  }
})
```
- 修改 app.vue 中的代码
```less
```
- 重启服务,查看效果
## 样式处理-重置样式
**目标:**能够使用功能[normalize.css](https://github.com/necolas/normalize.css)重置项目中的样式,normalize.css 是 CSS 重置的现代替代方法
**核心步骤**
- 安装 normalize.css  样式重置的库, 将浏览器中的一些默认样式, 进行重置统一
```js
yarn add normalize.css
```
- 使用 normalize.css
`main.js` 导入 `normalize.css` 即可。
```diff
import { createApp } from 'vue'
import App from './App.vue'
+ import 'normalize.css'
createApp(App).mount('#app')
```
- 在 app.vue 中测试
```jsx
  
```
**通过效果会发现,h1 ul 等样式还是保留的,但是不一致的样式已经被重置了**
## 样式处理-公共样式
**目标:**能够给项目设置通用的样式。虽然有了重置样式,但是项目中依旧需要通用样式,
**核心步骤**
- 新建文件 `src/assets/styles/common.less`
```less
// 按照网站自己的需求,提供公用的样式
* {
  box-sizing: border-box;
}
html {
  height: 100%;
  font-size: 14px;
}
body {
  height: 100%;
  color: #333;
  min-width: 1240px;
  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB',
    'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
}
ul,
h1,
h3,
h4,
p,
dl,
dd {
  padding: 0;
  margin: 0;
}
a {
  text-decoration: none;
  color: #333;
  outline: none;
}
i {
  font-style: normal;
}
input[type='text'],
input[type='search'],
input[type='password'],
input[type='checkbox'] {
  padding: 0;
  outline: none;
  border: none;
  -webkit-appearance: none;
  &::placeholder {
    color: #ccc;
  }
}
img {
  max-width: 100%;
  max-height: 100%;
  vertical-align: middle;
  //  background: #ebebeb;
}
ul {
  list-style: none;
}
#app {
  background: #f5f5f5;
  // 不能选中文字
  user-select: none;
}
.container {
  width: 1240px;
  margin: 0 auto;
  position: relative;
}
// 一行省略
.ellipsis {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}
// 二行省略
.ellipsis-2 {
  word-break: break-all;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
.fl {
  float: left;
}
.fr {
  float: right;
}
.clearfix:after {
  content: '.';
  display: block;
  visibility: hidden;
  height: 0;
  line-height: 0;
  clear: both;
}
// 闪动画
.shan {
  &::after {
    content: '';
    position: absolute;
    animation: shan 1.5s ease 0s infinite;
    top: 0;
    width: 30%;
    height: 100%;
    background: linear-gradient(
      to left,
      rgba(255, 255, 255, 0) 0,
      rgba(255, 255, 255, 0.3) 50%,
      rgba(255, 255, 255, 0) 100%
    );
    transform: skewX(-45deg);
  }
}
@keyframes shan {
  0% {
    left: -100%;
  }
  100% {
    left: 120%;
  }
}
// 离开淡出动画
.fade {
  &-leave {
    &-active {
      position: absolute;
      width: 100%;
      transition: opacity 0.5s 0.2s;
      z-index: 1;
    }
    &-to {
      opacity: 0;
    }
  }
}
// 1. 离开,透明度  1---->0    位移 0---->30
// 2. 进入,透明度  0---->1    位移 30---->0
// 执行顺序,先离开再进入
.pop {
  &-leave {
    &-from {
      opacity: 1;
      transform: none;
    }
    &-active {
      transition: all 0.5s;
    }
    &-to {
      opacity: 0;
      transform: translateX(20px);
    }
  }
  &-enter {
    &-from {
      opacity: 0;
      transform: translateX(20px);
    }
    &-active {
      transition: all 0.5s;
    }
    &-to {
      opacity: 1;
      transform: none;
    }
  }
}
// 表单
.xtx-form {
  padding: 50px 0;
  &-item {
    display: flex;
    align-items: center;
    width: 700px;
    margin: 0 auto;
    padding-bottom: 25px;
    .label {
      width: 180px;
      padding-right: 10px;
      text-align: right;
      color: #999;
      ~ .field {
        margin-left: 0;
      }
    }
    .field {
      width: 320px;
      height: 50px;
      position: relative;
      margin-left: 190px;
      .icon {
        position: absolute;
        left: 0;
        top: 0;
        width: 40px;
        height: 50px;
        text-align: center;
        line-height: 50px;
        color: #999;
        ~ .input {
          padding-left: 40px;
        }
      }
      .input {
        border: 1px solid #e4e4e4;
        width: 320px;
        height: 50px;
        line-height: 50px;
        padding: 0 10px;
        &.err {
          border-color: @priceColor;
        }
        &:focus,
        &:active {
          border-color: @xtxColor;
        }
      }
    }
    .error {
      width: 180px;
      padding-left: 10px;
      color: @priceColor;
    }
  }
  .submit {
    width: 320px;
    height: 50px;
    border-radius: 4px;
    background: @xtxColor;
    height: 50px;
    line-height: 50px;
    text-align: center;
    font-size: 16px;
    color: #fff;
    display: block;
    margin: 0 auto;
  }
}
```
- 在 `main.js` 导入即可。
```diff
import { createApp } from 'vue'
import App from './App.vue'
import 'normalize.css'
+ import '@/assets/styles/common.less'
createApp(App).mount('#app')
```