第16章 前端登录页面的定义实现

1 Vue与uni-app

    Uni-app是基于Vue的,但以不完成等同于Vue,Vue的前端实现只能使用浏览器进行渲染显示,而uni-app的前端实现,可以在小程序、App、浏览器都能够进行渲染显示。

  1. Vue与Razor

1、如果没能指定需要前后端分离实现,本从建议首先考虑Razor,因为Vue的变化太过频繁,会由于搭建环境的迭代变化从而导致,前端实现不能被正常运行。

    2、从前端实现的角度来说Razor,不如Vue, Vue有丰富的组件,可供渲染显示使用;Vue彻底脱离了后端代码,只关注前端实现,从专业性和团队开发来说依然是Vue胜出。

3、Razor实质上是一种特殊的“*.cs”文件,即它是后端实现的扩展和延伸,与后端实现有着紧密的耦合关系,不能脱离后端而单独存在。Vue只关注与后端数据的绑定和交互操作,不关心后端的逻辑实现,可以完全脱离后端而单独存在,从耦合的角度来说依然是Vue胜出。

3 VSCode与HBuilderX

    VSCode与HbuilderX是两个IDE开发环景两者都可以对Vue和uni-app前端进行开发实现,当前来说使用VSCode开发uni-app前端配置比较麻烦,由于前后端分离的主要需求是小程序和App开发,且HbuilderX比VSCode更加方便和容易上手,所建议使用Hbuilder进行Vue和uni-app前端开发,但不能放弃VSCode因为二者没有分出胜负。当前前端项目的开发将从使用VSCode转到HbuilderX中,因以后的工作重点是uni-app,而Vue则是学习使用uni-app的台阶。

4 重构博客文章

4.1 定义src\api\api.js

import { createApp } from 'vue'//vue-cli44.5.0版本的脚手架)后取代了:import Vue from 'vue';

import axios from 'axios';

//后端的根路由,该根路由来源于VisaulStuido调试,非IIS部署。

let base = 'https://localhost:7037';

// 如果是IIS部署,用这个,因为 IIS 只能是 CORS 跨域,不能代理

// let base = process.env.NODE_ENV=="production"? 'http://localhost:8081':'';

/****************************定义“axios”“http response”拦截操作************************************/

axios.interceptors.response.use(

    response => {

        return response;

    },

    error => {

        let errInfo = { success: false, message: "错误" }

        // 超时请求处理

        var originalRequest = error.config;

        if(error.code == 'ECONNABORTED' && error.message.indexOf('timeout')!=-1 && !originalRequest._retry){

            errInfo.message = "请求超时!";

            originalRequest._retry = true

        }

        else if (error.response) {

            if (error.response.status == 401) {

                var curTime = new Date()

                var refreshtime = new Date(Date.parse(window.localStorage.refreshtime))

                // 在用户操作的活跃期内

                if (window.localStorage.refreshtime && (curTime <= refreshtime)) {

                    return  refreshToken({token: window.localStorage.Token}).then((res) => {

                        if (res.success) {

                            createApp.prototype.$message({

                                message: '成功获取“TokenJwt”字符串,数据载入中...',

                                type: 'success'

                            });

                            //store.commit("saveToken", res.response.token);

                            //var curTime = new Date();

                            //var expiredate = new Date(curTime.setSeconds(curTime.getSeconds() + res.response.expires_in));

                           // store.commit("saveTokenExpire", expiredate);

                            error.config.__isRetryRequest = true;

                            error.config.headers.Authorization = 'Bearer ' + res.response.token;

                            return axios(error.config);

                        } else {

                            // 刷新token失败 清除token信息并跳转到登录页面

                            //ToLogin()

                        }

                    });

                } else {

                    // 返回 401,并且不知用户操作活跃期内 清除token信息并跳转到登录页面

                    //ToLogin()

                }

            }

            // 403 无权限

            else if (error.response.status == 403) {

               errInfo.message = "失败!该操作无权限";

            }

            // 429 ip限流

            else if (error.response.status == 429) {

                errInfo.message = "刷新次数过多,请稍事休息重试!";

            }else if (error.response.status == 404) {

                // 404 不存在

                errInfo.message = "失败!访问接口不存在";

           }else if (error.response.status == 500) {

               // 500 服务器异常

               errInfo.message = "失败!服务器异常";

           }else if (error.response.status == 405) {

               // 405 请求http方法错误

               errInfo.message = "失败!请求http方法错误";  

           }else if (error.response.status == 415) {

               // 415 参数没有指定Body还是Query

               errInfo.message = "失败!参数没有指定Body还是Query";  

           }else {

               //其他错误参数

                errInfo.message = '失败!请求错误' + error.response.status;    

           }

        }else{

            errInfo.message = "失败!服务器断开";  

        }

        createApp.prototype.$message({

            message: errInfo.message,

            type: 'error'

        });

        return errInfo; // 返回接口返回的错误信息

    }

);

/****************************API集中管理--博客文章模块************************************/

// 获取1分页内的所有的博客文章。

export const getBlogListPage = params => {

    return axios.get(`${base}/api/Blog`, {params: params});

};

/****************************API集中管理--登录模块************************************/

// 获取1个新的TokenJwt字符串,代替旧的TokenJwt字符串。

export const refreshToken = params => {

    return axios.get(`${base}/api/login/RefreshToken`, {params: params}).then(res => res.data);

};

// 登录后获取TokeneJwt3字符串。

export const requestLogin = params => {

    return axios.get(`${base}/api/login/jwttoken3.0`, {params: params}).then(res => res.data);

};

4.2 重构BlogsView.vue

<template>

    <!--列表-->

    <el-table :data="blogList" v-loading="listLoading" style="width: 100%" ref="table" class="custom-tbl">

        <el-table-column type="selection" width="50"> </el-table-column>

        <el-table-column prop="id" label="ID" width="100" sortable>

        </el-table-column>

        <el-table-column prop="btitle" label="标题" width="" sortable>

        </el-table-column>

        <el-table-column prop="content" label="内容" width="400" sortable>

            <template #default="scope">

                <span v-html="scope.row.content"></span>

            </template>

        </el-table-column>

        <el-table-column prop="createTime" label="创建时间" :formatter="formatCreateTime" width="250" sortable>

        </el-table-column>>

    </el-table>

</template>

<script>

    import { getBlogListPage } from "../../api/api.js";

    export default ({

        data() {

            return {

                //数组局部变量,用于存储从后端获取的博客文章实体的所有实例与当前页面进行绑定,以在页面进行渲染显示时,同时渲染显示出这些实例数据。

                blogList: [],

                listLoading: false,

                //获取指定参数下的博客文章实体的所有实例。

                filters: {

                    LinkUrl: "",

                },

                page: 1,

            };

        },

        methods: {

            //获取博客文章实体的所有实例。

            async getBlogList() {

                this.listLoading = true;

                let para = {

                    page: this.page,

                    key: this.filters.name,

                };

                let res = await getBlogListPage(para);

                //console.log(res);

                this.blogList = res.data.response.data;

                this.listLoading = false;

            },

            //格式化日期显示格式。

            formatCreateTime: function(row, column) {

                let data = row[column.property];

                if (data == null) {

                    return null;

                }

                let dt = new Date(data);

                return dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate();

            },

        },

        mounted() {

            //在页面渲染显示完成前,把数据加载到页面中,以便在页面渲染显示后把数据渲染显示出来;“mounted”方法执行结束即为页面渲染显示后。

            this.getBlogList();

        }

    });

</script>

<style scoped>

    .custom-tbl /deep/ .has-gutter .el-checkbox {

        display: none;

    }

</style>

5 定义登录页面

5.1 使用选项式Api(Options Api)定义登录页面

5.1.1 重构src\router\index.js

const routes = [{

        path: '/',

        name: 'QQ欢迎页',

        component: () => import('../views/WelcomeView.vue'),

        meta: {

            title: 'QQ欢迎页',

        }

    },

    {

        path: '/Login',

        name: '登录',

        component: () => import('../views/LoginView.vue'),

        meta: {

            title: '登录',

        }

    },

    {

        path: '/LoginComposition',

        name: '登录(组合API)',

        component: () => import('../views/LoginCompositionView.vue'),

        meta: {

            title: '登录(组合API)',

        }

    },

    {

        path: '/Blog/Blogs',

        name: '博客管理',

        component: () => import('../views/Blog/BlogsView.vue'),

        meta: {

            title: '博客管理',

        }

    },

    {

        path: '/home',

        name: 'home',

        component: () => import('../views/HomeView.vue')

    },

]

5.1.2 复制

    1、复制源程序“src\assets”目录中的所有图片到当前程序的同一目录。

    2、复制源程序“public”目录中的所有图片到当前程序的同一目录。

5.1.3 定义src\views\ LoginView.vue

<template>

    <div class="wrapper">

        <!-- 背景动画 -->

        <ul class="bg-bubbles">

            <li v-for="n in 10" :key="n + 'n'"></li>

            <ol v-for="m in 5" :key="m + 'm'"></ol>

        </ul>

        <!-- 背景 -->

        <div class="bg bg-blur" style="display: none;"></div>

        <div style="height: 10%;"></div>

        <!-- 登录表单 -->

        <el-form :model="formLogin" :rules="ruleLogin" ref="refRuleLogin" label-position="left" label-width="0px"

            class="demo-ruleForm login-container">

            <h3 class="title">系统登录</h3>

            <el-form-item prop="account">

                <el-input type="text" v-model="formLogin.account" auto-complete="off" placeholder="账号"></el-input>

            </el-form-item>

            <el-form-item prop="checkPass">

                <el-input v-model="formLogin.checkPass" auto-complete="off" show-password placeholder="密码"></el-input>

            </el-form-item>

            <el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>

            <el-form-item style="width:100%;">

                <el-button type="primary" style="width:100%;" @click="submitLogin" :loading="logining">

                    {{ loginStr }}

                </el-button>

            </el-form-item>

        </el-form>

    </div>

</template>

<script>

    import {

        requestLogin

    } from "../api/api.js";

    export default {

        data() {

            return {

                formLogin: {

                    account: 'test',

                    checkPass: 'test'

                },

                ruleLogin: {

                    account: [{

                        required: true,

                        message: '请输入账号',

                        trigger: 'blur'

                    }, ],

                    checkPass: [{

                        required: true,

                        message: '请输入密码',

                        trigger: 'blur'

                    }, ],

                },

                checked: true,

                loginStr: '登录',

                logining: false,

            };

        },

        methods: {

            async submitLogin() {

                this.$refs.refRuleLogin.validate(async (valid) => {

                    if (valid) {

                        this.logining = true;

                        this.loginStr = "登录中...";

                        let loginParams = {

                            name: this.formLogin.account,

                            pass: this.formLogin.checkPass

                        };

                        let res = await requestLogin(loginParams);

                        //console.log(res);

                        if (res.status == 200) {

                            this.$message.success("成功登录");

                            //通过定时器,3秒钟后跳转。

                            await setInterval(

                                async () => {

                                    await this.$router.replace(this.$route.query.redirect ? this

                                        .$route.query.redirect : "/");

                                }, 3000);

                               

                        } else {

                            this.$message.error(res.msg);

                            this.logining = false;

                            this.loginStr = "重新登录";

                        }

                    } else {

                        console.log('输入不能通过验证 !');

                        return false;

                    }

                });

            },

        },

        mounted() {

            //this.submitLogin();

        },

    }

</script>

<style lang="scss">

    .bg {

        margin: 0px;

        position: absolute;

        left: 0;

        top: 0;

        right: 0;

        bottom: 0;

        background: url(../assets/loginbck.png) no-repeat top left;

        background-repeat: no-repeat;

        background-size: cover;

        width: 100%;

        height: 100%;

    }

    .login-container {

        -webkit-border-radius: 5px;

        border-radius: 5px;

        -moz-border-radius: 5px;

        background-clip: padding-box;

        margin: auto;

        width: 350px;

        padding: 35px 35px 15px 35px;

        background: #fff;

        border: 1px solid #eaeaea;

        box-shadow: 0 0 25px #cac6c6;

        z-index: 9999;

        position: relative;

    }

    .login-container .title {

        margin: 0px auto 40px auto;

        text-align: center;

        color: #505458;

    }

    .login-container .remember {

        margin: 0px 0px 25px 0px;

    }

    .wrapper {

        background: #50a3a2;

        background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);

        background: linear-gradient(to bottom right, #127c7b 0, #50a3a2);

        opacity: 0.8;

        position: absolute;

        left: 0;

        width: 100%;

        height: 100%;

        overflow: hidden;

    }

    .wrapper.form-success .containerLogin h1 {

        -webkit-transform: translateY(85px);

        -ms-transform: translateY(85px);

        transform: translateY(85px);

    }

    .containerLogin {

        max-width: 600px;

        margin: 0 auto;

        padding: 80px 0;

        height: 400px;

        text-align: center;

    }

    .containerLogin h1 {

        font-size: 40px;

        -webkit-transition-duration: 1s;

        transition-duration: 1s;

        -webkit-transition-timing-function: ease-in-put;

        transition-timing-function: ease-in-put;

        font-weight: 200;

    }

    .bg-bubbles {

        position: absolute;

        top: 0;

        left: 0;

        width: 100%;

        height: 100%;

        z-index: 1;

    }

    .bg-bubbles li,

    .bg-bubbles ol {

        position: absolute;

        list-style: none;

        display: block;

        width: 40px;

        height: 40px;

        background-color: rgba(255, 255, 255, 0.15);

        bottom: -160px;

        -webkit-animation: square 25s infinite;

        animation: square 25s infinite;

        -webkit-transition-timing-function: linear;

        transition-timing-function: linear;

    }

    ol {

        padding: 0 !important;

    }

    .bg-bubbles ol:nth-child(11) {

        left: 10%;

        top: 10%;

        width: 20px;

        height: 20px;

    }

    .bg-bubbles ol:nth-child(12) {

        left: 20%;

        top: 40%;

        width: 60px;

        height: 60px;

    }

    .bg-bubbles ol:nth-child(13) {

        left: 65%;

        top: 30%;

        width: 100px;

        height: 60px;

    }

    .bg-bubbles ol:nth-child(14) {

        left: 70%;

        top: 30%;

        width: 100px;

        height: 150px;

    }

    .bg-bubbles ol:nth-child(15) {

        left: 50%;

        top: 70%;

        width: 40px;

        height: 60px;

    }

    .bg-bubbles li:nth-child(1) {

        left: 10%;

    }

    .bg-bubbles li:nth-child(2) {

        left: 20%;

        width: 80px;

        height: 80px;

        -webkit-animation-delay: 2s;

        animation-delay: 2s;

        -webkit-animation-duration: 17s;

        animation-duration: 17s;

    }

    .bg-bubbles li:nth-child(3) {

        left: 25%;

        -webkit-animation-delay: 4s;

        animation-delay: 4s;

    }

    .bg-bubbles li:nth-child(4) {

        left: 40%;

        width: 60px;

        height: 60px;

        -webkit-animation-duration: 22s;

        animation-duration: 22s;

        background-color: rgba(255, 255, 255, 0.25);

    }

    .bg-bubbles li:nth-child(5) {

        left: 70%;

    }

    .bg-bubbles li:nth-child(6) {

        left: 80%;

        width: 120px;

        height: 120px;

        -webkit-animation-delay: 3s;

        animation-delay: 3s;

        background-color: rgba(255, 255, 255, 0.2);

    }

    .bg-bubbles li:nth-child(7) {

        left: 32%;

        width: 160px;

        height: 160px;

        -webkit-animation-delay: 7s;

        animation-delay: 7s;

    }

    .bg-bubbles li:nth-child(8) {

        left: 55%;

        width: 20px;

        height: 20px;

        -webkit-animation-delay: 15s;

        animation-delay: 15s;

        -webkit-animation-duration: 40s;

        animation-duration: 40s;

    }

    .bg-bubbles li:nth-child(9) {

        left: 25%;

        width: 10px;

        height: 10px;

        -webkit-animation-delay: 2s;

        animation-delay: 2s;

        -webkit-animation-duration: 40s;

        animation-duration: 40s;

        background-color: rgba(255, 255, 255, 0.3);

    }

    .bg-bubbles li:nth-child(10) {

        left: 90%;

        width: 160px;

        height: 160px;

        -webkit-animation-delay: 11s;

        animation-delay: 11s;

    }

    @-webkit-keyframes square {

        0% {

            -webkit-transform: translateY(0);

            transform: translateY(0);

        }

        100% {

            -webkit-transform: translateY(-700px) rotate(600deg);

            transform: translateY(-700px) rotate(600deg);

        }

    }

    @keyframes square {

        0% {

            -webkit-transform: translateY(0);

            transform: translateY(0);

        }

        100% {

            -webkit-transform: translateY(-700px) rotate(600deg);

            transform: translateY(-700px) rotate(600deg);

        }

    }

</style>

5.2 使用组合式api (Composition Api)定义登录页面

5.2.1 定义src\views\LoginCompositionView.vue

<template>

    <div class="wrapper">

        <!-- 背景动画 -->

        <ul class="bg-bubbles">

            <li v-for="n in 10" :key="n + 'n'"></li>

            <ol v-for="m in 5" :key="m + 'm'"></ol>

        </ul>

        <!-- 背景 -->

        <div class="bg bg-blur" style="display: none;"></div>

        <div style="height: 10%;"></div>

        <!-- 登录表单 -->

        <el-form :model="formLogin" :rules="ruleLogin" ref="refRuleLogin" label-position="left" label-width="0px"

            class="demo-ruleForm login-container">

            <h3 class="title">系统登录</h3>

            <el-form-item prop="account">

                <el-input type="text" v-model="formLogin.account" auto-complete="off" placeholder="账号"></el-input>

            </el-form-item>

            <el-form-item prop="checkPass">

                <el-input v-model="formLogin.checkPass" auto-complete="off" show-password placeholder="密码"></el-input>

            </el-form-item>

            <el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox>

            <el-form-item style="width:100%;">

                <el-button type="primary" style="width:100%;" @click="submitLogin(refRuleLogin)" :loading="logining">

                    {{ loginStr }}

                </el-button>

            </el-form-item>

        </el-form>

    </div>

</template>

<script setup>

    import {

        ref,

        reactive,

        getCurrentInstance

    } from "vue";

    import {

        requestLogin

    } from "../api/api.js";

    const formLogin = reactive({

        account: 'test',

        checkPass: 'test',

    });

    const ruleLogin = reactive({

        account: [{

            required: true,

            message: '请输入账号',

            trigger: 'blur'

        }, ],

        checkPass: [{

            required: true,

            message: '请输入密码',

            trigger: 'blur'

        }, ],

    });

    const checked = ref(true);

    const loginStr = ref('登录');

    const logining = ref(false);

    //实例化表单“ref”属性所对应的值。

    const refRuleLogin = ref('');

    const {

        $router,

        $route,

        $message

    } = getCurrentInstance().root.ctx.$root;

    //表单单击提交事件。

    const submitLogin = (formEl) => {

        if (!formEl) return;

        formEl.validate(async (valid) => {

            if (valid) {

                logining.value = true;

                loginStr.value = "登录中...";

                let loginParams = {

                    name: formLogin.account,

                    pass: formLogin.checkPass

                };

                let res = await requestLogin(loginParams);

                //console.log(res);

                if (res.status == 200) {

                    $message.success("成功登录");

                    //通过定时器,3秒钟后跳转。

                    await setInterval(

                        async () => {

                            $router.replace($route.query.redirect ? $route.query.redirect : "/");

                        }, 3000);

                }

                else {

                    $message.error(res.msg);

                    logining.value = false;

                    loginStr.value = "重新登录";

                }

            } else {

                console.log('输入不能通过验证 !');

                return false;

            }

        });

    }

</script>

<style lang="scss">

    .bg {

        margin: 0px;

        position: absolute;

        left: 0;

        top: 0;

        right: 0;

        bottom: 0;

        background: url(../assets/loginbck.png) no-repeat top left;

        background-repeat: no-repeat;

        background-size: cover;

        width: 100%;

        height: 100%;

    }

    .login-container {

        -webkit-border-radius: 5px;

        border-radius: 5px;

        -moz-border-radius: 5px;

        background-clip: padding-box;

        margin: auto;

        width: 350px;

        padding: 35px 35px 15px 35px;

        background: #fff;

        border: 1px solid #eaeaea;

        box-shadow: 0 0 25px #cac6c6;

        z-index: 9999;

        position: relative;

    }

    .login-container .title {

        margin: 0px auto 40px auto;

        text-align: center;

        color: #505458;

    }

    .login-container .remember {

        margin: 0px 0px 25px 0px;

    }

    .wrapper {

        background: #50a3a2;

        background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);

        background: linear-gradient(to bottom right, #127c7b 0, #50a3a2);

        opacity: 0.8;

        position: absolute;

        left: 0;

        width: 100%;

        height: 100%;

        overflow: hidden;

    }

    .wrapper.form-success .containerLogin h1 {

        -webkit-transform: translateY(85px);

        -ms-transform: translateY(85px);

        transform: translateY(85px);

    }

    .containerLogin {

        max-width: 600px;

        margin: 0 auto;

        padding: 80px 0;

        height: 400px;

        text-align: center;

    }

    .containerLogin h1 {

        font-size: 40px;

        -webkit-transition-duration: 1s;

        transition-duration: 1s;

        -webkit-transition-timing-function: ease-in-put;

        transition-timing-function: ease-in-put;

        font-weight: 200;

    }

    .bg-bubbles {

        position: absolute;

        top: 0;

        left: 0;

        width: 100%;

        height: 100%;

        z-index: 1;

    }

    .bg-bubbles li,

    .bg-bubbles ol {

        position: absolute;

        list-style: none;

        display: block;

        width: 40px;

        height: 40px;

        background-color: rgba(255, 255, 255, 0.15);

        bottom: -160px;

        -webkit-animation: square 25s infinite;

        animation: square 25s infinite;

        -webkit-transition-timing-function: linear;

        transition-timing-function: linear;

    }

    ol {

        padding: 0 !important;

    }

    .bg-bubbles ol:nth-child(11) {

        left: 10%;

        top: 10%;

        width: 20px;

        height: 20px;

    }

    .bg-bubbles ol:nth-child(12) {

        left: 20%;

        top: 40%;

        width: 60px;

        height: 60px;

    }

    .bg-bubbles ol:nth-child(13) {

        left: 65%;

        top: 30%;

        width: 100px;

        height: 60px;

    }

    .bg-bubbles ol:nth-child(14) {

        left: 70%;

        top: 30%;

        width: 100px;

        height: 150px;

    }

    .bg-bubbles ol:nth-child(15) {

        left: 50%;

        top: 70%;

        width: 40px;

        height: 60px;

    }

    .bg-bubbles li:nth-child(1) {

        left: 10%;

    }

    .bg-bubbles li:nth-child(2) {

        left: 20%;

        width: 80px;

        height: 80px;

        -webkit-animation-delay: 2s;

        animation-delay: 2s;

        -webkit-animation-duration: 17s;

        animation-duration: 17s;

    }

    .bg-bubbles li:nth-child(3) {

        left: 25%;

        -webkit-animation-delay: 4s;

        animation-delay: 4s;

    }

    .bg-bubbles li:nth-child(4) {

        left: 40%;

        width: 60px;

        height: 60px;

        -webkit-animation-duration: 22s;

        animation-duration: 22s;

        background-color: rgba(255, 255, 255, 0.25);

    }

    .bg-bubbles li:nth-child(5) {

        left: 70%;

    }

    .bg-bubbles li:nth-child(6) {

        left: 80%;

        width: 120px;

        height: 120px;

        -webkit-animation-delay: 3s;

        animation-delay: 3s;

        background-color: rgba(255, 255, 255, 0.2);

    }

    .bg-bubbles li:nth-child(7) {

        left: 32%;

        width: 160px;

        height: 160px;

        -webkit-animation-delay: 7s;

        animation-delay: 7s;

    }

    .bg-bubbles li:nth-child(8) {

        left: 55%;

        width: 20px;

        height: 20px;

        -webkit-animation-delay: 15s;

        animation-delay: 15s;

        -webkit-animation-duration: 40s;

        animation-duration: 40s;

    }

    .bg-bubbles li:nth-child(9) {

        left: 25%;

        width: 10px;

        height: 10px;

        -webkit-animation-delay: 2s;

        animation-delay: 2s;

        -webkit-animation-duration: 40s;

        animation-duration: 40s;

        background-color: rgba(255, 255, 255, 0.3);

    }

    .bg-bubbles li:nth-child(10) {

        left: 90%;

        width: 160px;

        height: 160px;

        -webkit-animation-delay: 11s;

        animation-delay: 11s;

    }

    @-webkit-keyframes square {

        0% {

            -webkit-transform: translateY(0);

            transform: translateY(0);

        }

        100% {

            -webkit-transform: translateY(-700px) rotate(600deg);

            transform: translateY(-700px) rotate(600deg);

        }

    }

    @keyframes square {

        0% {

            -webkit-transform: translateY(0);

            transform: translateY(0);

        }

        100% {

            -webkit-transform: translateY(-700px) rotate(600deg);

            transform: translateY(-700px) rotate(600deg);

        }

    }

</style>

对以上功能更为具体实现和注释见:221215_002_admin(前端登录页面的定义实现)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值