├── .idea
├── .gitignore
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── vue-rabbit.iml
├── 01-PDF
├── day01.pdf
├── day02.pdf
├── day03.pdf
├── day04.pdf
├── day05.pdf
├── day06.pdf
└── day07.pdf
├── 02-笔记
├── 1. Vue3入门.md
├── 10. 购物车.md
├── 11. 订单页.md
├── 12. 支付页.md
├── 13. 会员中心.md
├── 14. 拓展部分.md
├── 2. Pinia入门.md
├── 3. 项目起步.md
├── 4. Layout页.md
├── 5. Home页.md
├── 6. 一级分类页.md
├── 7. 二级分类页.md
├── 8. 商品详情.md
└── 9. 登录页.md
├── 03-code
└── vue-rabbit
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── jsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ └── favicon.ico
│ ├── src
│ ├── App.vue
│ ├── apis
│ │ ├── cart.js
│ │ ├── category.js
│ │ ├── checkout.js
│ │ ├── detail.js
│ │ ├── home.js
│ │ ├── layout.js
│ │ ├── order.js
│ │ ├── pay.js
│ │ ├── testAPI.js
│ │ └── user.js
│ ├── assets
│ │ ├── base.css
│ │ ├── images
│ │ │ ├── 200.png
│ │ │ ├── center-bg.png
│ │ │ ├── load.gif
│ │ │ ├── loading.gif
│ │ │ ├── login-bg.png
│ │ │ ├── logo.png
│ │ │ ├── none.png
│ │ │ └── qrcode.jpg
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── ImageView
│ │ │ └── index.vue
│ │ ├── XtxSku
│ │ │ ├── index.vue
│ │ │ └── power-set.js
│ │ └── index.js
│ ├── composables
│ │ └── useCountDown.js
│ ├── directives
│ │ └── index.js
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── stores
│ │ ├── cartStore.js
│ │ ├── categoryStore.js
│ │ └── userStore.js
│ ├── styles
│ │ ├── common.scss
│ │ ├── element
│ │ │ └── index.scss
│ │ └── var.scss
│ ├── utils
│ │ └── http.js
│ └── views
│ │ ├── CartList
│ │ └── index.vue
│ │ ├── Category
│ │ ├── composables
│ │ │ ├── useBanner.js
│ │ │ └── useCategory.js
│ │ └── index.vue
│ │ ├── Checkout
│ │ └── index.vue
│ │ ├── Detail
│ │ ├── components
│ │ │ └── DetailHot.vue
│ │ └── index.vue
│ │ ├── Home
│ │ ├── components
│ │ │ ├── GoodsItem.vue
│ │ │ ├── HomeBanner.vue
│ │ │ ├── HomeCategory.vue
│ │ │ ├── HomeHot.vue
│ │ │ ├── HomeNew.vue
│ │ │ ├── HomePanel.vue
│ │ │ └── HomeProduct.vue
│ │ └── index.vue
│ │ ├── Layout
│ │ ├── components
│ │ │ ├── HeaderCart.vue
│ │ │ ├── LayoutFixed.vue
│ │ │ ├── LayoutFooter.vue
│ │ │ ├── LayoutHeader.vue
│ │ │ ├── LayoutHeaderUl.vue
│ │ │ └── LayoutNav.vue
│ │ └── index.vue
│ │ ├── Login
│ │ └── index.vue
│ │ ├── Member
│ │ ├── components
│ │ │ ├── UserInfo.vue
│ │ │ └── UserOrder.vue
│ │ └── index.vue
│ │ ├── Pay
│ │ ├── PayBack.vue
│ │ └── index.vue
│ │ └── SubCategory
│ │ └── index.vue
│ └── vite.config.js
├── 04-素材
├── XtxSku
│ ├── index.vue
│ └── power-set.js
├── common.scss
├── images
│ ├── 200.png
│ ├── center-bg.png
│ ├── load.gif
│ ├── loading.gif
│ ├── login-bg.png
│ ├── logo.png
│ ├── none.png
│ └── qrcode.jpg
├── 小兔鲜测试账号.xls
└── 接口文档.md
├── 05-Xmind(大纲)
└── 小兔鲜7天js版.xmind
├── Readme.md
├── my-rabbit
├── .eslintrc.cjs
├── .gitignore
├── .vscode
│ └── extensions.json
├── README.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── apis
│ │ ├── cart.js
│ │ ├── category.js
│ │ ├── checkout.js
│ │ ├── detail.js
│ │ ├── home.js
│ │ ├── layout.js
│ │ ├── order.js
│ │ ├── pay.js
│ │ ├── testAPI.js
│ │ └── user.js
│ ├── assets
│ │ ├── base.css
│ │ ├── images
│ │ │ ├── 200.png
│ │ │ ├── center-bg.png
│ │ │ ├── load.gif
│ │ │ ├── loading.gif
│ │ │ ├── login-bg.png
│ │ │ ├── logo.png
│ │ │ ├── none.png
│ │ │ └── qrcode.jpg
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── ImageView
│ │ │ └── index.vue
│ │ ├── XtxSku
│ │ │ ├── index.vue
│ │ │ └── power-set.js
│ │ └── index.js
│ ├── composables
│ │ └── useCountDown.js
│ ├── directives
│ │ └── index.js
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── stores
│ │ ├── cartStore.js
│ │ ├── categoryStore.js
│ │ ├── counterStore.js
│ │ └── userStore.js
│ ├── styles
│ │ ├── common.scss
│ │ ├── element
│ │ │ └── index.scss
│ │ └── var.scss
│ ├── utils
│ │ └── http.js
│ └── views
│ │ ├── CartList
│ │ └── index.vue
│ │ ├── Category
│ │ ├── composables
│ │ │ ├── useBanner.js
│ │ │ └── useCategory.js
│ │ └── index.vue
│ │ ├── Checkout
│ │ └── index.vue
│ │ ├── Detail
│ │ ├── components
│ │ │ └── DetailHot.vue
│ │ └── index.vue
│ │ ├── Home
│ │ ├── componets
│ │ │ ├── GoodsItem.vue
│ │ │ ├── HomeBanner.vue
│ │ │ ├── HomeCategory.vue
│ │ │ ├── HomeHot.vue
│ │ │ ├── HomeNew.vue
│ │ │ ├── HomePanel.vue
│ │ │ └── HomeProduct.vue
│ │ └── index.vue
│ │ ├── Layout
│ │ ├── components
│ │ │ ├── HeaderCart.vue
│ │ │ ├── LayoutFixed.vue
│ │ │ ├── LayoutFooter.vue
│ │ │ ├── LayoutHeader.vue
│ │ │ └── LayoutNav.vue
│ │ └── index.vue
│ │ ├── Login
│ │ └── index.vue
│ │ ├── Member
│ │ ├── components
│ │ │ ├── UserInfo.vue
│ │ │ └── UserOrder.vue
│ │ └── index.vue
│ │ ├── Pay
│ │ └── index.vue
│ │ ├── PayBack
│ │ └── index.vue
│ │ └── SubCategory
│ │ └── index.vue
└── vite.config.js
├── vue-pinia
├── .gitignore
├── .vscode
│ └── extensions.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── base.css
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ ├── WelcomeItem.vue
│ │ └── icons
│ │ │ ├── IconCommunity.vue
│ │ │ ├── IconDocumentation.vue
│ │ │ ├── IconEcosystem.vue
│ │ │ ├── IconSupport.vue
│ │ │ └── IconTooling.vue
│ ├── main.js
│ └── stores
│ │ └── counter.js
└── vite.config.js
└── vue3-project
├── .eslintrc.cjs
├── .gitignore
├── .vscode
└── extensions.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
└── favicon.ico
├── src
├── App-SonToFather.vue
├── App-computed.vue
├── App-fatherToSon.vue
├── App-life.vue
├── App-reactive.vue
├── App-ref.vue
├── App-ref2.vue
├── App-setup.vue
├── App-watch.vue
├── App-watch2.vue
├── App-watch3-immediate.vue
├── App-watch4-deep.vue
├── App-watch5.vue
├── App.vue
├── Sku
│ ├── Sku.vue
│ └── power-set.js
├── assets
│ ├── base.css
│ ├── logo.svg
│ └── main.css
├── components
│ ├── HelloWorld.vue
│ ├── TheWelcome.vue
│ ├── WelcomeItem.vue
│ └── icons
│ │ ├── IconCommunity.vue
│ │ ├── IconDocumentation.vue
│ │ ├── IconEcosystem.vue
│ │ ├── IconSupport.vue
│ │ └── IconTooling.vue
├── main.js
├── room-msg-comment.vue
├── room-msg-item.vue
├── room-page.vue
├── son-com-SonToFather.vue
├── son-com-fatherToSon.vue
├── son-com.vue
└── test-com.vue
└── vite.config.js
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vue-rabbit.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/01-PDF/day01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day01.pdf
--------------------------------------------------------------------------------
/01-PDF/day02.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day02.pdf
--------------------------------------------------------------------------------
/01-PDF/day03.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day03.pdf
--------------------------------------------------------------------------------
/01-PDF/day04.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day04.pdf
--------------------------------------------------------------------------------
/01-PDF/day05.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day05.pdf
--------------------------------------------------------------------------------
/01-PDF/day06.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day06.pdf
--------------------------------------------------------------------------------
/01-PDF/day07.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/01-PDF/day07.pdf
--------------------------------------------------------------------------------
/02-笔记/2. Pinia入门.md:
--------------------------------------------------------------------------------
1 | # 什么是pinia
2 | Pinia 是 Vue 的专属状态管理库,可以实现跨组件或页面共享状态,是 vuex 状态管理工具的替代品,和 Vuex相比,具备以下优势
3 |
4 | 1. 提供更加简单的API (去掉了 mutation )
5 | 2. 提供符合组合式API风格的API (和 Vue3 新语法统一)
6 | 3. 去掉了modules的概念,每一个store都是一个独立的模块
7 | 4. 搭配 TypeScript 一起使用提供可靠的类型推断
8 |
9 | # 创建空Vue项目并安装Pinia
10 | ### 1. 创建空Vue项目
11 | ```bash
12 | npm init vue@latest
13 | ```
14 |
15 | ### 2. 安装Pinia并注册
16 | ```bash
17 | npm i pinia
18 | ```
19 |
20 | ```javascript
21 |
22 | import { createPinia } from 'pinia'
23 |
24 | const app = createApp(App)
25 | // 以插件的形式注册
26 | app.use(createPinia())
27 | app.use(router)
28 | app.mount('#app')
29 | ```
30 | # 实现counter
31 | > 核心步骤:
32 | > 1. 定义store
33 | > 2. 组件使用store
34 |
35 | 1- 定义store
36 | ```javascript
37 | import { defineStore } from 'pinia'
38 | import { ref } from 'vue'
39 |
40 | export const useCounterStore = defineStore('counter', ()=>{
41 | // 数据 (state)
42 | const count = ref(0)
43 |
44 | // 修改数据的方法 (action)
45 | const increment = ()=>{
46 | count.value++
47 | }
48 |
49 | // 以对象形式返回
50 | return {
51 | count,
52 | increment
53 | }
54 | })
55 |
56 | ```
57 | 2- 组件使用store
58 | ```vue
59 |
65 |
66 |
67 |
68 | {{ counterStore.count }}
69 |
70 |
71 | ```
72 |
73 | # 实现getters
74 | > getters直接使用计算属性即可实现
75 |
76 | ```javascript
77 | // 数据(state)
78 | const count = ref(0)
79 | // getter (computed)
80 | const doubleCount = computed(() => count.value * 2)
81 | ```
82 |
83 | # 异步action
84 | > 思想:action函数既支持同步也支持异步,和在组件中发送网络请求写法保持一致
85 | > 步骤:
86 | > 1. store中定义action
87 | > 2. 组件中触发action
88 |
89 | 1- store中定义action
90 | ```javascript
91 | const API_URL = 'http://geek.itheima.net/v1_0/channels'
92 |
93 | export const useCounterStore = defineStore('counter', ()=>{
94 | // 数据
95 | const list = ref([])
96 | // 异步action
97 | const loadList = async ()=>{
98 | const res = await axios.get(API_URL)
99 | list.value = res.data.data.channels
100 | }
101 |
102 | return {
103 | list,
104 | loadList
105 | }
106 | })
107 | ```
108 | 2- 组件中调用action
109 | ```vue
110 |
116 |
117 |
118 |
119 | {{ item.name }}
120 |
121 |
122 | ```
123 | # storeToRefs保持响应式解构
124 | > 直接基于store进行解构赋值,响应式数据(state和getter)会丢失响应式特性,使用storeToRefs辅助保持响应式
125 |
126 | ```vue
127 |
137 |
138 |
139 |
140 | {{ count }}
141 |
142 |
143 | ```
144 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist-ssr
12 | *.local
13 |
14 | # Editor directories and files
15 | .vscode/*
16 | !.vscode/extensions.json
17 | .idea
18 | .DS_Store
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/README.md:
--------------------------------------------------------------------------------
1 | # vue-rabbit
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
31 | ### Lint with [ESLint](https://eslint.org/)
32 |
33 | ```sh
34 | npm run lint
35 | ```
36 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite App
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": [
6 | "src/*"
7 | ]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-rabbit",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
10 | },
11 | "dependencies": {
12 | "@vueuse/core": "^9.12.0",
13 | "axios": "^1.2.6",
14 | "dayjs": "^1.11.7",
15 | "element-plus": "^2.2.28",
16 | "pinia": "^2.0.28",
17 | "pinia-plugin-persistedstate": "^3.0.2",
18 | "vue": "^3.2.45",
19 | "vue-router": "^4.1.6"
20 | },
21 | "devDependencies": {
22 | "@vitejs/plugin-vue": "^4.0.0",
23 | "eslint": "^8.22.0",
24 | "eslint-plugin-vue": "^9.3.0",
25 | "sass": "^1.57.1",
26 | "unplugin-auto-import": "^0.13.0",
27 | "unplugin-vue-components": "^0.23.0",
28 | "vite": "^4.0.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/public/favicon.ico
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/App.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
73 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/cart.js:
--------------------------------------------------------------------------------
1 | // 封装购物车相关接口
2 | import request from '@/utils/http'
3 |
4 | // 加入购物车
5 | export const insertCartAPI = ({ skuId, count }) => {
6 | return request({
7 | url: '/member/cart',
8 | method: 'POST',
9 | data: {
10 | skuId,
11 | count
12 | }
13 | })
14 | }
15 |
16 | // 获取最新的购物车列表
17 | export const findNewCartListAPI = () => {
18 | return request({
19 | url: '/member/cart'
20 | })
21 | }
22 |
23 | // 删除购物车
24 | export const delCartAPI = (ids) => {
25 | return request({
26 | url: '/member/cart',
27 | method: 'DELETE',
28 | data: {
29 | ids
30 | }
31 | })
32 | }
33 |
34 | // 合并购物车
35 |
36 | export const mergeCartAPI = (data) => {
37 | return request({
38 | url: '/member/cart/merge',
39 | method: 'POST',
40 | data
41 | })
42 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/category.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/http'
2 |
3 |
4 | export function getCategoryAPI (id) {
5 | return request({
6 | url: '/category',
7 | params: {
8 | id
9 | }
10 | })
11 | }
12 |
13 | /**
14 | * @description: 获取二级分类列表数据
15 | * @param {*} id 分类id
16 | * @return {*}
17 | */
18 |
19 | export const getCategoryFilterAPI = (id) => {
20 | return request({
21 | url: '/category/sub/filter',
22 | params: {
23 | id
24 | }
25 | })
26 | }
27 |
28 | /**
29 | * @description: 获取导航数据
30 | * @data {
31 | categoryId: 1005000 ,
32 | page: 1,
33 | pageSize: 20,
34 | sortField: 'publishTime' | 'orderNum' | 'evaluateNum'
35 | }
36 | * @return {*}
37 | */
38 | export const getSubCategoryAPI = (data) => {
39 | return request({
40 | url: '/category/goods/temporary',
41 | method: 'POST',
42 | data
43 | })
44 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/checkout.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/http'
2 |
3 |
4 | // 获取详情接口
5 | export const getCheckInfoAPI = () => {
6 | return request({
7 | url: '/member/order/pre'
8 | })
9 | }
10 |
11 |
12 | // 创建订单
13 | export const createOrderAPI = (data) => {
14 | return request({
15 | url: '/member/order',
16 | method: 'POST',
17 | data
18 | })
19 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/detail.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/http'
2 |
3 |
4 | export const getDetail = (id) => {
5 | return request({
6 | url: '/goods',
7 | params: {
8 | id
9 | }
10 | })
11 | }
12 |
13 | export const getHotGoodsAPI = ({ id, type, limit = 3 }) => {
14 | return request({
15 | url: '/goods/hot',
16 | params: {
17 | id,
18 | type,
19 | limit
20 | }
21 | })
22 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/home.js:
--------------------------------------------------------------------------------
1 | import httpInstance from '@/utils/http'
2 |
3 |
4 | // 获取banner
5 |
6 | export function getBannerAPI (params = {}) {
7 | // 默认为1 商品为2
8 | const { distributionSite = '1' } = params
9 | return httpInstance({
10 | url: '/home/banner',
11 | params: {
12 | distributionSite
13 | }
14 | })
15 | }
16 |
17 | /**
18 | * @description: 获取新鲜好物
19 | * @param {*}
20 | * @return {*}
21 | */
22 | export const findNewAPI = () => {
23 | return httpInstance({
24 | url: '/home/new'
25 | })
26 | }
27 |
28 | /**
29 | * @description: 获取人气推荐
30 | * @param {*}
31 | * @return {*}
32 | */
33 | export const getHotAPI = () => {
34 | return httpInstance({
35 | url: '/home/hot'
36 | })
37 | }
38 |
39 | /**
40 | * @description: 获取所有商品模块
41 | * @param {*}
42 | * @return {*}
43 | */
44 | export const getGoodsAPI = () => {
45 | return httpInstance({
46 | url: '/home/goods'
47 | })
48 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/layout.js:
--------------------------------------------------------------------------------
1 |
2 | import httpInstance from "@/utils/http"
3 |
4 | export function getCategoryAPI () {
5 | return httpInstance({
6 | url: '/home/category/head'
7 | })
8 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/order.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/http'
2 |
3 | /*
4 | params: {
5 | orderState:0,
6 | page:1,
7 | pageSize:2
8 | }
9 | */
10 |
11 |
12 | export const getUserOrder = (params) => {
13 | return request({
14 | url: '/member/order',
15 | method: 'GET',
16 | params
17 | })
18 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/pay.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/http'
2 |
3 | export const getOrderAPI = (id) => {
4 | return request({
5 | url: `/member/order/${id}`
6 | })
7 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/testAPI.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http"
2 |
3 |
4 | export function getCategory () {
5 | return httpInstance({
6 | url: 'home/category/head'
7 | })
8 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/apis/user.js:
--------------------------------------------------------------------------------
1 | // 封装所有和用户相关的接口函数
2 | import request from '@/utils/http'
3 |
4 | export const loginAPI = ({ account, password }) => {
5 | return request({
6 | url: '/login',
7 | method: 'POST',
8 | data: {
9 | account,
10 | password
11 | }
12 | })
13 | }
14 |
15 |
16 | export const getLikeListAPI = ({ limit = 4 }) => {
17 | return request({
18 | url: '/goods/relevant',
19 | params: {
20 | limit
21 | }
22 | })
23 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | position: relative;
59 | font-weight: normal;
60 | }
61 |
62 | body {
63 | min-height: 100vh;
64 | color: var(--color-text);
65 | background: var(--color-background);
66 | transition: color 0.5s, background-color 0.5s;
67 | line-height: 1.6;
68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
70 | font-size: 15px;
71 | text-rendering: optimizeLegibility;
72 | -webkit-font-smoothing: antialiased;
73 | -moz-osx-font-smoothing: grayscale;
74 | }
75 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/200.png
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/center-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/center-bg.png
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/load.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/load.gif
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/loading.gif
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/login-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/login-bg.png
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/logo.png
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/none.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/none.png
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/images/qrcode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/03-code/vue-rabbit/src/assets/images/qrcode.jpg
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 |
3 | #app {
4 | max-width: 1280px;
5 | margin: 0 auto;
6 | padding: 2rem;
7 |
8 | font-weight: normal;
9 | }
10 |
11 | a,
12 | .green {
13 | text-decoration: none;
14 | color: hsla(160, 100%, 37%, 1);
15 | transition: 0.4s;
16 | }
17 |
18 | @media (hover: hover) {
19 | a:hover {
20 | background-color: hsla(160, 100%, 37%, 0.2);
21 | }
22 | }
23 |
24 | @media (min-width: 1024px) {
25 | body {
26 | display: flex;
27 | place-items: center;
28 | }
29 |
30 | #app {
31 | display: grid;
32 | grid-template-columns: 1fr 1fr;
33 | padding: 0 2rem;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/components/XtxSku/power-set.js:
--------------------------------------------------------------------------------
1 |
2 | export default function bwPowerSet (originalSet) {
3 | const subSets = []
4 |
5 | // We will have 2^n possible combinations (where n is a length of original set).
6 | // It is because for every element of original set we will decide whether to include
7 | // it or not (2 options for each set element).
8 | const numberOfCombinations = 2 ** originalSet.length
9 |
10 | // Each number in binary representation in a range from 0 to 2^n does exactly what we need:
11 | // it shows by its bits (0 or 1) whether to include related element from the set or not.
12 | // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
13 | // include only "2" to the current set.
14 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
15 | const subSet = []
16 |
17 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
18 | // Decide whether we need to include current element into the subset or not.
19 | if (combinationIndex & (1 << setElementIndex)) {
20 | subSet.push(originalSet[setElementIndex])
21 | }
22 | }
23 |
24 | // Add current subset to the list of all subsets.
25 | subSets.push(subSet)
26 | }
27 |
28 | return subSets
29 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/components/index.js:
--------------------------------------------------------------------------------
1 | // 把components中的所组件都进行全局化注册
2 | // 通过插件的方式
3 | import ImageView from './ImageView/index.vue'
4 | import Sku from './XtxSku/index.vue'
5 | export const componentPlugin = {
6 | install (app) {
7 | // app.component('组件名字',组件配置对象)
8 | app.component('XtxImageView', ImageView)
9 | app.component('XtxSku', Sku)
10 | }
11 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/composables/useCountDown.js:
--------------------------------------------------------------------------------
1 | // 封装倒计时逻辑函数
2 | import { computed, onUnmounted, ref } from 'vue'
3 | import dayjs from 'dayjs'
4 | export const useCountDown = () => {
5 | // 1. 响应式的数据
6 | let timer = null
7 | const time = ref(0)
8 | // 格式化时间 为 xx分xx秒
9 | const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
10 | // 2. 开启倒计时的函数
11 | const start = (currentTime) => {
12 | // 开始倒计时的逻辑
13 | // 核心逻辑的编写:每隔1s就减一
14 | time.value = currentTime
15 | timer = setInterval(() => {
16 | time.value--
17 | }, 1000)
18 | }
19 | // 组件销毁时清除定时器
20 | onUnmounted(() => {
21 | timer && clearInterval(timer)
22 | })
23 | return {
24 | formatTime,
25 | start
26 | }
27 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/directives/index.js:
--------------------------------------------------------------------------------
1 | // 定义懒加载插件
2 | import { useIntersectionObserver } from '@vueuse/core'
3 |
4 | export const lazyPlugin = {
5 | install (app) {
6 | // 懒加载指令逻辑
7 | app.directive('img-lazy', {
8 | mounted (el, binding) {
9 | // el: 指令绑定的那个元素 img
10 | // binding: binding.value 指令等于号后面绑定的表达式的值 图片url
11 | // console.log(el, binding.value)
12 | const { stop } = useIntersectionObserver(
13 | el,
14 | ([{ isIntersecting }]) => {
15 | console.log(isIntersecting)
16 | if (isIntersecting) {
17 | // 进入视口区域
18 | el.src = binding.value
19 | stop()
20 | }
21 | },
22 | )
23 | }
24 | })
25 | }
26 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import { createPinia } from 'pinia'
3 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
4 | import App from './App.vue'
5 | import router from './router'
6 |
7 | // 引入初始化样式文件
8 | import '@/styles/common.scss'
9 |
10 | // 引入懒加载指令插件并且注册
11 | import { lazyPlugin } from '@/directives'
12 | // 引入全局组件插件
13 | import { componentPlugin } from '@/components'
14 |
15 | const app = createApp(App)
16 | const pinia = createPinia()
17 | // 注册持久化插件
18 | pinia.use(piniaPluginPersistedstate)
19 | app.use(pinia)
20 | app.use(router)
21 | app.use(lazyPlugin)
22 | app.use(componentPlugin)
23 | app.mount('#app')
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/router/index.js:
--------------------------------------------------------------------------------
1 | // createRouter:创建router实例对象
2 | // createWebHistory:创建history模式的路由
3 |
4 | import { createRouter, createWebHistory } from 'vue-router'
5 | import Login from '@/views/Login/index.vue'
6 | import Layout from '@/views/Layout/index.vue'
7 | import Home from '@/views/Home/index.vue'
8 | import Category from '@/views/Category/index.vue'
9 | import SubCategory from '@/views/SubCategory/index.vue'
10 | import Detail from '@/views/Detail/index.vue'
11 | import CartList from '@/views/CartList/index.vue'
12 | import Checkout from '@/views/Checkout/index.vue'
13 | import Pay from '@/views/Pay/index.vue'
14 | import PayBack from '@/views/Pay/PayBack.vue'
15 | import Member from '@/views/Member/index.vue'
16 | import UserInfo from '@/views/Member/components/UserInfo.vue'
17 | import UserOrder from '@/views/Member/components/UserOrder.vue'
18 |
19 | const router = createRouter({
20 | history: createWebHistory(import.meta.env.BASE_URL),
21 | // path和component对应关系的位置
22 | routes: [
23 | {
24 | path: '/',
25 | component: Layout,
26 | children: [
27 | {
28 | path: '',
29 | component: Home
30 | },
31 | {
32 | path: 'category/:id',
33 | component: Category
34 | },
35 | {
36 | path: 'category/sub/:id',
37 | component: SubCategory
38 | },
39 | {
40 | path: 'detail/:id',
41 | component: Detail
42 | },
43 | {
44 | path: 'cartlist',
45 | component: CartList
46 | },
47 | {
48 | path: 'checkout',
49 | component: Checkout
50 | },
51 | {
52 | path: 'pay',
53 | component: Pay
54 | },
55 | {
56 | path: 'paycallback',
57 | component: PayBack
58 | },
59 | {
60 | path: 'member',
61 | component: Member,
62 | children: [
63 | {
64 | path: '',
65 | component: UserInfo
66 | },
67 | {
68 | path: 'order',
69 | component: UserOrder
70 | }
71 | ]
72 | }
73 | ]
74 | },
75 | {
76 | path: '/login',
77 | component: Login
78 | }
79 | ],
80 | // 路由滚动行为定制
81 | scrollBehavior () {
82 | return {
83 | top: 0
84 | }
85 | }
86 | })
87 |
88 | export default router
89 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/stores/cartStore.js:
--------------------------------------------------------------------------------
1 | // 封装购物车模块
2 |
3 | import { defineStore } from 'pinia'
4 | import { computed, ref } from 'vue'
5 | import { useUserStore } from './userStore'
6 | import { insertCartAPI, findNewCartListAPI, delCartAPI } from '@/apis/cart'
7 | export const useCartStore = defineStore('cart', () => {
8 | const userStore = useUserStore()
9 | const isLogin = computed(() => userStore.userInfo.token)
10 | // 1. 定义state - cartList
11 | const cartList = ref([])
12 | // 获取最新购物车列表action
13 | const updateNewList = async () => {
14 | const res = await findNewCartListAPI()
15 | cartList.value = res.result
16 | }
17 | // 2. 定义action - addCart
18 | const addCart = async (goods) => {
19 | const { skuId, count } = goods
20 | if (isLogin.value) {
21 | // 登录之后的加入购车逻辑
22 | await insertCartAPI({ skuId, count })
23 | updateNewList()
24 | } else {
25 | // 添加购物车操作
26 | // 已添加过 - count + 1
27 | // 没有添加过 - 直接push
28 | // 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
29 | const item = cartList.value.find((item) => goods.skuId === item.skuId)
30 | if (item) {
31 | // 找到了
32 | item.count++
33 | } else {
34 | // 没找到
35 | cartList.value.push(goods)
36 | }
37 | }
38 | }
39 |
40 | // 删除购物车
41 | const delCart = async (skuId) => {
42 | if (isLogin.value) {
43 | // 调用接口实现接口购物车中的删除功能
44 | await delCartAPI([skuId])
45 | updateNewList()
46 | } else {
47 | // 思路:
48 | // 1. 找到要删除项的下标值 - splice
49 | // 2. 使用数组的过滤方法 - filter
50 | const idx = cartList.value.findIndex((item) => skuId === item.skuId)
51 | cartList.value.splice(idx, 1)
52 | }
53 | }
54 |
55 | // 清除购物车
56 | const clearCart = () => {
57 | cartList.value = []
58 | }
59 |
60 | // 单选功能
61 | const singleCheck = (skuId, selected) => {
62 | // 通过skuId找到要修改的那一项 然后把它的selected修改为传过来的selected
63 | const item = cartList.value.find((item) => item.skuId === skuId)
64 | item.selected = selected
65 | }
66 |
67 | // 全选功能
68 | const allCheck = (selected) => {
69 | // 把cartList中的每一项的selected都设置为当前的全选框状态
70 | cartList.value.forEach(item => item.selected = selected)
71 | }
72 |
73 | // 计算属性
74 | // 1. 总的数量 所有项的count之和
75 | const allCount = computed(() => cartList.value.reduce((a, c) => a + c.count, 0))
76 | // 2. 总价 所有项的count*price之和
77 | const allPrice = computed(() => cartList.value.reduce((a, c) => a + c.count * c.price, 0))
78 |
79 | // 3. 已选择数量
80 | const selectedCount = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count, 0))
81 | // 4. 已选择商品价钱合计
82 | const selectedPrice = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count * c.price, 0))
83 |
84 | // 是否全选
85 | const isAll = computed(() => cartList.value.every((item) => item.selected))
86 |
87 | return {
88 | cartList,
89 | allCount,
90 | allPrice,
91 | isAll,
92 | selectedCount,
93 | selectedPrice,
94 | clearCart,
95 | addCart,
96 | delCart,
97 | singleCheck,
98 | allCheck,
99 | updateNewList
100 | }
101 | }, {
102 | persist: true,
103 | })
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/stores/categoryStore.js:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 | import { defineStore } from 'pinia'
3 | import { getCategoryAPI } from '@/apis/layout'
4 | export const useCategoryStore = defineStore('category', () => {
5 | // 导航列表的数据管理
6 | // state 导航列表数据
7 | const categoryList = ref([])
8 |
9 | // action 获取导航数据的方法
10 | const getCategory = async () => {
11 | const res = await getCategoryAPI()
12 | categoryList.value = res.result
13 | }
14 |
15 | return {
16 | categoryList,
17 | getCategory
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/stores/userStore.js:
--------------------------------------------------------------------------------
1 | // 管理用户数据相关
2 |
3 | import { defineStore } from 'pinia'
4 | import { ref } from 'vue'
5 | import { loginAPI } from '@/apis/user'
6 | import { useCartStore } from './cartStore'
7 | import { mergeCartAPI } from '@/apis/cart'
8 | export const useUserStore = defineStore('user', () => {
9 | const cartStore = useCartStore()
10 | // 1. 定义管理用户数据的state
11 | const userInfo = ref({})
12 | // 2. 定义获取接口数据的action函数
13 | const getUserInfo = async ({ account, password }) => {
14 | const res = await loginAPI({ account, password })
15 | userInfo.value = res.result
16 | // 合并购物车的操作
17 | await mergeCartAPI(cartStore.cartList.map(item => {
18 | return {
19 | skuId: item.skuId,
20 | selected: item.selected,
21 | count: item.count
22 | }
23 | }))
24 | cartStore.updateNewList()
25 | }
26 |
27 | // 退出时清除用户信息
28 | const clearUserInfo = () => {
29 | userInfo.value = {}
30 | // 执行清除购物车的action
31 | cartStore.clearCart()
32 | }
33 | // 3. 以对象的格式把state和action return
34 | return {
35 | userInfo,
36 | getUserInfo,
37 | clearUserInfo
38 | }
39 | }, {
40 | persist: true,
41 | })
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/styles/common.scss:
--------------------------------------------------------------------------------
1 | // 重置样式
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
6 | html {
7 | height: 100%;
8 | font-size: 14px;
9 | }
10 | body {
11 | height: 100%;
12 | color: #333;
13 | min-width: 1240px;
14 | font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
15 | 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
16 | sans-serif;
17 | }
18 | body,
19 | ul,
20 | h1,
21 | h3,
22 | h4,
23 | p,
24 | dl,
25 | dd {
26 | padding: 0;
27 | margin: 0;
28 | }
29 | a {
30 | text-decoration: none;
31 | color: #333;
32 | outline: none;
33 | }
34 | i {
35 | font-style: normal;
36 | }
37 | input[type='text'],
38 | input[type='search'],
39 | input[type='password'],
40 | input[type='checkbox'] {
41 | padding: 0;
42 | outline: none;
43 | border: none;
44 | -webkit-appearance: none;
45 | &::placeholder {
46 | color: #ccc;
47 | }
48 | }
49 | img {
50 | max-width: 100%;
51 | max-height: 100%;
52 | vertical-align: middle;
53 | background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
54 | }
55 | ul {
56 | list-style: none;
57 | }
58 |
59 | #app {
60 | background: #f5f5f5;
61 | user-select: none;
62 | }
63 |
64 | .container {
65 | width: 1240px;
66 | margin: 0 auto;
67 | position: relative;
68 | }
69 | .ellipsis {
70 | white-space: nowrap;
71 | text-overflow: ellipsis;
72 | overflow: hidden;
73 | }
74 |
75 | .ellipsis-2 {
76 | word-break: break-all;
77 | text-overflow: ellipsis;
78 | display: -webkit-box;
79 | -webkit-box-orient: vertical;
80 | -webkit-line-clamp: 2;
81 | overflow: hidden;
82 | }
83 |
84 | .fl {
85 | float: left;
86 | }
87 |
88 | .fr {
89 | float: right;
90 | }
91 |
92 | .clearfix:after {
93 | content: '.';
94 | display: block;
95 | visibility: hidden;
96 | height: 0;
97 | line-height: 0;
98 | clear: both;
99 | }
100 |
101 | // reset element
102 | .el-breadcrumb__inner.is-link {
103 | font-weight: 400 !important;
104 | }
105 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/styles/element/index.scss:
--------------------------------------------------------------------------------
1 | /* 只需要重写你需要的即可 */
2 | @forward 'element-plus/theme-chalk/src/common/var.scss' with (
3 | $colors: (
4 | 'primary': (
5 | // 主色
6 | 'base': #27ba9b,
7 | ),
8 | 'success': (
9 | // 成功色
10 | 'base': #1dc779,
11 | ),
12 | 'warning': (
13 | // 警告色
14 | 'base': #ffb302,
15 | ),
16 | 'danger': (
17 | // 危险色
18 | 'base': #e26237,
19 | ),
20 | 'error': (
21 | // 错误色
22 | 'base': #cf4444,
23 | ),
24 | )
25 | );
26 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/styles/var.scss:
--------------------------------------------------------------------------------
1 | $xtxColor: #27ba9b;
2 | $helpColor: #e26237;
3 | $sucColor: #1dc779;
4 | $warnColor: #ffb302;
5 | $priceColor: #cf4444;
6 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/utils/http.js:
--------------------------------------------------------------------------------
1 | // axios基础的封装
2 | import axios from 'axios'
3 | import { ElMessage } from 'element-plus'
4 | import { useUserStore } from '@/stores/userStore'
5 | const httpInstance = axios.create({
6 | baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
7 | timeout: 5000
8 | })
9 |
10 | // 拦截器
11 |
12 | // axios请求拦截器
13 | httpInstance.interceptors.request.use(config => {
14 | // 1. 从pinia获取token数据
15 | const userStore = useUserStore()
16 | // 2. 按照后端的要求拼接token数据
17 | const token = userStore.userInfo.token
18 | if (token) {
19 | config.headers.Authorization = `Bearer ${token}`
20 | }
21 | return config
22 | }, e => Promise.reject(e))
23 |
24 | // axios响应式拦截器
25 | httpInstance.interceptors.response.use(res => res.data, e => {
26 | // 统一错误提示
27 | ElMessage({
28 | type: 'warning',
29 | message: e.response.data.message
30 | })
31 | return Promise.reject(e)
32 | })
33 |
34 |
35 | export default httpInstance
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Category/composables/useBanner.js:
--------------------------------------------------------------------------------
1 | // 封装banner轮播图相关的业务代码
2 | import { ref, onMounted } from 'vue'
3 | import { getBannerAPI } from '@/apis/home'
4 |
5 | export function useBanner () {
6 | const bannerList = ref([])
7 |
8 | const getBanner = async () => {
9 | const res = await getBannerAPI({
10 | distributionSite: '2'
11 | })
12 | console.log(res)
13 | bannerList.value = res.result
14 | }
15 |
16 | onMounted(() => getBanner())
17 |
18 | return {
19 | bannerList
20 | }
21 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Category/composables/useCategory.js:
--------------------------------------------------------------------------------
1 | // 封装分类数据业务相关代码
2 | import { onMounted, ref } from 'vue'
3 | import { getCategoryAPI } from '@/apis/category'
4 | import { useRoute } from 'vue-router'
5 | import { onBeforeRouteUpdate } from 'vue-router'
6 |
7 | export function useCategory () {
8 | // 获取分类数据
9 | const categoryData = ref({})
10 | const route = useRoute()
11 | const getCategory = async (id = route.params.id) => {
12 | const res = await getCategoryAPI(id)
13 | categoryData.value = res.result
14 | }
15 | onMounted(() => getCategory())
16 |
17 | // 目标:路由参数变化的时候 可以把分类数据接口重新发送
18 | onBeforeRouteUpdate((to) => {
19 | // 存在问题:使用最新的路由参数请求最新的分类数据
20 | getCategory(to.params.id)
21 | })
22 | return {
23 | categoryData
24 | }
25 | }
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Category/index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 首页
19 | {{ categoryData.name }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
全部分类
32 |
33 |
34 |
35 |
36 | {{ i.name }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
- {{ item.name }}-
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Detail/components/DetailHot.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
38 |
39 |
{{ title }}
40 |
41 |
42 |
43 | {{ item.name }}
44 | {{ item.desc }}
45 | ¥{{ item.price }}
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/GoodsItem.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 | {{ goods.name }}
15 | {{ goods.desc }}
16 | ¥{{ goods.price }}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/HomeBanner.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/HomeHot.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ item.title }}
22 | {{ item.alt }}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/HomeNew.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ item.name }}
25 | ¥{{ item.price }}
26 |
27 |
28 |
29 |
30 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/HomePanel.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ title }}{{ subTitle }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/components/HomeProduct.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ cate.name }}馆
23 | {{ cate.saleInfo }}
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Home/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Layout/components/LayoutFixed.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Layout/components/LayoutHeader.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Layout/components/LayoutHeaderUl.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Layout/components/LayoutNav.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Member/components/UserInfo.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
45 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Member/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
我的账户
10 |
11 | 个人中心
12 |
13 |
交易管理
14 |
15 | 我的订单
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/Pay/PayBack.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
支付{{ $route.query.payResult === 'true' ? '成功' : '失败' }}
27 |
我们将尽快为您发货,收货期间请保持手机畅通
28 |
支付方式:支付宝
29 |
支付金额:¥{{ orderInfo.payMoney?.toFixed(2) }}
30 |
31 | 查看订单
32 | 进入首页
33 |
34 |
35 |
36 | 温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/src/views/SubCategory/index.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 首页
60 | {{ categoryData.parentName }}
61 |
62 | {{ categoryData.name }}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/03-code/vue-rabbit/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // elementPlus按需导入
7 | import AutoImport from 'unplugin-auto-import/vite'
8 | import Components from 'unplugin-vue-components/vite'
9 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
10 |
11 | // https://vitejs.dev/config/
12 | export default defineConfig({
13 | plugins: [
14 | vue(),
15 | // ...
16 | AutoImport({
17 | resolvers: [ElementPlusResolver()],
18 | }),
19 | Components({
20 | resolvers: [
21 | // 1. 配置elementPlus采用sass样式配色系统
22 | ElementPlusResolver({ importStyle: "sass" }),
23 | ],
24 | }),
25 | ],
26 | resolve: {
27 | // 实际的路径转换 @ -> src
28 | alias: {
29 | '@': fileURLToPath(new URL('./src', import.meta.url))
30 | }
31 | },
32 | css: {
33 | preprocessorOptions: {
34 | scss: {
35 | // 2. 自动导入定制化样式文件进行样式覆盖
36 | additionalData: `
37 | @use "@/styles/element/index.scss" as *;
38 | @use "@/styles/var.scss" as *;
39 | `,
40 | }
41 | }
42 | }
43 | })
44 |
--------------------------------------------------------------------------------
/04-素材/XtxSku/power-set.js:
--------------------------------------------------------------------------------
1 |
2 | export default function bwPowerSet (originalSet) {
3 | const subSets = []
4 |
5 | // We will have 2^n possible combinations (where n is a length of original set).
6 | // It is because for every element of original set we will decide whether to include
7 | // it or not (2 options for each set element).
8 | const numberOfCombinations = 2 ** originalSet.length
9 |
10 | // Each number in binary representation in a range from 0 to 2^n does exactly what we need:
11 | // it shows by its bits (0 or 1) whether to include related element from the set or not.
12 | // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
13 | // include only "2" to the current set.
14 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
15 | const subSet = []
16 |
17 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
18 | // Decide whether we need to include current element into the subset or not.
19 | if (combinationIndex & (1 << setElementIndex)) {
20 | subSet.push(originalSet[setElementIndex])
21 | }
22 | }
23 |
24 | // Add current subset to the list of all subsets.
25 | subSets.push(subSet)
26 | }
27 |
28 | return subSets
29 | }
--------------------------------------------------------------------------------
/04-素材/common.scss:
--------------------------------------------------------------------------------
1 | // 重置样式
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
6 | html {
7 | height: 100%;
8 | font-size: 14px;
9 | }
10 | body {
11 | height: 100%;
12 | color: #333;
13 | min-width: 1240px;
14 | font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
15 | 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
16 | sans-serif;
17 | }
18 | body,
19 | ul,
20 | h1,
21 | h3,
22 | h4,
23 | p,
24 | dl,
25 | dd {
26 | padding: 0;
27 | margin: 0;
28 | }
29 | a {
30 | text-decoration: none;
31 | color: #333;
32 | outline: none;
33 | }
34 | i {
35 | font-style: normal;
36 | }
37 | input[type='text'],
38 | input[type='search'],
39 | input[type='password'],
40 | input[type='checkbox'] {
41 | padding: 0;
42 | outline: none;
43 | border: none;
44 | -webkit-appearance: none;
45 | &::placeholder {
46 | color: #ccc;
47 | }
48 | }
49 | img {
50 | max-width: 100%;
51 | max-height: 100%;
52 | vertical-align: middle;
53 | background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
54 | }
55 | ul {
56 | list-style: none;
57 | }
58 |
59 | #app {
60 | background: #f5f5f5;
61 | user-select: none;
62 | }
63 |
64 | .container {
65 | width: 1240px;
66 | margin: 0 auto;
67 | position: relative;
68 | }
69 | .ellipsis {
70 | white-space: nowrap;
71 | text-overflow: ellipsis;
72 | overflow: hidden;
73 | }
74 |
75 | .ellipsis-2 {
76 | word-break: break-all;
77 | text-overflow: ellipsis;
78 | display: -webkit-box;
79 | -webkit-box-orient: vertical;
80 | -webkit-line-clamp: 2;
81 | overflow: hidden;
82 | }
83 |
84 | .fl {
85 | float: left;
86 | }
87 |
88 | .fr {
89 | float: right;
90 | }
91 |
92 | .clearfix:after {
93 | content: '.';
94 | display: block;
95 | visibility: hidden;
96 | height: 0;
97 | line-height: 0;
98 | clear: both;
99 | }
100 |
101 | // reset element
102 | .el-breadcrumb__inner.is-link {
103 | font-weight: 400 !important;
104 | }
105 |
--------------------------------------------------------------------------------
/04-素材/images/200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/200.png
--------------------------------------------------------------------------------
/04-素材/images/center-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/center-bg.png
--------------------------------------------------------------------------------
/04-素材/images/load.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/load.gif
--------------------------------------------------------------------------------
/04-素材/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/loading.gif
--------------------------------------------------------------------------------
/04-素材/images/login-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/login-bg.png
--------------------------------------------------------------------------------
/04-素材/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/logo.png
--------------------------------------------------------------------------------
/04-素材/images/none.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/none.png
--------------------------------------------------------------------------------
/04-素材/images/qrcode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/images/qrcode.jpg
--------------------------------------------------------------------------------
/04-素材/小兔鲜测试账号.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/04-素材/小兔鲜测试账号.xls
--------------------------------------------------------------------------------
/04-素材/接口文档.md:
--------------------------------------------------------------------------------
1 |
2 | 在线文档
3 | [https://www.apifox.cn/apidoc/shared-c05cb8d7-e591-4d9c-aff8-11065a0ec1de/api-67132167](https://www.apifox.cn/apidoc/shared-c05cb8d7-e591-4d9c-aff8-11065a0ec1de/api-67132167)
4 |
5 |
--------------------------------------------------------------------------------
/05-Xmind(大纲)/小兔鲜7天js版.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/05-Xmind(大纲)/小兔鲜7天js版.xmind
--------------------------------------------------------------------------------
/my-rabbit/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | root: true,
4 | 'extends': [
5 | 'plugin:vue/vue3-essential',
6 | 'eslint:recommended'
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 'latest'
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/my-rabbit/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/my-rabbit/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/my-rabbit/README.md:
--------------------------------------------------------------------------------
1 | # my-rabbit
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
31 | ### Lint with [ESLint](https://eslint.org/)
32 |
33 | ```sh
34 | npm run lint
35 | ```
36 |
--------------------------------------------------------------------------------
/my-rabbit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite App
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/my-rabbit/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions" : {
3 | "baseUrl" : "./",
4 | "paths" : {
5 | "@/*":["src/*"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/my-rabbit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-rabbit",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
10 | },
11 | "dependencies": {
12 | "@vueuse/core": "^10.1.2",
13 | "axios": "^1.4.0",
14 | "dayjs": "^1.11.8",
15 | "element-plus": "^2.3.6",
16 | "pinia": "^2.0.36",
17 | "pinia-plugin-persistedstate": "^3.1.0",
18 | "vue": "^3.3.2",
19 | "vue-router": "^4.2.0"
20 | },
21 | "devDependencies": {
22 | "@vitejs/plugin-vue": "^4.2.3",
23 | "eslint": "^8.39.0",
24 | "eslint-plugin-vue": "^9.11.0",
25 | "sass": "^1.62.1",
26 | "unplugin-auto-import": "^0.16.4",
27 | "unplugin-element-plus": "^0.7.1",
28 | "unplugin-vue-components": "^0.25.0",
29 | "vite": "^4.3.5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/my-rabbit/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/public/favicon.ico
--------------------------------------------------------------------------------
/my-rabbit/src/App.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/my-rabbit/src/apis/cart.js:
--------------------------------------------------------------------------------
1 | // 封装购物车相关接口
2 |
3 | import httpInstance from "@/utils/http";
4 | import http from "@/utils/http";
5 |
6 | // 加入购物车
7 | export const insertCartAPI = ({skuId, count}) => {
8 | return httpInstance({
9 | url: '/member/cart',
10 | method: 'POST',
11 | data: {
12 | skuId,
13 | count
14 | }
15 | })
16 | }
17 |
18 | // 获取最新购物车的列表
19 | export const findNewCartListAPI=()=>{
20 | return httpInstance({
21 | url:'/member/cart'
22 | })
23 | }
24 |
25 | // 删除购物车商品
26 | export const delCartAPI=(ids)=>{
27 | return httpInstance({
28 | url:'/member/cart',
29 | method:"DELETE",
30 | data:{
31 | ids
32 | }
33 | })
34 | }
35 |
36 | // 合并购物车
37 | export const mergeCartAPI=(data)=>{
38 | return httpInstance({
39 | url:'/member/cart/merge',
40 | method:'POST',
41 | data
42 | })
43 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/category.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | // 获取分类数据
4 | export function getCategoryAPI(id){
5 | return httpInstance({
6 | url:'/category',
7 | params:{
8 | id
9 | }
10 | })
11 | }
12 |
13 | // 获取二级分类列表数据
14 | export const getCategoryFilterAPI = (id) => {
15 | return httpInstance({
16 | url:'/category/sub/filter',
17 | params:{
18 | id
19 | }
20 | })
21 | }
22 |
23 | /**
24 | * @description: 获取导航数据
25 | * @data {
26 | categoryId: 1005000 ,
27 | page: 1,
28 | pageSize: 20,
29 | sortField: 'publishTime' | 'orderNum' | 'evaluateNum'
30 | }
31 | * @return {*}
32 | */
33 | export const getSubCategoryAPI = (data) => {
34 | return httpInstance({
35 | url:'/category/goods/temporary',
36 | method:'POST',
37 | data
38 | })
39 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/checkout.js:
--------------------------------------------------------------------------------
1 | // 结算页
2 |
3 | import httpInstance from "@/utils/http";
4 |
5 | // 获取结算信息
6 | export const getCheckoutInfoAPI = () => {
7 | return httpInstance({
8 | url:'/member/order/pre'
9 | })
10 | };
11 |
12 | // 创建订单
13 | export const createOrderAPI=(data)=>{
14 | return httpInstance({
15 | url: '/member/order',
16 | method: 'POST',
17 | data
18 | })
19 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/detail.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | // 获取商品详情
4 | export const getDetail = (id) => {
5 | return httpInstance({
6 | url: '/goods',
7 | params: {
8 | id
9 | }
10 | })
11 | }
12 |
13 | /**
14 | * 获取热榜商品
15 | * @param {Number} id - 商品id
16 | * @param {Number} type - 1代表24小时热销榜 2代表周热销榜
17 | * @param {Number} limit - 获取个数
18 | */
19 | export const fetchHotGoodsAPI = ({ id, type, limit = 3 }) => {
20 | return httpInstance({
21 | url:'/goods/hot',
22 | params:{
23 | id,
24 | type,
25 | limit
26 | }
27 | })
28 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/home.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | // 获取banner
4 | export function getBannerAPI(params={}) {
5 | // 默认为1 商品为2
6 | const {distributionSite='1' }=params
7 | return httpInstance({
8 | url:'/home/banner',
9 | params:{
10 | distributionSite
11 | }
12 | })
13 | }
14 |
15 | // 获取新鲜好物
16 | export const findNewAPI = () => {
17 | return httpInstance({
18 | url: '/home/new',
19 | })
20 | };
21 |
22 | // 获取人气推荐
23 | export const getHotAPI=()=>{
24 | return httpInstance({
25 | url:'/home/hot'
26 | })
27 | }
28 |
29 | /**
30 | * @description: 获取所有商品模块
31 | * @param {*}
32 | * @return {*}
33 | */
34 | export const getGoodsAPI = () => {
35 | return httpInstance({
36 | url: '/home/goods'
37 | })
38 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/layout.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | export function getCategoryAPI() {
4 | return httpInstance({
5 | url: '/home/category/head',
6 |
7 | })
8 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/order.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | export const getUserOrder = (params) => {
4 | return httpInstance({
5 | url:'/member/order',
6 | method:'GET',
7 | params
8 | })
9 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/pay.js:
--------------------------------------------------------------------------------
1 | // 支付API
2 |
3 | import httpInstance from "@/utils/http";
4 |
5 | export const getOrderAPI = (id) => {
6 | return httpInstance({
7 | url: `/member/order/${id}`
8 | })
9 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/testAPI.js:
--------------------------------------------------------------------------------
1 | import httpInstance from "@/utils/http";
2 |
3 | export function getCategory() {
4 | return httpInstance({
5 | url:'home/category/head'
6 | })
7 | }
--------------------------------------------------------------------------------
/my-rabbit/src/apis/user.js:
--------------------------------------------------------------------------------
1 | // 封装所有用户相关的接口
2 |
3 | import httpInstance from "@/utils/http";
4 |
5 | export const loginAPI = ({account, password}) => {
6 | return httpInstance({
7 | url: '/login',
8 | method: 'POST',
9 | data: {
10 | account,
11 | password
12 | }
13 | })
14 | }
15 |
16 | // 猜你喜欢接口
17 | export const getLikeListAPI = ({ limit = 4 }) => {
18 | return httpInstance({
19 | url:'/goods/relevant',
20 | params: {
21 | limit
22 | }
23 | })
24 | }
--------------------------------------------------------------------------------
/my-rabbit/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | font-weight: normal;
59 | }
60 |
61 | body {
62 | min-height: 100vh;
63 | color: var(--color-text);
64 | background: var(--color-background);
65 | transition: color 0.5s, background-color 0.5s;
66 | line-height: 1.6;
67 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
68 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
69 | font-size: 15px;
70 | text-rendering: optimizeLegibility;
71 | -webkit-font-smoothing: antialiased;
72 | -moz-osx-font-smoothing: grayscale;
73 | }
74 |
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/200.png
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/center-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/center-bg.png
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/load.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/load.gif
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/loading.gif
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/login-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/login-bg.png
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/logo.png
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/none.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/none.png
--------------------------------------------------------------------------------
/my-rabbit/src/assets/images/qrcode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/my-rabbit/src/assets/images/qrcode.jpg
--------------------------------------------------------------------------------
/my-rabbit/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/my-rabbit/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 |
3 | #app {
4 | max-width: 1280px;
5 | margin: 0 auto;
6 | padding: 2rem;
7 |
8 | font-weight: normal;
9 | }
10 |
11 | a,
12 | .green {
13 | text-decoration: none;
14 | color: hsla(160, 100%, 37%, 1);
15 | transition: 0.4s;
16 | }
17 |
18 | @media (hover: hover) {
19 | a:hover {
20 | background-color: hsla(160, 100%, 37%, 0.2);
21 | }
22 | }
23 |
24 | @media (min-width: 1024px) {
25 | body {
26 | display: flex;
27 | place-items: center;
28 | }
29 |
30 | #app {
31 | display: grid;
32 | grid-template-columns: 1fr 1fr;
33 | padding: 0 2rem;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/my-rabbit/src/components/ImageView/index.vue:
--------------------------------------------------------------------------------
1 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/my-rabbit/src/components/XtxSku/power-set.js:
--------------------------------------------------------------------------------
1 |
2 | export default function bwPowerSet (originalSet) {
3 | const subSets = []
4 |
5 | // We will have 2^n possible combinations (where n is a length of original set).
6 | // It is because for every element of original set we will decide whether to include
7 | // it or not (2 options for each set element).
8 | const numberOfCombinations = 2 ** originalSet.length
9 |
10 | // Each number in binary representation in a range from 0 to 2^n does exactly what we need:
11 | // it shows by its bits (0 or 1) whether to include related element from the set or not.
12 | // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
13 | // include only "2" to the current set.
14 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
15 | const subSet = []
16 |
17 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
18 | // Decide whether we need to include current element into the subset or not.
19 | if (combinationIndex & (1 << setElementIndex)) {
20 | subSet.push(originalSet[setElementIndex])
21 | }
22 | }
23 |
24 | // Add current subset to the list of all subsets.
25 | subSets.push(subSet)
26 | }
27 |
28 | return subSets
29 | }
--------------------------------------------------------------------------------
/my-rabbit/src/components/index.js:
--------------------------------------------------------------------------------
1 | // 把component中的所有组件都进行全局化注册
2 | // 通过插件的方式
3 |
4 | import XtxImageView from '@/components/ImageView/index.vue'
5 | import XtxSku from '@/components/XtxSku/index.vue'
6 |
7 | export const componentPlugin ={
8 | install(app) {
9 | // 全局组件注册
10 | app.component('XtxImageView',XtxImageView)
11 | app.component('XtxSku',XtxSku)
12 | },
13 | }
--------------------------------------------------------------------------------
/my-rabbit/src/composables/useCountDown.js:
--------------------------------------------------------------------------------
1 | // 封装倒计时逻辑函数
2 |
3 | import {computed, onUnmounted, ref} from "vue";
4 | import dayjs from "dayjs";
5 |
6 | export const useCountDown = () => {
7 | // 1. 响应式的数据
8 | let timer = null
9 | const time = ref(0)
10 | // 格式化时间 为 xx分xx秒
11 | const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
12 | // 2. 开启倒计时的函数
13 | const start = (currentTime) => {
14 | // 开始倒计时的逻辑
15 | // 核心逻辑的编写:每隔1s就减一
16 | time.value = currentTime
17 | timer = setInterval(() => {
18 | time.value--
19 | }, 1000)
20 | }
21 | // 组件销毁时清除定时器
22 | onUnmounted(() => {
23 | timer && clearInterval(timer)
24 | })
25 |
26 | return{
27 | formatTime,
28 | start
29 | }
30 | };
--------------------------------------------------------------------------------
/my-rabbit/src/directives/index.js:
--------------------------------------------------------------------------------
1 | // 定义懒加载插件
2 |
3 | import {useIntersectionObserver} from "@vueuse/core";
4 |
5 | export const lazyPlugin={
6 | install(app) {
7 | // 懒加载指令逻辑
8 | app.directive('img-lazy',{
9 | mounted(el, bingding) {
10 | // el:指令绑定的那个元素img
11 | // binding:bingding.value 指令等于号后面绑定的表达式的值 图片URL
12 | console.log(el,bingding.value)
13 |
14 | const {stop}=useIntersectionObserver(
15 | el,
16 | ([{isIntersecting}])=>{
17 | console.log(isIntersecting)
18 | if (isIntersecting) {
19 | // 进入视口区域
20 | el.src=bingding.value
21 | // 停止监听
22 | stop()
23 | }
24 | }
25 | )
26 | },
27 | })
28 | },
29 | }
--------------------------------------------------------------------------------
/my-rabbit/src/main.js:
--------------------------------------------------------------------------------
1 | import './assets/main.css'
2 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
3 |
4 | import { createApp } from 'vue'
5 | import { createPinia } from 'pinia'
6 |
7 | import App from './App.vue'
8 | import router from './router'
9 |
10 | // 引入初始化样式文件
11 | import '@/styles/common.scss'
12 | import {lazyPlugin} from "@/directives";
13 |
14 | // 引入全局组件插件
15 | import {componentPlugin} from "@/components"
16 |
17 | const app = createApp(App)
18 |
19 | // 注册持久化插件
20 | const pinia=createPinia()
21 | pinia.use(piniaPluginPersistedstate)
22 | app.use(createPinia())
23 | app.use(pinia)
24 |
25 | app.use(router)
26 | // 引入懒加载指令插件并且注册
27 | app.use(lazyPlugin)
28 | app.use(componentPlugin)
29 |
30 | app.mount('#app')
--------------------------------------------------------------------------------
/my-rabbit/src/router/index.js:
--------------------------------------------------------------------------------
1 | import {createRouter, createWebHistory} from 'vue-router'
2 | import Layout from "@/views/Layout/index.vue";
3 | import Login from "@/views/Login/index.vue";
4 | import Home from "@/views/Home/index.vue";
5 | import Category from "@/views/Category/index.vue";
6 | import SubCategory from "@/views/SubCategory/index.vue";
7 | import Detail from "@/views/Detail/index.vue";
8 | import CartList from "@/views/CartList/index.vue";
9 | import Checkout from "@/views/Checkout/index.vue";
10 | import Pay from "@/views/Pay/index.vue";
11 | import PayBack from "@/views/PayBack/index.vue";
12 | import Member from "@/views/Member/index.vue";
13 | import UserOrder from "@/views/Member/components/UserOrder.vue";
14 | import UserInfo from "@/views/Member/components/UserInfo.vue";
15 |
16 | const router = createRouter({
17 | history: createWebHistory(import.meta.env.BASE_URL),
18 | routes: [
19 | {
20 | path: '/',
21 | component: Layout,
22 | children: [
23 | {
24 | path: '',
25 | component: Home
26 | },
27 | {
28 | path: 'category/:id',
29 | component: Category,
30 | }, {
31 | path: 'category/sub/:id',
32 | component: SubCategory,
33 | }, {
34 | path: 'detail/:id',
35 | component: Detail,
36 | }, {
37 | path: 'cartlist',
38 | component: CartList,
39 | }, {
40 | path: 'checkout',
41 | component: Checkout
42 | }, {
43 | path: 'pay',
44 | component: Pay
45 | }, {
46 | path: 'paycallback',
47 | component: PayBack
48 | }, {
49 | path: 'member',
50 | component: Member,
51 | children: [
52 | {
53 | path: '',
54 | component: UserInfo
55 | },
56 | {
57 | path: 'order',
58 | component: UserOrder
59 | }
60 | ]
61 | }
62 | ]
63 | }, {
64 | path: '/login',
65 | component: Login
66 | }
67 | ],
68 | // 路由滚动行为定制
69 | scrollBehavior() {
70 | return {
71 | top: 0,
72 | }
73 | },
74 | })
75 |
76 | export default router
77 |
--------------------------------------------------------------------------------
/my-rabbit/src/stores/cartStore.js:
--------------------------------------------------------------------------------
1 | import {defineStore} from "pinia";
2 | import {computed, ref} from "vue";
3 | import {useUserStore} from "@/stores/userStore";
4 | import {delCartAPI, findNewCartListAPI, insertCartAPI} from "@/apis/cart";
5 |
6 |
7 | export const useCartStore = defineStore('cart', () => {
8 | const userStore = useUserStore();
9 | const isLogin = computed(() => userStore.userInfo.token);
10 |
11 | // 1.定义state - cartList
12 | const cartList = ref([])
13 |
14 | // 获取最新购物车列表action
15 | const updateNewList = async () => {
16 | // 获取最新购物车列表
17 | const res = await findNewCartListAPI()
18 | // 覆盖本地购物车列表
19 | cartList.value = res.result;
20 | }
21 |
22 | // 2.定义action - addCart
23 | // 添加购物车操作
24 | const addCart = async (goods) => {
25 | const {skuId, count} = goods;
26 | if (isLogin.value) {
27 | // 登陆之后加入购物车
28 | await insertCartAPI({skuId, count})
29 | updateNewList()
30 | } else {
31 | // 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
32 | const item = cartList.value.find(item => goods.skuId === item.skuId);
33 | if (item) {
34 | // 已经添加过 - count +1
35 | item.count++;
36 | } else {
37 | // 没有添加过 - 直接push
38 | cartList.value.push(goods)
39 | }
40 | }
41 | }
42 |
43 | // 3.删除商品
44 | const delCart = async (skuId) => {
45 | if (isLogin.value) {
46 | // 调用接口实现接口购物车中的删除功能
47 | await delCartAPI([skuId])
48 | updateNewList()
49 | } else {
50 | // 1.找到要删除项下标值
51 | // 2.使用数组的过滤方法
52 | const idx = cartList.value.findIndex(item => item.skuId === skuId);
53 | cartList.value.splice(idx, 1);
54 | }
55 | }
56 |
57 | // 计算属性
58 | // 4.总的数量 所有项的count之和
59 | const allCount = computed(() => cartList.value.reduce((a, c) => a + c.count, 0));
60 |
61 | // 5.总价 所有项的count*price之和
62 | const allPrice = computed(() => cartList.value.reduce((a, c) => a + c.count * c.price, 0));
63 |
64 | // 6.单选功能
65 | const singleCheck = (skuId, selected) => {
66 | // 通过skuId 找到要修改的那一项,然后把它的selected修改为传过来的selected
67 | const item = cartList.value.find(item => item.skuId === skuId)
68 | item.selected = selected;
69 | };
70 |
71 | // 7.是否全选
72 | const isAll = computed(() => cartList.value.every(item => item.selected))
73 |
74 | // 8.选中 / 取消选中 所有元素
75 | const allCheck = (selected) => {
76 | // 把cartList中的每一项的selected都设置为当前的全选框状态
77 | cartList.value.forEach(item => item.selected = selected);
78 | };
79 |
80 | // 计算属性
81 | // 9.已选择数量
82 | const selectedCount = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count, 0));
83 |
84 | // 10.已选择商品价格合计
85 | const selectedPrice = computed(() => cartList.value.filter(item => item.selected).reduce((a, c) => a + c.count * c.price, 0));
86 |
87 | // 11 清除购物车
88 | const clearCart=()=>{
89 | cartList.value = [];
90 | }
91 |
92 | return {
93 | cartList,
94 | addCart,
95 | delCart,
96 | allCount,
97 | allPrice,
98 | singleCheck,
99 | isAll,
100 | allCheck,
101 | selectedCount,
102 | selectedPrice,
103 | clearCart,
104 | updateNewList
105 | }
106 | },
107 | {
108 | persist: true, // 持久化到localStorage
109 | });
--------------------------------------------------------------------------------
/my-rabbit/src/stores/categoryStore.js:
--------------------------------------------------------------------------------
1 | import {ref} from 'vue'
2 | import {defineStore} from 'pinia'
3 | import {getCategoryAPI} from "@/apis/layout";
4 |
5 | export const useCategoryStore = defineStore('category', () => {
6 | // 导航列表的数据管理
7 | // state 导航列表数据
8 | const categoryList = ref([])
9 |
10 | // action 获取导航数据的方法
11 | const getCategory = async () => {
12 | const res = await getCategoryAPI();
13 | // console.log(res)
14 | categoryList.value = res.result
15 | }
16 |
17 | return {
18 | categoryList,
19 | getCategory
20 | }
21 | },
22 | {
23 | persist: true, // 持久化到localStorage
24 | })
25 |
--------------------------------------------------------------------------------
/my-rabbit/src/stores/counterStore.js:
--------------------------------------------------------------------------------
1 | import { ref, computed } from 'vue'
2 | import { defineStore } from 'pinia'
3 |
4 | export const useCounterStore = defineStore('counter', () => {
5 | const count = ref(0)
6 | const doubleCount = computed(() => count.value * 2)
7 | function increment() {
8 | count.value++
9 | }
10 |
11 | return { count, doubleCount, increment }
12 | })
13 |
--------------------------------------------------------------------------------
/my-rabbit/src/stores/userStore.js:
--------------------------------------------------------------------------------
1 | import {defineStore} from "pinia";
2 | import {ref} from "vue";
3 | import {loginAPI} from "@/apis/user";
4 | import {useCartStore} from "@/stores/cartStore";
5 | import {mergeCartAPI} from "@/apis/cart";
6 |
7 |
8 | export const useUserStore = defineStore('user', () => {
9 | const cartStore = useCartStore();
10 |
11 | // 1.定义管理用户数据的state
12 | const userInfo = ref({})
13 |
14 | // 2.定义获取接口数据的action函数
15 | const getUserInfo = async ({account, password}) => {
16 | const res = await loginAPI({account, password});
17 | userInfo.value = res.result
18 |
19 | // 合并购物车的操作
20 | mergeCartAPI(cartStore.cartList.map(item=>{
21 | return{
22 | skuId:item.skuId,
23 | selected:item.selected,
24 | count:item.count
25 | }
26 | }))
27 |
28 | cartStore.updateNewList();
29 | }
30 |
31 | // 4.退出时清除用户信息
32 | const clearUserInfo=()=>{
33 | userInfo.value = {};
34 | // 执行清除购物车的action
35 | cartStore.clearCart()
36 | }
37 |
38 | // 3.以对象的格式把state和action return
39 | return {
40 | userInfo,
41 | getUserInfo,
42 | clearUserInfo
43 | }
44 | },
45 | {
46 | persist: true, // 持久化到localStorage
47 | })
--------------------------------------------------------------------------------
/my-rabbit/src/styles/common.scss:
--------------------------------------------------------------------------------
1 | // 重置样式
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
6 | html {
7 | height: 100%;
8 | font-size: 14px;
9 | }
10 | body {
11 | height: 100%;
12 | color: #333;
13 | min-width: 1240px;
14 | font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
15 | 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
16 | sans-serif;
17 | }
18 | body,
19 | ul,
20 | h1,
21 | h3,
22 | h4,
23 | p,
24 | dl,
25 | dd {
26 | padding: 0;
27 | margin: 0;
28 | }
29 | a {
30 | text-decoration: none;
31 | color: #333;
32 | outline: none;
33 | }
34 | i {
35 | font-style: normal;
36 | }
37 | input[type='text'],
38 | input[type='search'],
39 | input[type='password'],
40 | input[type='checkbox'] {
41 | padding: 0;
42 | outline: none;
43 | border: none;
44 | -webkit-appearance: none;
45 | &::placeholder {
46 | color: #ccc;
47 | }
48 | }
49 | img {
50 | max-width: 100%;
51 | max-height: 100%;
52 | vertical-align: middle;
53 | background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
54 | }
55 | ul {
56 | list-style: none;
57 | }
58 |
59 | #app {
60 | background: #f5f5f5;
61 | user-select: none;
62 | }
63 |
64 | .container {
65 | width: 1240px;
66 | margin: 0 auto;
67 | position: relative;
68 | }
69 | .ellipsis {
70 | white-space: nowrap;
71 | text-overflow: ellipsis;
72 | overflow: hidden;
73 | }
74 |
75 | .ellipsis-2 {
76 | word-break: break-all;
77 | text-overflow: ellipsis;
78 | display: -webkit-box;
79 | -webkit-box-orient: vertical;
80 | -webkit-line-clamp: 2;
81 | overflow: hidden;
82 | }
83 |
84 | .fl {
85 | float: left;
86 | }
87 |
88 | .fr {
89 | float: right;
90 | }
91 |
92 | .clearfix:after {
93 | content: '.';
94 | display: block;
95 | visibility: hidden;
96 | height: 0;
97 | line-height: 0;
98 | clear: both;
99 | }
100 |
101 | // reset element
102 | .el-breadcrumb__inner.is-link {
103 | font-weight: 400 !important;
104 | }
105 |
--------------------------------------------------------------------------------
/my-rabbit/src/styles/element/index.scss:
--------------------------------------------------------------------------------
1 | /* 只需要重写你需要的即可 */
2 | @forward 'element-plus/theme-chalk/src/common/var.scss' with (
3 | $colors: (
4 | 'primary': (
5 | // 主色
6 | 'base': #27ba9b,
7 | ),
8 | 'success': (
9 | // 成功色
10 | 'base': #1dc779,
11 | ),
12 | 'warning': (
13 | // 警告色
14 | 'base': #ffb302,
15 | ),
16 | 'danger': (
17 | // 危险色
18 | 'base': #e26237,
19 | ),
20 | 'error': (
21 | // 错误色
22 | 'base': #cf4444,
23 | ),
24 | )
25 | );
--------------------------------------------------------------------------------
/my-rabbit/src/styles/var.scss:
--------------------------------------------------------------------------------
1 | $xtxColor: #27ba9b;
2 | $helpColor: #e26237;
3 | $sucColor: #1dc779;
4 | $warnColor: #ffb302;
5 | $priceColor: #cf4444;
--------------------------------------------------------------------------------
/my-rabbit/src/utils/http.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { ElMessage } from 'element-plus'
3 | import 'element-plus/theme-chalk/el-message.css'
4 | import {useUserStore} from "@/stores/userStore";
5 | import router from "@/router";
6 |
7 | // 创建axios实例
8 | const httpInstance = axios.create({
9 | baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
10 | timeout: 5000
11 | })
12 |
13 | // axios请求拦截器
14 | httpInstance.interceptors.request.use(config => {
15 | // 1.从pinia获取token数据
16 | const userStore=useUserStore()
17 |
18 | // 2.安装后端的要求拼接token数据
19 | const token=userStore.userInfo.token
20 | if (token) {
21 | config.headers.Authorization=`Bearer ${token}`
22 | }
23 |
24 | return config;
25 | }, e => Promise.reject(e))
26 |
27 |
28 | // axios响应式拦截器
29 | httpInstance.interceptors.response.use(res => res.data, e => {
30 | const userStore = useUserStore();
31 | // 统一错误提示
32 | ElMessage({
33 | type:'warning',
34 | message:e.response.data.message
35 | })
36 |
37 | // 401 token失效处理
38 | if (e.response.state === 401) {
39 | // 1.清除本地用户数据
40 | userStore.clearUserInfo()
41 | // 2.跳转到登录页
42 | router.push('/login')
43 | }
44 | return Promise.reject(e);
45 | })
46 |
47 |
48 | export default httpInstance
--------------------------------------------------------------------------------
/my-rabbit/src/views/Category/composables/useBanner.js:
--------------------------------------------------------------------------------
1 | // 封装banner轮播图业务代码
2 |
3 | import {onMounted, ref} from "vue";
4 | import {getBannerAPI} from "@/apis/home";
5 |
6 | export function useBanner() {
7 | const bannerList = ref([])
8 | const getBanner = async () => {
9 | const res = await getBannerAPI({
10 | distributionSite: '2'
11 | });
12 | console.log(res)
13 | bannerList.value = res.result
14 | }
15 |
16 | onMounted(() => {
17 | getBanner()
18 | })
19 | return {
20 | bannerList
21 | }
22 | }
--------------------------------------------------------------------------------
/my-rabbit/src/views/Category/composables/useCategory.js:
--------------------------------------------------------------------------------
1 | // 封装分类数据业务相关代码
2 |
3 | import {onBeforeRouteUpdate, useRoute} from "vue-router";
4 | import {onMounted, ref} from "vue";
5 | import {getCategoryAPI} from "@/apis/category";
6 |
7 | export function useCategory() {
8 | // 获取分类数据
9 | const route = useRoute()
10 | const categoryData = ref({})
11 | const getCategory = async (id = route.params.id) => {
12 | const res = await getCategoryAPI(id);
13 | console.log(id,res)
14 | categoryData.value = res.result
15 | }
16 | onMounted(() => getCategory())
17 |
18 | // 问题:存在路由缓存问题(路由只有参数变化时,会复用组件实例)
19 | // 目标:路由参数变化的时候 可以把分类数据接口重新发送
20 | onBeforeRouteUpdate((to) => {
21 | console.log('路由变化了', to)
22 | // 存在问题:使用最新的路由参数请求最新的分类数据
23 | getCategory(to.params.id)
24 | })
25 |
26 | // 两种方案都可以解决路由缓存问题,如何选择呢?
27 | // 在意性能问题,选择onBeforeUpdate,精细化控制
28 | // 不在意性能问题,选择key,简单粗暴
29 |
30 | return {
31 | categoryData
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/my-rabbit/src/views/Category/index.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 首页
17 | {{ categoryData.name }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
全部分类
33 |
34 |
35 |
36 |
37 | {{ i.name }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
- {{ item.name }}-
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Detail/components/DetailHot.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
{{ title }}
39 |
40 |
41 |
42 | {{ item.name }}
43 | {{ item.desc }}
44 | ¥{{ item.price }}
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/GoodsItem.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | {{ good.name }}
14 | {{ good.desc }}
15 | ¥{{ good.price }}
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/HomeBanner.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/HomeHot.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ item.title }}
25 | {{ item.alt }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/HomeNew.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ item.name }}
28 | ¥{{ item.price }}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/HomePanel.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ title }}{{ subTitle }}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/componets/HomeProduct.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ cate.name }}馆
25 | {{ cate.saleInfo }}
26 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Home/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Layout/components/LayoutFixed.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Layout/components/LayoutHeader.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Layout/components/LayoutNav.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Member/components/UserInfo.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
40 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/Member/index.vue:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
我的账户
9 |
10 | 个人中心
11 |
12 |
交易管理
13 |
14 | 我的订单
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/PayBack/index.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
支付{{ $route.query.payResult === 'true' ? '成功' : '失败' }}
27 |
我们将尽快为您发货,收货期间请保持手机畅通
28 |
支付方式:支付宝
29 |
支付金额:¥{{ orderInfo.payMoney?.toFixed(2) }}
30 |
31 | 查看订单
32 | 进入首页
33 |
34 |
35 |
36 | 温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/my-rabbit/src/views/SubCategory/index.vue:
--------------------------------------------------------------------------------
1 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 首页
64 | {{ categoryData.parentName }}
65 |
66 | {{ categoryData.name }}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/my-rabbit/vite.config.js:
--------------------------------------------------------------------------------
1 | import {fileURLToPath, URL} from 'node:url'
2 |
3 | import {defineConfig} from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 | import AutoImport from 'unplugin-auto-import/vite'
6 | import Components from 'unplugin-vue-components/vite'
7 | import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
8 | // 导入对应包
9 | import ElementPlus from 'unplugin-element-plus/vite'
10 |
11 | // https://vitejs.dev/config/
12 | export default defineConfig({
13 | // 配置插件
14 | plugins: [
15 | vue(),
16 | AutoImport({
17 | resolvers: [ElementPlusResolver()],
18 | }),
19 | Components({
20 | resolvers: [
21 | // 1.配置elementPlus采用scss样式配色系统
22 | ElementPlusResolver({importStyle: "sass"})
23 | ],
24 | }),
25 | // 按需定制主题配置
26 | ElementPlus({
27 | useSource: true,
28 | }),
29 | ],
30 | resolve: {
31 | alias: {
32 | '@': fileURLToPath(new URL('./src', import.meta.url))
33 | }
34 | },
35 | css: {
36 | preprocessorOptions: {
37 | scss: {
38 | // 2.自动导入定制化样式文件进行样式覆盖
39 | additionalData: `
40 | @use "@/styles/element/index.scss" as *;
41 | @use "@/styles/var.scss" as *;
42 | `,
43 | }
44 | }
45 | }
46 | })
47 |
--------------------------------------------------------------------------------
/vue-pinia/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/vue-pinia/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/vue-pinia/README.md:
--------------------------------------------------------------------------------
1 | # vue-pinia
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
--------------------------------------------------------------------------------
/vue-pinia/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/vue-pinia/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-pinia",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "axios": "^1.4.0",
12 | "pinia": "^2.1.3",
13 | "vue": "^3.3.2"
14 | },
15 | "devDependencies": {
16 | "@vitejs/plugin-vue": "^4.2.3",
17 | "vite": "^4.3.5"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/vue-pinia/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/vue-pinia/public/favicon.ico
--------------------------------------------------------------------------------
/vue-pinia/src/App.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 | {{count}}
36 | {{doubleCount}}
37 |
38 |
41 |
--------------------------------------------------------------------------------
/vue-pinia/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | font-weight: normal;
59 | }
60 |
61 | body {
62 | min-height: 100vh;
63 | color: var(--color-text);
64 | background: var(--color-background);
65 | transition: color 0.5s, background-color 0.5s;
66 | line-height: 1.6;
67 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
68 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
69 | font-size: 15px;
70 | text-rendering: optimizeLegibility;
71 | -webkit-font-smoothing: antialiased;
72 | -moz-osx-font-smoothing: grayscale;
73 | }
74 |
--------------------------------------------------------------------------------
/vue-pinia/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/vue-pinia/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 |
3 | #app {
4 | max-width: 1280px;
5 | margin: 0 auto;
6 | padding: 2rem;
7 |
8 | font-weight: normal;
9 | }
10 |
11 | a,
12 | .green {
13 | text-decoration: none;
14 | color: hsla(160, 100%, 37%, 1);
15 | transition: 0.4s;
16 | }
17 |
18 | @media (hover: hover) {
19 | a:hover {
20 | background-color: hsla(160, 100%, 37%, 0.2);
21 | }
22 | }
23 |
24 | @media (min-width: 1024px) {
25 | body {
26 | display: flex;
27 | place-items: center;
28 | }
29 |
30 | #app {
31 | display: grid;
32 | grid-template-columns: 1fr 1fr;
33 | padding: 0 2rem;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
{{ msg }}
13 |
14 | You’ve successfully created a project with
15 | Vite +
16 | Vue 3 .
17 |
18 |
19 |
20 |
21 |
44 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/TheWelcome.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Documentation
16 |
17 | Vue’s
18 | official documentation
19 | provides you with all information you need to get started.
20 |
21 |
22 |
23 |
24 |
25 |
26 | Tooling
27 |
28 | This project is served and bundled with
29 | Vite . The
30 | recommended IDE setup is
31 | VSCode +
32 | Volar . If
33 | you need to test your components and web pages, check out
34 | Cypress and
35 | Cypress Component Testing .
36 |
37 |
38 |
39 | More instructions are available in README.md
.
40 |
41 |
42 |
43 |
44 |
45 |
46 | Ecosystem
47 |
48 | Get official tools and libraries for your project:
49 | Pinia ,
50 | Vue Router ,
51 | Vue Test Utils , and
52 | Vue Dev Tools . If
53 | you need more resources, we suggest paying
54 | Awesome Vue
55 | a visit.
56 |
57 |
58 |
59 |
60 |
61 |
62 | Community
63 |
64 | Got stuck? Ask your question on
65 | Vue Land , our official
66 | Discord server, or
67 | StackOverflow . You should also subscribe to
70 | our mailing list and follow
71 | the official
72 | @vuejs
73 | twitter account for latest news in the Vue world.
74 |
75 |
76 |
77 |
78 |
79 |
80 | Support Vue
81 |
82 | As an independent project, Vue relies on community backing for its sustainability. You can help
83 | us by
84 | becoming a sponsor .
85 |
86 |
87 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/WelcomeItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
88 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/icons/IconCommunity.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/icons/IconDocumentation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/icons/IconEcosystem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue-pinia/src/components/icons/IconTooling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/vue-pinia/src/main.js:
--------------------------------------------------------------------------------
1 | import './assets/main.css'
2 |
3 | import { createApp } from 'vue'
4 | import App from './App.vue'
5 |
6 | // 1.导入createPina
7 | import {createPinia} from "pinia";
8 |
9 | // 2.执行方法得到实例
10 | const pinia=createPinia()
11 |
12 | // 3.把pinia实例加入到app应用中
13 | createApp(App).use(pinia).mount('#app')
14 |
--------------------------------------------------------------------------------
/vue-pinia/src/stores/counter.js:
--------------------------------------------------------------------------------
1 | import {defineStore} from "pinia";
2 | import {computed, ref} from "vue";
3 | import axios from "axios";
4 | const API_URL = 'http://geek.itheima.net/v1_0/channels'
5 | export const useCounterStore = defineStore('counter', () => {
6 | // 定义数据(state)
7 | const count = ref(0);
8 |
9 | // 定义修改数据的方法(action 同步+异步)
10 | const increment = () => {
11 | count.value++
12 | }
13 |
14 | // getter定义
15 | const doubleCount=computed(()=>count.value*2)
16 |
17 | // 定义异步action
18 | const list = ref([]);
19 | const getList=async ()=>{
20 | const res = await axios.get(API_URL);
21 | console.log(res)
22 | list.value=res.data.data.channels
23 | }
24 |
25 | // 以对象的方式return供组件使用
26 | return {
27 | count,
28 | increment,
29 | doubleCount,
30 | getList,
31 | list
32 | }
33 | });
--------------------------------------------------------------------------------
/vue-pinia/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | '@': fileURLToPath(new URL('./src', import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------
/vue3-project/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | root: true,
4 | 'extends': [
5 | 'plugin:vue/vue3-essential',
6 | 'eslint:recommended'
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 'latest'
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/vue3-project/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/vue3-project/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/vue3-project/README.md:
--------------------------------------------------------------------------------
1 | # vue3-project
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
31 | ### Lint with [ESLint](https://eslint.org/)
32 |
33 | ```sh
34 | npm run lint
35 | ```
36 |
--------------------------------------------------------------------------------
/vue3-project/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/vue3-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-project",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
10 | },
11 | "dependencies": {
12 | "axios": "^1.4.0",
13 | "sass": "^1.62.1",
14 | "vue": "^3.3.2"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-vue": "^4.2.3",
18 | "eslint": "^8.39.0",
19 | "eslint-plugin-vue": "^9.11.0",
20 | "vite": "^4.3.5"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/vue3-project/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuhongfan/vue-rabbit/02f0bc7c3782d31be841681dc28291a282cebf6e/vue3-project/public/favicon.ico
--------------------------------------------------------------------------------
/vue3-project/src/App-SonToFather.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
父组件App
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/vue3-project/src/App-computed.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 | 原始响应式数据- {{list}}
23 | 计算属性响应式数据- {{computedList}}
24 |
--------------------------------------------------------------------------------
/vue3-project/src/App-fatherToSon.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
父组件App
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/vue3-project/src/App-life.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/vue3-project/src/App-reactive.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {{state.count}}
21 |
22 |
--------------------------------------------------------------------------------
/vue3-project/src/App-ref.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 | {{count}}
33 |
34 |
--------------------------------------------------------------------------------
/vue3-project/src/App-ref2.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 | 我是dom标签h1
26 |
27 |
--------------------------------------------------------------------------------
/vue3-project/src/App-setup.vue:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
40 | {{message}}
41 |
42 | log
43 |
44 |
--------------------------------------------------------------------------------
/vue3-project/src/App-watch.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 | {{count}}
19 |
20 |
--------------------------------------------------------------------------------
/vue3-project/src/App-watch2.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 | {{count}}
23 |
24 |
25 |
26 | {{name}}
27 |
28 |
--------------------------------------------------------------------------------
/vue3-project/src/App-watch3-immediate.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {{count}}
21 |
22 |
--------------------------------------------------------------------------------
/vue3-project/src/App-watch4-deep.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {{state.count}}
21 | 通过count修改
22 |
23 |
--------------------------------------------------------------------------------
/vue3-project/src/App-watch5.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
当前name---{{state.name}}
36 |
当前age--{{state.age}}
37 |
38 | 修改name
39 | 修改age
40 |
41 |
42 |
--------------------------------------------------------------------------------
/vue3-project/src/App.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue3-project/src/Sku/power-set.js:
--------------------------------------------------------------------------------
1 | // 幂集算法
2 | export default function bwPowerSet (originalSet) {
3 | const subSets = []
4 |
5 | // We will have 2^n possible combinations (where n is a length of original set).
6 | // It is because for every element of original set we will decide whether to include
7 | // it or not (2 options for each set element).
8 | const numberOfCombinations = 2 ** originalSet.length
9 |
10 | // Each number in binary representation in a range from 0 to 2^n does exactly what we need:
11 | // it shows by its bits (0 or 1) whether to include related element from the set or not.
12 | // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
13 | // include only "2" to the current set.
14 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
15 | const subSet = []
16 |
17 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
18 | // Decide whether we need to include current element into the subset or not.
19 | if (combinationIndex & (1 << setElementIndex)) {
20 | subSet.push(originalSet[setElementIndex])
21 | }
22 | }
23 |
24 | // Add current subset to the list of all subsets.
25 | subSets.push(subSet)
26 | }
27 |
28 | return subSets
29 | }
--------------------------------------------------------------------------------
/vue3-project/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | font-weight: normal;
59 | }
60 |
61 | body {
62 | min-height: 100vh;
63 | color: var(--color-text);
64 | background: var(--color-background);
65 | transition: color 0.5s, background-color 0.5s;
66 | line-height: 1.6;
67 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
68 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
69 | font-size: 15px;
70 | text-rendering: optimizeLegibility;
71 | -webkit-font-smoothing: antialiased;
72 | -moz-osx-font-smoothing: grayscale;
73 | }
74 |
--------------------------------------------------------------------------------
/vue3-project/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/vue3-project/src/assets/main.css:
--------------------------------------------------------------------------------
1 | @import './base.css';
2 |
3 | #app {
4 | max-width: 1280px;
5 | margin: 0 auto;
6 | padding: 2rem;
7 |
8 | font-weight: normal;
9 | }
10 |
11 | a,
12 | .green {
13 | text-decoration: none;
14 | color: hsla(160, 100%, 37%, 1);
15 | transition: 0.4s;
16 | }
17 |
18 | @media (hover: hover) {
19 | a:hover {
20 | background-color: hsla(160, 100%, 37%, 0.2);
21 | }
22 | }
23 |
24 | @media (min-width: 1024px) {
25 | body {
26 | display: flex;
27 | place-items: center;
28 | }
29 |
30 | #app {
31 | display: grid;
32 | grid-template-columns: 1fr 1fr;
33 | padding: 0 2rem;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/vue3-project/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
{{ msg }}
13 |
14 | You’ve successfully created a project with
15 | Vite +
16 | Vue 3 .
17 |
18 |
19 |
20 |
21 |
44 |
--------------------------------------------------------------------------------
/vue3-project/src/components/TheWelcome.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Documentation
16 |
17 | Vue’s
18 | official documentation
19 | provides you with all information you need to get started.
20 |
21 |
22 |
23 |
24 |
25 |
26 | Tooling
27 |
28 | This project is served and bundled with
29 | Vite . The
30 | recommended IDE setup is
31 | VSCode +
32 | Volar . If
33 | you need to test your components and web pages, check out
34 | Cypress and
35 | Cypress Component Testing .
36 |
37 |
38 |
39 | More instructions are available in README.md
.
40 |
41 |
42 |
43 |
44 |
45 |
46 | Ecosystem
47 |
48 | Get official tools and libraries for your project:
49 | Pinia ,
50 | Vue Router ,
51 | Vue Test Utils , and
52 | Vue Dev Tools . If
53 | you need more resources, we suggest paying
54 | Awesome Vue
55 | a visit.
56 |
57 |
58 |
59 |
60 |
61 |
62 | Community
63 |
64 | Got stuck? Ask your question on
65 | Vue Land , our official
66 | Discord server, or
67 | StackOverflow . You should also subscribe to
70 | our mailing list and follow
71 | the official
72 | @vuejs
73 | twitter account for latest news in the Vue world.
74 |
75 |
76 |
77 |
78 |
79 |
80 | Support Vue
81 |
82 | As an independent project, Vue relies on community backing for its sustainability. You can help
83 | us by
84 | becoming a sponsor .
85 |
86 |
87 |
--------------------------------------------------------------------------------
/vue3-project/src/components/WelcomeItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
88 |
--------------------------------------------------------------------------------
/vue3-project/src/components/icons/IconCommunity.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue3-project/src/components/icons/IconDocumentation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue3-project/src/components/icons/IconEcosystem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue3-project/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vue3-project/src/components/icons/IconTooling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/vue3-project/src/main.js:
--------------------------------------------------------------------------------
1 | import './assets/main.css'
2 |
3 | import { createApp } from 'vue'
4 | import App from './App.vue'
5 |
6 | // createApp == new Vue() 创建一个应用实例对象
7 | // 1.以App作为参数生成一个应用实例对象
8 | // 2.挂在到id为app的节点上
9 | createApp(App).mount('#app')
10 |
--------------------------------------------------------------------------------
/vue3-project/src/room-msg-comment.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 | 底层组件
18 |
来自顶层组件中的数据为:{{roomData}}
19 |
来自顶层组件的响应式数据:{{countData}}
20 |
21 |
22 | 修改顶层数据
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/vue3-project/src/room-msg-item.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/vue3-project/src/room-page.vue:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/vue3-project/src/son-com-SonToFather.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
子组件SON
13 | 触发自定义事件
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/vue3-project/src/son-com-fatherToSon.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
子组件SON
14 |
15 | 父组件传入的数据--{{message}}---{{count}}
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/vue3-project/src/son-com.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
子组件SON
13 | 触发自定义事件
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/vue3-project/src/test-com.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 我是test组件
19 |
20 |
--------------------------------------------------------------------------------
/vue3-project/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | // 基于Vite的配置
8 | export default defineConfig({
9 | plugins: [vue()],
10 | resolve: {
11 | alias: {
12 | '@': fileURLToPath(new URL('./src', import.meta.url))
13 | }
14 | }
15 | })
16 |
--------------------------------------------------------------------------------