6.7旅游网站小项目练习

项目预热

环境安装:

1.下载Node.js 通过cmd指令node -v npm -v查看是否完成安装

2.注册码云,新建项目。通过Git对本地和线上项目做关联

3.下载Git 通过cmd指令git --version查看是否完成安装

4.在Git Bash上通过Linux命令ssh-keygen -t rsa -C "邮箱" 获取公钥,cat ~/.ssh/id_rsa.pub 查看公钥。在码云上添加公钥,通过公钥与私钥的配对实现了本地与线上的绑定

5.克隆下载, git clone 把线上的项目下载到本地

6.命令行工具:Vue提供了一个官方的cli
通过指令npm install --global vue-cli 安装cli
安装之后,通过指令 vue init webpack my-project 在文件夹my-project中创建一个基于webpack打包工具的项目
在这里插入图片描述
创建完成后进入文件夹,运行指令npm run dev 会进行一个自动化的打包
在这里插入图片描述
得到一个网址
在这里插入图片描述

7.本地项目创建完成后,创建后的若干文件添加到线上仓库(码云)中
git status本地新增的文件

把这些新增的文件添加到线上仓库:
git add . 先添加到git的缓冲区
git commit -m 'project initialized'(注意要配置用户名和邮箱)
git push

··········································································································································································

单文件组件与Vue中的路由:

在实际项目开发中,子组件一般放在独立的以vue为后缀的文件中,这些文件就叫做单文件组件
单文件组件一般由三部分组成:template模板标签、script逻辑标签、style样式标签

路由就是根据网址的不同,返回不同的内容给用户

//根组件中的template标签内
<router-view/>//显示的是当前路由地址所对应的内容

··········································································································································································

多页应用与单页应用:

每一次页面跳转的时候,后台服务器都会返回一个新的html文档(可以在Network中的Doc查看),这种类型的网站就称作多页面应用
页面跳转=>返回html
优点:首屏时间快,SEO(搜索引擎优化)效果好
缺点:页面切换慢

用vue写的项目是单页面应用,每当页面跳转时,不会请求html文档

<router-link to=""></router-link>//vue中的跳转(相当于html中的a标签)

why?
JS会感知到URL的变化,通过JS感知到变化之后,JS可以动态的把当前页面的内容清除,再把下一个页面的内容挂载到页面上,所以这个路由不是后端来做,而是前端来做
页面跳转=>JS渲染
优点:页面切换快
缺点:首屏时间稍慢,SEO差

PS:搜索引擎只认html内容,不认js内容,所以单页首屏慢,因此,Vue还提供了一些其他技术,比如服务器端渲染等等。解决了单页应用的缺点

··········································································································································································

项目代码初始化

1.添加网页的viewport这个meta标签

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

2.引入reset.css(重置页面样式表)
因为在不同的手机浏览器上,默认的样式是不统一的
在入口文件(main.js)中import

import './assets/styles/reset.css'

3.引入border.css解决1像素边框的问题(手机分辨率造成的问题)

import './assets/styles/border.css'

4.移动端点击延迟问题
移动端开发者,某些机器浏览器上,使用click事件会延迟300ms执行
引入fastclick库
npm install fastclick --save

import fastclick from 'fastclick'
fastclick.attach(document.body)

5.iconfont(图标制作)
是个网站,注册之后就可以用来获取图标


首页

页面组件化,把一个复杂的页面拆分成一部分一部分的内容
··········································································································································································
一、Home页面(首页)header区
知识点:

1.添加一些依赖包
1)stylus(css样式辅助开发工具) 方便我们快速的编写css代码
2)stylus-loader等等
添加方法与引入fastclick包相同npm install stylus--save

2.关于rem的问题,在之前引入的reset.css中,默认了html的font-size为50px。所以在项目开发时,样式书写时,1rem=50px,按照这个关系来决定大小

3.通过iconfont下载的图标资源,通过引入iconfont.css(并且要把那些字体文件放到项目中)之后可以使用。注意。要在标签的class属性中加上"iconfont"<span class="iconfont">&#xe632;</span>

4.代码优化,比如整个项目的背景颜色,可以放到一个***.styl的文件中,在样式中引入这个文件,就可以直接使用了,这样方便代码的更新及维护

5.在build文件夹下的webpack.base文件中可以对路径设置简洁的别名,需要调用时就简洁很多

header区vue组件相关代码

··········································································································································································

二、Home页面(首页)图片轮播区

过程(1和3适用于其他功能的实现步骤)
1.首先,在码云线上创建一个分支,在终端命名窗口中,把线上新添加的分支下载到本地:git pull,然后切换到这个新分支:git checkout ‘分支名’(在实际的项目开发中,实现一个新的功能都会放到一个新的分支当中,最后再整合)

2.安装一个第三方的轮播插件vue-awesome-swiper
npm install vue-awesome-swiper@2.6.7 --save (2.6.7版本稳定)

①在入口文件(main.js)内引入该插件

import VueAwesomeSwiper from 'vue-awesome-swiper'

②要使用该插件还要引入该文件的css文件

import 'swiper/dist/css/swiper.css'

③使用该插件

Vue.use(VueAwesomeSwiper)

3.功能代码实现之后,把本地代码上传到线上,上传的是刚刚创建的分支,然后再把这个分支合并到master分支上

git add .
git commit -m
git push

切换到master分支git checkout master

把刚刚创建的index-swiper分支上新增加的内容合并到本地的master分支git merge origin/index-swiper

最后在git push一下,把本地master提交到线上

知识点:

1.如果要设置宽高比(不能直接设置height)
高比宽=n%则要设置:
在轮播标签外增加一个div,div设置样式:

width:100%
height: 0
overflow: hidden
padding-bottom: n%

或者可以直接设置:height:nvw(这样写存在某些浏览器的兼容问题)
这样做的原因是为了防止网速过慢,图片还未加载完成,后面的内容跑到图片的位置

2.样式穿透,由于组件化,所以为了让当前页面的样式只应用于当前页面(即,只),需要在样式属性中加上scoped
但是,在某个组件中,某个标签没有class=“a”的样式(在别的组件中有,为什么会有呢,我也不知道,可能是由于插件的原因吧),却又想修改这个样式,就需要穿透

.x >>> .a
	background: red
	//意思是:当前页面class=“x”的标签下的所有标签,只要出现class=“a”,那么背景色就是红色。这就是穿透,虽然在这个页面中标签的class并没有=a的

3.关于这个插件的数据返回属性

swiperOption: {
            pagination: '.swiper-pagination',
            loop: true
        }

子组件的返回的数据属性。
swiperOption是绑定上swiper标签

<swiper :options="swiperOption">

pagination: '.swiper-pagination’是显示图片上的小圆点(页码)
loop: true是实现图片轮播的循环(第一张右滑=>最后一张。。。)

图片轮播区Vue组件相关代码

··········································································································································································

三、Home页面(首页)图标区域页面布局

过程
1.码云新建分支index-icons
2.代码书写(重点是css样式的书写)
3.代码git到线上

知识点:
1.比较复杂的是一些css样式属性的处理
先写一个存放所有图标的div布局,称作icons
再写一个单个div,称作icon,在这个icon中存放图片与文本
在写一个div,当作包裹图片的盒子,他的主要作用是落实位置关系
剩下的就是图片和文本了

2.如果图标过多,需要轮播,那么在外层包裹轮播标签(<swiper><swiper-slide>)即可(这些标签是上文下载的插件,引入库之后可以直接使用)
but,因为循环是循环在div标签而不是swiper-slide标签,所以如果要让轮播正常显示,需要他的计算属性来设置

computed: {
        pages () {
            const pages=[]
            this.iconList.forEach((item,index)=>{
                const page=Math.floor(index/8)
                if(!pages[page]){
                    pages[page]=[]
                }
                pages[page].push(item)
            })
            return pages
        }
    }

//大体的意思就是通过遍历iconList,判断生成几页(一页里面放8个)。把item(即遍历来iconList中的每一项给push到pages这个数组变量中),然后返回这个数组。
注意:计算属性设置完之后,在swiper-slide标签中有遍历了,遍历的就是pages(有几页)。在div标签中遍历的就变成了page(之前是iconList,因为每一项赋值给了pages:pages[page].push(item))

3.有时字体会超出容器范围,这时就需要设置三个属性。分别是

overflow: hidden
white-space: nowrap
text-overflow: ellipsis

又因为这种情况经常出现,所以可以把他封装起来。方便调用
新建一个mixins.stly文件写入:

ellipsis()
	overflow: hidden
	white-space: nowrap
	text-overflow: ellipsis

调用方法与常用样式代码优化(上文header区代码优化)相同

图标区域页面布局Vue组件代码

··········································································································································································

四、Home页面(首页)推荐组件区

同上,创建分支。。。。上传线上

知识点:
1.上文有个关于文本溢出变成省略号的知识点。but,如果该文本有个flew的属性,那就不会显示小数点了,解决方法就是在父标签的样式中加上:min-width: 0就ok了

也没什么新的知识点,之前的也是,都是一些关于css样式的问题。多接触就会了

2.倘若在img外包裹一个div,上文说了作用是落实位置关系,除此之外,还可以通过这个包裹设置宽高比

推荐组件区与一日游vue组件代码


Ajax获取页面数据

ajax:动态的获取页面的数据内容,不像练习中的把数据写死在页面中

过程:
1.创建分支index-ajax
2.安装第三方模块axios npm install axios --save
3.添加模拟数据
4.子组件获取模拟数据
5.上线

知识点:
1.由于vue组件化的原因,不可能让每个组件页面都发送一个ajax请求,这大大降低了性能。因此,合理化的解决方法是父组件中发送一个ajax请求,父组件获得数据后再传给子组件

父组件(Home)ajax请求(即引入axios):

import axios from 'axios'

使用axios:(生命周期函数mounted)

  methods: {
    getHomeInfo () {
      axios.get('/api/index.json')
        .then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      console.log(res)
    }
  },
  mounted () {
    this.getHomeInfo()
  }

2.在没有后端支持下(上文axios获取路径为无),怎样实现后端模拟
在static文件中添加数据文件(静态文件都放在static文件夹中,且只有这个文件夹可被外部访问)
注意:由于该文件内容是模拟数据,不希望提交到线上,做法是在.gitignore文件中加上该文件夹的路径,那么该文件夹就不会提交到线上

3.假设数据放在/sattic/mock/index.json中,那么使用axios应该为接口模拟地址axios.get('/sattic/mock/index.json')。但是,如果项目上线,这种地址就是错误的。这就需要把地址替换成/api这种格式
转发机制: 把api下所有的对json文件的请求,转发到本地的mock文件夹下就ok了。在config文件夹下的index.js文件中,有一个proxyTable配置项

proxyTable: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api': '/static/mock'
        }
      }
    },

意思是当我们请求api这个目录时,会把请求转发到这台服务器的端口上,并且请求路径如果是api开头,就替换成/static/mock
(这个功能是webpack-dev-server提供的)(如果修改了配置项,需要重启项目)

··········································································································································································

首页父子组件中的数据传递

在Home中返回各种数据(如上文所说,只需在这个父组件中发生一个ajax请求),通过v-bind传递给子组件,子组件props之后就可以使用
具体的数据怎么得到呢。就在上文的那个getHomeInfoSucc函数中写

getHomeInfoSucc (res) {
      res = res.data//res是那个index.json,里面有很多属性,这里只要data属性
      if(res.ret && res.data){//判断ret是否为true和是否有数据
        const data = res.data//只要res的data项,不需要ret了
        this.city = data.city//把json文件中的数据赋值给这个父组件中的数据
      }
    }

其他的数据同理

遇到的问题:
在图片轮播是,默认的首次出现的图片是最后一张
原因:在轮播页面构建时,ajax还未请求,子组件接收的父组件的List是空数组,只有ajax请求之后,这个List才有数据。才会渲染成轮播图,因为一开始swiper的创建是根据空数组创建,所以显示的所有图片的最后一张
解决方法:在轮播页面的swiper标签中加上v-if=“list.length”解释:让swiper标签的创建由真实的数据创建,而不是空数组创建(在模板中尽量不要出现.length这种逻辑性的代码,因此,可以添加计算属性,一个函数返回list.length。让v-if=“这个函数”)


城市列表页

1.线上创建新分支city-router(通过改变路由地址改变所显示的内容)(当实现一个新功能时,如下文中的搜索功能、字母表功能等,都要创建新分支,便于维护与修改)
2.路由配置项

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },{//城市列表页
      path: '/city',
      name: 'City',
      component: City
    }
  ]
})

3.城市列表页各组件代码
4.利用ajax实现城市的动态数据渲染
5.实现功能(兄弟组件间的联动,即字母表与列表联动;Vuex数据共享)
6.合并上线

城市列表页head区代码
城市列表页搜索区代码

知识点:
1.由于列表城市过多,导致页面溢出。于是采用Better-scroll(一个第三方的包)解决城市列表布局等等类似的问题
①.安装包:npm install better-scorll --save
②.导包:在哪导呢,哪一组件需要滚动在哪导

import BScroll from '@better-scroll/core'

③.创建一个实例属性scroll,他等于一个new BScroll(Bscroll对象),并把需要滚动的那个标签内容(.wrapper)传入进去

let wrapper = document.querySelector('.wrapper')//传统dom写法
let scroll = new BScroll(wrapper)

mounted () {//Vue
        this.scroll = new BScroll(this.$refs.wrapper)
    }

2.右侧字母表垂直方向居中

display: flex
flex-direction: column
justify-content: center

城市列表页右侧字母表区代码

3.兄弟组件间的联动
可以通过子组件=>父组件=>子组件的方式进行兄弟组件的信息传递

①:点击字母表,实现列表的滚动效果
数据传递之后,对这个数据绑定一个侦听器,来实现列表的滚动

watch: {
        letter () {//letter传递进来的数据
            if(this.letter){//如果数据不为空
                const element=this.$refs[this.letter][0]//为列表循环绑定ref,因为这个ref是循环绑定的,所以是个数组,要加上[0]才是一个DOM元素
                this.scroll.scrollToElement(element)//better-scroll自带的api,选择展示哪一个地方,参数element是个dom元素
            }
        }
    }

②:滚动字母表,实现列表的滚动

handleTouchStart () {
            this.touchStatus=true
        },
        handleTouchMove (e) {//实现滚动字母表,列表相应滚动的功能
            if(this.touchStatus){
                //通过距离得出是哪个字母
                const startY=this.$refs['A'][0].offsetTop//(第0项才是dom元素)获取字母表中A的距离顶部(和下文的页面最顶部不同,所以下文把距离减出来)的高度
                const touchY=e.touches[0].clientY-79//touchmove事件自带的参数有一个touches的数组,第0项表示当前手指的信息,clientY表示距离页面最顶部的距离,-79是把header区减出来
                const index=Math.floor((touchY-startY)/20)
                if(index >= 0 && index < this.letters.length){
                    this.$emit('transmitLetter',this.letters[index])//值传递给父组件
                }
            }
        },
        handleTouchEnd () {
            this.touchStatus=false
        }

补充:代码优化
1)有些数据的值是固定不变的,比如handleTouchMove 方法中的 const startY=this.$refs[‘A’][0].offsetTop,他是计算的a到顶部的距离
但是在每次执行这个方法时,都要重新定义。因此,可以把他提出来放到datat中,然后加一个updated生命周期函数

data () {
        return {
            touchStatus: false,//标识位,只有触发handleTouchStart之后才可以触发handleTouchMove
            startY: 0,
            timer: null
        }
    },
    updated () {
        this.startY=this.$refs['A'][0].offsetTop
    },

运行过程:一开始页面挂载,startY为0,且父组件传递过来的cities为空 = >通过ajax获取数据后,父组件传递过来的cities不为空,这时执行updated生命周期函数,计算出startY的值

2)handleTouchMove方法的执行频率是非常高的,影响了浏览器性能,可以加一个定时器解决频率过高的问题

handleTouchMove (e) {//实现滚动字母表,列表相应滚动的功能
            if(this.touchStatus){
                //通过距离得出是哪个字母
                if(this.timer){
                    clearTimeout(this.timer)
                }
                this.timer=setTimeout(()=>{
                    //const startY=this.$refs['A'][0].offsetTop(优化了)//(第0项才是dom元素)获取字母表中A的距离顶部(和下文的页面最顶部不同,所以下文把距离减出来)的高度
                    const touchY=e.touches[0].clientY-79//touchmove事件自带的参数有一个touches的数组,第0项表示当前手指的信息,clientY表示距离页面最顶部的距离,-79是把header区减出来
                    const index=Math.floor((touchY-this.startY)/20)
                    if(index >= 0 && index < this.letters.length){
                        this.$emit('transmitLetter',this.letters[index])
                    }
                },20)
            }
        },

原理:手指的滚动延迟20毫秒执行(写在定时器函数里了),假如在20ms之内,手指又滚动了,就把之前的定时器clear,重新执行这一次的操作,也是延迟20ms。这种方法叫做函数节流

4.搜索逻辑的实现
①:input框双向绑定数据项keyword,创建新数组list
②:为keyword数据项绑定侦听器watch
③:如果keyword为空,则list为空,不为空进行下列操作(即把传递进来的cities筛选之后赋值给list)

this.timer=setTimeout(()=>{//节流,同上
                const resule=[]
                for(let i in this.cities){//遍历cities对象
                    this.cities[i].forEach((value)=>{//遍历cities对象中的每一项
                        if(value.spell.indexOf(this.keyword) > -1|| value.name.indexOf(this.keyword) > -1){//如果value的spell或name中包含keyword字符串
                            resule.push(value)//符合的push到result
                        }
                    })
                }
                this.list=resule//结果放到list中
            },100)

④:为循环该list的li标签的父标签div添加v-show="keyword",保证页面不重叠。并且添加一个具有提示功能的div

<li class="search-item border-bottom" v-show="hasNoData">No Search</li>

5.Vuex实现数据共享
①,安装vuexnpm install vuex --save
②,创建vuex区域

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {//公用数据存放的地方
        city: '北京'
    }
})

再在入口文件mian.js中引入

import store from './store/index.js'

③:组件使用公用的数据

{{this.$store.state.city}}//北京(因为在入口文件引入,所以所有组件都可以直接使用)

④:改变内容:
1)点击按钮,向store传递一个actions ‘changeCity’,参数为city

methods: {
        handleCityClick (city) {
            this.$store.dispatch('changeCity',city)
        }
    }

2)Vuex.Store中三个属性,分别是actions、mutations、state
changeCity 传递过来之后,通过commit方法向mutations传递一个changeCity,在mutations中更改内容
参数见注释

state: {
        city: '济南'
    },
    actions: {
        changeCity (ctx, city) {//第一个是上下文参数,用来调用commit,第二个参数是传递过来的参数
            ctx.commit('changeCity',city)//第一个参数是向mutations传递的方法名,第二个参数同上
        }
    },
    mutations: {
        changeCity (state, city) {//mutations中第一个参数是state属性,第二个参数是传递过来的
            state.city=city
        }
    }

补充:
1.如果操作不复杂,不是异步操作,可以省略掉action这一步,组件直接调用了mutations。就是把dispatch换成commit
2.页面跳转不仅可以通过标签的形式(router-link)还可以通过js的形式

this.$router.push('/')//跳转到首页 

6.localStorage
//state文件下,trycatch是为了防止某些浏览器不支持localStorage

let defaultCity="济南"
try {
    if(localStorage.city){
        defaultCity=localStorage.city
    }
} catch (e) {}

export default {
    city: defaultCity
}

代码优化:
还记得vuex数据共享时{{this.$store.state.city}}这一块,可以更加简洁

<script>
import {mapState} from 'vuex'
export default {
  name: 'HomeHeader',
  computed: {
	  ...mapState(['city'])
  }
}
</script>

把city中的内容映射在一个计算属性city中,这样直接写{{this.city}}即可
除此之外,还可以映射mutations

import {mapState,mapMutations} from 'vuex'
methods: {
        handleCityClick (city) {
            //this.$store.dispatch('changeCity',city)
            this.changeCity(city)
            this.$router.push('/')
        },
        ...mapMutations(['changeCity'])//把mutations中的changeCity映射到这个组件的一个名叫changeCity的方法中
    }

7.keep-alive标签优化网页

<keep-alive>
      <!--显示路由所对应的内容-->
      <router-view/>
   </keep-alive>

作用是路由内容放到内存中,下次再进这个路由,不用重新获取数据(即不用重新渲染页面,不用再执行mounted生命周期函数了)

but:比如当我们切换城市时,首页的热销推荐部分应该对应切换到的城市,因此,这部分需要重新获取数据
虽然使用keep-alive标签后第二次加载该页面时mounted生命周期函数不再执行了,但是多出一个生命周期函数activated,因此,可以在生命周期函数activated里面ajax数据
比如:

activated () {
    if(this.lastCity !== this.city){//切换城市后判断是否和之前的城市(lastCity)相同
      this.lastCity = this.city//不想等的话,让lastCity=city,为了下次切换的判断
      this.getHomeInfo()
    }
  }

详情页面

一.动态路由及banner布局
动态路由实现:在首页的每日推荐组件中,循环遍历数据创建的li标签,改为router-link标签,添加属性tag=“li”(默认的话router-link类似a标签,使他为li标签)、:to=“’/detail/‘+id”(设置循环动态的绑定,指向detail/id,id为循环项自带的id数)。
然后配置路由项

{
      path: '/detail/:id',
      name: 'Detail',
      component: Detail
    }

知识点:
//渐变

background-image: linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.8))

二,公用图片画廊组件拆分
这种公用的组件可以放在单独的文件夹中

知识点:

1.内容垂直居中

display: flex
flex-direction: column
justify-content: center

2.swiper下标数字表示

paginationType: 'fraction'

3.轮播页面由隐藏变为现实时,重新计算width会出错,设置下列两个属性,表示有改变则刷新

observer: true,
observeParents: true

三.header区

知识点:

header区的渐隐渐现效果

let opacity = top / 140//实现渐隐渐现效果
opacity = opacity > 1 ? 1 : opacity
this.opacityStyle = { opacity }

四.对全局事件的解绑
在每个组件中绑定的事件,如click。。。。在退出这个组件时就不会执行。
但是如果采用了全局事件,则不关进入哪一个页面,都会执行此方法,影响了性能
比如header区的。这里的window就是一个全局事件

activated () {//添加一个scroll事件,执行handleScroll方法
        window.addEventListener('scroll',this.handleScroll)
    }

因此,要对全局事件进行解绑

deactivated () {//页面隐藏时,移除这个scroll事件
        window.removeEventListener('scroll',this.handleScroll)
    }

补充:activated和deactivated是添加了keep-alive标签后,增加的生命周期函数(见城市列表页知识点七)

五.使用递归组件实现详情页列表

<div v-if="item.children" class="item-children">
    <detail-list :list="item.children"></detail-list>
 </div>

组件的自我嵌套实现递归

六.Ajax获取动态数据

知识点:
1.页面跳转时,距离顶部的位置与上一页的相同,而想让他默认顶部
可以在路由配置项中加入下列代码

scrollBehavior (to, from, savedPosition) {
      return {x: 0, y: 0}
    }

2.记得上文的keep-alive标签,是把路由跳转的内容放到内存中,下次再进入这个路由页面时,不用重新加载数据了
但有时候需要重新加载,比如切换城市后进入首页,需要重新加载。再比如进入详情页面(详情页是根据路由的id项决定的,实际上,detail组件只有一个,根据id的不同,加载不同的内容)时,所以也需要重新加载。
除了采用activated生命周期函数外,还可以使用exclude的方法

  <keep-alive exclude="Detail">//keep-alive把name为Detail的组件排除
      <!--显示路由所对应的内容-->
      <router-view/>
    </keep-alive>

补充:还记得关于详情页渐隐渐现的header区,因为整个Detail组件被keep-alive排除,所以就需要把添加事件的方法从activated函数中转到mountedt中

 mounted () {
        window.addEventListener('scroll',this.handleScroll)
    },
    destroyed () {
        window.removeEventListener('scroll',this.handleScroll)
    }
    /*activated () {
        window.addEventListener('scroll',this.handleScroll)
    },
    deactivated () {
        window.removeEventListener('scroll',this.handleScroll)
    }*/

3.添加渐隐渐现的动画组件

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值