当前位置: 首页 > news >正文

为何网站需改版本地wordpress建站

为何网站需改版,本地wordpress建站,哈尔滨h5制作,丹灶网站制作目录 项目学习 初始化项目 建立项目 引入elementplus elementPlus主题设置 配置axios 路由 引入静态资源 自动导入scss变量 Layout页 组件结构快速搭建 字体图标渲染 一级导航渲染 吸顶导航交互实现 Pinia优化重复请求 Home页 分类实现 banner轮播图 …目录 项目学习  初始化项目  建立项目  引入elementplus elementPlus主题设置  配置axios 路由  引入静态资源 自动导入scss变量 Layout页 组件结构快速搭建 字体图标渲染 一级导航渲染  吸顶导航交互实现  Pinia优化重复请求 Home页  分类实现 banner轮播图  新鲜好物实现 人气推荐实现  懒加载指令实现  产品列表实现 GoodsItem组件封装  一级分类页  导航栏 分类Banner渲染  导航激活  分类列表渲染  路由缓存问题  业务逻辑的函数拆分  二级分类页  实现 定制路由的滚动行为 商品详情 路由配置 数据渲染 热榜区域  适配热榜类型  图片预览组件封装 sku插件 登录页 表单 登录实现  登录失败的提示 Pinia管理用户 Pinia持久化存储 登录和非登录状态 请求拦截器携带token 退出登录 token失效处理 购物车 本地加入购物车 头部购物车 购物车页面 订单页  支付页  会员中心  基本页面 个人中心信息渲染 我的订单  分页 项目学习  视频黑马程序员-小兔鲜 黑马资料文档 vue调试vue-devtools DEVHBuilder 初始化项目  建立项目  安装node 安装过node的需要查看node的版本是否大于或等于15否则报错 Error: vitejs/plugin-vue requires vue (3.2.13) or vue/compiler-sfc to be present in the dependency tree. windows下更新node需要重新安装即覆盖原有的node。  查看镜像地址  npm config get registry 设置淘宝镜像地址  npm config set registry https://registry.npm.taobao.org/ 安装脚手架  npm i -g vue/cli 下载create-vue npm install create-vue3.7.3 注意版本或者安装最新版。 初始化项目 安装依赖 npm install 配置别名路径 在根目录下新建jsconfig.json {compilerOptions : {baseUrl : ./,paths : {/*:[src/*]}} } 引入elementplus 官网 // 引入插件 import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolversexport default defineConfig({plugins: [// 配置插件AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),] }) vit.config.js script setup import { RouterLink, RouterView } from vue-router import HelloWorld from ./components/HelloWorld.vue /scripttemplateheaderimg altVue logo classlogo src/assets/logo.svg width125 height125 /div classwrapperHelloWorld msgYou did it! /navRouterLink to/Home/RouterLinkRouterLink to/aboutAbout/RouterLink/nav/div/headerel-button typeprimaryPrimary/el-buttonRouterView / /templatestyle scoped header {line-height: 1.5;max-height: 100vh; }.logo {display: block;margin: 0 auto 2rem; }nav {width: 100%;font-size: 12px;text-align: center;margin-top: 2rem; }nav a.router-link-exact-active {color: var(--color-text); }nav a.router-link-exact-active:hover {background-color: transparent; }nav a {display: inline-block;padding: 0 1rem;border-left: 1px solid var(--color-border); }nav a:first-of-type {border: 0; }media (min-width: 1024px) {header {display: flex;place-items: center;padding-right: calc(var(--section-gap) / 2);}.logo {margin: 0 2rem 0 0;}header .wrapper {display: flex;place-items: flex-start;flex-wrap: wrap;}nav {text-align: left;margin-left: -1rem;font-size: 1rem;padding: 1rem 0;margin-top: 1rem;} } /style测试以下elementplus是否生效。 在app.vue的template下引入 el-button typeprimaryPrimary/el-button 重启运行。 测试完成。 elementPlus主题设置  安装sass npm i sass -D 样式文件 styles/element/index.scss /* 只需要重写你需要的即可 */ forward element-plus/theme-chalk/src/common/var.scss with ($colors: (primary: (// 主色base: #27ba9b,),success: (// 成功色base: #1dc779,),warning: (// 警告色base: #ffb302,),danger: (// 危险色base: #e26237,),error: (// 错误色base: #cf4444,),) ) 配置 vite.config.js import { fileURLToPath, URL } from node:urlimport { defineConfig } from vite import vue from vitejs/plugin-vue //elementplus按需导入 import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers// https://vitejs.dev/config/ export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [//配置elementPlus采用sass样式配色系统ElementPlusResolver({importStyle:sass}),],}),],resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))}},css: {preprocessorOptions: {scss: {// 自动导入定制化样式文件进行样式覆盖additionalData: use /styles/element/index.scss as *;,}},} })测试 重启运行 配置axios 安装axios npm i axios 配置  utils/http.js import axios from axios// 创建axios实例 let http axios.create({baseURL: http://pcapi-xiaotuxian-front-devtest.itheima.net,timeout: 5000 })// axios请求拦截器 http.interceptors.request.use(config {return config }, e Promise.reject(e))// axios响应式拦截器 http.interceptors.response.use(res res.data, e {return Promise.reject(e) })export default http 测试接口 api/testAPI.js下 import http from /utils/httpexport function getCategory () {return http({url: home/category/head}) } main.js下新增 //测试接口 import {getCategory} from /api/testAPI getCategory().then(res {console.log(res) }) 重启运行 路由  views/Login/index.vue template我是登录页 /template views/Layout/index.vue template我是首页 /template views/Home/index.vue template我是home页 /template views/Category/index.vue template我是分类页 /template router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import Login from /views/login/index.vue import Layout from /views/layout/index.vue import Home from /views/home/index.vue import Category from /views/category/index.vueconst router createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: /,component: Layout,children: [{path: ,component: Home},{path: category,component: Category}]},{path: /login,component: Login}] })export default router重启输入对应url可以看到不同页面。 引入静态资源 图片资源 - 把 images 文件夹放到 assets 目录下样式资源 - 把 common.scss 文件放到 styles 目录下 链接https://pan.baidu.com/s/15PoJhfpPDzf_WTsakO0jmg?pwd3kgh  提取码3kgh 自动导入scss变量 var.scss $xtxColor: #27ba9b; $helpColor: #e26237; $sucColor: #1dc779; $warnColor: #ffb302; $priceColor: #cf4444; 修改main.js css: {preprocessorOptions: {scss: {// 自动导入scss文件additionalData: use /styles/element/index.scss as *;use /styles/var.scss as *;,}} } 测试  修改App.vue script setup import { RouterLink, RouterView } from vue-router import HelloWorld from ./components/HelloWorld.vue /scripttemplateRouterView /div classtesttest/div /templatestyle scoped langscss header {line-height: 1.5;max-height: 100vh; } .test {color: $priceColor; } .logo {display: block;margin: 0 auto 2rem; }nav {width: 100%;font-size: 12px;text-align: center;margin-top: 2rem; }nav a.router-link-exact-active {color: var(--color-text); }nav a.router-link-exact-active:hover {background-color: transparent; }nav a {display: inline-block;padding: 0 1rem;border-left: 1px solid var(--color-border); }nav a:first-of-type {border: 0; }media (min-width: 1024px) {header {display: flex;place-items: center;padding-right: calc(var(--section-gap) / 2);}.logo {margin: 0 2rem 0 0;}header .wrapper {display: flex;place-items: flex-start;flex-wrap: wrap;}nav {text-align: left;margin-left: -1rem;font-size: 1rem;padding: 1rem 0;margin-top: 1rem;} } /style测试成功还原代码。 Layout页 组件结构快速搭建 LayoutNav.vue script setup/scripttemplatenav classapp-topnavdiv classcontainerultemplate v-iftruelia hrefjavascript:;i classiconfont icon-user/i周杰伦/a/liliel-popconfirm title确认退出吗? confirm-button-text确认 cancel-button-text取消template #referencea hrefjavascript:;退出登录/a/template/el-popconfirm/lilia hrefjavascript:;我的订单/a/lilia hrefjavascript:;会员中心/a/li/templatetemplate v-elselia hrefjavascript:;请先登录/a/lilia hrefjavascript:;帮助中心/a/lilia hrefjavascript:;关于我们/a/li/template/ul/div/nav /templatestyle scoped langscss .app-topnav {background: #333;ul {display: flex;height: 53px;justify-content: flex-end;align-items: center;li {a {padding: 0 15px;color: #cdcdcd;line-height: 1;display: inline-block;i {font-size: 14px;margin-right: 2px;}:hover {color: $xtxColor;}}~li {a {border-left: 2px solid #666;}}}} } /style LayoutHeader.vue script setup/scripttemplateheader classapp-headerdiv classcontainerh1 classlogoRouterLink to/小兔鲜/RouterLink/h1ul classapp-header-navli classhomeRouterLink to/首页/RouterLink/lili RouterLink to/居家/RouterLink /lili RouterLink to/美食/RouterLink /lili RouterLink to/服饰/RouterLink /li/uldiv classsearchi classiconfont icon-search/iinput typetext placeholder搜一搜/div!-- 头部购物车 --/div/header /templatestyle scoped langscss .app-header {background: #fff;.container {display: flex;align-items: center;}.logo {width: 200px;a {display: block;height: 132px;width: 100%;text-indent: -9999px;background: url(/assets/images/logo.png) no-repeat center 18px / contain;}}.app-header-nav {width: 820px;display: flex;padding-left: 40px;position: relative;z-index: 998;li {margin-right: 40px;width: 38px;text-align: center;a {font-size: 16px;line-height: 32px;height: 32px;display: inline-block;:hover {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}}.search {width: 170px;height: 32px;position: relative;border-bottom: 1px solid #e7e7e7;line-height: 32px;.icon-search {font-size: 18px;margin-left: 5px;}input {width: 140px;padding-left: 5px;color: #666;}}.cart {width: 50px;.curr {height: 32px;line-height: 32px;text-align: center;position: relative;display: block;.icon-cart {font-size: 22px;}em {font-style: normal;position: absolute;right: 0;top: 0;padding: 1px 6px;line-height: 1;background: $helpColor;color: #fff;font-size: 12px;border-radius: 10px;font-family: Arial;}}} } /style LayoutFooter.vue templatefooter classapp_footer!-- 联系我们 --div classcontactdiv classcontainerdldt客户服务/dtddi classiconfont icon-kefu/i 在线客服/ddddi classiconfont icon-question/i 问题反馈/dd/dldldt关注我们/dtddi classiconfont icon-weixin/i 公众号/ddddi classiconfont icon-weibo/i 微博/dd/dldldt下载APP/dtdd classqrcodeimg src/assets/images/qrcode.jpg //dddd classdownloadspan扫描二维码/spanspan立马下载APP/spana hrefjavascript:;下载页面/a/dd/dldldt服务热线/dtdd classhotline400-0000-000 small周一至周日 8:00-18:00/small/dd/dl/div/div!-- 其它 --div classextradiv classcontainerdiv classslogana hrefjavascript:;i classiconfont icon-footer01/ispan价格亲民/span/aa hrefjavascript:;i classiconfont icon-footer02/ispan物流快捷/span/aa hrefjavascript:;i classiconfont icon-footer03/ispan品质新鲜/span/a/div!-- 版权信息 --div classcopyrightpa hrefjavascript:;关于我们/aa hrefjavascript:;帮助中心/aa hrefjavascript:;售后服务/aa hrefjavascript:;配送与验收/aa hrefjavascript:;商务合作/aa hrefjavascript:;搜索推荐/aa hrefjavascript:;友情链接/a/ppCopyRight © 小兔鲜儿/p/div/div/div/footer /templatestyle scoped langscss .app_footer {overflow: hidden;background-color: #f5f5f5;padding-top: 20px;.contact {background: #fff;.container {padding: 60px 0 40px 25px;display: flex;}dl {height: 190px;text-align: center;padding: 0 72px;border-right: 1px solid #f2f2f2;color: #999;:first-child {padding-left: 0;}:last-child {border-right: none;padding-right: 0;}}dt {line-height: 1;font-size: 18px;}dd {margin: 36px 12px 0 0;float: left;width: 92px;height: 92px;padding-top: 10px;border: 1px solid #ededed;.iconfont {font-size: 36px;display: block;color: #666;}:hover {.iconfont {color: $xtxColor;}}:last-child {margin-right: 0;}}.qrcode {width: 92px;height: 92px;padding: 7px;border: 1px solid #ededed;}.download {padding-top: 5px;font-size: 14px;width: auto;height: auto;border: none;span {display: block;}a {display: block;line-height: 1;padding: 10px 25px;margin-top: 5px;color: #fff;border-radius: 2px;background-color: $xtxColor;}}.hotline {padding-top: 20px;font-size: 22px;color: #666;width: auto;height: auto;border: none;small {display: block;font-size: 15px;color: #999;}}}.extra {background-color: #333;}.slogan {height: 178px;line-height: 58px;padding: 60px 100px;border-bottom: 1px solid #434343;display: flex;justify-content: space-between;a {height: 58px;line-height: 58px;color: #fff;font-size: 28px;i {font-size: 50px;vertical-align: middle;margin-right: 10px;font-weight: 100;}span {vertical-align: middle;text-shadow: 0 0 1px #333;}}}.copyright {height: 170px;padding-top: 40px;text-align: center;color: #999;font-size: 15px;p {line-height: 1;margin-bottom: 20px;}a {color: #999;line-height: 1;padding: 0 10px;border-right: 1px solid #999;:last-child {border-right: none;}}} } /style Layout/index.vue script setup import LayoutNav from ./components/LayoutNav.vue import LayoutHeader from ./components/LayoutHeader.vue import LayoutFooter from ./components/LayoutFooter.vue /scripttemplateLayoutNav /LayoutHeader /RouterView /LayoutFooter / /template 重启项目 字体图标渲染 根目录下index.html引入 link relstylesheet href//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css 一级导航渲染  api/layout.js  import httpInstance from /utils/httpexport function getCategoryAPI () {return httpInstance({url: /home/category/head}) } LayoutHeader.vue script setupimport { getCategoryAPI } from /api/layoutimport { onMounted, ref } from vueconst categoryList ref([])const getCategory async () {const res await getCategoryAPI()categoryList.value res.result}onMounted(() getCategory())/scripttemplateheader classapp-headerdiv classcontainerh1 classlogoRouterLink to/小兔鲜/RouterLink/h1ul classapp-header-navli classhome v-foritem in categoryList :keyitem.idRouterLink to/{{ item.name }}/RouterLink/li/uldiv classsearchi classiconfont icon-search/iinput typetext placeholder搜一搜/div!-- 头部购物车 --/div/header /templatestyle scoped langscss .app-header {background: #fff;.container {display: flex;align-items: center;}.logo {width: 200px;a {display: block;height: 132px;width: 100%;text-indent: -9999px;background: url(/assets/images/logo.png) no-repeat center 18px / contain;}}.app-header-nav {width: 820px;display: flex;padding-left: 40px;position: relative;z-index: 998;li {margin-right: 40px;width: 38px;text-align: center;a {font-size: 16px;line-height: 32px;height: 32px;display: inline-block;:hover {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}}.search {width: 170px;height: 32px;position: relative;border-bottom: 1px solid #e7e7e7;line-height: 32px;.icon-search {font-size: 18px;margin-left: 5px;}input {width: 140px;padding-left: 5px;color: #666;}}.cart {width: 50px;.curr {height: 32px;line-height: 32px;text-align: center;position: relative;display: block;.icon-cart {font-size: 22px;}em {font-style: normal;position: absolute;right: 0;top: 0;padding: 1px 6px;line-height: 1;background: $helpColor;color: #fff;font-size: 12px;border-radius: 10px;font-family: Arial;}}} } /style 吸顶导航交互实现  实现吸顶交互 script setupimport { useScroll } from vueuse/coreconst { y } useScroll(window) /script components/LayoutFixed.vue script setupimport { useScroll } from vueuse/coreconst { y } useScroll(window) /scripttemplatediv classapp-header-sticky :class{ show: y 78 }div classcontainerRouterLink classlogo to/ /!-- 导航区域 --ul classapp-header-nav li classhomeRouterLink to/首页/RouterLink/liliRouterLink to/居家/RouterLink/liliRouterLink to/美食/RouterLink/liliRouterLink to/服饰/RouterLink/liliRouterLink to/母婴/RouterLink/liliRouterLink to/个护/RouterLink/liliRouterLink to/严选/RouterLink/liliRouterLink to/数码/RouterLink/liliRouterLink to/运动/RouterLink/liliRouterLink to/杂项/RouterLink/li/uldiv classrightRouterLink to/品牌/RouterLinkRouterLink to/专题/RouterLink/div/div/div /templatestyle scoped langscss .app-header-sticky {width: 100%;height: 80px;position: fixed;left: 0;top: 0;z-index: 999;background-color: #fff;border-bottom: 1px solid #e4e4e4;// 此处为关键样式!!!// 状态一往上平移自身高度 完全透明transform: translateY(-100%);opacity: 0;// 状态二移除平移 完全不透明.show {transition: all 0.3s linear;transform: none;opacity: 1;}.container {display: flex;align-items: center;}.logo {width: 200px;height: 80px;background: url(/assets/images/logo.png) no-repeat right 2px;background-size: 160px auto;}.right {width: 220px;display: flex;text-align: center;padding-left: 40px;border-left: 2px solid $xtxColor;a {width: 38px;margin-right: 40px;font-size: 16px;line-height: 1;:hover {color: $xtxColor;}}} }.app-header-nav {width: 820px;display: flex;padding-left: 40px;position: relative;z-index: 998;li {margin-right: 40px;width: 38px;text-align: center;a {font-size: 16px;line-height: 32px;height: 32px;display: inline-block;:hover {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;}} } /style Pinia优化重复请求 两个导航栏会分别发送一个请求可以利用pinia只发送一个请求。 stores/category.js import { ref } from vue import { defineStore } from pinia import { getCategoryAPI } from /api/layout export const useCategoryStore defineStore(category, () {// 导航列表的数据管理// state 导航列表数据const categoryList ref([])// action 获取导航数据的方法const getCategory async () {const res await getCategoryAPI()categoryList.value res.result}return {categoryList,getCategory} }) layout/index.vue script setup import LayoutNav from ./components/LayoutNav.vue import LayoutHeader from ./components/LayoutHeader.vue import LayoutFooter from ./components/LayoutFooter.vue import LayoutFixed from ./components/LayoutFixed.vue//触发导航列表的action import {useCategoryStore} from /stores/category import {onMounted} from vue const categoryStore useCategoryStore() onMounted(() categoryStore.getCategory()) /scripttemplateLayoutFixed /LayoutNav /LayoutHeader /RouterView /LayoutFooter / /template layout/components/LayoutFixed.vue script setupimport {useCategoryStore} from /stores/categoryimport { useScroll } from vueuse/coreconst { y } useScroll(window)//使用pinia中的数据const categoryStore useCategoryStore()/scripttemplatediv classapp-header-sticky :class{ show: y 78 }div classcontainerRouterLink classlogo to/ /!-- 导航区域 --ul classapp-header-nav li classhome v-foritem in categoryStore.categoryList :keyitem.idRouterLink to/{{ item.name }}/RouterLink/li/uldiv classrightRouterLink to/品牌/RouterLinkRouterLink to/专题/RouterLink/div/div/div /templatestyle scoped langscss .app-header-sticky {width: 100%;height: 80px;position: fixed;left: 0;top: 0;z-index: 999;background-color: #fff;border-bottom: 1px solid #e4e4e4;// 此处为关键样式!!!// 状态一往上平移自身高度 完全透明transform: translateY(-100%);opacity: 0;// 状态二移除平移 完全不透明.show {transition: all 0.3s linear;transform: none;opacity: 1;}.container {display: flex;align-items: center;}.logo {width: 200px;height: 80px;background: url(/assets/images/logo.png) no-repeat right 2px;background-size: 160px auto;}.right {width: 220px;display: flex;text-align: center;padding-left: 40px;border-left: 2px solid $xtxColor;a {width: 38px;margin-right: 40px;font-size: 16px;line-height: 1;:hover {color: $xtxColor;}}} }.app-header-nav {width: 820px;display: flex;padding-left: 40px;position: relative;z-index: 998;li {margin-right: 40px;width: 38px;text-align: center;a {font-size: 16px;line-height: 32px;height: 32px;display: inline-block;:hover {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;}} } /style layout/components/LayoutHeader.vue script setupimport {useCategoryStore} from /stores/categoryimport { useScroll } from vueuse/coreconst { y } useScroll(window)//使用pinia中的数据const categoryStore useCategoryStore()/scripttemplatediv classapp-header-sticky :class{ show: y 78 }div classcontainerRouterLink classlogo to/ /!-- 导航区域 --ul classapp-header-nav li classhome v-foritem in categoryStore.categoryList :keyitem.idRouterLink to/{{ item.name }}/RouterLink/li/uldiv classrightRouterLink to/品牌/RouterLinkRouterLink to/专题/RouterLink/div/div/div /templatestyle scoped langscss .app-header-sticky {width: 100%;height: 80px;position: fixed;left: 0;top: 0;z-index: 999;background-color: #fff;border-bottom: 1px solid #e4e4e4;// 此处为关键样式!!!// 状态一往上平移自身高度 完全透明transform: translateY(-100%);opacity: 0;// 状态二移除平移 完全不透明.show {transition: all 0.3s linear;transform: none;opacity: 1;}.container {display: flex;align-items: center;}.logo {width: 200px;height: 80px;background: url(/assets/images/logo.png) no-repeat right 2px;background-size: 160px auto;}.right {width: 220px;display: flex;text-align: center;padding-left: 40px;border-left: 2px solid $xtxColor;a {width: 38px;margin-right: 40px;font-size: 16px;line-height: 1;:hover {color: $xtxColor;}}} }.app-header-nav {width: 820px;display: flex;padding-left: 40px;position: relative;z-index: 998;li {margin-right: 40px;width: 38px;text-align: center;a {font-size: 16px;line-height: 32px;height: 32px;display: inline-block;:hover {color: $xtxColor;border-bottom: 1px solid $xtxColor;}}.active {color: $xtxColor;border-bottom: 1px solid $xtxColor;}} } /style 发现只有一次请求。 Home页  在home/components下新建5个vue文件 HomeCategory script setup /scripttemplatediv 我是分类 /div /template HomeBanner script setup /scripttemplatediv 我是banner /div /template HomeNew script setup /scripttemplatediv 人气推荐 /div /template HomeHot script setup /scripttemplatediv 新鲜好物 /div /template HomeProduct script setup /scripttemplatediv 产品列表 /div /template home/index.vue script setup import HomeCategory from ./components/HomeCategory.vue import HomeBanner from ./components/HomeBanner.vue import HomeNew from ./components/HomeNew.vue import HomeHot from ./components/HomeHot.vue import HomeProduct from ./components/HomeProduct.vue /scripttemplatediv classcontainerHomeCategory /HomeBanner //divHomeNew /HomeHot /HomeProduct / /template 分类实现 home/components/HomeCategory.vue script setupimport {useCategoryStore} from /stores/categoryconst categoryStore useCategoryStore() /scripttemplatediv classhome-categoryul classmenuli v-foritem in categoryStore.categoryList :keyitem.idRouterLink to/{{item.name}}/RouterLinkRouterLink v-fori in item.children.slice(0,2) :keyi to/{{i.name}}/RouterLink!-- 弹层layer位置 --div classlayerh4分类推荐 small根据您的购买或浏览记录推荐/small/h4ulli v-fori in item.goods :keyi.idRouterLink to/img :srci.picture alt /div classinfop classname ellipsis-2{{i.name}}/pp classdesc ellipsis{{i.desc}}/pp classpricei¥/i{{i.price}}/p/div/RouterLink/li/ul/div/li/ul/div /templatestyle scoped langscss .home-category {width: 250px;height: 500px;background: rgba(0, 0, 0, 0.8);position: relative;z-index: 99;.menu {li {padding-left: 40px;height: 55px;line-height: 55px;:hover {background: $xtxColor;}a {margin-right: 4px;color: #fff;:first-child {font-size: 16px;}}.layer {width: 990px;height: 500px;background: rgba(255, 255, 255, 0.8);position: absolute;left: 250px;top: 0;display: none;padding: 0 15px;h4 {font-size: 20px;font-weight: normal;line-height: 80px;small {font-size: 16px;color: #666;}}ul {display: flex;flex-wrap: wrap;li {width: 310px;height: 120px;margin-right: 15px;margin-bottom: 15px;border: 1px solid #eee;border-radius: 4px;background: #fff;:nth-child(3n) {margin-right: 0;}a {display: flex;width: 100%;height: 100%;align-items: center;padding: 10px;:hover {background: #e3f9f4;}img {width: 95px;height: 95px;}.info {padding-left: 10px;line-height: 24px;overflow: hidden;.name {font-size: 16px;color: #666;}.desc {color: #999;}.price {font-size: 22px;color: $priceColor;i {font-size: 16px;}}}}}}}// 关键样式 hover状态下的layer盒子变成block:hover {.layer {display: block;}}}} } /style banner轮播图  home/components/HomeBanner.vue script setup/scripttemplatediv classhome-bannerel-carousel height500pxel-carousel-item v-foritem in 4 :keyitemimg srchttp://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/6d202d8e-bb47-4f92-9523-f32ab65754f4.jpg alt/el-carousel-item/el-carousel/div /templatestyle scoped langscss .home-banner {width: 1240px;height: 500px;position: absolute;left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;} } /style 获取数据  api/home.js import httpInstance from /utils/http export function getBannerAPI (){return httpInstance({url:home/banner}) } home/components/HomeBanner.vue script setup import { getBannerAPI } from /api/home import { onMounted, ref } from vueconst bannerList ref([])const getBanner async () {const res await getBannerAPI()console.log(res)bannerList.value res.result }onMounted(() getBanner())/scripttemplatediv classhome-bannerel-carousel height500pxel-carousel-item v-foritem in bannerList :keyitem.idimg :srcitem.imgUrl alt/el-carousel-item/el-carousel/div /templatestyle scoped langscss .home-banner {width: 1240px;height: 500px;position: absolute;left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;} } /style 轮播图就实现了。 新鲜好物实现 home/components/HomePanel.vue script setupdefineProps({title: {type: String,default: },subTitle: {type: String,default: } })/scripttemplatediv classhome-paneldiv classcontainerdiv classhead!-- 主标题和副标题 --h3{{ title }}small{{ subTitle }}/small/h3/div!-- 主体内容区域 --slot namemain //div/div /templatestyle scoped langscss .home-panel {background-color: #fff;.head {padding: 40px 0;display: flex;align-items: flex-end;h3 {flex: 1;font-size: 32px;font-weight: normal;margin-left: 6px;height: 35px;line-height: 35px;small {font-size: 16px;color: #999;margin-left: 20px;}}} } /style api/home添加 /*** description: 获取新鲜好物* param {*}* return {*}*/ export const getNewAPI () {return httpInstance({url:/home/new}) } home/components/HomeNew.vue script setup import HomePanel from ./HomePanel.vue import { getNewAPI } from /api/home import { ref } from vue const newList ref([]) const getNewList async () {const res await getNewAPI()newList.value res.result }getNewList() /scripttemplateHomePanel title新鲜好物 sub-title新鲜出炉 品质靠谱template #mainul classgoods-listli v-foritem in newList :keyitem.idRouterLink :to/detail/${item.id}img :srcitem.picture alt /p classname{{ item.name }}/pp classpriceyen;{{ item.price }}/p/RouterLink/li/ul/template/HomePanel /templatestyle scoped langscss .goods-list {display: flex;justify-content: space-between;height: 406px;li {width: 306px;height: 406px;background: #f0f9f4;transition: all .5s;:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 306px;height: 306px;}p {font-size: 22px;padding-top: 12px;text-align: center;text-overflow: ellipsis;overflow: hidden;white-space: nowrap;}.price {color: $priceColor;}} } /style人气推荐实现  api/home添加 /*** description: 获取人气推荐* param {*}* return {*}*/ export const getHotAPI () {return httpInstance({url:/home/hot}) } home/components/HomeHot.vue script setup import HomePanel from ./HomePanel.vue import { getHotAPI } from /api/home import { ref } from vue const hotList ref([]) const getHotList async () {const res await getHotAPI()hotList.value res.result } getHotList() console.log(hotList)/scripttemplateHomePanel title人气推荐 sub-title人气爆款 不容错过template #mainul classgoods-listli v-foritem in hotList :keyitem.idRouterLink :to/detail/${item.id}img :srcitem.picture alt /p classname{{ item.title }}/pp classdesc{{ item.alt }}/p/RouterLink/li/ul/template/HomePanel /templatestyle scoped langscss .goods-list {display: flex;justify-content: space-between;height: 426px;li {width: 306px;height: 406px;transition: all .5s;:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 306px;height: 306px;}p {font-size: 22px;padding-top: 12px;text-align: center;}.desc {color: #999;font-size: 18px;}} } /style 懒加载指令实现  directives/index.js // 定义懒加载插件 import { useIntersectionObserver } from vueuse/coreexport const directivePlugin {install (app) {// 懒加载指令逻辑app.directive(img-lazy, {mounted (el, binding) {// el: 指令绑定的那个元素 img// binding: binding.value 指令等于号后面绑定的表达式的值 图片urlconsole.log(el, binding.value)useIntersectionObserver(el,([{ isIntersecting }]) {console.log(isIntersecting)if (isIntersecting) {// 进入视口区域el.src binding.valuestop()}},)}})} } 修改main.js app.use(createPinia()) app.use(router) app.use(directivePlugin) app.mount(#app) 产品列表实现 api/home.js新增 /*** description: 获取所有商品模块* param {*}* return {*}*/ export const getGoodsAPI () {return httpInstance({url: /home/goods}) } home/components/HomeProduct.vue script setup import HomePanel from ./HomePanel.vue import {getGoodsAPI} from /api/home import {onMounted,ref} from vue//获取数据列表 const goodsProduct ref([]) const getGoods async () {const res await getGoodsAPI()goodsProduct.value res.result }onMounted(()getGoods())/scripttemplatediv classhome-productHomePanel :titlecate.name v-forcate in goodsProduct :keycate.idtemplate #maindiv classboxRouterLink classcover to/img :srccate.picture /strong classlabelspan{{ cate.name }}馆/spanspan{{ cate.saleInfo }}/span/strong/RouterLinkul classgoods-listli v-forgood in cate.goods :keygood.idRouterLink to/ classgoods-itemimg :srcgood.picture alt /p classname ellipsis{{ good.name }}/pp classdesc ellipsis{{ good.desc }}/pp classpriceyen;{{ good.price }}/p/RouterLink/li/ul/div/template/HomePanel/div /templatestyle scoped langscss .home-product {background: #fff;margin-top: 20px;.sub {margin-bottom: 2px;a {padding: 2px 12px;font-size: 16px;border-radius: 4px;:hover {background: $xtxColor;color: #fff;}:last-child {margin-right: 80px;}}}.box {display: flex;.cover {width: 240px;height: 610px;margin-right: 10px;position: relative;img {width: 100%;height: 100%;}.label {width: 188px;height: 66px;display: flex;font-size: 18px;color: #fff;line-height: 66px;font-weight: normal;position: absolute;left: 0;top: 50%;transform: translate3d(0, -50%, 0);span {text-align: center;:first-child {width: 76px;background: rgba(0, 0, 0, 0.9);}:last-child {flex: 1;background: rgba(0, 0, 0, 0.7);}}}}.goods-list {width: 990px;display: flex;flex-wrap: wrap;li {width: 240px;height: 300px;margin-right: 10px;margin-bottom: 10px;:nth-last-child(-n 4) {margin-bottom: 0;}:nth-child(4n) {margin-right: 0;}}}.goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all .5s;:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}}} } /style 这里没有用懒加载的形式因为总有一些图片加载不出来原因不明。  GoodsItem组件封装  home/components/GoodsItem.vue script setup defineProps({goods: {type: Object,default: () { }} }) /scripttemplateRouterLink to/ classgoods-itemimg :srcgoods.picture alt /p classname ellipsis{{ goods.name }}/pp classdesc ellipsis{{ goods.desc }}/pp classpriceyen;{{ goods.price }}/p/RouterLink /templatestyle scoped langscss .goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all .5s;:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;} } /style home/components/HomeProduct.vue修改 import GoodsItem from ./GoodsItem.vueul classgoods-listli v-forgood in cate.goods :keygood.idGoodsItem :goodsgood//li /ul 一级分类页  导航栏 router/index.js修改 children: [{path: ,component: Home},{path: category/:id,component: Category}] layout/components/LayoutHeader.vue修改 layout/components/LayoutFixed.vue修改 RouterLink :to/category/${item.id}{{ item.name }}/RouterLink api/category.vue import httpInstance from /utils/http/*** description: 获取分类数据* param {*} id 分类id * return {*}*/ export const findTopCategoryAPI (id) {return httpInstance({url:/category,params:{id}}) } views/category/index.vue script setupimport { findTopCategoryAPI } from /api/categoryimport {ref} from vueimport {useRoute} from vue-routerconst categoryData ref({})const route useRoute()const getCategory async (id) {// 如何在setup中获取路由参数 useRoute() - route 等价于this.$routeconst res await findTopCategoryAPI(id)categoryData.value res.result}getCategory(route.params.id) /scripttemplatediv classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item{{ categoryData.name }}/el-breadcrumb-item/el-breadcrumb/div /templatestyle scoped langscss .top-category {h3 {font-size: 28px;color: #666;font-weight: normal;text-align: center;line-height: 100px;}.sub-list {margin-top: 20px;background-color: #fff;ul {display: flex;padding: 0 32px;flex-wrap: wrap;li {width: 168px;height: 160px;a {text-align: center;display: block;font-size: 16px;img {width: 100px;height: 100px;}p {line-height: 40px;}:hover {color: $xtxColor;}}}}}.ref-goods {background-color: #fff;margin-top: 20px;position: relative;.head {.xtx-more {position: absolute;top: 20px;right: 20px;}.tag {text-align: center;color: #999;font-size: 20px;position: relative;top: -20px;}}.body {display: flex;justify-content: space-around;padding: 0 40px 30px;}}.bread-container {padding: 25px 0;} } /style 分类Banner渲染  api/home.js修改 export function getBannerAPI (params {}) {// 默认为1 商品为2const { distributionSite 1 } paramsreturn httpInstance({url: /home/banner,params: {distributionSite}}) } category/index.vue  script setupimport { findTopCategoryAPI } from /api/categoryimport {ref,onMounted} from vueimport {useRoute} from vue-routerimport { getBannerAPI } from /api/homeconst categoryData ref({})const route useRoute()const getCategory async (id) {// 如何在setup中获取路由参数 useRoute() - route 等价于this.$routeconst res await findTopCategoryAPI(id)categoryData.value res.result}getCategory(route.params.id)//获取bannerconst bannerList ref([])const getBanner async () {const res await getBannerAPI()console.log(res)bannerList.value res.result}onMounted(() getBanner()) /scripttemplatediv classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item{{ categoryData.name }}/el-breadcrumb-item/el-breadcrumb/div!-- 轮播图 --div classhome-bannerel-carousel height500pxel-carousel-item v-foritem in bannerList :keyitem.idimg :srcitem.imgUrl alt/el-carousel-item/el-carousel/div /templatestyle scoped langscss .home-banner {width: 1240px;height: 500px;margin: 0 auto;z-index: 98;img {width: 100%;height: 500px;} } .top-category {h3 {font-size: 28px;color: #666;font-weight: normal;text-align: center;line-height: 100px;}.sub-list {margin-top: 20px;background-color: #fff;ul {display: flex;padding: 0 32px;flex-wrap: wrap;li {width: 168px;height: 160px;a {text-align: center;display: block;font-size: 16px;img {width: 100px;height: 100px;}p {line-height: 40px;}:hover {color: $xtxColor;}}}}}.ref-goods {background-color: #fff;margin-top: 20px;position: relative;.head {.xtx-more {position: absolute;top: 20px;right: 20px;}.tag {text-align: center;color: #999;font-size: 20px;position: relative;top: -20px;}}.body {display: flex;justify-content: space-around;padding: 0 40px 30px;}}.bread-container {padding: 25px 0;} } /style 导航激活  layout/components/LayoutHeader.vue修改 layout/components/LayoutFixed.vue修改 RouterLink active-classactive :to/category/${item.id}{{ item.name }}/RouterLink 分类列表渲染  category/index.vue script setupimport { findTopCategoryAPI } from /api/categoryimport {ref,onMounted} from vueimport {useRoute} from vue-routerimport { getBannerAPI } from /api/homeimport GoodsItem from ../home/components/GoodsItem.vueconst categoryData ref({})const route useRoute()const getCategory async (id) {// 如何在setup中获取路由参数 useRoute() - route 等价于this.$routeconst res await findTopCategoryAPI(id)categoryData.value res.result}getCategory(route.params.id)//获取bannerconst bannerList ref([])const getBanner async () {const res await getBannerAPI()console.log(res)bannerList.value res.result}onMounted(() getBanner()) /scripttemplate!-- 分类 --div classtop-categorydiv classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item{{ categoryData.name }}/el-breadcrumb-item/el-breadcrumb/div!-- 轮播图 --div classhome-bannerel-carousel height500pxel-carousel-item v-foritem in bannerList :keyitem.idimg :srcitem.imgUrl alt/el-carousel-item/el-carousel/divdiv classsub-listh3全部分类/h3ulli v-fori in categoryData.children :keyi.idRouterLink to/img :srci.picture /p{{ i.name }}/p/RouterLink/li/ul/divdiv classref-goods v-foritem in categoryData.children :keyitem.iddiv classheadh3- {{ item.name }}-/h3/divdiv classbodyGoodsItem v-forgood in item.goods :goodsgood :keygood.id //div/div/div /templatestyle scoped langscss .top-category {width: 1240px;margin: 0 auto;h3 {font-size: 28px;color: #666;font-weight: normal;text-align: center;line-height: 100px;}.bread-container {padding: 25px 0;}.home-banner {width: 1240px;height: 500px;margin: 0 auto;z-index: 98;img {width: 100%;height: 500px;}}.sub-list {margin-top: 20px;background-color: #fff;ul {display: flex;padding: 0 32px;flex-wrap: wrap;justify-content: space-around;li {width: 168px;height: 160px;a {text-align: center;display: block;font-size: 16px;img {width: 100px;height: 100px;}p {line-height: 40px;}:hover {color: $xtxColor;}}}}}.ref-goods {background-color: #fff;margin-top: 20px;position: relative;.head {.xtx-more {position: absolute;top: 20px;right: 20px;}.tag {text-align: center;color: #999;font-size: 20px;position: relative;top: -20px;}}.body {display: flex;justify-content: space-around;padding: 0 40px 30px;}} } /style 路由缓存问题  banner与分类商品在刷新的时候都会请求一次但banner是没必要刷新的且导航栏需要刷新才能更新。 category/index.vue修改 import {useRoute,onBeforeRouteUpdate} from vue-router// 目标:路由参数变化的时候 可以把分类数据接口重新发送onBeforeRouteUpdate((to) {// 存在问题使用最新的路由参数请求最新的分类数据getCategory(to.params.id)}) 业务逻辑的函数拆分  将banner和分类抽象出来。 useBanner.js // 封装banner轮播图相关的业务代码 import { ref, onMounted } from vue import { getBannerAPI } from /api/homeexport function useBanner () {//获取bannerconst bannerList ref([])const getBanner async () {const res await getBannerAPI()console.log(res)bannerList.value res.result}onMounted(() getBanner())return {bannerList} } useCategory.js // 封装分类数据业务相关代码 import { onMounted, ref } from vue import { findTopCategoryAPI } from /api/category import { useRoute } from vue-router import { onBeforeRouteUpdate } from vue-routerexport function useCategory () {const categoryData ref({})const route useRoute()const getCategory async (id) {// 如何在setup中获取路由参数 useRoute() - route 等价于this.$routeconst res await findTopCategoryAPI(id)categoryData.value res.result}getCategory(route.params.id)// 目标:路由参数变化的时候 可以把分类数据接口重新发送onBeforeRouteUpdate((to) {// 存在问题使用最新的路由参数请求最新的分类数据getCategory(to.params.id)})return {categoryData} } index.vue修改 script setupimport GoodsItem from ../home/components/GoodsItem.vueimport {useBanner} from ./composables/useBannerimport { useCategory } from ./composables/useCategoryconst {bannerList} useBanner()const { categoryData } useCategory() /script 二级分类页  实现 views下新建 index.vue script setup/scripttemplatediv classcontainer !-- 面包屑 --div classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item :to{ path: / }居家/el-breadcrumb-itemel-breadcrumb-item居家生活用品/el-breadcrumb-item/el-breadcrumb/divdiv classsub-containerel-tabsel-tab-pane label最新商品 namepublishTime/el-tab-paneel-tab-pane label最高人气 nameorderNum/el-tab-paneel-tab-pane label评论最多 nameevaluateNum/el-tab-pane/el-tabsdiv classbody!-- 商品列表--/div/div/div/templatestyle langscss scoped .bread-container {padding: 25px 0;color: #666; }.sub-container {padding: 20px 10px;background-color: #fff;.body {display: flex;flex-wrap: wrap;padding: 0 10px;}.goods-item {display: block;width: 220px;margin-right: 20px;padding: 20px 30px;text-align: center;img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}}.pagination-container {margin-top: 20px;display: flex;justify-content: center;}} /style router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import Login from /views/login/index.vue import Layout from /views/layout/index.vue import Home from /views/home/index.vue import Category from /views/category/index.vue import SubCategory from /views/subCategory/index.vueconst router createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: /,component: Layout,children: [{path: ,component: Home},{path: category/:id,component: Category},{path: category/sub/:id,name: subCategory,component: SubCategory},]},{path: /login,component: Login}] })export default routercategory/index.vue修改 div classsub-listh3全部分类/h3ulli v-fori in categoryData.children :keyi.idRouterLink :to/category/sub/${i.id}img :srci.picture /p{{ i.name }}/p/RouterLink/li/ul/div api/category.js /*** description: 获取二级分类列表数据* param {*} id 分类id * return {*}*/export const getCategoryFilterAPI (id) {return httpInstance({url:/category/sub/filter,params:{id}}) } /*** description: 获取导航数据* data { categoryId: 1005000 ,page: 1,pageSize: 20,sortField: publishTime | orderNum | evaluateNum} * return {*}*/ export const getSubCategoryAPI (data) {return httpInstance({url:/category/goods/temporary,method:POST,data}) } subCategory/index.vue script setup import { getCategoryFilterAPI,getSubCategoryAPI} from /api/category import {ref,onMounted} from vue import { useRoute } from vue-router import GoodsItem from ../home/components/GoodsItem.vue // 获取面包屑导航数据 const filterData ref({}) const route useRoute() const getFilterData async () {const res await getCategoryFilterAPI(route.params.id)filterData.value res.result } getFilterData()// 获取基础列表数据渲染 const goodList ref([]) const reqData ref({categoryId: route.params.id,page: 1,pageSize: 20,sortField: publishTime })const getGoodList async () {const res await getSubCategoryAPI(reqData.value)console.log(res)goodList.value res.result.items }onMounted(() getGoodList()) /scripttemplatediv classcontainerdiv classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item :to{ path: /category/${filterData.parentId} }{{ filterData.parentName }}/el-breadcrumb-itemel-breadcrumb-item{{ filterData.name }}/el-breadcrumb-item/el-breadcrumb/divdiv classsub-containerel-tabsel-tab-pane label最新商品 namepublishTime/el-tab-paneel-tab-pane label最高人气 nameorderNum/el-tab-paneel-tab-pane label评论最多 nameevaluateNum/el-tab-pane/el-tabsdiv classbody!-- 商品列表--GoodsItem v-forgoods in goodList :goodsgoods :keygoods.id/GoodsItem/div/div/div /templatestyle langscss scoped .container {width: 1240px;margin: 0 auto;.bread-container {padding: 25px 0;color: #666;}.sub-container {padding: 20px 10px;background-color: #fff;.body {display: flex;flex-wrap: wrap;padding: 0 10px;}.goods-item {display: block;width: 220px;margin-right: 20px;padding: 20px 30px;text-align: center;img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}}.pagination-container {margin-top: 20px;display: flex;justify-content: center;}} }/style subCategory/index.vue修改  div classbody v-infinite-scrollload!-- 商品列表--GoodsItem v-forgoods in goodList :goodsgoods :keygoods.id/GoodsItem /divscript setup import { getCategoryFilterAPI,getSubCategoryAPI} from /api/category import {ref,onMounted} from vue import { useRoute } from vue-router import GoodsItem from ../home/components/GoodsItem.vue // 获取面包屑导航数据 const filterData ref({}) const route useRoute() const getFilterData async () {const res await getCategoryFilterAPI(route.params.id)filterData.value res.result } getFilterData()// 获取基础列表数据渲染 const goodList ref([]) const reqData ref({categoryId: route.params.id,page: 1,pageSize: 20,sortField: publishTime })const getGoodList async () {const res await getSubCategoryAPI(reqData.value)console.log(res)goodList.value res.result.items }onMounted(() getGoodList())// tab切换回调 const tabChange () {console.log(tab切换了, reqData.value.sortField)reqData.value.page 1getGoodList() } // 加载更多 const disabled ref(false) const load async () {console.log(加载更多数据咯)// 获取下一页的数据reqData.value.pageconst res await getSubCategoryAPI(reqData.value)goodList.value [...goodList.value, ...res.result.items]// 加载完毕 停止监听if (res.result.items.length 0) {disabled.value true} } /script 无限加载。 定制路由的滚动行为 自动回到顶部。 router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import Login from /views/login/index.vue import Layout from /views/layout/index.vue import Home from /views/home/index.vue import Category from /views/category/index.vue import SubCategory from /views/subCategory/index.vueconst router createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: /,component: Layout,children: [{path: ,component: Home},{path: category/:id,component: Category},{path: category/sub/:id,name: subCategory,component: SubCategory},]},{path: /login,component: Login}],//路由滚动行为scrollBehavior() {return {top: 0}} })export default router商品详情 路由配置 views/detail/index.vue script setup/scripttemplatediv classxtx-goods-pagediv classcontainerdiv classbread-containerel-breadcrumb separatorel-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item :to{ path: / }母婴/el-breadcrumb-itemel-breadcrumb-item :to{ path: / }跑步鞋/el-breadcrumb-itemel-breadcrumb-item抓绒保暖毛毛虫子儿童运动鞋/el-breadcrumb-item/el-breadcrumb/div!-- 商品信息 --div classinfo-containerdivdiv classgoods-infodiv classmedia!-- 图片预览区 --!-- 统计数量 --ul classgoods-saleslip销量人气/pp 100 /ppi classiconfont icon-task-filling/i销量人气/p/lilip商品评价/pp200/ppi classiconfont icon-comment-filling/i查看评价/p/lilip收藏人气/pp300/ppi classiconfont icon-favorite-filling/i收藏商品/p/lilip品牌信息/pp400/ppi classiconfont icon-dynamic-filling/i品牌主页/p/li/ul/divdiv classspec!-- 商品信息区 --p classg-name 抓绒保暖毛毛虫儿童鞋 /pp classg-desc好穿 /pp classg-pricespan200/spanspan 100/span/pdiv classg-servicedldt促销/dtdd12月好物放送App领券购买直降120元/dd/dldldt服务/dtddspan无忧退货/spanspan快速退款/spanspan免费包邮/spana hrefjavascript:;了解详情/a/dd/dl/div!-- sku组件 --!-- 数据组件 --!-- 按钮组件 --divel-button sizelarge classbtn加入购物车/el-button/div/div/divdiv classgoods-footerdiv classgoods-article!-- 商品详情 --div classgoods-tabsnava商品详情/a/navdiv classgoods-detail!-- 属性 --ul classattrsli v-foritem in 3 :keyitem.valuespan classdt白色/spanspan classdd纯棉/span/li/ul!-- 图片 --/div/div/div!-- 24热榜专题推荐 --div classgoods-aside/div/div/div/div/div/div /templatestyle scoped langscss .xtx-goods-page {.goods-info {min-height: 600px;background: #fff;display: flex;.media {width: 580px;height: 600px;padding: 30px 50px;}.spec {flex: 1;padding: 30px 30px 30px 0;}}.goods-footer {display: flex;margin-top: 20px;.goods-article {width: 940px;margin-right: 20px;}.goods-aside {width: 280px;min-height: 1000px;}}.goods-tabs {min-height: 600px;background: #fff;}.goods-warn {min-height: 600px;background: #fff;margin-top: 20px;}.number-box {display: flex;align-items: center;.label {width: 60px;color: #999;padding-left: 10px;}}.g-name {font-size: 22px;}.g-desc {color: #999;margin-top: 10px;}.g-price {margin-top: 10px;span {::before {content: ¥;font-size: 14px;}:first-child {color: $priceColor;margin-right: 10px;font-size: 22px;}:last-child {color: #999;text-decoration: line-through;font-size: 16px;}}}.g-service {background: #f5f5f5;width: 500px;padding: 20px 10px 0 10px;margin-top: 10px;dl {padding-bottom: 20px;display: flex;align-items: center;dt {width: 50px;color: #999;}dd {color: #666;:last-child {span {margin-right: 10px;::before {content: •;color: $xtxColor;margin-right: 2px;}}a {color: $xtxColor;}}}}}.goods-sales {display: flex;width: 400px;align-items: center;text-align: center;height: 140px;li {flex: 1;position: relative;~li::after {position: absolute;top: 10px;left: 0;height: 60px;border-left: 1px solid #e4e4e4;content: ;}p {:first-child {color: #999;}:nth-child(2) {color: $priceColor;margin-top: 10px;}:last-child {color: #666;margin-top: 10px;i {color: $xtxColor;font-size: 14px;margin-right: 2px;}:hover {color: $xtxColor;cursor: pointer;}}}}} }.goods-tabs {min-height: 600px;background: #fff;nav {height: 70px;line-height: 70px;display: flex;border-bottom: 1px solid #f5f5f5;a {padding: 0 40px;font-size: 18px;position: relative;span {color: $priceColor;font-size: 16px;margin-left: 10px;}}} }.goods-detail {padding: 40px;.attrs {display: flex;flex-wrap: wrap;margin-bottom: 30px;li {display: flex;margin-bottom: 10px;width: 50%;.dt {width: 100px;color: #999;}.dd {flex: 1;color: #666;}}}img {width: 100%;} }.btn {margin-top: 20px;}.bread-container {padding: 25px 0; } /style router/index.js中children中添加 {path: detail/:id,component: Detail } home/components/HomeNew.vue修改 li v-foritem in newList :keyitem.idRouterLink :to/detail/${item.id}img :srcitem.picture alt /p classname{{ item.name }}/pp classpriceyen;{{ item.price }}/p/RouterLink/li 数据渲染 detail/index.vue script setup import { ref } from vue import { getDetail } from /api/detail import { useRoute } from vue-routerconst goods ref([]) const route useRoute() const getGoods async () {const res await getDetail(route.params.id)goods.value res.result } getGoods()/scripttemplatediv classxtx-goods-pagediv classcontainerdiv classbread-containerel-breadcrumb separator!-- 可选链 --el-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item :to{ path: /category/${goods.categories?.[1].id} }{{goods.categories?.[1].name}}/el-breadcrumb-itemel-breadcrumb-item :to{ path: /category/sub/${goods.categories?.[0].id} }{{goods.categories?.[0].name}}/el-breadcrumb-itemel-breadcrumb-item{{goods.name}}/el-breadcrumb-item/el-breadcrumb/div!-- 商品信息 --div classinfo-containerdivdiv classgoods-infodiv classmedia!-- 图片预览区 --!-- 统计数量 --ul classgoods-saleslip销量人气/pp {{goods.salesCount}}/ppi classiconfont icon-task-filling/i销量人气/p/lilip商品评价/pp{{goods.commentCount}}/ppi classiconfont icon-comment-filling/i查看评价/p/lilip收藏人气/pp{{goods.collectCount}}/ppi classiconfont icon-favorite-filling/i收藏商品/p/lilip品牌信息/pp{{goods.brand.name}}/ppi classiconfont icon-dynamic-filling/i品牌主页/p/li/ul/divdiv classspec!-- 商品信息区 --p classg-name {{goods.name}} /pp classg-desc{{goods.desc}} /pp classg-pricespan{{goods.oldPrice}}/spanspan{{goods.price}}/span/pdiv classg-servicedldt促销/dtdd12月好物放送App领券购买直降120元/dd/dldldt服务/dtddspan无忧退货/spanspan快速退款/spanspan免费包邮/spana hrefjavascript:;了解详情/a/dd/dl/div!-- sku组件 --!-- 数据组件 --!-- 按钮组件 --divel-button sizelarge classbtn加入购物车/el-button/div/div/divdiv classgoods-footerdiv classgoods-article!-- 商品详情 --div classgoods-tabsnava商品详情/a/navdiv classgoods-detail!-- 属性 --ul classattrsli v-foritem in goods.details.properties :keyitem.valuespan classdt{{item.name}}/spanspan classdd{{item.value}}/span/li/ul!-- 图片 --img v-forimg in goods.details.pictures :srcimg keyimg alt//div/div/div!-- 24热榜专题推荐 --div classgoods-aside/div/div/div/div/div/div /templatestyle scoped langscss .xtx-goods-page {.goods-info {min-height: 600px;background: #fff;display: flex;.media {width: 580px;height: 600px;padding: 30px 50px;}.spec {flex: 1;padding: 30px 30px 30px 0;}}.goods-footer {display: flex;margin-top: 20px;.goods-article {width: 940px;margin-right: 20px;}.goods-aside {width: 280px;min-height: 1000px;}}.goods-tabs {min-height: 600px;background: #fff;}.goods-warn {min-height: 600px;background: #fff;margin-top: 20px;}.number-box {display: flex;align-items: center;.label {width: 60px;color: #999;padding-left: 10px;}}.g-name {font-size: 22px;}.g-desc {color: #999;margin-top: 10px;}.g-price {margin-top: 10px;span {::before {content: ¥;font-size: 14px;}:first-child {color: $priceColor;margin-right: 10px;font-size: 22px;}:last-child {color: #999;text-decoration: line-through;font-size: 16px;}}}.g-service {background: #f5f5f5;width: 500px;padding: 20px 10px 0 10px;margin-top: 10px;dl {padding-bottom: 20px;display: flex;align-items: center;dt {width: 50px;color: #999;}dd {color: #666;:last-child {span {margin-right: 10px;::before {content: •;color: $xtxColor;margin-right: 2px;}}a {color: $xtxColor;}}}}}.goods-sales {display: flex;width: 400px;align-items: center;text-align: center;height: 140px;li {flex: 1;position: relative;~li::after {position: absolute;top: 10px;left: 0;height: 60px;border-left: 1px solid #e4e4e4;content: ;}p {:first-child {color: #999;}:nth-child(2) {color: $priceColor;margin-top: 10px;}:last-child {color: #666;margin-top: 10px;i {color: $xtxColor;font-size: 14px;margin-right: 2px;}:hover {color: $xtxColor;cursor: pointer;}}}}} }.goods-tabs {min-height: 600px;background: #fff;nav {height: 70px;line-height: 70px;display: flex;border-bottom: 1px solid #f5f5f5;a {padding: 0 40px;font-size: 18px;position: relative;span {color: $priceColor;font-size: 16px;margin-left: 10px;}}} }.goods-detail {padding: 40px;.attrs {display: flex;flex-wrap: wrap;margin-bottom: 30px;li {display: flex;margin-bottom: 10px;width: 50%;.dt {width: 100px;color: #999;}.dd {flex: 1;color: #666;}}}img {width: 100%;} }.btn {margin-top: 20px;}.bread-container {padding: 25px 0; } /style 热榜区域  api/detail.js添加 export const fetchHotGoodsAPI ({ id, type, limit 3 }) {return httpInstance({url:/goods/hot,params:{id, type, limit}}) } DetailHot.vue script setup import { ref } from vue import { fetchHotGoodsAPI } from /api/detail import { useRoute } from vue-routerconst goodList ref([]) const route useRoute() const getHotList async () {const res await fetchHotGoodsAPI({id: route.params.id,type: 1})goodList.value res.result } getHotList()/scripttemplatediv classgoods-hoth3 24小时热榜 /h3!-- 商品区块 --RouterLink :to/detail/${item.id} classgoods-item v-foritem in goodList :keyitem.idimg :srcitem.picture alt /p classname ellipsis{{ item.name }}/pp classdesc ellipsis{{ item.desc }}/pp classpriceyen;{{ item.price }}/p/RouterLink/div /templatestyle scoped langscss .goods-hot {h3 {height: 70px;background: $helpColor;color: #fff;font-size: 18px;line-height: 70px;padding-left: 25px;margin-bottom: 10px;font-weight: normal;}.goods-item {display: block;padding: 20px 30px;text-align: center;background: #fff;img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}} } /style detail/index.vue script setup import { ref } from vue import { getDetail } from /api/detail import { useRoute } from vue-router import DetailHot from ./components/DetailHot.vueconst goods ref([]) const route useRoute() const getGoods async () {const res await getDetail(route.params.id)goods.value res.result } getGoods() /scripttemplatediv classxtx-goods-pagediv classcontainerdiv classbread-containerel-breadcrumb separator!-- 可选链 --el-breadcrumb-item :to{ path: / }首页/el-breadcrumb-itemel-breadcrumb-item :to{ path: /category/${goods.categories?.[1].id} }{{goods.categories?.[1].name}}/el-breadcrumb-itemel-breadcrumb-item :to{ path: /category/sub/${goods.categories?.[0].id} }{{goods.categories?.[0].name}}/el-breadcrumb-itemel-breadcrumb-item{{goods.name}}/el-breadcrumb-item/el-breadcrumb/div!-- 商品信息 --div classinfo-containerdivdiv classgoods-infodiv classmedia!-- 图片预览区 --!-- 统计数量 --ul classgoods-saleslip销量人气/pp {{goods.salesCount}}/ppi classiconfont icon-task-filling/i销量人气/p/lilip商品评价/pp{{goods.commentCount}}/ppi classiconfont icon-comment-filling/i查看评价/p/lilip收藏人气/pp{{goods.collectCount}}/ppi classiconfont icon-favorite-filling/i收藏商品/p/lilip品牌信息/pp{{goods?.brand?.name}}/ppi classiconfont icon-dynamic-filling/i品牌主页/p/li/ul/divdiv classspec!-- 商品信息区 --p classg-name {{goods.name}} /pp classg-desc{{goods.desc}} /pp classg-pricespan{{goods.oldPrice}}/spanspan{{goods.price}}/span/pdiv classg-servicedldt促销/dtdd12月好物放送App领券购买直降120元/dd/dldldt服务/dtddspan无忧退货/spanspan快速退款/spanspan免费包邮/spana hrefjavascript:;了解详情/a/dd/dl/div!-- sku组件 --!-- 数据组件 --!-- 按钮组件 --divel-button sizelarge classbtn加入购物车/el-button/div/div/divdiv classgoods-footerdiv classgoods-article!-- 商品详情 --div classgoods-tabsnava商品详情/a/navdiv classgoods-detail!-- 属性 --ul classattrsli v-foritem in goods?.details?.properties :keyitem.valuespan classdt{{item.name}}/spanspan classdd{{item.value}}/span/li/ul!-- 图片 --img v-forimg in goods?.details?.pictures :srcimg keyimg alt//div/div/div!-- 24热榜专题推荐 --div classgoods-asideDetailHot/DetailHot//div/div/div/div/div/div /templatestyle scoped langscss .xtx-goods-page {.goods-info {min-height: 600px;background: #fff;display: flex;.media {width: 580px;height: 600px;padding: 30px 50px;}.spec {flex: 1;padding: 30px 30px 30px 0;}}.goods-footer {display: flex;margin-top: 20px;.goods-article {width: 940px;margin-right: 20px;}.goods-aside {width: 280px;min-height: 1000px;}}.goods-tabs {min-height: 600px;background: #fff;}.goods-warn {min-height: 600px;background: #fff;margin-top: 20px;}.number-box {display: flex;align-items: center;.label {width: 60px;color: #999;padding-left: 10px;}}.g-name {font-size: 22px;}.g-desc {color: #999;margin-top: 10px;}.g-price {margin-top: 10px;span {::before {content: ¥;font-size: 14px;}:first-child {color: $priceColor;margin-right: 10px;font-size: 22px;}:last-child {color: #999;text-decoration: line-through;font-size: 16px;}}}.g-service {background: #f5f5f5;width: 500px;padding: 20px 10px 0 10px;margin-top: 10px;dl {padding-bottom: 20px;display: flex;align-items: center;dt {width: 50px;color: #999;}dd {color: #666;:last-child {span {margin-right: 10px;::before {content: •;color: $xtxColor;margin-right: 2px;}}a {color: $xtxColor;}}}}}.goods-sales {display: flex;width: 400px;align-items: center;text-align: center;height: 140px;li {flex: 1;position: relative;~li::after {position: absolute;top: 10px;left: 0;height: 60px;border-left: 1px solid #e4e4e4;content: ;}p {:first-child {color: #999;}:nth-child(2) {color: $priceColor;margin-top: 10px;}:last-child {color: #666;margin-top: 10px;i {color: $xtxColor;font-size: 14px;margin-right: 2px;}:hover {color: $xtxColor;cursor: pointer;}}}}} }.goods-tabs {min-height: 600px;background: #fff;nav {height: 70px;line-height: 70px;display: flex;border-bottom: 1px solid #f5f5f5;a {padding: 0 40px;font-size: 18px;position: relative;span {color: $priceColor;font-size: 16px;margin-left: 10px;}}} }.goods-detail {padding: 40px;.attrs {display: flex;flex-wrap: wrap;margin-bottom: 30px;li {display: flex;margin-bottom: 10px;width: 50%;.dt {width: 100px;color: #999;}.dd {flex: 1;color: #666;}}}img {width: 100%;} }.btn {margin-top: 20px;}.bread-container {padding: 25px 0; } /style 适配热榜类型  detail/components/DeatinHot.vue修改 script setup import { ref,computed} from vue import { fetchHotGoodsAPI } from /api/detail import { useRoute } from vue-router// type适配不同类型热榜数据 const props defineProps({hotType: {type: Number, // 1代表24小时热销榜 2代表周热销榜 3代表总热销榜 可以使用type去适配title和数据列表default: 1} }) const TITLEMAP {1: 24小时热榜,2: 周热榜, } const title computed(() TITLEMAP[props.hotType])const goodList ref([]) const route useRoute() const getHotList async () {const res await fetchHotGoodsAPI({id: route.params.id,type: props.hotType})goodList.value res.result } getHotList()/script!-- 24热榜专题推荐 --div classgoods-asideDetailHot :hotType1/DetailHot :hotType2//div detail/index.vue !-- 24热榜专题推荐 --div classgoods-asideDetailHot :hotType1/DetailHot :hotType2//div 图片预览组件封装 src/components/imgView/index.vue script setup import { ref, watch } from vue import { useMouseInElement } from vueuse/coredefineProps({imageList: {type: Array,default: () []} })// // 图片列表 // const imageList [ // https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png, // https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg, // https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg, // https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg, // https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg // ]// 1.小图切换大图显示 const activeIndex ref(0)const enterhandler (i) {activeIndex.value i }// 2. 获取鼠标相对位置 const target ref(null) const { elementX, elementY, isOutside } useMouseInElement(target)// 3. 控制滑块跟随鼠标移动监听elementX/Y变化一旦变化 重新设置left/top const left ref(0) const top ref(0)const positionX ref(0) const positionY ref(0) watch([elementX, elementY, isOutside], () {console.log(xy变化了)// 如果鼠标没有移入到盒子里面 直接不执行后面的逻辑if (isOutside.value) returnconsole.log(后续逻辑执行了)// 有效范围内控制滑块距离// 横向if (elementX.value 100 elementX.value 300) {left.value elementX.value - 100}// 纵向if (elementY.value 100 elementY.value 300) {top.value elementY.value - 100}// 处理边界if (elementX.value 300) { left.value 200 }if (elementX.value 100) { left.value 0 }if (elementY.value 300) { top.value 200 }if (elementY.value 100) { top.value 0 }// 控制大图的显示positionX.value -left.value * 2positionY.value -top.value * 2})/scripttemplatediv classgoods-image!-- 左侧大图--div classmiddle reftargetimg :srcimageList[activeIndex] alt /!-- 蒙层小滑块 --div classlayer v-show!isOutside :style{ left: ${left}px, top: ${top}px }/div/div!-- 小图列表 --ul classsmallli v-for(img, i) in imageList :keyi mouseenterenterhandler(i) :class{ active: i activeIndex }img :srcimg alt //li/ul!-- 放大镜大图 --div classlarge :style[{backgroundImage: url(${imageList[activeIndex]}),backgroundPositionX: ${positionX}px,backgroundPositionY: ${positionY}px,},] v-show!isOutside/div/div /templatestyle scoped langscss .goods-image {width: 480px;height: 400px;position: relative;display: flex;.middle {width: 400px;height: 400px;background: #f5f5f5;}.large {position: absolute;top: 0;left: 412px;width: 400px;height: 400px;z-index: 500;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);background-repeat: no-repeat;// 背景图:盒子的大小 2:1 将来控制背景图的移动来实现放大的效果查看 background-positionbackground-size: 800px 800px;background-color: #f8f8f8;}.layer {width: 200px;height: 200px;background: rgba(0, 0, 0, 0.2);// 绝对定位 然后跟随咱们鼠标控制left和top属性就可以让滑块移动起来left: 0;top: 0;position: absolute;}.small {width: 80px;li {width: 68px;height: 68px;margin-left: 12px;margin-bottom: 15px;cursor: pointer;:hover,.active {border: 2px solid $xtxColor;}}} } /style detail/index.vue修改 import ImageView from /components/imageView/index.vue!-- 图片预览区 -- ImageView :image-listgoods.mainPictures/ sku插件 XtxSku 链接https://pan.baidu.com/s/1KQ5-OFMoD7bpL8DJjsmsIA?pwd3kgh  提取码3kgh detain/index.vue修改  import XtxSku from /components/XtxSku/index.vue!-- sku组件 -- XtxSku :goodsgoods/ 登录页 表单 login/index.vue script setup import { ref } from vue // 表单数据对象 const userInfo ref({account: 1311111111,password: 123456,agree: true })// 规则数据对象 const rules {account: [{ required: true, message: 用户名不能为空 }],password: [{ required: true, message: 密码不能为空 },{ min: 6, max: 24, message: 密码长度要求6-14个字符 }],agree: [{validator: (rule, val, callback) {return val ? callback() : new Error(请先同意协议)}}] }/scripttemplatedivheader classlogin-headerdiv classcontainer m-top-20h1 classlogoRouterLink to/小兔鲜/RouterLink/h1RouterLink classentry to/进入网站首页i classiconfont icon-angle-right/ii classiconfont icon-angle-right/i/RouterLink/div/headersection classlogin-sectiondiv classwrappernava hrefjavascript:;账户登录/a/navdiv classaccount-boxdiv classformel-form refformRef :modeluserInfo :rulesrules status-iconel-form-item propaccount label账户el-input v-modeluserInfo.account //el-form-itemel-form-item proppassword label密码el-input v-modeluserInfo.password //el-form-itemel-form-item propagree label-width22pxel-checkbox v-modeluserInfo.agree sizelarge我已同意隐私条款和服务条款/el-checkbox/el-form-itemel-button sizelarge classsubBtn 点击登录/el-button/el-form/div/div/div/sectionfooter classlogin-footerdiv classcontainerpa hrefjavascript:;关于我们/aa hrefjavascript:;帮助中心/aa hrefjavascript:;售后服务/aa hrefjavascript:;配送与验收/aa hrefjavascript:;商务合作/aa hrefjavascript:;搜索推荐/aa hrefjavascript:;友情链接/a/ppCopyRight copy; 小兔鲜儿/p/div/footer/div /templatestyle scoped langscss .login-header {background: #fff;border-bottom: 1px solid #e4e4e4;.container {display: flex;align-items: flex-end;justify-content: space-between;}.logo {width: 200px;a {display: block;height: 132px;width: 100%;text-indent: -9999px;background: url(/assets/images/logo.png) no-repeat center 18px / contain;}}.sub {flex: 1;font-size: 24px;font-weight: normal;margin-bottom: 38px;margin-left: 20px;color: #666;}.entry {width: 120px;margin-bottom: 38px;font-size: 16px;i {font-size: 14px;color: $xtxColor;letter-spacing: -5px;}} }.login-section {background: url(/assets/images/login-bg.png) no-repeat center / cover;height: 488px;position: relative;.wrapper {width: 380px;background: #fff;position: absolute;left: 50%;top: 54px;transform: translate3d(100px, 0, 0);box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);nav {font-size: 14px;height: 55px;margin-bottom: 20px;border-bottom: 1px solid #f5f5f5;display: flex;padding: 0 40px;text-align: right;align-items: center;a {flex: 1;line-height: 1;display: inline-block;font-size: 18px;position: relative;text-align: center;}}} }.login-footer {padding: 30px 0 50px;background: #fff;p {text-align: center;color: #999;padding-top: 20px;a {line-height: 1;padding: 0 10px;color: #999;display: inline-block;~a {border-left: 1px solid #ccc;}}} }.account-box {.toggle {padding: 15px 40px;text-align: right;a {color: $xtxColor;i {font-size: 14px;}}}.form {padding: 0 20px 20px 20px;-item {margin-bottom: 28px;.input {position: relative;height: 36px;i {width: 34px;height: 34px;background: #cfcdcd;color: #fff;position: absolute;left: 1px;top: 1px;text-align: center;line-height: 34px;font-size: 18px;}input {padding-left: 44px;border: 1px solid #cfcdcd;height: 36px;line-height: 36px;width: 100%;.error {border-color: $priceColor;}.active,:focus {border-color: $xtxColor;}}.code {position: absolute;right: 1px;top: 1px;text-align: center;line-height: 34px;font-size: 14px;background: #f5f5f5;color: #666;width: 90px;height: 34px;cursor: pointer;}}.error {position: absolute;font-size: 12px;line-height: 28px;color: $priceColor;i {font-size: 14px;margin-right: 2px;}}}.agree {a {color: #069;}}.btn {display: block;width: 100%;height: 40px;color: #fff;text-align: center;line-height: 40px;background: $xtxColor;.disabled {background: #cfcdcd;}}}.action {padding: 20px 40px;display: flex;justify-content: space-between;align-items: center;.url {a {color: #999;margin-left: 10px;}}} }.subBtn {background: $xtxColor;width: 100%;color: #fff; } /style 登录实现  账号cdshi0006 密码123456 login/index.vue修改 script setup import { ref } from vue import {loginAPI} from /api/user import { ElMessage } from element-plus import {useRouter} from vue-router import element-plus/theme-chalk/el-message.css // 表单数据对象 const userInfo ref({account: ,password: ,agree: true })// 规则数据对象 const rules {account: [{ required: true, message: 用户名不能为空 }],password: [{ required: true, message: 密码不能为空 },{ min: 6, max: 24, message: 密码长度要求6-14个字符 }],agree: [{validator: (rule, val, callback) {return val ? callback() : new Error(请先同意协议)}}] }const formRef ref(null) const router useRouter() const doLogin () {const {account,password} userInfo.valueformRef.value.validate(async(valid) {if(valid) {const res await loginAPI({account,password})// 1. 提示用户ElMessage({ type: success, message: 登录成功 })// 2. 跳转首页router.replace({ path: / })}}) }/script 登录失败的提示 utils/http.js修改 import element-plus/theme-chalk/el-message.css import { ElMessage } from element-plus// axios响应式拦截器 http.interceptors.response.use(resres.data,e {ElMessage({type: warning,message: e.response.data.msg})console.log(e)return Promise.reject(e) }) Pinia管理用户 stores/user.js // 管理用户数据相关import { defineStore } from pinia import { ref } from vue import { loginAPI } from /api/userexport const useUserStore defineStore(user, () {// 1. 定义管理用户数据的stateconst userInfo ref({})// 2. 定义获取接口数据的action函数const getUserInfo async ({ account, password }) {const res await loginAPI({ account, password })userInfo.value res.result}// 3. 以对象的格式把state和action returnreturn {userInfo,getUserInfo} }, {persist: true, }) Pinia持久化存储 需要使用插件自动本地存储官网 下载pinia-plugin-persistedstate npm i pinia-plugin-persistedstate main.js修改 import { createPinia } from pinia import piniaPluginPersistedstate from pinia-plugin-persistedstateconst pinia createPinia() pinia.use(piniaPluginPersistedstate) login/index.vue修改 export const useUserStore defineStore(user, () {//...... }, {persist: true, }) 登录和非登录状态 LayoutNav.vue修改 script setupimport {useUserStore} from /stores/userconst userStore useUserStore() /scripttemplatenav classapp-topnavdiv classcontainerultemplate v-ifuserStore.userInfo.tokenlia hrefjavascript:;i classiconfont icon-user/i{{userStore.userInfo.account}}/a/lili....../ul/div/nav /template 请求拦截器携带token http.js import {useUserStore} from /stores/userhttp.interceptors.request.use(config {// 1. 从pinia获取token数据const userStore useUserStore()// 2. 按照后端的要求拼接token数据const token userStore.userInfo.tokenif (token) {config.headers.Authorization Bearer ${token}}return config }, e Promise.reject(e)) 退出登录 user.js // 管理用户数据相关import { defineStore } from pinia import { ref } from vue import { loginAPI } from /api/userexport const useUserStore defineStore(user, () {// 1. 定义管理用户数据的stateconst userInfo ref({})// 2. 定义获取接口数据的action函数const getUserInfo async ({ account, password }) {const res await loginAPI({ account, password })userInfo.value res.result}// 退出时清除用户信息const clearUserInfo () {userInfo.value {}}// 3. 以对象的格式把state和action returnreturn {userInfo,getUserInfo,clearUserInfo} }, {persist: true, }) LayoutNav.vue修改 script setupimport {useUserStore} from /stores/userimport {useRouter} from vue-routerconst userStore useUserStore()const router useRouter()const confirm () {console.log(登录)userStore.clearUserInfo()router.push(/login)} /scripttemplatenav classapp-topnavdiv classcontainerultemplate v-ifuserStore.userInfo.tokenlia hrefjavascript:;i classiconfont icon-user/i{{userStore.userInfo.account}}/a/liliel-popconfirm confirmconfirm title确认退出吗? confirm-button-text确认 cancel-button-text取消。。。。。。/div/nav /template token失效处理 http.js import router from /router// axios响应式拦截器 http.interceptors.response.use(resres.data,e {const userStore useUserStore()ElMessage({type: warning,message: e.response.data.msg})if(e.response.status 401) {userStore.clearUserInfo()router.push(/login)}return Promise.reject(e) }) 购物车 本地加入购物车 stores/car.js // 封装购物车模块import { defineStore } from pinia import { ref } from vueexport const useCartStore defineStore(cart, () {// 1. 定义state - cartListconst cartList ref([])// 2. 定义action - addCartconst addCart (goods) {console.log(添加, goods)// 添加购物车操作// 已添加过 - count 1// 没有添加过 - 直接push// 思路通过匹配传递过来的商品对象中的skuId能不能在cartList中找到找到了就是添加过const item cartList.value.find((item) goods.skuId item.skuId)if (item) {// 找到了item.count} else {// 没找到cartList.value.push(goods)}}return {cartList,addCart} }, {persist: true, }) detail/index.vue修改 script setup import { ref } from vue import { getDetail } from /api/detail import { useRoute } from vue-router import DetailHot from ./components/DetailHot.vue import ImageView from /components/imageView/index.vue import XtxSku from /components/XtxSku/index.vue import { ElMessage } from element-plus import {useCartStore} from /stores/carconst carStore useCartStore() const goods ref([]) const route useRoute() const getGoods async () {const res await getDetail(route.params.id)goods.value res.result } getGoods()//sku let skuObj {} const skuChange (sku) {skuObj sku } //count const count ref(1) const countChange (count) {console.log(count) } //购物车 const addCar () {if(skuObj.skuId) {carStore.addCart({id: goods.value.id,name: goods.value.name,picture: goods.value.mainPictures[0],orice: goods.value.price,count: count.value,skuId: skuObj.skuId,attrsText: skuObj.specsText,selected: true})} else {ElMessage.warning(请选择规格)} } /script!-- sku组件 --XtxSku :goodsgoods changeskuChange/!-- 数据组件 --el-input-number v-modelcount changecountChange/el-input-number!-- 按钮组件 --divel-button sizelarge classbtn clickaddCar加入购物车/el-button/div头部购物车 detail/index.vue修改 script setup import { ref } from vue import { getDetail } from /api/detail import { useRoute } from vue-router import DetailHot from ./components/DetailHot.vue import ImageView from /components/imageView/index.vue import XtxSku from /components/XtxSku/index.vue import { ElMessage } from element-plus import {useCartStore} from /stores/carconst carStore useCartStore() const goods ref([]) const route useRoute() const getGoods async () {const res await getDetail(route.params.id)goods.value res.result } getGoods()//sku let skuObj {} const skuChange (sku) {skuObj sku } //count const count ref(1) const countChange (count) {console.log(count) } //购物车 const addCar () {if(skuObj.skuId) {carStore.addCart({id: goods.value.id,name: goods.value.name,picture: goods.value.mainPictures[0],price: goods.value.price,count: count.value,skuId: skuObj.skuId,attrsText: skuObj.specsText,selected: true})} else {ElMessage.warning(请选择规格)} } /script car.js // 封装购物车模块import { defineStore } from pinia import { computed, ref } from vueexport const useCartStore defineStore(cart, () {// 1. 定义state - cartListconst cartList ref([])// 2. 定义action - addCartconst addCart (goods) {// console.log(添加, goods)// 添加购物车操作// 已添加过 - count 1// 没有添加过 - 直接push// 思路通过匹配传递过来的商品对象中的skuId能不能在cartList中找到找到了就是添加过const item cartList.value.find((item) goods.skuId item.skuId)if (item) {// 找到了item.count} else {// 没找到cartList.value.push(goods)}}//删除购物车const delCart (skuId) {const idx cartList.value.findIndex((item)skuId item.skuId)cartList.value.splice(idx,1)}//总数const allCount computed(()cartList.value.reduce((a,c)ac.count,0))//总价const allPrice computed(()cartList.value.reduce((a,c)ac.price*c.count,0))return {cartList,addCart,delCart,allPrice,allCount} }, {persist: true, }) 购物车页面 router.js添加 {path: cartlist,component: CartList} views/cartList/index.vue script setupimport {useCartStore} from /stores/carconst cartStore useCartStore()const singleCheck (i, selected) {cartStore.singleCheck(i.skuId,selected)}const allCheck (selected) {cartStore.allCheck(selected)} /scripttemplatediv classxtx-cart-pagediv classcontainer m-top-20div classcarttabletheadtrth width120el-checkbox :model-valuecartStore.isAll changeallCheck//thth width400商品信息/thth width220单价/thth width180数量/thth width180小计/thth width140操作/th/tr/thead!-- 商品列表 --tbodytr v-fori in cartStore.cartList :keyi.idtdel-checkbox :model-valuei.selected change(selected)singleCheck(i,selected)//tdtddiv classgoodsRouterLink to/img :srci.picture alt //RouterLinkdivp classname ellipsis{{ i.name }}/p/div/div/tdtd classtcpyen;{{ i.price }}/p/tdtd classtcel-input-number v-modeli.count //tdtd classtcp classf16 redyen;{{ (i.price * i.count).toFixed(2) }}/p/tdtd classtcpel-popconfirm title确认删除吗? confirm-button-text确认 cancel-button-text取消 confirmdelCart(i)template #referencea hrefjavascript:;删除/a/template/el-popconfirm/p/td/trtr v-ifcartStore.cartList.length 0td colspan6div classcart-noneel-empty description购物车列表为空el-button typeprimary随便逛逛/el-button/el-empty/div/td/tr/tbody/table/div!-- 操作栏 --div classactiondiv classbatch共 {{cartStore.allCount}} 件商品已选择 {{cartStore.selectCount}} 件商品合计span classred¥ {{cartStore.selectPrice.toFixed(2)}} /span/divdiv classtotalel-button sizelarge typeprimary 下单结算/el-button/div/div/div/div /templatestyle scoped langscss .xtx-cart-page {margin-top: 20px;.cart {background: #fff;color: #666;table {border-spacing: 0;border-collapse: collapse;line-height: 24px;th,td {padding: 10px;border-bottom: 1px solid #f5f5f5;:first-child {text-align: left;padding-left: 30px;color: #999;}}th {font-size: 16px;font-weight: normal;line-height: 50px;}}}.cart-none {text-align: center;padding: 120px 0;background: #fff;p {color: #999;padding: 20px 0;}}.tc {text-align: center;a {color: $xtxColor;}.xtx-numbox {margin: 0 auto;width: 120px;}}.red {color: $priceColor;}.green {color: $xtxColor;}.f16 {font-size: 16px;}.goods {display: flex;align-items: center;img {width: 100px;height: 100px;}div {width: 280px;font-size: 16px;padding-left: 10px;.attr {font-size: 14px;color: #999;}}}.action {display: flex;background: #fff;margin-top: 20px;height: 80px;align-items: center;font-size: 16px;justify-content: space-between;padding: 0 30px;.xtx-checkbox {color: #999;}.batch {a {margin-left: 20px;}}.red {font-size: 18px;margin-right: 20px;font-weight: bold;}}.tit {color: #666;font-size: 16px;font-weight: normal;line-height: 50px;}} /style stores/car.js // 封装购物车模块import { defineStore } from pinia import { computed, ref } from vue import {insertCartAPI,findNewCartListAPI,delCartAPI} from /api/cart import {useUserStore} from /stores/userexport const useCartStore defineStore(cart, () {const userStore useUserStore()const isLogin computed(()userStore.userInfo.token)// 1. 定义state - cartListconst cartList ref([])// 2. 定义action - addCartconst addCart async (goods) {const {skuId,count} goodsif(isLogin.value) {//登录过后await insertCartAPI({skuId,count}) const res await findNewCartListAPI()cartList.value res.result} else {// console.log(添加, goods)// 添加购物车操作// 已添加过 - count 1// 没有添加过 - 直接push// 思路通过匹配传递过来的商品对象中的skuId能不能在cartList中找到找到了就是添加过const item cartList.value.find((item) goods.skuId item.skuId)if (item) {// 找到了item.count} else {// 没找到cartList.value.push(goods)}}}//更新const updateNewList async () {const res await findNewCartListAPI()cartList.value res.result}//删除购物车const delCart async(skuId) {if(isLogin.value) {await delCartAPI([skuId])const res await findNewCartListAPI()cartList.value res.result} else {const idx cartList.value.findIndex((item)skuId item.skuId)cartList.value.splice(idx,1)}}//总数const allCount computed(()cartList.value.reduce((a,c)ac.count,0))//总价const allPrice computed(()cartList.value.reduce((a,c)ac.price*c.count,0))// 单选功能const singleCheck (skuId, selected) {// 通过skuId找到要修改的那一项 然后把它的selected修改为传过来的selectedconst item cartList.value.find((item) item.skuId skuId)item.selected selected}//是否全选const isAll computed(()cartList.value.every((item) item.selected))//全选const allCheck (selected) {cartList.value.forEach(item item.selected selected)}//已选择的数量const selectCount computed(()cartList.value.filter(itemitem.selected).reduce((a,c)ac.count,0))//已选择价钱const selectPrice computed(()cartList.value.filter(itemitem.selected).reduce((a,c)ac.price*c.count,0))//清除购物车const clearCart () {cartList.value []}return {cartList,addCart,delCart,allPrice,allCount,singleCheck,isAll,allCheck,selectCount,selectPrice,clearCart,updateNewList} }, {persist: true, }) api/cart.js import httpInstance from /utils/http // 加入购物车 export const insertCartAPI ({ skuId, count }) {return httpInstance({url: /member/cart,method: POST,data: {skuId,count}}) }//登录购物车 export const findNewCartListAPI () {return httpInstance({url: /member/cart}) }// 删除购物车 export const delCartAPI (ids) {return httpInstance({url: /member/cart,method: DELETE,data: {ids}}) }//合并购物车 export const mergeCartAPI (data) {return httpInstance ({url: /member/cart/merge,method: POST,data}) } stores/user.js // 管理用户数据相关import { defineStore } from pinia import { ref } from vue import { loginAPI } from /api/user import {useCartStore} from ./car import {mergeCartAPI} from /api/cartexport const useUserStore defineStore(user, () {const cartStore useCartStore()// 1. 定义管理用户数据的stateconst userInfo ref({})// 2. 定义获取接口数据的action函数const getUserInfo async ({ account, password }) {const res await loginAPI({ account, password })userInfo.value res.resultmergeCartAPI(cartStore.cartList.map(item {return {skuId: item.skuId,selected: item.selected,count: item.count}}))cartStore.updateNewList()}// 退出时清除用户信息const clearUserInfo () {userInfo.value {}cartStore.clearCart()}// 3. 以对象的格式把state和action returnreturn {userInfo,getUserInfo,clearUserInfo} }, {persist: true, }) 单选按钮 全选按钮 加入、删除购物车 清空购物车 合并本地购物车  订单页  路由 {path: checkout,component: CheckOut} api/checkout.js import httpInstance from /utils/http /*** 获取结算信息*/ export const getCheckoutInfoAPI () {return httpInstance({url:/member/order/pre}) }// 创建订单 export const createOrderAPI (data) {return httpInstance({url: /member/order,method: POST,data}) } views/pay/index.vue script setupimport {getCheckoutInfoAPI,createOrderAPI} from /api/checkoutimport {ref,onMounted} from vueimport {useRouter} from vue-routerimport {useCartStore} from /stores/carconst cartStore useCartStore()const router useRouter()const checkInfo ref({}) // 订单对象const curAddress ref({})const getCheckInfo async () {const res await getCheckoutInfoAPI()checkInfo.value res.resultconst item checkInfo.value.userAddresses.find(itemitem.isDefault 0)curAddress.value item}onMounted(()getCheckInfo())const showDialog ref(false)const activeAddress ref({})//切换地址const switchAddress (item) {switchAddress.value item}const confirm () {curAddress.value activeAddress.valueshowDialog.value falseactiveAddress.value {}}//创建订单const createOrder async () {const res await createOrderAPI({deliveryTimeType: 1,payType: 1,payChannel: 1,buyerMessage: ,goods: checkInfo.value.goods.map(item {return {skuId: item.skuId,count: item.count}}),addressId: curAddress.value.id})const orderId res.result.idrouter.push({path: /pay,query: {id: orderId}})//更新购物车cartStore.updateNewList()}/scripttemplatediv classxtx-pay-checkout-pagediv classcontainerdiv classwrapper!-- 收货地址 --h3 classbox-title收货地址/h3div classbox-bodydiv classaddressdiv classtextdiv classnone v-if!curAddress您需要先添加收货地址才可提交订单。/divul v-elselispan收i /货i /人/span{{ curAddress.receiver }}/lilispan联系方式/span{{ curAddress.contact }}/lilispan收货地址/span{{ curAddress.fullLocation }} {{ curAddress.address }}/li/ul/divdiv classactionel-button sizelarge clickshowDialog true切换地址/el-buttonel-button sizelarge clickaddFlag true添加地址/el-button/div/div/div!-- 商品信息 --h3 classbox-title商品信息/h3div classbox-bodytable classgoodstheadtrth width520商品信息/thth width170单价/thth width170数量/thth width170小计/thth width170实付/th/tr/theadtbodytr v-fori in checkInfo.goods :keyi.idtda hrefjavascript:; classinfoimg :srci.picture altdiv classrightp{{ i.name }}/pp{{ i.attrsText }}/p/div/a/tdtdyen;{{ i.price }}/tdtd{{ i.price }}/tdtdyen;{{ i.totalPrice }}/tdtdyen;{{ i.totalPayPrice }}/td/tr/tbody/table/div!-- 配送时间 --h3 classbox-title配送时间/h3div classbox-bodya classmy-btn active hrefjavascript:;不限送货时间周一至周日/aa classmy-btn hrefjavascript:;工作日送货周一至周五/aa classmy-btn hrefjavascript:;双休日、假日送货周六至周日/a/div!-- 支付方式 --h3 classbox-title支付方式/h3div classbox-bodya classmy-btn active hrefjavascript:;在线支付/aa classmy-btn hrefjavascript:;货到付款/aspan stylecolor:#999货到付款需付5元手续费/span/div!-- 金额明细 --h3 classbox-title金额明细/h3div classbox-bodydiv classtotaldldt商品件数/dtdd{{ checkInfo.summary?.goodsCount }}件/dd/dldldt商品总价/dtdd¥{{ checkInfo.summary?.totalPrice.toFixed(2) }}/dd/dldldt运i/i费/dtdd¥{{ checkInfo.summary?.postFee.toFixed(2) }}/dd/dldldt应付总额/dtdd classprice{{ checkInfo.summary?.totalPayPrice.toFixed(2) }}/dd/dl/div/div!-- 提交订单 --div classsubmitel-button typeprimary sizelarge clickcreateOrder提交订单/el-button/div/div/div/div!-- 切换地址 --el-dialog v-modelshowDialog title切换收货地址 width30% centerdiv classaddressWrapperdiv classtext item :class{active:activeAddress.iditem.id} clickswitchAddress(item) v-foritem in checkInfo.userAddresses :keyitem.idullispan收i /货i /人/span{{ item.receiver }} /lilispan联系方式/span{{ item.contact }}/lilispan收货地址/span{{ item.fullLocation item.address }}/li/ul/div/divtemplate #footerspan classdialog-footerel-button取消/el-buttonel-button typeprimary clickconfirm确定/el-button/span/template/el-dialog!-- 添加地址 -- /templatestyle scoped langscss .xtx-pay-checkout-page {margin-top: 20px;.wrapper {background: #fff;padding: 0 20px;.box-title {font-size: 16px;font-weight: normal;padding-left: 10px;line-height: 70px;border-bottom: 1px solid #f5f5f5;}.box-body {padding: 20px 0;}} }.address {border: 1px solid #f5f5f5;display: flex;align-items: center;.text {flex: 1;min-height: 90px;display: flex;align-items: center;.none {line-height: 90px;color: #999;text-align: center;width: 100%;}ul {flex: 1;padding: 20px;li {line-height: 30px;span {color: #999;margin-right: 5px;i {width: 0.5em;display: inline-block;}}}}a {color: $xtxColor;width: 160px;text-align: center;height: 90px;line-height: 90px;border-right: 1px solid #f5f5f5;}}.action {width: 420px;text-align: center;.btn {width: 140px;height: 46px;line-height: 44px;font-size: 14px;:first-child {margin-right: 10px;}}} }.goods {width: 100%;border-collapse: collapse;border-spacing: 0;.info {display: flex;text-align: left;img {width: 70px;height: 70px;margin-right: 20px;}.right {line-height: 24px;p {:last-child {color: #999;}}}}tr {th {background: #f5f5f5;font-weight: normal;}td,th {text-align: center;padding: 20px;border-bottom: 1px solid #f5f5f5;:first-child {border-left: 1px solid #f5f5f5;}:last-child {border-right: 1px solid #f5f5f5;}}} }.my-btn {width: 228px;height: 50px;border: 1px solid #e4e4e4;text-align: center;line-height: 48px;margin-right: 25px;color: #666666;display: inline-block;.active,:hover {border-color: $xtxColor;} }.total {dl {display: flex;justify-content: flex-end;line-height: 50px;dt {i {display: inline-block;width: 2em;}}dd {width: 240px;text-align: right;padding-right: 70px;.price {font-size: 20px;color: $priceColor;}}} }.submit {text-align: right;padding: 60px;border-top: 1px solid #f5f5f5; }.addressWrapper {max-height: 500px;overflow-y: auto; }.text {flex: 1;min-height: 90px;display: flex;align-items: center;.item {border: 1px solid #f5f5f5;margin-bottom: 10px;cursor: pointer;.active,:hover {border-color: $xtxColor;background: lighten($xtxColor, 50%);}ul {padding: 10px;font-size: 14px;line-height: 30px;}} } /style 支付页  router.js {path: pay,component: Pay} api/pay.js import httpInstance from /utils/httpexport const getOrderAPI (id) {return httpInstance({url: /member/order/${id}}) } composables/useCountDown.js // 封装倒计时逻辑函数 import { computed, onUnmounted, ref } from vue import dayjs from dayjs export const useCountDown () {// 1. 响应式的数据let timer nullconst time ref(0)// 格式化时间 为 xx分xx秒const formatTime computed(() dayjs.unix(time.value).format(mm分ss秒))// 2. 开启倒计时的函数const start (currentTime) {// 开始倒计时的逻辑// 核心逻辑的编写每隔1s就减一time.value currentTimetimer setInterval(() {time.value--}, 1000)}// 组件销毁时清除定时器onUnmounted(() {timer clearInterval(timer)})return {formatTime,start} } pay/index.vue script setup import { getOrderAPI } from /api/pay import { onMounted, ref } from vue import { useRoute } from vue-router import {useCountDown} from /composables/useCountDown const {formatTime,start} useCountDown() // 获取订单数据 const route useRoute() const payInfo ref({}) const getPayInfo async () {const res await getOrderAPI(route.query.id)payInfo.value res.resultstart(res.result.countdown) } onMounted(() getPayInfo())// 支付地址 const baseURL http://pcapi-xiaotuxian-front-devtest.itheima.net/ const backURL http://127.0.0.1:5173/paycallback const redirectUrl encodeURIComponent(backURL) const payUrl ${baseURL}pay/aliPay?orderId${route.query.id}redirect${redirectUrl} /scripttemplatediv classxtx-pay-pagediv classcontainer!-- 付款信息 --div classpay-infospan classicon iconfont icon-queren2/spandiv classtipp订单提交成功请尽快完成支付。/pp支付还剩 span{{ formatTime }}/span, 超时后将取消订单/p/divdiv classamountspan应付总额/spanspan¥{{ payInfo.payMoney?.toFixed(2) }}/span/div/div!-- 付款方式 --div classpay-typep classhead选择以下支付方式付款/pdiv classitemp支付平台/pa classbtn wx hrefjavascript:;/aa classbtn alipay :hrefpayUrl/a/divdiv classitemp支付方式/pa classbtn hrefjavascript:;招商银行/aa classbtn hrefjavascript:;工商银行/aa classbtn hrefjavascript:;建设银行/aa classbtn hrefjavascript:;农业银行/aa classbtn hrefjavascript:;交通银行/a/div/div/div/div /templatestyle scoped langscss .xtx-pay-page {margin-top: 20px; }.pay-info {background: #fff;display: flex;align-items: center;height: 240px;padding: 0 80px;.icon {font-size: 80px;color: #1dc779;}.tip {padding-left: 10px;flex: 1;p {:first-child {font-size: 20px;margin-bottom: 5px;}:last-child {color: #999;font-size: 16px;}}}.amount {span {:first-child {font-size: 16px;color: #999;}:last-child {color: $priceColor;font-size: 20px;}}} }.pay-type {margin-top: 20px;background-color: #fff;padding-bottom: 70px;p {line-height: 70px;height: 70px;padding-left: 30px;font-size: 16px;.head {border-bottom: 1px solid #f5f5f5;}}.btn {width: 150px;height: 50px;border: 1px solid #e4e4e4;text-align: center;line-height: 48px;margin-left: 30px;color: #666666;display: inline-block;.active,:hover {border-color: $xtxColor;}.alipay {background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png) no-repeat center / contain;}.wx {background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg) no-repeat center / contain;}} } /style 账号 askgxl8276sandbox.com  登录密码 111111 支付密码 111111 会员中心  基本页面 router.js import Member from /views/member/index.vue import MemberInfo from /views/member/components/UserInfo.vue import MemberOrder from /views/member/components/UserOrder.vue{path: member,component: Member,children: [{path: user,component: MemberInfo},{path: order,component: MemberOrder},]} LayoutNav.vue lia hrefjavascript:; click$router.push(/member)会员中心/a/li UserInfo.vue script setupimport { useUserStore } from /stores/userconst userStore useUserStore() /scripttemplatediv classhome-overview!-- 用户信息 --div classuser-metadiv classavatarimg :srcuserStore.userInfo?.avatar //divh4{{ userStore.userInfo?.account }}/h4/divdiv classitema hrefjavascript:;span classiconfont icon-hy/spanp会员中心/p/aa hrefjavascript:;span classiconfont icon-aq/spanp安全设置/p/aa hrefjavascript:;span classiconfont icon-dw/spanp地址管理/p/a/div/divdiv classlike-containerdiv classhome-paneldiv classheaderh4 data-v-bcb266e0猜你喜欢/h4/divdiv classgoods-list!-- GoodsItem v-forgood in likeList :keygood.id :goodsgood / --/div/div/div /templatestyle scoped langscss .home-overview {height: 132px;background: url(/assets/images/center-bg.png) no-repeat center / cover;display: flex;.user-meta {flex: 1;display: flex;align-items: center;.avatar {width: 85px;height: 85px;border-radius: 50%;overflow: hidden;margin-left: 60px;img {width: 100%;height: 100%;}}h4 {padding-left: 26px;font-size: 18px;font-weight: normal;color: white;}}.item {flex: 1;display: flex;align-items: center;justify-content: space-around;:first-child {border-right: 1px solid #f4f4f4;}a {color: white;font-size: 16px;text-align: center;.iconfont {font-size: 32px;}p {line-height: 32px;}}} }.like-container {margin-top: 20px;border-radius: 4px;background-color: #fff; }.home-panel {background-color: #fff;padding: 0 20px;margin-top: 20px;height: 400px;.header {height: 66px;border-bottom: 1px solid #f5f5f5;padding: 18px 0;display: flex;justify-content: space-between;align-items: baseline;h4 {font-size: 22px;font-weight: 400;}}.goods-list {display: flex;justify-content: space-around;} } /style UserOrder.vue script setup // tab列表 const tabTypes [{ name: all, label: 全部订单 },{ name: unpay, label: 待付款 },{ name: deliver, label: 待发货 },{ name: receive, label: 待收货 },{ name: comment, label: 待评价 },{ name: complete, label: 已完成 },{ name: cancel, label: 已取消 } ] // 订单列表 const orderList []/scripttemplatediv classorder-containerel-tabs!-- tab切换 --el-tab-pane v-foritem in tabTypes :keyitem.name :labelitem.label /div classmain-containerdiv classholder-container v-iforderList.length 0el-empty description暂无订单数据 //divdiv v-else!-- 订单列表 --div classorder-item v-fororder in orderList :keyorder.iddiv classheadspan下单时间{{ order.createTime }}/spanspan订单编号{{ order.id }}/span!-- 未付款倒计时时间还有 --span classdown-time v-iforder.orderState 1i classiconfont icon-down-time/ib付款截止: {{order.countdown}}/b/span/divdiv classbodydiv classcolumn goodsulli v-foritem in order.skus :keyitem.ida classimage hrefjavascript:;img :srcitem.image alt //adiv classinfop classname ellipsis-2{{ item.name }}/pp classattr ellipsisspan{{ item.attrsText }}/span/p/divdiv classprice¥{{ item.realPay?.toFixed(2) }}/divdiv classcountx{{ item.quantity }}/div/li/ul/divdiv classcolumn statep{{ order.orderState }}/pp v-iforder.orderState 3a hrefjavascript:; classgreen查看物流/a/pp v-iforder.orderState 4a hrefjavascript:; classgreen评价商品/a/pp v-iforder.orderState 5a hrefjavascript:; classgreen查看评价/a/p/divdiv classcolumn amountp classred¥{{ order.payMoney?.toFixed(2) }}/pp含运费¥{{ order.postFee?.toFixed(2) }}/pp在线支付/p/divdiv classcolumn actionel-button v-iforder.orderState 1 typeprimarysizesmall立即付款/el-buttonel-button v-iforder.orderState 3 typeprimary sizesmall确认收货/el-buttonpa hrefjavascript:;查看详情/a/pp v-if[2, 3, 4, 5].includes(order.orderState)a hrefjavascript:;再次购买/a/pp v-if[4, 5].includes(order.orderState)a hrefjavascript:;申请售后/a/pp v-iforder.orderState 1a hrefjavascript:;取消订单/a/p/div/div/div!-- 分页 --div classpagination-containerel-pagination background layoutprev, pager, next //div/div/div/el-tabs/div/templatestyle scoped langscss .order-container {padding: 10px 20px;.pagination-container {display: flex;justify-content: center;}.main-container {min-height: 500px;.holder-container {min-height: 500px;display: flex;justify-content: center;align-items: center;}} }.order-item {margin-bottom: 20px;border: 1px solid #f5f5f5;.head {height: 50px;line-height: 50px;background: #f5f5f5;padding: 0 20px;overflow: hidden;span {margin-right: 20px;.down-time {margin-right: 0;float: right;i {vertical-align: middle;margin-right: 3px;}b {vertical-align: middle;font-weight: normal;}}}.del {margin-right: 0;float: right;color: #999;}}.body {display: flex;align-items: stretch;.column {border-left: 1px solid #f5f5f5;text-align: center;padding: 20px;p {padding-top: 10px;}:first-child {border-left: none;}.goods {flex: 1;padding: 0;align-self: center;ul {li {border-bottom: 1px solid #f5f5f5;padding: 10px;display: flex;:last-child {border-bottom: none;}.image {width: 70px;height: 70px;border: 1px solid #f5f5f5;}.info {width: 220px;text-align: left;padding: 0 10px;p {margin-bottom: 5px;.name {height: 38px;}.attr {color: #999;font-size: 12px;span {margin-right: 5px;}}}}.price {width: 100px;}.count {width: 80px;}}}}.state {width: 120px;.green {color: $xtxColor;}}.amount {width: 200px;.red {color: $priceColor;}}.action {width: 140px;a {display: block;:hover {color: $xtxColor;}}}}} } /style 个人中心信息渲染 api/user.js export const getLikeListAPI ({ limit 4 }) {return httpInstance({url:/goods/relevant,params: {limit }}) } UserInfo.vue script setupimport { useUserStore } from /stores/userimport {getLikeListAPI} from /api/userimport { onMounted, ref } from vue// 导入GoodsItem组件import GoodsItem from /views/home/components/GoodsItem.vueconst userStore useUserStore()// 获取猜你喜欢列表const likeList ref([])const getLikeList async () {const res await getLikeListAPI({ limit: 4 })likeList.value res.result}onMounted(() getLikeList()) /script 我的订单  api/user.js  export const getUserOrder (params) {return httpInstance({url:/member/order,method:GET,params}) } UserOrder.vue script setup import { getUserOrder } from /api/user import { onMounted, ref } from vue // tab列表 const tabTypes [{ name: all, label: 全部订单 },{ name: unpay, label: 待付款 },{ name: deliver, label: 待发货 },{ name: receive, label: 待收货 },{ name: comment, label: 待评价 },{ name: complete, label: 已完成 },{ name: cancel, label: 已取消 } ] // tab切换 const tabChange (type) {params.value.orderState typegetOrderList() }// 获取订单列表 const orderList ref([]) const params ref({orderState: 0,page: 1,pageSize: 2 }) const getOrderList async () {const res await getUserOrder(params.value)orderList.value res.result.itemstotal.value res.result.counts } onMounted(() getOrderList())/script el-tabs tab-changetabChange 分页 UserOrder.vue !-- 分页 --div classpagination-containerel-pagination :totaltotal current-changepageChange :page-sizeparams.pageSize background layoutprev, pager, next //div // 页数切换 const pageChange (page) {params.value.page pagegetOrderList() }
文章转载自:
http://www.morning.gmnmh.cn.gov.cn.gmnmh.cn
http://www.morning.gmmyn.cn.gov.cn.gmmyn.cn
http://www.morning.fjptn.cn.gov.cn.fjptn.cn
http://www.morning.qpqb.cn.gov.cn.qpqb.cn
http://www.morning.rkwlg.cn.gov.cn.rkwlg.cn
http://www.morning.ttdxn.cn.gov.cn.ttdxn.cn
http://www.morning.wxrbl.cn.gov.cn.wxrbl.cn
http://www.morning.pgxjl.cn.gov.cn.pgxjl.cn
http://www.morning.jnbsx.cn.gov.cn.jnbsx.cn
http://www.morning.kmldm.cn.gov.cn.kmldm.cn
http://www.morning.nxnrt.cn.gov.cn.nxnrt.cn
http://www.morning.kybpj.cn.gov.cn.kybpj.cn
http://www.morning.mnkz.cn.gov.cn.mnkz.cn
http://www.morning.qnpyz.cn.gov.cn.qnpyz.cn
http://www.morning.3dcb8231.cn.gov.cn.3dcb8231.cn
http://www.morning.zrlms.cn.gov.cn.zrlms.cn
http://www.morning.jtdrz.cn.gov.cn.jtdrz.cn
http://www.morning.zhqfn.cn.gov.cn.zhqfn.cn
http://www.morning.rwzqn.cn.gov.cn.rwzqn.cn
http://www.morning.dhwyl.cn.gov.cn.dhwyl.cn
http://www.morning.msgnx.cn.gov.cn.msgnx.cn
http://www.morning.slpcl.cn.gov.cn.slpcl.cn
http://www.morning.rnrfs.cn.gov.cn.rnrfs.cn
http://www.morning.zcfsq.cn.gov.cn.zcfsq.cn
http://www.morning.rftk.cn.gov.cn.rftk.cn
http://www.morning.mltsc.cn.gov.cn.mltsc.cn
http://www.morning.wmmjw.cn.gov.cn.wmmjw.cn
http://www.morning.xdhcr.cn.gov.cn.xdhcr.cn
http://www.morning.tqrjj.cn.gov.cn.tqrjj.cn
http://www.morning.elmtw.cn.gov.cn.elmtw.cn
http://www.morning.mszls.cn.gov.cn.mszls.cn
http://www.morning.rdzgm.cn.gov.cn.rdzgm.cn
http://www.morning.jgrjj.cn.gov.cn.jgrjj.cn
http://www.morning.fbbmg.cn.gov.cn.fbbmg.cn
http://www.morning.xkqjw.cn.gov.cn.xkqjw.cn
http://www.morning.lxngn.cn.gov.cn.lxngn.cn
http://www.morning.nhpgm.cn.gov.cn.nhpgm.cn
http://www.morning.rjkfj.cn.gov.cn.rjkfj.cn
http://www.morning.gnlyq.cn.gov.cn.gnlyq.cn
http://www.morning.yrmpr.cn.gov.cn.yrmpr.cn
http://www.morning.tkzrh.cn.gov.cn.tkzrh.cn
http://www.morning.qwbtr.cn.gov.cn.qwbtr.cn
http://www.morning.ptqpd.cn.gov.cn.ptqpd.cn
http://www.morning.wgzgr.cn.gov.cn.wgzgr.cn
http://www.morning.kgkph.cn.gov.cn.kgkph.cn
http://www.morning.ymsdr.cn.gov.cn.ymsdr.cn
http://www.morning.gtnyq.cn.gov.cn.gtnyq.cn
http://www.morning.plznfnh.cn.gov.cn.plznfnh.cn
http://www.morning.zlfxp.cn.gov.cn.zlfxp.cn
http://www.morning.kkhf.cn.gov.cn.kkhf.cn
http://www.morning.btpll.cn.gov.cn.btpll.cn
http://www.morning.bnbtp.cn.gov.cn.bnbtp.cn
http://www.morning.wttzp.cn.gov.cn.wttzp.cn
http://www.morning.kpyyf.cn.gov.cn.kpyyf.cn
http://www.morning.sthp.cn.gov.cn.sthp.cn
http://www.morning.kjnfs.cn.gov.cn.kjnfs.cn
http://www.morning.ympcj.cn.gov.cn.ympcj.cn
http://www.morning.lpppg.cn.gov.cn.lpppg.cn
http://www.morning.jhkzl.cn.gov.cn.jhkzl.cn
http://www.morning.gcfg.cn.gov.cn.gcfg.cn
http://www.morning.wkmpx.cn.gov.cn.wkmpx.cn
http://www.morning.yqqxj1.cn.gov.cn.yqqxj1.cn
http://www.morning.tdscl.cn.gov.cn.tdscl.cn
http://www.morning.sfswj.cn.gov.cn.sfswj.cn
http://www.morning.bmzxp.cn.gov.cn.bmzxp.cn
http://www.morning.nzklw.cn.gov.cn.nzklw.cn
http://www.morning.rfwrn.cn.gov.cn.rfwrn.cn
http://www.morning.znqmh.cn.gov.cn.znqmh.cn
http://www.morning.kgltb.cn.gov.cn.kgltb.cn
http://www.morning.pqnpd.cn.gov.cn.pqnpd.cn
http://www.morning.rtkgc.cn.gov.cn.rtkgc.cn
http://www.morning.kjfsd.cn.gov.cn.kjfsd.cn
http://www.morning.rqxhp.cn.gov.cn.rqxhp.cn
http://www.morning.heleyo.com.gov.cn.heleyo.com
http://www.morning.pznhn.cn.gov.cn.pznhn.cn
http://www.morning.bnpn.cn.gov.cn.bnpn.cn
http://www.morning.tlrxp.cn.gov.cn.tlrxp.cn
http://www.morning.fxjnn.cn.gov.cn.fxjnn.cn
http://www.morning.wkmyt.cn.gov.cn.wkmyt.cn
http://www.morning.tnjkg.cn.gov.cn.tnjkg.cn
http://www.tj-hxxt.cn/news/279169.html

相关文章:

  • 不用域名推广网站进不去的网站用什么浏览器
  • 有做美食的视频网站么wordpress导出主题代码
  • 网站建设和优化的营销话术同城信息发布平台
  • 沈阳网站定制开发网页制作的公司企业
  • 泉州企业网站维护定制企业如何进行网站建设
  • 常州网站公司网站七牛 wordpress
  • 网站未在腾讯云备案厦门建设网站首页
  • 抚州哪里有做企业网站的公司小企业网站建设哪找
  • 形象设计公司网站建设方案书网络营销制度课完整版
  • 上海 建网站王者荣耀网页设计素材
  • 柳州游戏网站建设建筑行业平台
  • 检察网站建设自媒体网络营销是什么
  • 网站设计语言翻译公众号如何推广宣传
  • 盘锦网站建设多少钱淘宝网站怎么做的好
  • 淘宝优惠券网站建设教程上海最大的广告公司
  • 陕西企业网站建设价格微商城网站建设公司
  • 大连制作公司网站广阳区建设局网站
  • 怎么让网站绑定域名访问金融公司网站建设模板下载
  • 网站 无限下拉系统开发是什么意思
  • 无网站做cpa推广网线制作方法
  • 在电脑新建网站站点18款禁用黄app入口直接看
  • 邯郸信息港征婚交友seo公司哪家
  • 360元网站建设 网络服务seo排名优化表格工具
  • 网站里宣传视频怎么做房产网上查询系统
  • 深圳集团网站建设报价济南专业做网站公司
  • 门户网站后台管理系统模板营销网站建设hanyous
  • 英文建站系统顺德网站制作案例机构
  • phpcms律师网站模板企业微信开发公司
  • 曲阜做网站哪家好珠江新城网站建设
  • 网站怎么收费wordpress默认字体改黑色