├── .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 | 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 | 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 | 122 | ``` 123 | # storeToRefs保持响应式解构 124 | > 直接基于store进行解构赋值,响应式数据(state和getter)会丢失响应式特性,使用storeToRefs辅助保持响应式 125 | 126 | ```vue 127 | 137 | 138 | 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 | 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 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Detail/components/DetailHot.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/GoodsItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 19 | 20 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/HomeBanner.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/HomeHot.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/HomeNew.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/HomePanel.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 31 | 32 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/components/HomeProduct.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Home/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Layout/components/LayoutFixed.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Layout/components/LayoutHeader.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Layout/components/LayoutHeaderUl.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Layout/components/LayoutNav.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Member/components/UserInfo.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 56 | 57 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Member/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/Pay/PayBack.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 42 | 43 | -------------------------------------------------------------------------------- /03-code/vue-rabbit/src/views/SubCategory/index.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 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 | 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 | 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 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Detail/components/DetailHot.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/GoodsItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | 19 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/HomeBanner.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/HomeHot.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | 32 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/HomeNew.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/HomePanel.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 36 | 37 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/componets/HomeProduct.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Home/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 28 | 29 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Layout/components/LayoutFixed.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Layout/components/LayoutHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Layout/components/LayoutNav.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | 28 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Member/components/UserInfo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 51 | 52 | -------------------------------------------------------------------------------- /my-rabbit/src/views/Member/index.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /my-rabbit/src/views/PayBack/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 42 | 43 | -------------------------------------------------------------------------------- /my-rabbit/src/views/SubCategory/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 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 | -------------------------------------------------------------------------------- /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 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /vue-pinia/src/components/TheWelcome.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 87 | -------------------------------------------------------------------------------- /vue-pinia/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /vue-pinia/src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue-pinia/src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue-pinia/src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue-pinia/src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue-pinia/src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /vue3-project/src/App-computed.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vue3-project/src/App-fatherToSon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /vue3-project/src/App-life.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /vue3-project/src/App-reactive.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /vue3-project/src/App-ref.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /vue3-project/src/App-ref2.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | -------------------------------------------------------------------------------- /vue3-project/src/App-setup.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | -------------------------------------------------------------------------------- /vue3-project/src/App-watch.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /vue3-project/src/App-watch2.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /vue3-project/src/App-watch3-immediate.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /vue3-project/src/App-watch4-deep.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /vue3-project/src/App-watch5.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | -------------------------------------------------------------------------------- /vue3-project/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /vue3-project/src/components/TheWelcome.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 87 | -------------------------------------------------------------------------------- /vue3-project/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /vue3-project/src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-project/src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-project/src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-project/src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vue3-project/src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 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 | 25 | 26 | -------------------------------------------------------------------------------- /vue3-project/src/room-msg-item.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /vue3-project/src/room-page.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 42 | 43 | -------------------------------------------------------------------------------- /vue3-project/src/son-com-SonToFather.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /vue3-project/src/son-com-fatherToSon.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /vue3-project/src/son-com.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /vue3-project/src/test-com.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 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 | --------------------------------------------------------------------------------