Mobile模块前端开发

宗冠宇
2023-12-01

(一) MVVM概念

​ MVVM是视图层概念,主要关注于视图层分离:MVVM把前端的视图层分为Model,View,VM ViewModel.

app.js:项目的入口模块,一切请求都要进入这里处理(无路由分发的功能,需要调用router.js)

router.js:路由分发模块:为了保证路由模块的职能单一,不负责具体业务逻辑的处理。(业务处理调用controller模块)

controller:业务逻辑处理层。封装具体代码,不负责处理数据的CRUD(CRUD需要调用Model层.

Model:只负责操作数据库,执行对应的SQL语句,进行数据的CRUD(C:create; R:read; U:update; D:delete)

View视图层:每当用户操作界面,如果需要进行业务处理,如果需要进行业务处理,都会通过网络请求后端的服务器,此时请求会被后端的App.js监听。

M:保存的是每个页面中单独的数据;

VM:是调度者,分割了M和V,每当V层想要获取保存数据的时候,都要由VM做中间处理。

V:是每个页面中的HTML结构

MVVM让数据双向绑定(由VM提供,VM是核心)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7JIFnKu7-1630673191818)(C:\Users\xyx\Downloads\mvc.png)]

(二)前期准备

  • 全局安装vue-cli脚手架工具:cnpm install -g vue-cli
  • 初始化sell项目:vue init webpack flash-waimai-mobile
  • 进入sell目录:cd flash-waimai-mobile
  • 安装依赖(依据package.json文件):cnpm install
  • 运行项目(package.json中配置):cnpm run dev 或者 node build/dev-server.js

(三)mobile大体框架与开发步骤

1. build模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDwxx8fl-1630673191830)(C:\Users\xyx\AppData\Roaming\Typora\typora-user-images\image-20210819162840108.png)]

目录和作用如下:

build.js 生产环境构建代码

utils.js 构建工具相关

dev-client.js-配合dev-server.js监听html文件改动也能够触发自动刷新

// 引入 webpack-hot-middleware/client 
var hotClient = require('webpack-hot-middleware/client');

// 订阅事件,当 event.action === 'reload' 时执行页面刷新
hotClient.subscribe(function (event) {
    if (event.action === 'reload') {
        window.location.reload();
    }
})

webpack.base.conf.js webpack配置路径别名

module.exports = {
    entry: {
        app: './src/main.js'
    },
    output: {
        path: config.build.assetsRoot,
        publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
        filename: '[name].js'
    },
    resolve: {
        extensions: ['', '.js', '.vue', '.less', '.css', '.scss'],
        fallback: [path.join(__dirname, '../node_modules')],
        alias: {
             // 路径别名配置(自定义)
            'vue$': 'vue/dist/vue.common.js',
            'src': path.resolve(__dirname, '../src'),
            'assets': path.resolve(__dirname, '../src/assets'),
            'components': path.resolve(__dirname, '../src/components')
        }
    },
    resolveLoader: {
        fallback: [path.join(__dirname, '../node_modules')]
    },
    ......

webpack.dev.conf.js webpack开发环境设置,构建本地服务器
webpack.pro.conf.js webpack生产环境配置

2. 整体路由配置
  • 安装ajax异步请求插件vue-resource:cnpm install vue-resource --save-dev

  • 配置项目整体路由

    // 文件位置:src/APP.vue
    <template>
    	<div>
            <!--路由刷新缓存-->
    		<transition name="router-fade" mode="out-in">
    			<keep-alive>
    			    <router-view v-if="$route.meta.keepAlive"></router-view>
    			</keep-alive>
        	</transition>
        	<transition name="router-fade" mode="out-in">
    			<router-view v-if="!$route.meta.keepAlive"></router-view>
    		</transition>
    		<svg-icon></svg-icon>	
        </div>
    
    </template>
    
    <script>
    	import svgIcon from './components/common/svg';
      	export default {
        	components:{
                svgIcon
            },
      	}
    
    </script>
    
    <style lang="scss">
      	@import './style/common';
    	.router-fade-enter-active, .router-fade-leave-active {
    	  	transition: opacity .3s;
    	}
    	.router-fade-enter, .router-fade-leave-active {
    	  	opacity: 0;
    	}
    </style>
    
    
    // 文件位置:src/router/index.js
    // 截取部分代码
    import App from '../App'
    //通过webpack来分模块加载路由
    const home = r => require.ensure([], () => r(require('../page/home/home')), 'home')
    const city = r => require.ensure([], () => r(require('../page/city/city')), 'city')
    const msite = r => require.ensure([], () => r(require('../page/msite/msite')), 'msite')
    const search = r => require.ensure([], () => r(require('../page/search/search')), 'search')
    ..............
    export default [{
        path: '/',
        component: App, //顶层路由,对应index.html
        children: [ //二级路由。对应App.vue
            //地址为空时跳转home页面
            {
                path: '',
                redirect: '/home'
            },
            //首页城市列表页
            {
                path: '/home',
                component: home
            },
            //当前选择城市页
            {
                path: '/city/:cityid',
                component: city
            },
            //所有商铺列表页
            {
                path: '/msite',
                component: msite,
                meta: { keepAlive: false },
            },
            .........
               ]
    }]
    
  • 配置项目整体依赖

    // 文件位置:src/main.js
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import routes from './router/router'
    import store from './store/'
    import {routerMode} from './config/env'
    import './config/rem'
    
    // 使用Vue-resource必须放在前面,放在后面报错
    Vue.use(VueRouter)
    const router = new VueRouter({
    	routes,
    	mode: routerMode,
    	strict: process.env.NODE_ENV !== 'production',
    	scrollBehavior (to, from, savedPosition) {
    	    if (savedPosition) {
    		    return savedPosition
    		} else {
    			if (from.meta.keepAlive) {
    				from.meta.savedPosition = document.body.scrollTop;
    			}
    		    return { x: 0, y: to.meta.savedPosition || 0 }
    		}
    	}
    })
    
    new Vue({
    	router,
    	store,
    }).$mount('#app')
    
3. 通用组件开发
  • 通用样式

    • 总体样式
    //文件位置src/style/common.scss
    body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption, {
        padding: 0;
        margin: 0;
        list-style: none;
        font-style: normal;
        text-decoration: none;
        border: none;
        color: #333;
        font-weight: normal;
        font-family: "Microsoft Yahei";
        box-sizing: border-box;
        -webkit-tap-highlight-color:transparent;
        -webkit-font-smoothing: antialiased;
        &:hover{
            outline: none;
        }
    }
    
    /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/  
    ::-webkit-scrollbar  
    {  
        width: 0px;  
        height: 0px;  
        background-color: #F5F5F5;  
    }  
      
    /*定义滚动条轨道 内阴影+圆角*/  
    ::-webkit-scrollbar-track  
    {  
        -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0);  
        border-radius: 10px;  
        background-color: #F5F5F5;  
    }  
      
    /*定义滑块 内阴影+圆角*/  
    ::-webkit-scrollbar-thumb  
    {  
        border-radius: 10px;  
        -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);  
        background-color: #555;  
    }  
    
    input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] {
        -webkit-appearance: none;
    }
    
    textarea { -webkit-appearance: none;}   
    
    html,body{
        height: 100%;
        width: 100%;
        background-color: #F5F5F5;
    }
    
    
    .clear:after{
        content: '';
        display: block;
        clear: both;
    }
    
    .clear{
        zoom:1;
    }
    
    .back_img{
        background-repeat: no-repeat;
        background-size: 100% 100%;
    }
    
    .margin{
        margin: 0 auto;
    }
    
    .left{
        float: left;
    }
    
    .right{
        float: right;
    }
    
    .hide{
        display: none;
    }
    
    .show{
        display: block;
    }
    
    .ellipsis{
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    
    .paddingTop{
        padding-top: 1.95rem;
    }
    
    @keyframes backOpacity{
       0%   { opacity: 1 }
       25%  { opacity: .5 }
       50%  { opacity: 1 }
       75%  { opacity: .5 }
       100% { opacity: 1 }
    }
    
    .animation_opactiy{
        animation: backOpacity 2s ease-in-out infinite;
    }
    
    
    //文件位置src/style/mixin.scss
    $blue: #3190e8;  
    $bc: #e4e4e4;
    $fc:#fff;
    
    // 背景图片地址和大小
    @mixin bis($url) { 
    	background-image: url($url);
    	background-repeat: no-repeat;
    	background-size: 100% 100%;
    }
    
    @mixin borderRadius($radius) {
        -webkit-border-radius: $radius;
        -moz-border-radius: $radius;
        -ms-border-radius: $radius;
        -o-border-radius: $radius;
        border-radius: $radius;
    }
    //定位全屏
    @mixin allcover{
    	position:absolute;
    	top:0;
    	right:0;
    }
    
    //定位上下左右居中
    @mixin center {  
    	position: absolute;
    	top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }
    
    //定位上下居中
    @mixin ct {  
    	position: absolute;
    	top: 50%;
        transform: translateY(-50%);
    }
    
    //定位左右居中
    @mixin cl {  
    	position: absolute;
    	left: 50%;
        transform: translateX(-50%);
    }
    
    //宽高
    @mixin wh($width, $height){
    	width: $width;
    	height: $height;
    }
    
    //字体大小、行高、字体
    @mixin font($size, $line-height, $family: 'Microsoft YaHei') {  
    	font: #{$size}/#{$line-height} $family;
    }
    
    //字体大小,颜色
    @mixin sc($size, $color){
    	font-size: $size;
    	color: $color;
    }
    
    //flex 布局和 子元素 对其方式
    @mixin fj($type: space-between){
    	display: flex;
    	justify-content: $type;
    
    }
    
    
    • 构建通用版块

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BQMUK42F-1630673191836)(C:\Users\xyx\AppData\Roaming\Typora\typora-user-images\image-20210819195116555.png)]

    ​ alertTip.vue:使用mixin样式,作用是移动端小弹窗。

    ​ buyCart.vue:购物模块,用于添加商品。

    ​ computeTime.vue:下单后支付时间倒计时模块。

    ​ loading.vue:使用mixin样式,加载时屏幕上显示弹跳小水果。

    ​ ratingstar.vue:五星好评效果。

    ​ shoplist.vue:商铺展示,数据最多20位。

    ​ svg.vue:字体显示。

    ​ 以商铺展示为例:

    <template>
    	<div class="shoplist_container">
    		<ul v-load-more="loaderMore" v-if="shopListArr.length" type="1">
    			<router-link :to="{path: 'shop', query:{geohash, id: item.id}}" v-for="item in shopListArr" tag='li' :key="item.id" class="shop_li">
    				<section>
    					<img :src="imgBaseUrl + item.image_path" class="shop_img">
    				</section>
    				<hgroup class="shop_right">
    					<header class="shop_detail_header">
    						<h4 :class="item.is_premium? 'premium': ''" class="" class="shop_title ellipsis">{{item.name}}</h4>
    						<ul class="shop_detail_ul">
    							<li v-for="item in item.supports" :key="item.id" class="supports">{{item.icon_name}}</li>
    						</ul>
    					</header>
    					<h5 class="rating_order_num">
    						<section class="rating_order_num_left">
    							<section class="rating_section">
    								<rating-star :rating='item.rating'></rating-star>
    								<span class="rating_num">{{item.rating}}</span>
    							</section>
    							<section class="order_section">
    								月售{{item.recent_order_num}}单
    							</section>
    						</section>
    						<section class="rating_order_num_right">
    							<span class="delivery_style delivery_left" v-if="item.delivery_mode">{{item.delivery_mode.text}}</span>
    							<span class="delivery_style delivery_right" v-if="zhunshi(item.supports)">准时达</span>
    						</section>
    					</h5>
    					<h5 class="fee_distance">
    						<p class="fee">
    							¥{{item.float_minimum_order_amount}}起送
    							<span class="segmentation">|</span>
    							{{item.piecewise_agent_fee.tips}}
    						</p>
    						<p class="distance_time">
    							<template v-if="Number(item.distance)">{{item.distance > 1000? (item.distance/1000).toFixed(2) + 'km': item.distance + 'm'}}
    								<span class="segmentation">|</span>
    							</template>
    							<template v-else>
                    {{item.distance}}
    							<span class="segmentation">|</span>
                  </template>
    							<span class="order_time">{{item.order_lead_time}}</span>
    						</p>
    					</h5>
    				</hgroup>
    			</router-link>
    		</ul>
    		<ul v-else class="animation_opactiy">
    			<li class="list_back_li" v-for="item in 10" :key="item">
    				<img src="../../images/shopback.svg" class="list_back_svg">
    			</li>
    		</ul>
    		<p v-if="touchend" class="empty_data">没有更多了</p>
    		<aside class="return_top" @click="backTop" v-if="showBackStatus">
    			<svg class="back_top_svg">
    				<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#backtop"></use>
    			</svg>
    		</aside>
    		<div ref="abc" style="background-color: red;"></div>
    		<transition name="loading">
    			<loading v-show="showLoading"></loading>
    		</transition>
    	</div>
    </template>
    
    <script>
    
    import {mapState} from 'vuex'
    import {shopList} from 'src/service/getData'
    import {imgBaseUrl} from 'src/config/env'
    import {showBack, animate} from 'src/config/mUtils'
    import {loadMore, getImgPath} from './mixin'
    import loading from './loading'
    import ratingStar from './ratingStar'
    
    export default {
    	data(){
    		return {
    			offset: 0, // 批次加载店铺列表,每次加载20个 limit = 20
    			shopListArr:[], // 店铺列表数据
    			preventRepeatReuqest: false, //到达底部加载数据,防止重复加载
    			showBackStatus: false, //显示返回顶部按钮
    			showLoading: true, //显示加载动画
    			touchend: false, //没有更多数据
    			imgBaseUrl,
    		}
    	},
    	mounted(){
    		this.initData();
    	},
    	components: {
    		loading,
    		ratingStar,
    	},
    	props: ['restaurantCategoryId', 'restaurantCategoryIds', 'sortByType', 'deliveryMode', 'supportIds', 'confirmSelect', 'geohash'],
    	mixins: [loadMore, getImgPath],
    	computed: {
    		...mapState([
    			'latitude','longitude'
    		]),
    	},
    	updated(){
    		// console.log(this.supportIds, this.sortByType)
    	},
    	methods: {
    		async initData(){
    			//获取数据
    			let resResponse = await shopList(this.latitude, this.longitude, this.offset, this.restaurantCategoryId);
    			console.log('resresponse',resResponse)
    			let res = resResponse.records
          console.log('res',res)
    			this.shopListArr = [...res];
    			if (res.length < 20) {
    				this.touchend = true;
    			}
    			this.hideLoading();
    			//开始监听scrollTop的值,达到一定程度后显示返回顶部按钮
    			showBack(status => {
    				this.showBackStatus = status;
    			});
    		},
    		//到达底部加载更多数据
    		async loaderMore(){
    			if (this.touchend) {
    				return
    			}
    			//防止重复请求
    			if (this.preventRepeatReuqest) {
    				return
    			}
    			this.showLoading = true;
    			this.preventRepeatReuqest = true;
    
    			//数据的定位加20位
    			this.offset += 20;
    			let res = await shopList(this.latitude, this.longitude, this.offset, this.restaurantCategoryId);
    			this.hideLoading();
    			this.shopListArr = [...this.shopListArr, ...res];
    			//当获取数据小于20,说明没有更多数据,不需要再次请求数据
    			if (res.length < 20) {
    				this.touchend = true;
    				return
    			}
    			this.preventRepeatReuqest = false;
    		},
    		//返回顶部
    		backTop(){
    			animate(document.body, {scrollTop: '0'}, 400,'ease-out');
    		},
    		//监听父级传来的数据发生变化时,触发此函数重新根据属性值获取数据
    		async listenPropChange(){
    			this.showLoading = true;
    			this.offset = 0;
    			let resResponse = await shopList(this.latitude, this.longitude, this.offset, '' , this.restaurantCategoryIds, this.sortByType, this.deliveryMode, this.supportIds);
    			let res = resResponse.records
    			this.hideLoading();
    			//考虑到本地模拟数据是引用类型,所以返回一个新的数组
    			this.shopListArr = [...res];
    		},
    		//开发环境与编译环境loading隐藏方式不同
    		hideLoading(){
    			this.showLoading = false;
    		},
    		zhunshi(supports){
    			let zhunStatus;
    			if ((supports instanceof Array) && supports.length) {
     				supports.forEach(item => {
     					if (item.icon_name === '准') {
     						zhunStatus = true;
     					}
     				})
    			}else{
    				zhunStatus = false;
    			}
    			return zhunStatus
    		},
    	},
    	watch: {
    		//监听父级传来的restaurantCategoryIds,当值发生变化的时候重新获取餐馆数据,作用于排序和筛选
    		restaurantCategoryIds: function (value){
    		  console.log('watchids',value)
    			this.listenPropChange();
    		},
    		//监听父级传来的排序方式
    		sortByType: function (value){
    			this.listenPropChange();
    		},
    		//监听父级的确认按钮是否被点击,并且返回一个自定义事件通知父级,已经接收到数据,此时父级才可以清除已选状态
    		confirmSelect: function (value){
    			this.listenPropChange();
    		}
    	}
    }
    </script>
    
    <style lang="scss" scoped>
    	@import 'src/style/mixin';
    	.shoplist_container{
    		background-color: #fff;
    		margin-bottom: 2rem;
    	}
    	.shop_li{
    		display: flex;
    		border-bottom: 0.025rem solid #f1f1f1;
    		padding: 0.7rem 0.4rem;
    	}
    	.shop_img{
    		@include wh(2.7rem, 2.7rem);
    		display: block;
    		margin-right: 0.4rem;
    	}
    	.list_back_li{
    		height: 4.85rem;
    		.list_back_svg{
    			@include wh(100%, 100%)
    		}
    	}
    	.shop_right{
    		flex: auto;
    		.shop_detail_header{
    			@include fj;
    			align-items: center;
    			.shop_title{
    				width: 8.5rem;
    				color: #333;
    				padding-top: .01rem;
    				@include font(0.65rem, 0.65rem, 'PingFangSC-Regular');
    				font-weight: 700;
    			}
    			.premium::before{
    				content: '品牌';
    				display: inline-block;
    				font-size: 0.5rem;
    				line-height: .6rem;
    				color: #333;
    				background-color: #ffd930;
    				padding: 0 0.1rem;
    				border-radius: 0.1rem;
    				margin-right: 0.2rem;
    			}
    			.shop_detail_ul{
    				display: flex;
    				transform: scale(.8);
    				margin-right: -0.3rem;
    				.supports{
    					@include sc(0.5rem, #999);
    					border: 0.025rem solid #f1f1f1;
    					padding: 0 0.04rem;
    					border-radius: 0.08rem;
    					margin-left: 0.05rem;
    				}
    			}
    		}
    		.rating_order_num{
    			@include fj(space-between);
    			height: 0.6rem;
    			margin-top: 0.52rem;
    			.rating_order_num_left{
    				@include fj(flex-start);
    				.rating_section{
    					display: flex;
    					.rating_num{
    						@include sc(0.4rem, #ff6000);
    						margin: 0 0.2rem;
    					}
    				}
    				.order_section{
    					transform: scale(.8);
    					margin-left: -0.2rem;
    					@include sc(0.4rem, #666);
    				}
    			}
    			.rating_order_num_right{
    				display: flex;
    				align-items: center;
    				transform: scale(.7);
    				min-width: 5rem;
    				justify-content: flex-end;
    				margin-right: -0.8rem;
    				.delivery_style{
    					font-size: 0.4rem;
    					padding: 0.04rem 0.08rem 0;
    					border-radius: 0.08rem;
    					margin-left: 0.08rem;
    					border: 1px;
    				}
    				.delivery_left{
    					color: #fff;
    					background-color: $blue;
    					border: 0.025rem solid $blue;
    				}
    				.delivery_right{
    					color: $blue;
    					border: 0.025rem solid $blue;
    				}
    			}
    		}
    		.fee_distance{
    			margin-top: 0.52rem;
    			@include fj;
    			@include sc(0.5rem, #333);
    			.fee{
    				transform: scale(.9);
    				@include sc(0.5rem, #666);
    			}
    			.distance_time{
    				transform: scale(.9);
    				span{
    					color: #999;
    				}
    				.order_time{
    					color: $blue;
    				}
    				.segmentation{
    					color: #ccc;
    				}
    			}
    		}
    	}
    	.loader_more{
    		@include font(0.6rem, 3);
    		text-align: center;
    	    color: #999;
    	}
    	.empty_data{
    		@inlude sc(0.5rem, #666);
    		text-align: center;
    		line-height: 2rem;
    	}
    	.return_top{
    		position: fixed;
    		bottom: 3rem;
    		right: 1rem;
    		.back_top_svg{
    			@include wh(2rem, 2rem);
    		}
    	}
    	.loading-enter-active, .loading-leave-active {
    		transition: opacity 1s
    	}
    	.loading-enter, .loading-leave-active {
    		opacity: 0
    	}
    </style>
    
    
4. 数据获取
import fetch from '../config/fetch'
import {getStore} from '../config/mUtils'

/**
 * 获取首页默认地址
 */

export const cityGuess = () => fetch('/v1/cities', {
  type: 'guess'
});


/**
 * 获取首页热门城市
 */

export const hotcity = () => fetch('/v1/cities', {
  type: 'hot'
});


/**
 * 获取首页所有城市
 */

export const groupcity = () => fetch('/v1/cities', {
  type: 'group'
});


/**
 * 获取当前所在城市
 */

export const currentcity = number => fetch('/v1/cities/' + number);


/**
 * 获取搜索地址
 */

export const searchplace = (cityid, value) => fetch('/v1/pois', {
  type: 'search',
  city_id: cityid,
  keyword: value
});


/**
 * 获取msite页面地址信息
 */

export const msiteAddress = geohash => fetch('/v1/position/pois', {
  geohash
});


/**
 * 获取msite页面食品分类列表
 */

export const msiteFoodTypes = geohash => fetch('/v2/index_entry', {
  geohash,
  group_type: '1'
});


/**
 * 获取msite商铺列表
 */

export const shopList = (latitude, longitude, offset, restaurant_category_id = '', restaurant_category_ids = '', order_by = '', delivery_mode = '', support_ids = []) => {
  let supportStr = '';
  support_ids.forEach(item => {
    if (item.status) {
      supportStr += '&support_ids[]=' + item.id;
    }
  });
  let data = {
    latitude,
    longitude,
    offset,
    limit: '20',
    'extras': 'activities',
    keyword: '',
    restaurant_category_id,
    'restaurant_category_ids': restaurant_category_ids,
    order_by,
    'delivery_mode': delivery_mode + supportStr
  };
  return fetch('/shopping/restaurants', data);
};


/**
 * 获取search页面搜索结果
 */

export const searchRestaurant = (geohash, keyword) => fetch('/v4/restaurants', {
  'extras': 'restaurant_activity',
  geohash,
  keyword,
  type: 'search'
});


/**
 * 获取food页面的 category 种类列表
 */

export const foodCategory = (latitude, longitude) => fetch('/shopping/v2/restaurant/category', {
  latitude,
  longitude
});


/**
 * 获取food页面的配送方式
 */

export const foodDelivery = (latitude, longitude) => fetch('/shopping/v1/restaurants/delivery_modes', {
  latitude,
  longitude,
  kw: ''
});


/**
 * 获取food页面的商家属性活动列表
 */

export const foodActivity = (latitude, longitude) => fetch('/shopping/v1/restaurants/activity_attributes', {
  latitude,
  longitude,
  kw: ''
});


/**
 * 获取shop页面商铺详情
 */

export const shopDetails = (shopid, latitude, longitude) => fetch('/shopping/restaurant/' + shopid, {
  latitude,
  longitude: longitude + '&extras=activities&extras=album&extras=license&extras=identification&extras=statistics'
});


/**
 * 获取shop页面菜单列表
 */

export const foodMenu = restaurant_id => fetch('/shopping/v2/menu', {
  restaurant_id
});


/**
 * 获取商铺评价列表
 */

export const getRatingList = (shopid, offset, tag_name = '') => fetch('/ugc/v2/restaurants/' + shopid + '/ratings', {
  has_content: true,
  offset,
  limit: 10,
  tag_name
});


/**
 * 获取商铺评价分数
 */

export const ratingScores = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/scores');


/**
 * 获取商铺评价分类
 */

export const ratingTags = shopid => fetch('/ugc/v2/restaurants/' + shopid + '/ratings/tags');


/**
 * 获取短信验证码
 */

export const mobileCode = phone => fetch('/v4/mobile/verify_code/send', {
  mobile: phone,
  scene: 'login',
  type: 'sms'
}, 'POST');


/**
 * 获取图片验证码
 */

export const getcaptchas = () => fetch('/v1/captchas', {}, 'POST');


/**
 * 检测帐号是否存在
 */

export const checkExsis = (checkNumber, type) => fetch('/v1/users/exists', {
  [type]: checkNumber,
  type
});


/**
 * 发送帐号
 */

export const sendMobile = (sendData, captcha_code, type, password) => fetch('/v1/mobile/verify_code/send', {
  action: "send",
  captcha_code,
  [type]: sendData,
  type: "sms",
  way: type,
  password,
}, 'POST');


/**
 * 确认订单
 */

export const checkout = (geohash, entities, shopid) => fetch('/v1/carts/checkout', {
  come_from: "web",
  geohash,
  entities,
  restaurant_id: shopid,
}, 'POST');


/**
 * 获取快速备注列表
 */

export const getRemark = (id, sig) => fetch('/v1/carts/' + id + '/remarks', {
  sig
});


/**
 * 获取地址列表
 */

export const getAddress = (id, sig) => fetch('/v1/carts/' + id + '/addresses', {
  sig
});


/**
 * 搜索地址
 */

export const searchNearby = keyword => fetch('/v1/pois', {
  type: 'nearby',
  keyword
});


/**
 * 添加地址
 */

export const postAddAddress = (userId, address, address_detail, geohash, name, phone, phone_bk, poi_type, sex, tag, tag_type) => fetch('/v1/users/' + userId + '/addresses', {
  address,
  address_detail,
  geohash,
  name,
  phone,
  phone_bk,
  poi_type,
  sex,
  tag,
  tag_type,
}, 'POST');


/**
 * 下订单
 */

export const placeOrders = (user_id, cart_id, address_id, description, entities, geohash, sig) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', {
  address_id,
  come_from: "mobile_web",
  deliver_time: "",
  description,
  entities,
  geohash,
  paymethod_id: 1,
  sig,
}, 'POST');


/**
 * 重新发送订单验证码
 */

export const rePostVerify = (cart_id, sig, type) => fetch('/v1/carts/' + cart_id + '/verify_code', {
  sig,
  type,
}, 'POST');


/**
 * 下订单
 */

export const validateOrders = ({
                                 user_id,
                                 cart_id,
                                 address_id,
                                 description,
                                 entities,
                                 geohash,
                                 sig,
                                 validation_code,
                                 validation_token
                               }) => fetch('/v1/users/' + user_id + '/carts/' + cart_id + '/orders', {
  address_id,
  come_from: "mobile_web",
  deliver_time: "",
  description,
  entities,
  geohash,
  paymethod_id: 1,
  sig,
  validation_code,
  validation_token,
}, 'POST');


/**
 * 重新发送订单验证码
 */

export const payRequest = (merchantOrderNo, userId) => fetch('/payapi/payment/queryOrder', {
  merchantId: 5,
  merchantOrderNo,
  source: 'MOBILE_WAP',
  userId,
  version: '1.0.0',
});


/**
 * 获取服务中心信息
 */

export const getService = () => fetch('/v3/profile/explain');


/**
 *兑换会员卡
 */

export const vipCart = (id, number, password) => fetch('/member/v1/users/' + id + '/delivery_card/physical_card/bind', {
  number,
  password
}, 'POST')


/**
 * 获取红包
 */

export const getHongbaoNum = id => fetch('/promotion/v2/users/' + id + '/hongbaos?limit=20&offset=0');


/**
 * 获取过期红包
 */


export const getExpired = id => fetch('/promotion/v2/users/' + id + '/expired_hongbaos?limit=20&offset=0');


/**
 * 兑换红包
 */

export const exChangeHongbao = (id, exchange_code, captcha_code) => fetch('/v1/users/' + id + '/hongbao/exchange', {
  exchange_code,
  captcha_code,
}, 'POST');


/**
 * 获取用户信息
 */

export const getUser = () => fetch('/v1/users', {user_id: getStore('user_id')});


/**
 * 手机号登录
 */

var sendLogin = (code, mobile, validate_token) => fetch('/v1/login/app_mobile', {
  code,
  mobile,
  validate_token
}, 'POST');


/**
 * 获取订单列表
 */

export const getOrderList = (user_id, offset) => fetch('/bos/v2/users/' + user_id + '/orders', {
  limit: 10,
  offset,
  t: new Date().getTime()
});

export const finishOrder = (user_id, orderid) => fetch('/bos/v1/users/' + user_id + '/orders/' + orderid + '/finish');


/**
 * 获取订单详情
 */

export const getOrderDetail = (user_id, orderid) => fetch('/bos/v1/users/' + user_id + '/orders/' + orderid + '/snapshot');


/**
 *个人中心里编辑地址
 */

export const getAddressList = (user_id) => fetch('/v1/users/' + user_id + '/addresses')

/**
 *个人中心里搜索地址
 */

export const getSearchAddress = (keyword) => fetch('v1/pois', {
  keyword: keyword,
  type: 'nearby'
})

/**
 * 删除地址
 */

export const deleteAddress = (userid, addressid) => fetch('/v1/users/' + userid + '/addresses/' + addressid, {}, 'DELETE')


/**
 * 账号密码登录
 */
export const accountLogin = (username, password, captchaCode, captchCodeId) => fetch('/v1/users/v2/login', {username, password, captchaCode, captchCodeId}, 'POST');


/**
 * 退出登录
 */
export const signout = () => fetch('/v1/users/v2/signout');


/**
 * 改密码
 */
export const changePassword = (username, oldpassWord, newpassword, confirmpassword, captcha_code) => fetch('/v2/changepassword', {
  username,
  oldpassWord,
  newpassword,
  confirmpassword,
  captcha_code
}, 'POST');

5. 具体功能开发

详见注释

<template>
    <div class="food_container">
    	<head-top :head-title="headTitle" goBack="true"></head-top>
    	<section class="sort_container">
			<!-- 分类 -->
    		<div class="sort_item" :class="{choose_type:sortBy == 'food'}" >
    			<div class="sort_item_container" @click="chooseType('food')">
    				<div class="sort_item_border">
    					<span :class="{category_title: sortBy == 'food'}">{{foodTitle}}</span>
		    			<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg" version="1.1" class="sort_icon">
			    			<polygon points="0,3 10,3 5,8"/>
			    		</svg>
    				</div>
    			</div>
	    		<transition name="showlist" v-show="category">
	    			<section v-show="sortBy == 'food'" class="category_container sort_detail_type">
	    				<section class="category_left">
	    					<ul>
	    						<li v-for="(item, index) in category" :key="index" class="category_left_li" :class="{category_active:restaurant_category_id == item.id}" @click="selectCategoryName(item.id, index)">
									<section>
										<img :src="getImgPath(item.image_url)" v-if="index" class="category_icon">
										<span>{{item.name}}</span>
									</section>
									<section>
	    								<span class="category_count">{{item.count}}</span>
	    								<svg v-if="index" width="8" height="8" xmlns="http://www.w3.org/2000/svg" version="1.1" class="category_arrow" >
							    			<path d="M0 0 L6 4 L0 8"  stroke="#bbb" stroke-width="1" fill="none"/>
							    		</svg>
									</section>
	    						</li>
	    					</ul>
	    				</section>
	    				<section class="category_right">
	    					<ul>
	    						<li v-for="(item, index) in categoryDetail" v-if="index" :key="index" class="category_right_li" @click="getCategoryIds(item.id, item.name)" :class="{category_right_choosed: restaurant_category_ids == item.id || (!restaurant_category_ids)&&index == 0}">
	    							<span>{{item.name}}</span>
	    							<span>{{item.count}}</span>
	    						</li>
	    					</ul>
	    				</section>
	    			</section>
	    		</transition>
    		</div>
			<!-- 排序 -->
    		<div class="sort_item" :class="{choose_type:sortBy == 'sort'}">
    			<div class="sort_item_container" @click="chooseType('sort')">
    				<div class="sort_item_border">
		    			<span :class="{category_title: sortBy == 'sort'}">排序</span>
		    			<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg" version="1.1" class="sort_icon">
			    			<polygon points="0,3 10,3 5,8"/>
			    		</svg>
    				</div>
    			</div>
	    		<transition name="showlist">
	    			<section v-show="sortBy == 'sort'" class="sort_detail_type">
	    				<ul class="sort_list_container" @click="sortList($event)">
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#default"></use>
								</svg>
	    						<p data="0" :class="{sort_select: sortByType == 0}">
	    							<span>智能排序</span>
	    							<svg v-if="sortByType == 0">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    						</p>
	    					</li>
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#distance"></use>
								</svg>
	    						<p data="5" :class="{sort_select: sortByType == 5}">
	    							<span>距离最近</span>
	    							<svg v-if="sortByType == 5">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    						</p>
	    					</li>
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#hot"></use>
								</svg>
	    						<p data="6" :class="{sort_select: sortByType == 6}">
	    							<span>销量最高</span>
	    							<svg v-if="sortByType == 6">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    						</p>
	    					</li>
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#price"></use>
								</svg>
	    						<p data="1" :class="{sort_select: sortByType == 1}">
	    							<span>起送价最低</span>
	    							<svg v-if="sortByType == 1">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
								</p>
	    					</li>
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#speed"></use>
								</svg>
	    						<p data="2" :class="{sort_select: sortByType == 2}">
	    							<span>配送速度最快</span>
	    							<svg v-if="sortByType == 2">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    						</p>
	    					</li>
	    					<li class="sort_list_li">
	    						<svg>
									<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#rating"></use>
								</svg>
	    						<p data="3" :class="{sort_select: sortByType == 3}">
	    							<span>评分最高</span>
	    							<svg v-if="sortByType == 3">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    						</p>
	    					</li>
	    				</ul>
	    			</section>
	    		</transition>
    		</div>
			<!-- 筛选 -->
    		<div class="sort_item" :class="{choose_type:sortBy == 'activity'}">
    			<div class="sort_item_container" @click="chooseType('activity')">
	    			<span :class="{category_title: sortBy == 'activity'}">筛选</span>
	    			<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg" version="1.1" class="sort_icon">
		    			<polygon points="0,3 10,3 5,8"/>
		    		</svg>
    			</div>
	    		<transition name="showlist">
	    			<section v-show="sortBy == 'activity'" class="sort_detail_type filter_container">
	    				<section style="width: 100%;">
	    					<header class="filter_header_style">配送方式</header>
	    					<ul class="filter_ul">
	    						<li v-for="(item, index) in Delivery" :key="index" class="filter_li" @click="selectDeliveryMode(item.id)">
	    							<svg :style="{opacity: (item.id == 0)&&(delivery_mode !== 0)? 0: 1}">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" :xlink:href="delivery_mode == item.id? '#selected':'#fengniao'"></use>
									</svg>
	    							<span :class="{selected_filter: delivery_mode == item.id}">{{item.text}}</span>
	    						</li>
	    					</ul>
	    				</section>
	    				<section style="width: 100%;">
	    					<header class="filter_header_style">商家属性(可以多选)</header>
	    					<ul class="filter_ul" style="paddingBottom: .5rem;">
	    						<li v-for="(item,index) in Activity" :key="index" class="filter_li" @click="selectSupportIds(index, item.id)">
	    							<svg v-show="support_ids[index].status" class="activity_svg">
										<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#selected"></use>
									</svg>
	    							<span class="filter_icon" :style="{color: '#' + item.icon_color, borderColor: '#' + item.icon_color}" v-show="!support_ids[index].status">{{item.icon_name}}</span>
	    							<span :class="{selected_filter: support_ids[index].status}">{{item.name}}</span>
	    						</li>
	    					</ul>
	    				</section>
	    				<footer class="confirm_filter">
	    					<div class="clear_all filter_button_style" @click="clearSelect">清空</div>
	    					<div class="confirm_select filter_button_style" @click="confirmSelectFun">确定<span v-show="filterNum">({{filterNum}})</span></div>
	    				</footer>
	    			</section>
	    		</transition>
    		</div>
    	</section>
    	<transition name="showcover">
    		<div class="back_cover" v-show="sortBy"></div>
    	</transition>
    	<section class="shop_list_container">
	    	<shop-list :geohash="geohash" :restaurantCategoryId="restaurant_category_id" :restaurantCategoryIds="restaurant_category_ids" :sortByType='sortByType' :deliveryMode="delivery_mode" :confirmSelect="confirmStatus" :supportIds="support_ids" v-if="latitude"></shop-list>
        <shop-list :geohash="geohash" :restaurantCategoryId="restaurant_category_id" :restaurantCategoryIds="restaurant_category_ids" :sortByType='sortByType' :deliveryMode="delivery_mode" :confirmSelect="confirmStatus" :supportIds="support_ids" v-if="latitude"></shop-list>
    	</section>
    </div>
</template>

<script>
import { mapState, mapMutations } from "vuex";
import headTop from "src/components/header/head";
import shopList from "src/components/common/shoplist";
import { getImgPath } from "src/components/common/mixin";
import {
  msiteAddress,
  foodCategory,
  foodDelivery,
  foodActivity
} from "src/service/getData";

export default {
  data() {
    return {
      geohash: "", // city页面传递过来的地址geohash
      headTitle: "", // msiet页面头部标题
      foodTitle: "", // 排序左侧头部标题
      restaurant_category_id: "", // 食品类型id值
      restaurant_category_ids: "", //筛选类型的id
      sortBy: "", // 筛选的条件
      category: null, // category分类左侧数据
      categoryDetail: null, // category分类右侧的详细数据
      sortByType: null, // 根据何种方式排序
      Delivery: null, // 配送方式数据
      Activity: null, // 商家支持活动数据
      delivery_mode: null, // 选中的配送方式
      support_ids: [], // 选中的商铺活动列表
      filterNum: 0, // 所选中的所有样式的集合
      confirmStatus: false // 确认选择
    };
  },
  created() {
    this.initData();
  },
  mixins: [getImgPath],
  components: {
    headTop,
    shopList
  },
  computed: {
    ...mapState(["latitude", "longitude"])
  },
  methods: {
    ...mapMutations(["RECORD_ADDRESS"]),
    //初始化获取数据
    async initData() {
      //获取从msite页面传递过来的参数
      this.geohash = this.$route.query.geohash;
      this.headTitle = this.$route.query.title;
      this.foodTitle = this.headTitle;
      this.restaurant_category_id = this.$route.query.restaurant_category_id;
      console.log('geohash',this.geohash)
      console.log('restaurant_category_id',this.restaurant_category_id)
      //防止刷新页面时,vuex状态丢失,经度纬度需要重新获取,并存入vuex
      if (!this.latitude) {
        console.log('经度' ,this.latitude)
        //获取位置信息
        let res = await msiteAddress(this.geohash);
        console.log('位置信息',res)
        // 记录当前经度纬度进入vuex
        this.RECORD_ADDRESS(res);
      }
      //获取category分类左侧数据
      this.category = await foodCategory(this.latitude, this.longitude);
      console.log(this.category)
      //初始化时定位当前category分类左侧默认选择项,在右侧展示出其sub_categories列表
      this.category.forEach(item => {
        if (this.restaurant_category_id == item.id) {
          this.categoryDetail = item.sub_categories;
        }
      })

      console.log(1)
      //获取筛选列表的配送方式
      this.Delivery = await foodDelivery(this.latitude, this.longitude);
      //获取筛选列表的商铺活动
      console.log('delivery',this.Delivery)
      this.Activity = await foodActivity(this.latitude, this.longitude);
      console.log('ac1',this.Activity)
      //记录support_ids的状态,默认不选中,点击状态取反,status为true时为选中状态
      this.Activity.forEach((item, index) => {
        this.support_ids[index] = { status: false, id: item.id };
      });
      console.log('activity',this.Activity)
    },
    // 点击顶部三个选项,展示不同的列表,选中当前选项进行展示,同时收回其他选项
    async chooseType(type) {
      if (this.sortBy !== type) {
        this.sortBy = type;
        //food选项中头部标题发生改变,需要特殊处理
        if (type == "food") {
          this.foodTitle = "分类";
        } else {
          //将foodTitle 和 headTitle 进行同步
          this.foodTitle = this.headTitle;
        }
      } else {
        //再次点击相同选项时收回列表
        this.sortBy = "";
        if (type == "food") {
          //将foodTitle 和 headTitle 进行同步
          this.foodTitle = this.headTitle;
        }
      }
    },
    //选中Category左侧列表的某个选项时,右侧渲染相应的sub_categories列表
    selectCategoryName(id, index) {
      //第一个选项 -- 全部商家 因为没有自己的列表,所以点击则默认获取选所有数据
      if (index === 0) {
        this.restaurant_category_ids = null;
        this.sortBy = "";
        //不是第一个选项时,右侧展示其子级sub_categories的列表
      } else {
        this.restaurant_category_id = id;
        this.categoryDetail = this.category[index].sub_categories;
      }
    },
    //选中Category右侧列表的某个选项时,进行筛选,重新获取数据并渲染
    getCategoryIds(id, name) {
	    console.log(id, name)
      this.restaurant_category_ids = id;
      this.restaurant_category_id = id;
      this.sortBy = "";
      this.foodTitle = this.headTitle = name;
    },
    //点击某个排序方式,获取事件对象的data值,并根据获取的值重新获取数据渲染
    sortList(event) {
      let node;
      // 如果点击的是 span 中的文字,则需要获取到 span 的父标签 p
      if (event.target.nodeName.toUpperCase() !== "P") {
        node = event.target.parentNode;
      } else {
        node = event.target;
      }
      this.sortByType = node.getAttribute("data");
      this.sortBy = "";
    },
    //筛选选项中的配送方式选择
    selectDeliveryMode(id) {
      //delivery_mode为空时,选中当前项,并且filterNum加一
      if (this.delivery_mode == null) {
        this.filterNum++;
        this.delivery_mode = id;
        //delivery_mode为当前已有值时,清空所选项,并且filterNum减一
      } else if (this.delivery_mode == id) {
        this.filterNum--;
        this.delivery_mode = null;
        //delivery_mode已有值且不等于当前选择值,则赋值delivery_mode为当前所选id
      } else {
        this.delivery_mode = id;
      }
    },
    //点击商家活动,状态取反
    selectSupportIds(index, id) {
      //数组替换新的值
      this.support_ids.splice(index, 1, {
        status: !this.support_ids[index].status,
        id
      });
      //重新计算filterNum的个数
      this.filterNum = this.delivery_mode == null ? 0 : 1;
      this.support_ids.forEach(item => {
        if (item.status) {
          this.filterNum++;
        }
      });
    },
    //只有点击清空按钮才清空数据,否则一直保持原有状态
    clearSelect() {
      this.support_ids.map(item => (item.status = false));
      this.filterNum = 0;
      this.delivery_mode = null;
    },
    //点击确认时,将需要筛选的id值传递给子组件,并且收回列表
    confirmSelectFun() {
      //状态改变时,因为子组件进行了监听,会重新获取数据进行筛选
      this.confirmStatus = !this.confirmStatus;
      this.sortBy = "";
    }
  }
};
</script>

<style lang="scss" scoped>
@import "src/style/mixin";
.food_container {
  padding-top: 3.6rem;
}
.sort_container {
  background-color: #fff;
  border-bottom: 0.025rem solid #f1f1f1;
  position: fixed;
  top: 1.95rem;
  right: 0;
  width: 100%;
  display: flex;
  z-index: 13;
  box-sizing: border-box;
  .sort_item {
    @include sc(0.55rem, #444);
    @include wh(33.3%, 1.6rem);
    text-align: center;
    line-height: 1rem;
    .sort_item_container {
      @include wh(100%, 100%);
      position: relative;
      z-index: 14;
      background-color: #fff;
      box-sizing: border-box;
      padding-top: 0.3rem;
      .sort_item_border {
        height: 1rem;
        border-right: 0.025rem solid $bc;
      }
    }
    .sort_icon {
      vertical-align: middle;
      transition: all 0.3s;
      fill: #666;
    }
  }
  .choose_type {
    .sort_item_container {
      .category_title {
        color: $blue;
      }
      .sort_icon {
        transform: rotate(180deg);
        fill: $blue;
      }
    }
  }
  .showlist-enter-active,
  .showlist-leave-active {
    transition: all 0.3s;
    transform: translateY(0);
  }
  .showlist-enter,
  .showlist-leave-active {
    opacity: 0;
    transform: translateY(-100%);
  }
  .sort_detail_type {
    width: 100%;
    position: absolute;
    display: flex;
    top: 1.6rem;
    left: 0;
    border-top: 0.025rem solid $bc;
    background-color: #fff;
  }
  .category_container {
    .category_left {
      flex: 1;
      background-color: #f1f1f1;
      height: 16rem;
      overflow-y: auto;
      span {
        @include sc(0.5rem, #666);
        line-height: 1.8rem;
      }
      .category_left_li {
        @include fj;
        padding: 0 0.5rem;
        .category_icon {
          @include wh(0.8rem, 0.8rem);
          vertical-align: middle;
          margin-right: 0.2rem;
        }
        .category_count {
          background-color: #ccc;
          @include sc(0.4rem, #fff);
          padding: 0 0.1rem;
          border: 0.025rem solid #ccc;
          border-radius: 0.8rem;
          vertical-align: middle;
          margin-right: 0.25rem;
        }
        .category_arrow {
          vertical-align: middle;
        }
      }
      .category_active {
        background-color: #fff;
      }
    }
    .category_right {
      flex: 1;
      background-color: #fff;
      padding-left: 0.5rem;
      height: 16rem;
      overflow-y: auto;
      .category_right_li {
        @include fj;
        height: 1.8rem;
        line-height: 1.8rem;
        padding-right: 0.5rem;
        border-bottom: 0.025rem solid $bc;
        span {
          color: #666;
        }
      }
      .category_right_choosed {
        span {
          color: $blue;
        }
      }
    }
  }
  .sort_list_container {
    width: 100%;
    .sort_list_li {
      height: 2.5rem;
      display: flex;
      align-items: center;
      svg {
        @include wh(0.7rem, 0.7rem);
        margin: 0 0.3rem 0 0.8rem;
      }
      p {
        line-height: 2.5rem;
        flex: auto;
        text-align: left;
        text-indent: 0.25rem;
        border-bottom: 0.025rem solid $bc;
        @include fj;
        align-items: center;
        span {
          color: #666;
        }
      }
      .sort_select {
        span {
          color: $blue;
        }
      }
    }
  }
  .filter_container {
    flex-direction: column;
    align-items: flex-start;
    min-height: 10.6rem;
    background-color: #f1f1f1;
    .filter_header_style {
      @include sc(0.4rem, #333);
      line-height: 1.5rem;
      height: 1.5rem;
      text-align: left;
      padding-left: 0.5rem;
      background-color: #fff;
    }
    .filter_ul {
      display: flex;
      flex-wrap: wrap;
      padding: 0 0.5rem;
      background-color: #fff;
      .filter_li {
        display: flex;
        align-items: center;
        border: 0.025rem solid #eee;
        @include wh(4.7rem, 1.4rem);
        margin-right: 0.25rem;
        border-radius: 0.125rem;
        padding: 0 0.25rem;
        margin-bottom: 0.25rem;
        svg {
          @include wh(0.8rem, 0.8rem);
          margin-right: 0.125rem;
        }
        span {
          @include sc(0.4rem, #333);
        }
        .filter_icon {
          @include wh(0.8rem, 0.8rem);
          font-size: 0.5rem;
          border: 0.025rem solid $bc;
          border-radius: 0.15rem;
          margin-right: 0.25rem;
          line-height: 0.8rem;
          text-align: center;
        }
        .activity_svg {
          margin-right: 0.25rem;
        }
        .selected_filter {
          color: $blue;
        }
      }
    }
    .confirm_filter {
      display: flex;
      background-color: #f1f1f1;
      width: 100%;
      padding: 0.3rem 0.2rem;
      .filter_button_style {
        @include wh(50%, 1.8rem);
        font-size: 0.8rem;
        line-height: 1.8rem;
        border-radius: 0.2rem;
      }
      .clear_all {
        background-color: #fff;
        margin-right: 0.5rem;
        border: 0.025rem solid #fff;
      }
      .confirm_select {
        background-color: #56d176;
        color: #fff;
        border: 0.025rem solid #56d176;
        span {
          color: #fff;
        }
      }
    }
  }
}
.showcover-enter-active,
.showcover-leave-active {
  transition: opacity 0.3s;
}
.showcover-enter,
.showcover-leave-active {
  opacity: 0;
}
.back_cover {
  position: fixed;
  @include wh(100%, 100%);
  z-index: 10;
  background-color: rgba(0, 0, 0, 0.3);
}
</style>

 类似资料: