最近看到react 和vuetify 的输入框聚焦文字会变小上移,觉得它们框架有的效果,element你也应该有,于是乎,就尝试修改,试了很久发现 el-form-item 中的label和input根本连不上关系,就想到了一个解决办法,那就是el-input中有个自定义的slot属性,借此属性得以实现效果。
下面就是文档的属性。
这是最终效果图。截图的颜色不好看,有条件的自己修改一下样式即可。
代码有点套娃,有条件的可以使用slot去进行二次封装。
<template>
<div class="login">
<el-form ref="form" :model="form" :rules="rules" class="login-form" auto-complete="off">
<el-form-item prop="username">
<el-input v-model="form.username" type="text" @keyup.enter.native="handleLogin" required clearable>
<span slot="prefix" class="wrap">
<i class="el-input__icon el-icon-user-solid" />
<span>名称</span>
</span>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" type="password" @keyup.enter.native="handleLogin" required clearable>
<span slot="prefix" class="wrap">
<i class="el-input__icon el-icon-lock" />
<span>密码</span>
</span>
</el-input>
</el-form-item>
<el-form-item prop="captcha">
<div class="flex justify-around">
<el-input class="w--55%" v-model="form.captcha" placeholder="验证码" @keyup.enter.native="submit">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code w--40%">
<canvas ref="captcha" width="100%" height="38px" @click="captcha('captcha')" />
</div>
</div>
</el-form-item>
<el-checkbox v-model="form.rememberMe" style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="medium" type="primary" style="width: 100%" @click.native.prevent="submit">
{{ loading ? "登 录 中..." : "登 录" }}
</el-button>
<div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2022 ruoyi.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import { mapActions } from 'pinia';
import { user } from '@/pinia';
export default {
name: "Login",
data() {
const validator = (rule, value, callback) => {
if (value !== this.captchaCode) return callback(new Error(rule.message))
callback();
};
return {
form: {
username: "admin",
password: "123456",
captcha: "",
rememberMe: false,
uuid: "ccf0a46bfcf9409b95e9aea8700c9ed7",
},
rules: {
username: [{ required: true, trigger: "blur", message: "请输入账号" }],
password: [{ required: true, trigger: "blur", message: "请输入密码" }],
captcha: [{ required: true, trigger: 'blur', message: "请输入验证码" }, { validator, trigger: 'blur', message: "验证码错误" }],
},
captchaCode: '',
loading: false,
register: false, // 注册开关
redirect: undefined,
};
},
mounted() {
this.getCookie();
this.captcha('captcha');
},
methods: {
...mapActions(user, ['login']),
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
this.form = {
username: !username ? this.form.username : username,
password: !password ? this.form.password : decrypt(password),
rememberMe: !rememberMe ? false : Boolean(rememberMe),
};
},
submit() {
this.$refs.form.validate(async (valid) => {
if (!valid) return;
this.loading = true;
if (this.form.rememberMe) {
Cookies.set("username", this.form.username, { expires: 30 });
Cookies.set("password", encrypt(this.form.password), { expires: 30 });
Cookies.set("rememberMe", this.form.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
}
try {
await this.login(this.form);
this.$router.push({ path: this.redirect || "/" }).catch(() => { });
} catch (error) {
console.error(error);
this.loading = false;
this.captcha('captcha');
}
});
this.loading = false;
},
// 获取生成验证码的canvas
captcha(el) {
const container = {
scope: (max, min) => parseInt(Math.random() * (max - min) + min),
color: (max, min, o = 1) => `rgba(${container.scope(max, min)},${container.scope(max, min)},${container.scope(max, min)},${o})`,
num: (n) => Math.floor(Math.random() * n),
isnum: (n) => typeof n === 'number' && !isNaN(n),
operator: ['+', '-', '*'],
rect: [0, 0, 100, 38],
calculate: [0, '+-*/', 0, '=?']
}
const { scope, color, num, isnum, operator, rect, calculate: calcu } = container;
const ctx = this.$refs[el].getContext("2d");
const [x1, y1, x2, y2] = rect;
ctx.clearRect(x1, y1, x2, y2);
ctx.fillStyle = color(180, 230);
ctx.save()
ctx.fillRect(x1, y1, x2, y2);
Array.from({ length: 4 }, (_, index) => {
ctx.beginPath();
ctx.strokeStyle = color(80, 180, .33);
ctx.save();
ctx.moveTo(scope(0, x2), scope(0, y2));
ctx.moveTo(scope(0, x2), scope(0, y2));
ctx.lineTo(scope(0, x2), scope(0, y2));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = color(0, 255, .33);
ctx.save();
ctx.arc(scope(0, x2), scope(0, y2), 1, 0, 2 * Math.PI);
ctx.fill();
ctx.font = `${scope(18, 30)}px Simhei`;
ctx.fillStyle = color(80, 150);
ctx.save();
let item = calcu[index];
isnum(item) && (item = num(10));
item === '+-*/' && (item = operator[num(3)]);
index === 2 && calcu[1] === '-' && calcu[0] <= item && (item = calcu[0] ? calcu[0] : 0);
calcu[index] = item;
ctx.translate(22 * index + -12, 10);
ctx.rotate((scope(-12, 12) * Math.PI) / 180);
ctx.fillText(item, y2 / 2, y2 / 2);
ctx.restore();
});
const result = eval(calcu.slice(0, calcu.length - 1).join(""));
this.captchaCode = result.toString();
return result.toString();
},
},
watch: {
$route: {
handler(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true,
},
},
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: linear-gradient(
to right,
#00c6fb,
#005bea,
#00c6fb,
#005bea
);
background-size: cover;
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.el-form {
position: absolute;
top: 50%;
left: 75%;
width: 400px;
padding: 40px 40px 40px 40px;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.2);
box-sizing: border-box;
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.6);
border-radius: 10px;
.el-form-item {
margin-bottom: 30px;
h2 {
text-align: center;
color: #c0c4cc;
}
/deep/ {
.el-input__inner {
border: none;
border-radius: 0;
border-bottom: 1px solid #fff;
background-color: transparent !important;
color: #c0c4cc;
// 伪元素的修改 line
& + .el-input__prefix::before {
content: "";
position: absolute;
display: block;
bottom: 0%;
left: 50%;
width: 0;
height: 2px;
background-color: #409eff;
transition: all 0.8s;
}
&:focus + .el-input__prefix::before {
left: 0;
width: 100%;
}
// label的修改 文本存在时的显示css
&:-webkit-autofill ~ .el-input__prefix > .wrap,
&:focus ~ .el-input__prefix > .wrap,
&:valid ~ .el-input__prefix > .wrap {
color: #fff;
display: inline-block;
font-size: 14px;
transform: perspective(1000px) translate3d(-10px, -24px, -100px);
transition: all 0.4s;
}
}
.el-input__prefix {
width: 100%;
text-align: left;
color: #c0c4cc;
font-size: 18px;
left: 0px;
pointer-events: none;
transform-style: preserve-3d;
& .wrap {
transform: perspective(1000px) translate3d(0, 0, 0);
transition: all 0.4s;
}
}
}
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
}
</style>
<style lang="scss">
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
-webkit-text-fill-color: #ededed !important; //字体颜色
background-color: transparent;
background-image: none;
transition: background-color 50000s ease-in-out 0s; //背景色透明 生效时长 过渡效果 启用时延迟的时间
}
</style>
<style lang="scss">
$num: 0; // padding、margin
@while $num <= 60 {
.p-#{$num} {
padding: #{$num}px !important;
}
.pt-#{$num} {
padding-top: #{$num}px !important;
}
.pb-#{$num} {
padding-bottom: #{$num}px !important;
}
.pl-#{$num} {
padding-left: #{$num}px !important;
}
.pr-#{$num} {
padding-right: #{$num}px !important;
}
.px-#{$num} {
padding-left: #{$num}px !important;
padding-right: #{$num}px !important;
}
.py-#{$num} {
padding-top: #{$num}px !important;
padding-bottom: #{$num}px !important;
}
.m-#{$num} {
margin: #{$num}px !important;
}
.mt-#{$num} {
margin-top: #{$num}px !important;
}
.mb-#{$num} {
margin-bottom: #{$num}px !important;
}
.ml-#{$num} {
margin-left: #{$num}px !important;
}
.mr-#{$num} {
margin-right: #{$num}px !important;
}
.mx-#{$num} {
margin-left: #{$num}px !important;
margin-right: #{$num}px !important;
}
.my-#{$num} {
margin-top: #{$num}px !important;
margin-bottom: #{$num}px !important;
}
$num: $num + 5;
}
$pos: -20; // top、left、right、bottom
@while $pos <= 20 {
.top-#{$pos} {
top: #{$pos}px !important;
}
.bottom-#{$pos} {
bottom: #{$pos}px !important;
}
.left-#{$pos} {
left: #{$pos}px !important;
}
.right-#{$pos} {
right: #{$pos}px !important;
}
$pos: $pos + 5;
}
$spacing: 0; // width-px、height-px
@while $spacing <= 100 {
.w--#{$spacing} {
width: #{$spacing}px !important;
}
.h--#{$spacing} {
height: #{$spacing}px !important;
}
$spacing: $spacing + 5;
}
$percentage: 0; // width-%、height-%
@while $percentage <= 100 {
.w--#{$percentage}\% {
width: #{$percentage + "%"} !important;
}
.h--#{$percentage}\% {
height: #{$percentage + "%"} !important;
}
$percentage: $percentage + 5;
}
.w--unset {
width: unset !important;
}
.font-style {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
html,
body {
height: 100% !important;
}
.fr {
float: right !important;
&::after {
content: "";
display: block;
clear: both;
}
}
.fl {
float: right !important;
&::after {
content: "";
display: block;
clear: both;
}
}
.text-xs {
font-size: 12px;
}
.text-sm {
font-size: 14px;
}
.text-base {
font-size: 16px;
}
.text-lg {
font-size: 18px;
}
.text-xl {
font-size: 20px;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line-clamp-1 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
.line-clamp-2 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.line-clamp-3 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.line-clamp-4 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
}
.select-none {
user-select: none !important;
}
.w-full {
width: 100% !important;
}
.h-full {
height: 100% !important;
}
.relative {
position: relative !important;
}
.absolute {
position: absolute !important;
}
.fixed {
position: fixed !important;
}
.flex {
display: flex !important;
}
.inline-block {
display: inline-block !important;
}
.hidden {
display: none !important;
}
.text-center {
text-align: center !important;
}
.text-left {
text-align: left !important;
}
.text-right {
text-align: right !important;
}
.cursor-pointer {
cursor: pointer !important;
}
.flex-wrap {
flex-wrap: wrap !important;
}
.flex-nowrap {
flex-wrap: nowrap !important;
}
.flex-col {
flex-direction: column !important;
}
.flex-row {
flex-direction: row !important;
}
.justify-center {
justify-content: center !important;
}
.self-center {
align-self: center !important;
}
.justify-between {
justify-content: space-between !important;
}
.justify-around {
justify-content: space-around !important;
}
.justify-evenly {
justify-content: space-evenly !important;
}
.items-baseline {
align-items: baseline !important;
}
.items-center {
align-items: center !important;
}
.items-stretch {
align-items: stretch !important;
}
.align-top {
vertical-align: top !important;
}
.align-middle {
vertical-align: middle !important;
}
.align-bottom {
vertical-align: bottom !important;
}
.overflow-hidden {
overflow: hidden !important;
}
.overflow-y {
overflow-y: scroll !important;
}
.overflow-x {
overflow-x: scroll !important;
}
.rounded-none {
border-radius: 0 !important;
}
.rounded-sm {
border-radius: 2px !important;
}
.rounded {
border-radius: 4px !important;
}
.rounded-md {
border-radius: 6px !important;
}
.rounded-lg {
border-radius: 8px !important;
}
.rounded-xl {
border-radius: 12px !important;
}
.rounded-2xl {
border-radius: 16px !important;
}
.rounded-3xl {
border-radius: 24px !important;
}
.rounded-full {
border-radius: 99999px !important;
}
.shadow-sm {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.shadow {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
.shadow-md {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.shadow-lg {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
}
.shadow-xl {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
.shadow-2xl {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.shadow-inner {
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05);
}
.shadow-none {
box-shadow: 0 0 #0000;
}
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
@at-root .gap {
gap: 4px !important;
}
@at-root .gap-2 {
gap: 8px !important;
}
@at-root .gap-3 {
gap: 12px !important;
}
.grid-item-1 {
grid-area: auto / span 1 !important;
}
.grid-item-2 {
grid-area: auto / span 2 !important;
}
.grid-item-3 {
grid-area: auto / span 3 !important;
}
.grid-item-4 {
grid-area: auto / span 4 !important;
}
.grid-item-5 {
grid-area: auto / span 5 !important;
}
.grid-item-6 {
grid-area: auto / span 6 !important;
}
.grid-item-7 {
grid-area: auto / span 7 !important;
}
.grid-item-8 {
grid-area: auto / span 8 !important;
}
.grid-item-9 {
grid-area: auto / span 9 !important;
}
.grid-item-10 {
grid-area: auto / span 10 !important;
}
.grid-item-11 {
grid-area: auto / span 11 !important;
}
.grid-item-12 {
grid-area: auto / span 12 !important;
}
}
<style>