商家详情页开发
在开发之前准备好mock接口,返回mock如下:
{
code: 200,
data: [
{
id: '1',
name: '某什么玛1',
imgUrl: '/i18n/9_16/img/near.png',
sales: 1000,
expressLimit: 0,
expressPrice: 5,
slogon: 'VIP尊享xx元减x元运费券(每月x张)'
},
{
id: '1',
name: '某什么玛2',
imgUrl: '/i18n/9_16/img/near.png',
sales: 2000,
expressLimit: 0,
expressPrice: 5,
slogon: 'VIP尊享xx元减x元运费券(每月x张)'
},
{
id: '1',
name: '某什么玛3',
imgUrl: '/i18n/9_16/img/near.png',
sales: 200,
expressLimit: 0,
expressPrice: 5,
slogon: 'VIP尊享xx元减x元运费券(每月x张)'
},
{
id: '1',
name: '某什么玛4',
imgUrl: '/i18n/9_16/img/near.png',
sales: 100,
expressLimit: 0,
expressPrice: 5,
slogon: 'VIP尊享xx元减x元运费券(每月x张)'
}
],
desc: '成功'
}
然后优化一下axios封装工具类src\utils\request.js
:
import axios from 'axios'
const baseURL = 'https://www.fastmock.site/mock/xxxxxx/mock'
const timeout = 10000
/** axios初始化实例 */
const instance = axios.create({
baseURL: baseURL,
timeout: timeout
})
/** 封装的axios get请求方法 */
export const get = (url, params = {}) => {
return new Promise((resolve, reject) => {
instance.get(url, {
params
}).then((res) => {
resolve(res.data)
}, err => {
reject(err)
})
})
}
/** 封装的axios post请求方法 */
export const post = (url, data = {}) => {
return new Promise((resolve, reject) => {
instance.post(url, data, {
headers: {
'Content-Tpye': 'application/json'
}
}).then((res) => {
resolve(res.data)
}, err => {
reject(err)
})
})
}
修改src\views\home\Nearby.vue
:
<template>
<!-- 主体商铺展示内容 -->
<div class="nearby">
<h3 class="nearby__title">附近店铺</h3>
<!-- 5个 -->
<div class="nearby__item" v-for="(item, index) in nearbyList" :key="index">
<img :src="item.headImg" class="nearby__item__img" />
<div class="nearby__item__content">
<div class="nearby__item__content__title">{{ item.title }}</div>
<div class="nearby__item__content__tags">
<span class="nearby__item__content__tag">月售:{{ item.sales }}</span>
<span class="nearby__item__content__tag"
>起送:¥{{ item.expressLimit }}</span
>
<span class="nearby__item__content__tag"
>基础运费:¥{{ item.expressPrice }}</span
>
</div>
<p class="nearby__item__content__highlight">
{{ item.highlight }}
</p>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
// import { useRouter } from 'vue-router'
import { get } from '@/utils/request'
const useNearbyListEffect = () => {
const nearbyList = ref([])
const getNearbyList = async () => {
try {
const resultData = await get('/api/user/hot_list')
if (resultData?.code === 200 && resultData?.data?.length) {
resultData.data.forEach(item => {
nearbyList.value.push({
id: item?.id,
title: item?.name,
headImg: item?.imgUrl,
sales: item?.sales,
expressLimit: item?.expressLimit,
expressPrice: item?.expressPrice,
highlight: item?.slogon
})
})
} else {
console.log('暂无数据')
}
} catch (e) {
console.log('数据请求失败')
}
}
return { nearbyList, getNearbyList }
}
export default {
name: 'Nearby',
setup () {
const { nearbyList, getNearbyList } = useNearbyListEffect()
getNearbyList()
return {
nearbyList
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
@import '@/style/mixins';
.nearby {
&__title {
margin: 0.16rem 0 0.02rem 0;
font-size: 0.18rem;
color: $content-font-color;
font-weight: normal; //不加粗展示
}
}
</style>
远程数据加载完毕。
商家详情界面开发
1.0 创建商检详情页面的路由
src\router\index.js
:
import {
createRouter,
createWebHistory
} from 'vue-router'
const routes = [{
path: '/',
name: 'Home',
component: () => import(/* webpackChunkName: "home" */ '../views/home/Home.vue')
}, {
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: "login" */ '../views/login/Login.vue'),
beforeEnter: (to, from, next) => {
// 只有访问Login页面之前才会执行次函数
const {
isLogin
} = localStorage // 从本地存储中取isLogin
// 如果登录,就跳到首页页面;否则跳转到登录页面
isLogin
? next({
name: 'Home'
})
: next()
}
},
{
path: '/register',
name: 'Register',
component: () => import(/* webpackChunkName: "register" */ '../views/register/Register.vue'),
beforeEnter: (to, from, next) => {
const {
isLogin
} = localStorage
isLogin
? next({
name: 'Home'
})
: next()
}
}, {
path: '/shop',
name: 'Shop',
component: () => import(/* webpackChunkName: "shop" */ '../views/shop/Shop.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// beforeEach:全局,每次做路由跳转之前都会执行这个操作。
router.beforeEach((to, from, next) => {
// to and from are Route Object,
// to:跳转的时候想要跳转的页面的信息
// from :指从哪里跳过来的信息
// next() must be called to resolve the hook}
// 中间件继续执行的方法
// 从本地存储中取isLogin
const {
isLogin
} = localStorage
console.log(to, from)
/** 判断是否登录 */
// 必须双循环,才能防止死循环
// 如果没有登录,就跳到登录页面
const {
name
} = to
const
isLoginOrRegister = (name === 'Login' || name === 'Register');
(isLogin || isLoginOrRegister) ? next() : next({
name: 'Login'
})
})
export default router
将src\views\home\Nearby.vue
中店铺的信息摘离成组件,因为要在商店详情页面中使用。
新建src\components\ShopInfo\ShopInfo.vue
:
<template>
<!-- v-for="(item, index) in nearbyList" :key="index" -->
<div class="shop">
<img :src="item.headImg" class="shop__img" />
<div class="shop__content">
<div class="shop__content__title">{{ item.title }}</div>
<div class="shop__content__tags">
<span class="shop__content__tag">月售:{{ item.sales }}</span>
<span class="shop__content__tag">起送:¥{{ item.expressLimit }}</span>
<span class="shop__content__tag"
>基础运费:¥{{ item.expressPrice }}</span
>
</div>
<p class="shop__content__highlight">
{{ item.highlight }}
</p>
</div>
</div>
</template>
<script>
export default {
name: 'ShopInfo',
props: ['item']
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
@import '@/style/mixins';
.shop {
display: flex;
padding-top: 0.12rem;
// 图片
&__img {
margin-right: 0.16rem;
width: 0.56rem;
height: 0.56rem;
}
//右侧文字内容
&__content {
padding-bottom: 0.12rem;
border-bottom: 1px solid $content-bg-color;
flex: 1;
&__title {
line-height: 0.22rem;
font-size: 0.16rem;
color: $content-font-color;
}
&__tags {
margin-top: 0.08rem;
line-height: 0.18rem;
font-size: 0.13rem;
color: $content-font-color;
}
&__tag {
margin-right: 0.16rem;
}
&__highlight {
color: #e93b3b;
line-height: 0.18rem;
font-size: 0.13rem;
margin: 0.08rem 0 0 0;
}
}
}
</style>
调整src\views\home\Nearby.vue
:
<template>
<!-- 主体商铺展示内容 -->
<div class="nearby">
<h3 class="nearby__title">附近店铺</h3>
<!-- 主体商铺展示内容 -->
<ShopInfo v-for="(item, index) in nearbyList" :key="index" :item="item" />
</div>
</template>
<script>
import { ref } from 'vue'
// import { useRouter } from 'vue-router'
import { get } from '@/utils/request'
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
const useNearbyListEffect = () => {
const nearbyList = ref([])
const getNearbyList = async () => {
try {
const resultData = await get('/api/user/hot_list')
if (resultData?.code === 200 && resultData?.data?.length) {
resultData.data.forEach(item => {
nearbyList.value.push({
id: item?.id,
title: item?.name,
headImg: item?.imgUrl,
sales: item?.sales,
expressLimit: item?.expressLimit,
expressPrice: item?.expressPrice,
highlight: item?.slogon
})
})
} else {
console.log('暂无数据')
}
} catch (e) {
console.log('数据请求失败')
}
}
return { nearbyList, getNearbyList }
}
export default {
name: 'Nearby',
components: { ShopInfo },
setup () {
const { nearbyList, getNearbyList } = useNearbyListEffect()
getNearbyList()
return {
nearbyList
}
}
}
</script>
商家详情页修改src\views\shop\Shop.vue
:
<template>
<div class="wrapper">
<ShopInfo :item="item" :hideBorder="true" />
</div>
</template>
<script>
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
export default {
name: 'Shop',
components: { ShopInfo },
setup () {
const item = ''
return { item }
}
}
</script>
<style lang="scss" scoped>
.wrapper {
padding: 0 0.18rem;
}
</style>
为了控制下边的border划线,需要修改一下组件src\components\ShopInfo\ShopInfo.vue
:
<template>
<!-- v-for="(item, index) in nearbyList" :key="index" -->
<div class="shop">
<img :src="item.headImg" class="shop__img" />
<div
:class="{
shop__content: true,
'shop__content--bordered': hideBorder ? false : true
}"
>
<div class="shop__content__title">{{ item.title }}</div>
<div class="shop__content__tags">
<span class="shop__content__tag">月售:{{ item.sales }}</span>
<span class="shop__content__tag">起送:¥{{ item.expressLimit }}</span>
<span class="shop__content__tag"
>基础运费:¥{{ item.expressPrice }}</span
>
</div>
<p class="shop__content__highlight">
{{ item.highlight }}
</p>
</div>
</div>
</template>
<script>
export default {
name: 'ShopInfo',
props: ['item', 'hideBorder']
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
@import '@/style/mixins';
.shop {
display: flex;
padding-top: 0.12rem;
// 图片
&__img {
margin-right: 0.16rem;
width: 0.56rem;
height: 0.56rem;
}
//右侧文字内容
&__content {
padding-bottom: 0.12rem;
&--bordered {
border-bottom: 1px solid $content-bg-color;
}
flex: 1;
&__title {
line-height: 0.22rem;
font-size: 0.16rem;
color: $content-font-color;
}
&__tags {
margin-top: 0.08rem;
line-height: 0.18rem;
font-size: 0.13rem;
color: $content-font-color;
}
&__tag {
margin-right: 0.16rem;
}
&__highlight {
color: #e93b3b;
line-height: 0.18rem;
font-size: 0.13rem;
margin: 0.08rem 0 0 0;
}
}
}
</style>
准备下图标:
src\views\shop\Shop.vue
:
<template>
<div class="wrapper">
<div class="search">
<div class="search__back">
<i class="custom-icon custom-icon-back"></i>
</div>
<div class="search__content">
<span class="search__content__icon"
><i class="custom-icon custom-icon-search"></i
></span>
<input class="search__content__input" />
</div>
</div>
<ShopInfo :item="item" :hideBorder="true" />
</div>
</template>
<script>
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
export default {
name: 'Shop',
components: { ShopInfo },
setup () {
const item = {
id: '1',
title: '某什么玛1',
headImg: '/i18n/9_16/img/near.png',
sales: 1000,
expressLimit: 0,
expressPrice: 5,
highlight: 'VIP尊享xx元减x元运费券(每月x张)'
}
return { item }
}
}
</script>
<style lang="scss" scoped>
.wrapper {
padding: 0 0.18rem;
}
.search {
margin: 0.2rem 0 0.16rem 0;
display: flex;
&__back {
width: 0.3rem;
height: 0.32rem; //高度会将父元素撑开
}
&__content {
display: flex;
flex: 1;
background: #f5f5f5;
border-radius: 0.16rem;
&__icon {
width: 0.44rem;
height: 0.32rem;
}
&__input {
padding-right: 0.2rem;
width: 100%;
display: block;
border: none;
outline: none;
background: none;
height: 0.32rem;
}
}
}
</style>
调整,主要是设置行高,并把字体大小放大:
<template>
<div class="wrapper">
<div class="search">
<div class="search__back">
<i class="search__back__icon custom-icon custom-icon-back"></i>
</div>
......
</div>
<ShopInfo :item="item" :hideBorder="true" />
</div>
</template>
<script>
......
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
.wrapper {
padding: 0 0.18rem;
}
.search {
margin: 0.2rem 0 0.16rem 0;
display: flex;
&__back {
width: 0.3rem;
line-height: 0.32rem; //高度会将父元素撑开
&__icon {
font-size: 0.2rem;
color: #b6b6b6;
}
}
......
</style>
进一步优化:
<template>
<div class="wrapper">
<div class="search">
<div class="search__back">
<i class="search__back__icon custom-icon custom-icon-back"></i>
</div>
<div class="search__content">
<span
><i class="search__content__icon custom-icon custom-icon-search"></i
></span>
<input class="search__content__input" placeholder="请输入商品名称" />
</div>
</div>
<ShopInfo :item="item" :hideBorder="true" />
</div>
</template>
<script>
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
export default {
name: 'Shop',
components: { ShopInfo },
setup () {
const item = {
id: '1',
title: '某什么玛1',
headImg: '/i18n/9_16/img/near.png',
sales: 1000,
expressLimit: 0,
expressPrice: 5,
highlight: 'VIP尊享xx元减x元运费券(每月x张)'
}
return { item }
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
.wrapper {
padding: 0 0.18rem;
}
.search {
margin: 0.2rem 0 0.16rem 0;
display: flex;
line-height: 0.32rem; //高度会将父元素撑开
&__back {
width: 0.3rem;
&__icon {
font-size: 0.2rem;
color: #b6b6b6;
}
}
&__content {
display: flex;
flex: 1;
background: #f5f5f5;
border-radius: 0.16rem;
&__icon {
padding-left: 0.1rem;
padding-right: 0.1rem;
width: 0.44rem;
text-align: center;
color: #b7b7b7;
}
&__input {
padding-right: 0.2rem;
width: 100%;
display: block;
border: none;
outline: none;
background: none;
height: 0.32rem;
font-size: 0.14rem;
&::placeholder {
color: #333;
}
}
}
}
</style>
最终效果如下:
增加点击返回事件:
<template>
<div class="wrapper">
<div class="search">
<div class="search__back" @click="handleBackClick">
<i class="search__back__icon custom-icon custom-icon-back"></i>
</div>
<div class="search__content">
<span
><i class="search__content__icon custom-icon custom-icon-search"></i
></span>
<input class="search__content__input" placeholder="请输入商品名称" />
</div>
</div>
<ShopInfo :item="item" :hideBorder="true" />
</div>
</template>
<script>
// 路由跳转方法
import { useRouter } from 'vue-router'
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
export default {
name: 'Shop',
components: { ShopInfo },
setup () {
const router = useRouter()
const item = {
id: '1',
title: '某什么玛1',
headImg: '/i18n/9_16/img/near.png',
sales: 1000,
expressLimit: 0,
expressPrice: 5,
highlight: 'VIP尊享xx元减x元运费券(每月x张)'
}
const handleBackClick = () => {
router.back()
}
return { item, handleBackClick }
}
}
</script>
在Home首页,点击某个店铺跳到详情页,可以如下编写代码:
src\views\home\Nearby.vue
:
<template>
<!-- 主体商铺展示内容 -->
<div class="nearby">
<h3 class="nearby__title">附近店铺</h3>
<!-- router-link必须有to的属性 -->
<router-link to="/shop" v-for="(item, index) in nearbyList" :key="index">
<!-- 主体商铺展示内容 -->
<ShopInfo :item="item" />
</router-link>
</div>
</template>
<script>
import { ref } from 'vue'
// import { useRouter } from 'vue-router'
import { get } from '@/utils/request'
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
const useNearbyListEffect = () => {
const nearbyList = ref([])
const getNearbyList = async () => {
try {
const resultData = await get('/api/user/hot_list')
if (resultData?.code === 200 && resultData?.data?.length) {
resultData.data.forEach(item => {
nearbyList.value.push({
id: item?.id,
title: item?.name,
headImg: item?.imgUrl,
sales: item?.sales,
expressLimit: item?.expressLimit,
expressPrice: item?.expressPrice,
highlight: item?.slogon
})
})
} else {
console.log('暂无数据')
}
} catch (e) {
console.log('数据请求失败')
}
}
return { nearbyList, getNearbyList }
}
export default {
name: 'Nearby',
components: { ShopInfo },
setup () {
const { nearbyList, getNearbyList } = useNearbyListEffect()
getNearbyList()
return {
nearbyList
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
@import '@/style/mixins';
.nearby {
&__title {
margin: 0.16rem 0 0.02rem 0;
font-size: 0.18rem;
color: $content-font-color;
font-weight: normal; //不加粗展示
}
}
</style>
这时候发现每个文字下都有下划线,路由的to属性会在最外层包裹一个a标签,解决方案如下:
src\views\home\Nearby.vue
<template>
<!-- 主体商铺展示内容 -->
<div class="nearby">
<h3 class="nearby__title">附近店铺</h3>
<!-- router-link必须有to的属性 -->
<router-link to="/shop" v-for="(item, index) in nearbyList" :key="index">
<!-- 主体商铺展示内容 -->
<ShopInfo :item="item" />
</router-link>
</div>
</template>
<script>
import { ref } from 'vue'
// import { useRouter } from 'vue-router'
import { get } from '@/utils/request'
import ShopInfo from '@/components/ShopInfo/ShopInfo.vue'
const useNearbyListEffect = () => {
const nearbyList = ref([])
const getNearbyList = async () => {
try {
const resultData = await get('/api/user/hot_list')
if (resultData?.code === 200 && resultData?.data?.length) {
resultData.data.forEach(item => {
nearbyList.value.push({
id: item?.id,
title: item?.name,
headImg: item?.imgUrl,
sales: item?.sales,
expressLimit: item?.expressLimit,
expressPrice: item?.expressPrice,
highlight: item?.slogon
})
})
} else {
console.log('暂无数据')
}
} catch (e) {
console.log('数据请求失败')
}
}
return { nearbyList, getNearbyList }
}
export default {
name: 'Nearby',
components: { ShopInfo },
setup () {
const { nearbyList, getNearbyList } = useNearbyListEffect()
getNearbyList()
return {
nearbyList
}
}
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables';
@import '@/style/mixins';
.nearby {
&__title {
margin: 0.16rem 0 0.02rem 0;
font-size: 0.18rem;
color: $content-font-color;
font-weight: normal; //不加粗展示
}
a {
text-decoration: none;
}
}
</style>
这里继续摘离2种颜色:
修改src\style\viriables.scss
:
/**
* 内容主体文字颜色
**/
$content-font-color: #333;
/**
* 无内容、背景灰、留白灰的颜色
**/
$content-bg-color: #f1f1f1;
/**
* 文字灰色字体
*
**/
$centent-notice-fontcolor: #777;
/**
* 搜索框的背景色
**/
$search-bg-color: #f5f5f5;
/**
* 搜索框内文字颜色
**/
$search-font-color: #b7b7b7;
修改用到的2个地方:
src\views\home\StaticPart.vue
:
......
.search {
margin-bottom: 0.12rem;
line-height: 0.32rem; //行高:将会自动撑开
background: $search-bg-color;
color: $search-font-color;
border-radius: 0.16rem;
font-size: 0.14rem;
&__icon {
display: inline-block;
padding: 0 0.05rem 0 0.16rem;
font-size: 0.15rem;
}
&__text {
display: inline-block;
font-size: 0.14rem;
}
}
......
src\views\shop\Shop.vue
......
&__content {
display: flex;
flex: 1;
background: $search-bg-color;
border-radius: 0.16rem;
&__icon {
padding-left: 0.1rem;
padding-right: 0.1rem;
width: 0.44rem;
text-align: center;
color: $search-font-color;
}
&__input {
padding-right: 0.2rem;
width: 100%;
display: block;
border: none;
outline: none;
background: none;
height: 0.32rem;
font-size: 0.14rem;
color: $content-font-color;
&::placeholder {
color: $content-font-color;
}
}
}
......