目录
项目准备✌️
样式开发👍
防抖👊
底部横条✌️
样式编写 👐
本项目的源代码在文章末尾哦
我们先看一下在本文中我们这个项目要做的页面,分别是首页,登录和注册页面:
项目首页:

登录页面:

注册页面和登录页面相似,这里就不展示了。在本文中我们会完成京东到家项目首页和登录注册页面的样式开发,其中会用到 element-plus 组件库,登录注册会使用 axios 发送 Mock 请求来实现,贴近真实项目开发。
我们通过脚手架已经构建好了项目,如果还不会怎么搭建vue3项目的同学,可以看看我的这篇博客,对vue3有个大概的了解:
Vue3全家桶入门 (通过vue-cli脚手架搭建todolist项目环境,深入vue3.0核心知识)
https://blog.csdn.net/qq_49900295/article/details/124726599?spm=1001.2014.3001.5501首先,在 main.js 中引入项目需要的依赖:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'normalize.css'
import './style/base.scss'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(ElementPlus)
app.mount('#app')
这里 normalize.css 和 element-plus 需要我们先安装:
npm install normalize.css --save
npm install element-plus --save
normalize.css 是css的初始化文件,它在默认的HTML元素样式上提供了跨浏览器的高度一致性,总之按装它就完事了,element-plus在我们的项目中可以用到一些弹窗,引入它会非常方便。
除了 normalize.css 外,在 main.js 里我们还引入了 base.scss,在这里我们设置了 html 与 body 的字号,方便在样式中使用rem来实现响应式的字体:
html {
font-size: 100px;
}
body {
font-size: .12rem;
}
下面是项目的 style 目录:

在 viriables.scss 文件中我们主要来定义一些颜色变量:
$content-fontcolor: #333;
$content-bgcolor: #F1F1F1;
因为在各个组件中使用这两个颜色特别多,所以我们可以把它单独拿出来,通过变量的形式我们就可以统一更改颜色,这样就非常方便。
下面是 App.vue 中的代码,我们把里面的内容都删掉,我们并不需要 router-link,我们只需要 router-view 渲染子路由就行:
<template>
<router-view />
</template>
<script>
export default ({
name: 'App'
})
</script>
这是首页的目录结构:

这里HomeView相当于首页的根组件,也就是相当于一个容器里面放着 FooTer,NearBy,StaticView三个子组件,从下图可以看出这三个子组件负责的区域。他们共同构建出了首页的样式。

我们看一下 HomeView 中的代码:
<template>
<div class="wrapper">
<static-view />
<near-by />
</div>
<foo-ter />
</template>
<script>
import StaticView from './StaticView.vue'
import NearBy from './NearBy.vue'
import FooTer from './FooTer.vue'
export default {
name: 'HomeView',
components: {
StaticView,
NearBy,
FooTer
}
}
</script>
<style lang="scss">
@import '../../style/viriables.scss';
.wrapper {
overflow-y: auto;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: .5rem;
padding: 0 .18rem .1rem .18rem;
.wrapper--content {
color: $content-fontcolor;
}
}
.fl {
float: left;
}
</style>
我们在 HomeView 中引入三个子组件然后在视图模板中使用引入的子组件。记得要在父组件的 components 中声明引入的子组件。wrapper是 StartView与NearBy组件的容器。
在 wrapper 的样式中有一个 overflow-y ,如果不加这个样式的话,首页在拖动滚动条时,Footer 底部就会变成这样:

加上 overflow-y 后表示可以在y轴放心滚动。
在修改 FooTer 组件样式时,我们想给底部字体10px大小,但是浏览器里默认我们的最小字号是12px,强制修改10px也只会显示12px,我们应该这么修改:
.docker__title {
font-size: .2rem;
transform: scale(.5,.5);
transform-origin: center top;
}
通过 transform 让元素缩放的方式改变大小,最后要设置它的变换中心点,我们设置水平居中,垂直靠着顶部就正好
当我们在做到顶部的一个轮播图的时候,因为图片要从服务器中读取,所以加载速度很慢,就会产生一个抖动的现象。比如在图片下面加个文字,看一下网页的加载过程:

因为图片加载的太慢,所以下面的内容会产生这种抖动,我们通过css的方法去解决它:
.banner {
height: 0;
overflow: hidden;
padding-bottom: 25.4%;
&__img {
width: 100%;
}
}
我们先计算一下图片的高宽比,得出是0.254,那这里给个底部内边距百分之25.4指的就是屏幕宽度的百分之25.4,又因为图片的宽度和屏幕宽度一样,所以这就相当于图片的高度。所以我们这么想,就在图片没加载出来之前就会把位置占住。这样就实现了防抖效果。

现在我们想给一个底部的条条这个该怎么做呢?这里的难点是我们最外层的盒子有一个padding值,所以我们的横条就不会占满宽度,这个问题应该怎么解决呢?很简单,把这个条条的外边距设置为负的就行:
.gap {
margin: 0 -.18rem;
height: .1rem;
background: #F1F1F1;
}
在 views 目录下的LoginView和RegisterView分别表示登录和注册的页面。

先编写一下基础代码:
<template>
hello world
</template>
<script>
export default {
name: 'LoginView'
}
</script>
<style scoped lang="scss">
</style>
我们先写一下它的视图模板:
<template>
<div class="wrapper">
<img src="http://www.dell-lee.com/imgs/vue3/user.png" alt="" class="wrapper__img">
<div class="wrapper__inp">
<input type="text" class="wrapper__input__content" />
</div>
<div class="wrapper__inp">
<input type="text" class="wrapper__input__content" />
</div>
<div class="wrapper__login-button">登录</div>
<div class="wrapper__login-link">立即注册</div>
</div>
</template>
下面我们再写登陆页面的样式:
.wrapper {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
right: 0;
&__img {
display: block;
margin: 0 auto .4rem auto;
width: .66rem;
height: .66rem;
}
&__input {
box-sizing: border-box;
height: .48rem;
margin: 0 .4rem .16rem .4rem;
background: #F9F9F9;
padding: 0 .16rem;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 6px;
&__content {
width: 100%;
border: 0;
background: none;
outline: none;
line-height: .48rem;
font-size: .16rem;
color: rgba(0, 0, 0, .5);
&::placeholder {
color: rgba(0, 0, 0, .5);
}
}
}
&__login-button {
height: .48rem;
line-height: .48rem;
margin: .32rem .4rem .16rem .4rem;
text-align: center;
background: #0091FF;
box-shadow: 0 .04rem .08rem 0 rgba(0,145,255,0.32);
border-radius: .04rem;
color: #fff;
}
&__login-link {
text-align: center;
font-size: .14rem;
color: rgba(0, 0, 0, .5);
}
}
现在我们做一些路由跳转,先修改一下 router 里的 index.js 中的内容:
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/home/HomeView.vue'
import LoginView from '../views/login/LoginView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/login',
name: 'login',
component: LoginView
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
现在我们想实现一个功能,只有当登陆的时候才能访问首页,否则禁止访问首页
我们在 index.js 中通过 beforeEach 实现这个功能,先做一下简单的输出:
router.beforeEach((to, from, next) => {
console.log(to, from)
next()
})
这里 to 指的是要跳转到哪个页面的信息,from 指的是从哪个页面跳转的信息。router.beforEach 的意思就是每次在路由跳转前都要执行这个方法。
我们每次在跳转前先判断是否登录,如果用户之前登录过就跳转到对应的页面,否则就跳转到登陆页面。但是如果用户没有登录他跳转到登录页面的时候还会 router.beforEach 进行判断,这样他又会跳到登陆页面,这样会一直循环,所以我们在 if 中还得加一个条件,如果跳转的是登陆页面的话就允许跳转。
router.beforeEach((to, from, next) => {
const isLogin = false
if (isLogin || to.name === 'login') {
next()
} else {
next({ name: 'login' })
}
})
现在我们的页面默认就会跳转到登陆页面,就算在浏览器路径中让它跳到首页,他也不会跳转。
现在我们就去实现当用户点击登陆时候的跳转功能,在 LoginView 里,先给按钮一个点击事件:
<div class="wrapper__login-button" @click="handleLogin">登录</div>
当点击登陆时,会先判断输入的电话号码是否符合格式,符合与不符合都会有相应的弹窗(这里弹窗是element-plus的组件),当符合格式时就让 isLogin 为 ture,表示在登陆状态:
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
export default {
name: 'LoginView',
setup () {
const router = useRouter()
let number = ref('')
let password = ref('')
let handleLogin = () => {
let reg = /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/
if (reg.test(number.value)) {
open2()
localStorage.isLogin = true
setTimeout(() => {
router.push({ name: 'home' })
}, 2000)
} else {
open()
}
}
let open = () => {
ElMessage({
message: '您输入的电话号码格式错误',
type: 'error',
duration: 2000
})
}
let open2 = () => {
ElMessage({
message: '登陆成功欢迎您',
type: 'success',
duration: 2000
})
}
return {
handleLogin,
number,
password,
open,
open2,
router
}
}
}
然后我们完善一下 router 下 登陆路由的配置:
path: '/login',
name: 'login',
component: LoginView,
beforeEnter (to, from, next) {
const isLogin = localStorage.isLogin
if (isLogin) {
next({ name: 'home' })
} else {
next()
}
}
路由的 beforeEnter 事件就是当跳转路由之前执行的,如果我们当前已经登录成功,跳转到首页了。如果我们想跳转回登录页面的话就会来到 beforeEnter 判断,登陆成功后 isLogin 这个登录状态就是 true,这样就不会再退回到登录页面,还会跳转到首页。
在完成登录页面后,我们照猫画虎继续完成注册页面,把登录页面的内容复制一份,注册页面就比登录页面多了个确认密码,然后去 index.js 中配置路由信息。
只不过我们还得完成登录与注册页面之间的切换,在登录页面点击立即注册后跳转到注册页面:

let handleRegisterClick = () => {
ElNotification({
title: '尊敬的用户您好',
message: h('i', { style: 'color: teal' }, '正在跳转到注册页面'),
duration: 800
})
setTimeout(() => {
router.push({ name: 'register' })
}, 900)
}
在登录页面单击立即注册时,通过 element-plus 弹框提示,然后跳转路由,注册页面也是相同的操作。

现在我们的登录注册都是前端模拟出来的,现在我们要学习如何在前端调用接口,和后端做交互。

在我们的登录组件中,如果用户点击登录了,我们就让登录状态为 true,显然这是不合理的,我们应该先向后端发送请求,如果用户名和密码匹配,才允许登录将状态改为 true。
那我们首先安装一下 axios:
npm install axios ---save
把他引入到登录组件中:
import axios from 'axios'
这里的后端接口是我们通过 fastmock 模拟的后端接口,大家可以通过这个 url 来得到我们这个项目需要用到的数据,下面是我们这个项目的接口文档,可以看到登录是通过发送 post 请求来实现的,后面跟着的有登录的接口地址:

下面是我们这个项目的接口文档的地址:
https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/
我们给登录按钮加一个点击事件,然后在这个函数里实现我们想要的功能,这里加上 setTimeout 是因为 element-plus 的弹窗有两秒的时间,然后两秒后我们再实现登录跳转就更贴近实际一些。
post 请求中我们还需要把请求的数据返给后端接口, 下面就是登录事件的代码:
let handleLogin = () => {
axios.post('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
username: username,
password: password
}).then(() => {
open2()
localStorage.isLogin = true
setTimeout(() => {
router.push({ name: 'home' })
}, 2000)
}).catch(() => {
open()
})
}
现在当我们点击登录时,就模拟了一个post请求,把输入的内容返回给后端接口,这里的接口只是用来模拟请求过程,并不是我们项目的真实后端接口。

注意要把返回内容的格式设置为 json ,因为 fastmock 是这么要求的。
vue3 已经支持 async 和 await 这样的语法,我们重新写一下上一节的代码,输出一下返回的结果:
let handleLogin = async () => {
const result = await axios.post('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
username: username,
password: password
// }).then(() => {
// open2()
// localStorage.isLogin = true
// setTimeout(() => {
// router.push({ name: 'home' })
// }, 2000)
// }).catch(() => {
// open()
// })
})
console.log(result)
}
启动项目,点击登录,result就在控制台中输出出来了:

这里data中的 errno 表示返回的错误个数,如果是0就表示请求发送成功,那么我们继续完善代码:
let handleLogin = async () => {
const result = await axios.post('https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
username: username,
password: password
})
if (result.data.errno === 0) {
localStorage.isLogin = true
open2()
setTimeout(() => {
router.push({ name: 'home' })
}, 2000)
} else {
open()
}
}
现在我们故意把 url 地址写错:

启动项目,看看有什么效果:

控制台的网络这块就报错了,但是并没有弹窗,这是为什么呢?
因为异常会在 await 那里抛出,不会走下面的代码了,自然不会执行弹窗语句,那我们通过 try catch就能解决这个问题:
let handleLogin = async () => {
try {
const result = await axios.post('https://www.fastmck.site/mock/ae8e9031947a302fed5f92425995aa19/jd/api/user/login', {
username: username,
password: password
})
if (result.data.errno === 0) {
localStorage.isLogin = true
open2()
setTimeout(() => {
router.push({ name: 'home' })
}, 2000)
} else {
open()
}
} catch (e) {
open3()
}
}
这样在输入错误的 url 时,他就会提示我们请求失败了:

现在我们每发一个请求都要写一段很长的请求地址,后面请求别的接口还得再写这样一段代码,那我们就把它封装一下。
我们在 src 目录下新建一个 utils 文件,在 request 里封装 post 请求:

我们对 post 请求做了一个封装:
import axios from 'axios'
export const post = (url, data = {}) => {
return new Promise((resolve, reject) => {
axios.post(url, data, {
baseURL: 'https://www.fastmock.site/mock/ae8e9031947a302fed5f92425995aa19/jd',
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
resolve(response)
}, err => {
reject(err)
})
})
}
这样在 Login 中我们就不需要引入 axios 了,直接把 post 方法引入进来就行:
import { post } from '../../utils/request'
现在我们的弹窗用的是 element plus 提供的组件,但是用的多了的时候就会有大量冗余的代码,下面我们对他也进行一下封装:
let alertmessage = (thecontent, thetype, theduration) => {
ElMessage({
message: thecontent,
type: thetype,
duration: theduration
})
}
这样当我们用到弹窗这个功能时,直接指定参数就行了:

在 register 组件里我们点击注册的时候也是发送 post 请求,和登录页面的实现逻辑相同,只是请求的接口不一样,接口地址在上面的接口文档中有,大家可以自行查看,在这一节中我们封装了请求函数,然后在登录页面和注册页面中使用我们封装的这个函数实现了数据的请求。
现在我们的登录注册页面的功能就大致完成了,启动项目看看最终的效果:

现在我们把各种函数都放在了 setup 中,这样做肯定没有出错,但是这样会让我们的 setup 函数非常长,如果项目做到后面我们要在里面找某一个函数或者变量的时候,都很麻烦,如果把关于登录逻辑的数据和方法都放在 setup 外面的一个函数中,关于注册逻辑的数据和方法放在另一个函数中,这样再把这些函数在 setup 中接收,在 setup 中我们只关心整个页面的实现逻辑就行,这样整个代码的维护性和可读性都大大提高了。
在登录页面我们把向后端发送登录请求的相关逻辑从 setup 中抽离出来:
const useLoginEffect = () => {
const router = useRouter()
let username = ref('')
let password = ref('')
let handleLogin = async () => {
if (username.value === '' || password.value === '') {
alertmessage('输入内容不能为空', 'warning', 1500)
return
}
try {
const result = await post('/api/user/login', {
username: username,
password: password
})
if (result.data.errno === 0) {
localStorage.isLogin = true
alertmessage('登录成功欢迎您', 'success', 2000)
setTimeout(() => {
router.push({ name: 'home' })
}, 2000)
} else {
alertmessage('登录失败', 'error', 2000)
}
} catch (e) {
alertmessage('请求失败', 'error', 2000)
}
}
return { username, password, handleLogin }
}
这里我们重新定义了一个useLoginEffect 函数,然后把需要用到的数据和方法都放进来,最后通过 return 把数据和方法返回出来,以便在 setup 中接收。
我们再把点击注册这个函数的相关逻辑抽离出来:
const useRegisterEffect = () => {
const router = useRouter()
let handleRegisterClick = () => {
ElNotification({
title: '尊敬的用户您好',
message: h('i', { style: 'color: teal' }, '正在跳转到注册页面'),
duration: 800
})
setTimeout(() => {
router.push({ name: 'register' })
}, 900)
}
return { handleRegisterClick }
}
这样我们就把登录页面相关的功能都从 setup 里面抽离了出来,现在再看 setup 里的代码就优雅了许多,浅显易懂:
setup () {
const { username, password, handleLogin } = useLoginEffect()
const { handleRegisterClick } = useRegisterEffect()
return {
handleLogin,
username,
password,
handleRegisterClick
}
}
在 setup 里我们很清晰的知道这个页面的实现逻辑,如果想修改跳转登录这个函数就去对应的函数里修改就可以,方便了很多。
在注册页面的代码拆分和登录页面的相同,这里就不过多阐述。在本文中我们暂时完成了项目首页和登陆注册页面的样式,实现了登陆注册时向后端发送请求获取数据的功能,最后通过代码拆分增加逻辑可维护性。下一篇文章我们会实现商家展示功能的开发,大家记得关注哦!
项目代码地址:
https://gitee.com/jie_shao1112/jingdong-home
https://gitee.com/jie_shao1112/jingdong-home
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建
在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?
参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍 介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。 内容有: ①:Hub模型的方法介绍 ②:服务器端代码介绍 ③:前端vue3安装并调用后端方法 ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke() 去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on
require'pp'p*1..10这会打印出1-10。为什么这么简洁?您还可以用它做什么? 最佳答案 它是“splat”运算符。它可用于分解数组和范围并在赋值期间收集值。这里收集赋值中的值:a,*b=1,2,3,4=>a=1b=[2,3,4]在此示例中,内部数组([3,4])中的值被分解并收集到包含数组中:a=[1,2,*[3,4]]=>a=[1,2,3,4]您可以定义将参数收集到数组中的函数:deffoo(*args)pargsendfoo(1,2,"three",4)=>[1,2,"three",4]
我读过的关于Ruby符号的每一篇文章都在谈论符号相对于字符串的效率。但是,这不是1970年代。我的电脑可以处理一些额外的垃圾收集。我错了吗?我拥有最新最好的奔腾双核处理器和4GBRAM。我认为这应该足以处理一些字符串。 最佳答案 您的计算机可能能够处理“一点点额外的垃圾收集”,但是当“一点点”发生在运行数百万次的内部循环中时呢?如果它在内存有限的嵌入式系统上运行呢?有很多地方你可以随意使用字符串,但在某些地方你不能。这完全取决于上下文。 关于ruby-现代计算机的功能是否不足以处理字符串
我正在使用Windows并尝试运行一个现有的功能包,该功能包最初是在MacOS上构建的,这允许他们通过使用带空格的"\"来解决问题。我正在使用Ruby2.2.3和Cucumber。功能名称包含空格,我无法更改它。我尝试使用""和''来绕过空白,但每次都有同样的问题。这是问题的一个例子。如果我运行:cucumberfeatures/'Namecontainingwhitespaces.feature'它工作正常。但是当我运行时:cucumber-pmy_profile和cucumber.yml包含:my_profile:features/'Namecontainingwhitespace
单元测试的好方法是测试脚本在执行之间保持正确数据的能力——在使用Ctrl-C终止脚本然后重新运行之后?是否有针对执行类似操作的现有模块或脚本的任何测试可以针对最佳实践进行审查? 最佳答案 像http://avdi.org/devblog/2010/07/19/greenletters-painless-automation-and-testing-for-command-line-applications/一样使用库或者期望、运行、终止并重新运行您的程序,并检查它是否运行正确。好的做法是将程序设计为独立的模块,每个模块都经过良好测试