测试平台开发(六)Vue实现首页布局

首页布局一般由三个部分组成,头部(header)、左侧边栏(sidebar)、主内容(main)

一、编写首页组件

1.编写一些需要的配置

1.1 通过 Event Bus 进行组件间通信,来折叠侧边栏;在components下创建common目录,在目录下创建bus.js

bus.js

// srccomponentscommonus.js
import Vue from 'vue';

// 使用 Event Bus
const bus = new Vue();

export default bus;

1.2 编写全局css样式;在assets下创建css目录,在目录下创建color-dark.css和main.css

color-dark.css

 1 /* srcassetscsscolor-dark.css */
 2 .header{
 3     background-color: #242f42;
 4 }
 5 .login-wrap{
 6     background: #324157;
 7 }
 8 .plugins-tips{
 9     background: #eef1f6;
10 }
11 .plugins-tips a{
12     color: #20a0ff;
13 }
14 .el-upload--text em {
15     color: #20a0ff;
16 }
17 .pure-button{
18     background: #20a0ff;
19 }
20 .tags-li.active {
21     border: 1px solid #409EFF;
22     background-color: #409EFF;
23 }
24 .message-title{
25     color: #20a0ff;
26 }
27 .collapse-btn:hover{
28     background: rgb(40,52,70);
29 }

main.css

  1 /* srcassetscssmain.css */
  2 * {
  3     margin: 0;
  4     padding: 0;
  5 }
  6 
  7 html,
  8 body,
  9 #app,
 10 .wrapper {
 11     width: 100%;
 12     height: 100%;
 13     overflow: hidden;
 14 }
 15 
 16 body {
 17     font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
 18 }
 19 
 20 a {
 21     text-decoration: none
 22 }
 23 
 24 
 25 .content-box {
 26     position: absolute;
 27     left: 250px;
 28     right: 0;
 29     top: 60px;
 30     bottom: 0;
 31     padding-bottom: 30px;
 32     -webkit-transition: left .3s ease-in-out;
 33     transition: left .3s ease-in-out;
 34     background: #f0f0f0;
 35 }
 36 
 37 .content {
 38     width: auto;
 39     height: 100%;
 40     padding: 10px;
 41     overflow-y: scroll;
 42     box-sizing: border-box;
 43 }
 44 
 45 .content-collapse {
 46     left: 65px;
 47 }
 48 
 49 .container {
 50     padding: 30px;
 51     background: #fff;
 52     border: 1px solid #ddd;
 53     border-radius: 5px;
 54 }
 55 
 56 .crumbs {
 57     margin: 10px 0;
 58 }
 59 
 60 .el-table th {
 61     background-color: #f5f7fa !important;
 62 }
 63 
 64 .pagination {
 65     margin: 20px 0;
 66     text-align: right;
 67 }
 68 
 69 .plugins-tips {
 70     padding: 20px 10px;
 71     margin-bottom: 20px;
 72 }
 73 
 74 .el-button+.el-tooltip {
 75     margin-left: 10px;
 76 }
 77 
 78 .el-table tr:hover {
 79     background: #f6faff;
 80 }
 81 
 82 .mgb20 {
 83     margin-bottom: 20px;
 84 }
 85 
 86 .move-enter-active,
 87 .move-leave-active {
 88     transition: opacity .5s;
 89 }
 90 
 91 .move-enter,
 92 .move-leave {
 93     opacity: 0;
 94 }
 95 
 96 /*BaseForm*/
 97 
 98 .form-box {
 99     width: 600px;
100 }
101 
102 .form-box .line {
103     text-align: center;
104 }
105 
106 .el-time-panel__content::after,
107 .el-time-panel__content::before {
108     margin-top: -7px;
109 }
110 
111 .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
112     padding-bottom: 0;
113 }

1.3 修改App.vue

// srcApp.vue
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
</script>

<style>
    @import "./assets/css/main.css";
    @import "./assets/css/color-dark.css";
</style>

2. 编写布局头部(Header),在srccomponentscommon目录下创建Header.vue文件

  1 // srccomponentscommonHeader.vue
  2 <template>
  3     <div class="header">
  4         <!-- 折叠按钮 -->
  5         <div class="collapse-btn" @click="collapseChage">
  6             <i v-if="!collapse" class="el-icon-s-fold"></i>
  7             <i v-else class="el-icon-s-unfold"></i>
  8         </div>
  9         <div class="logo">自动化测试平台</div>
 10         <div class="header-right">
 11             <div class="header-user-con">
 12                 <!-- 全屏显示 -->
 13                 <div class="btn-fullscreen" @click="handleFullScreen">
 14                     <el-tooltip effect="dark" :content="fullscreen?`取消全屏`:`全屏`" placement="bottom">
 15                         <i class="el-icon-rank"></i>
 16                     </el-tooltip>
 17                 </div>
 18                 <!-- 用户头像 -->
 19                 <div class="user-avator">
 20                     <!-- <img src="../../assets/img/img.jpg" /> -->
 21                 </div>
 22                 <!-- 用户名下拉菜单 -->
 23                 <el-dropdown class="user-name" trigger="click" @command="handleCommand">
 24                     <span class="el-dropdown-link">
 25                         {{username}}
 26                         <i class="el-icon-caret-bottom"></i>
 27                     </span>
 28                     <el-dropdown-menu slot="dropdown">
 29                         <el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
 30                     </el-dropdown-menu>
 31                 </el-dropdown>
 32             </div>
 33         </div>
 34     </div>
 35 </template>
 36 <script>
 37 import bus from '../common/bus';
 38 export default {
 39     data() {
 40         return {
 41             collapse: false,
 42             fullscreen: false,
 43             name: '普通用户',
 44             message: 2
 45         };
 46     },
 47     computed: {
 48         username() {
 49             let username = localStorage.getItem('ms_username');
 50             return username ? username : this.name;
 51         }
 52     },
 53     methods: {
 54         // 用户名下拉菜单选择事件
 55         handleCommand(command) {
 56             if (command == 'loginout') {
 57                 localStorage.removeItem('ms_username');
 58                 this.$router.push('/');
 59             }
 60         },
 61         // 侧边栏折叠
 62         collapseChage() {
 63             this.collapse = !this.collapse;
 64             bus.$emit('collapse', this.collapse);
 65         },
 66         // 全屏事件
 67         handleFullScreen() {
 68             let element = document.documentElement;
 69             if (this.fullscreen) {
 70                 if (document.exitFullscreen) {
 71                     document.exitFullscreen();
 72                 } else if (document.webkitCancelFullScreen) {
 73                     document.webkitCancelFullScreen();
 74                 } else if (document.mozCancelFullScreen) {
 75                     document.mozCancelFullScreen();
 76                 } else if (document.msExitFullscreen) {
 77                     document.msExitFullscreen();
 78                 }
 79             } else {
 80                 if (element.requestFullscreen) {
 81                     element.requestFullscreen();
 82                 } else if (element.webkitRequestFullScreen) {
 83                     element.webkitRequestFullScreen();
 84                 } else if (element.mozRequestFullScreen) {
 85                     element.mozRequestFullScreen();
 86                 } else if (element.msRequestFullscreen) {
 87                     // IE11
 88                     element.msRequestFullscreen();
 89                 }
 90             }
 91             this.fullscreen = !this.fullscreen;
 92         }
 93     },
 94     mounted() {
 95         if (document.body.clientWidth < 1500) {
 96             this.collapseChage();
 97         }
 98     }
 99 };
100 </script>
101 <style scoped>
102 .header {
103     position: relative;
104     box-sizing: border-box;
105      100%;
106     height: 60px;
107     font-size: 22px;
108     color: #fff;
109 }
110 .collapse-btn {
111     float: left;
112     padding: 0 21px;
113     cursor: pointer;
114     line-height: 60px;
115 }
116 .header .logo {
117     float: left;
118      250px;
119     line-height: 60px;
120 }
121 .header-right {
122     float: right;
123     padding-right: 50px;
124 }
125 .header-user-con {
126     display: flex;
127     height: 70px;
128     align-items: center;
129 }
130 .btn-fullscreen {
131     transform: rotate(45deg);
132     margin-right: 5px;
133     font-size: 24px;
134 }
135 .btn-bell,
136 .btn-fullscreen {
137     position: relative;
138      30px;
139     height: 30px;
140     text-align: center;
141     border-radius: 15px;
142     cursor: pointer;
143 }
144 .btn-bell-badge {
145     position: absolute;
146     right: 0;
147     top: -2px;
148      8px;
149     height: 8px;
150     border-radius: 4px;
151     background: #f56c6c;
152     color: #fff;
153 }
154 .btn-bell .el-icon-bell {
155     color: #fff;
156 }
157 .user-name {
158     margin-left: 10px;
159 }
160 .user-avator {
161     margin-left: 20px;
162 }
163 .user-avator img {
164     display: block;
165      40px;
166     height: 40px;
167     border-radius: 50%;
168 }
169 .el-dropdown-link {
170     color: #fff;
171     cursor: pointer;
172 }
173 .el-dropdown-menu__item {
174     text-align: center;
175 }
176 </style>

3. 编写布局左侧边栏(Sidebar),在srccomponentscommon目录下创建Sidebar.vue文件

  1 // srccomponentscommonSidebar.vue
  2 <template>
  3     <div class="sidebar">
  4         <el-menu
  5             class="sidebar-el-menu"
  6             :default-active="onRoutes"
  7             :collapse="collapse"
  8             background-color="#324157"
  9             text-color="#bfcbd9"
 10             active-text-color="#20a0ff"
 11             unique-opened
 12             router
 13         >
 14             <template v-for="item in items">
 15                 <template v-if="item.subs">
 16                     <el-submenu :index="item.index" :key="item.index">
 17                         <template slot="title">
 18                             <i :class="item.icon"></i>
 19                             <span slot="title">{{ item.title }}</span>
 20                         </template>
 21                         <template v-for="subItem in item.subs">
 22                             <el-submenu
 23                                 v-if="subItem.subs"
 24                                 :index="subItem.index"
 25                                 :key="subItem.index"
 26                             >
 27                                 <template slot="title">{{ subItem.title }}</template>
 28                                 <el-menu-item
 29                                     v-for="(threeItem,i) in subItem.subs"
 30                                     :key="i"
 31                                     :index="threeItem.index"
 32                                 >{{ threeItem.title }}</el-menu-item>
 33                             </el-submenu>
 34                             <el-menu-item
 35                                 v-else
 36                                 :index="subItem.index"
 37                                 :key="subItem.index"
 38                             >{{ subItem.title }}</el-menu-item>
 39                         </template>
 40                     </el-submenu>
 41                 </template>
 42                 <template v-else>
 43                     <el-menu-item :index="item.index" :key="item.index">
 44                         <i :class="item.icon"></i>
 45                         <span slot="title">{{ item.title }}</span>
 46                     </el-menu-item>
 47                 </template>
 48             </template>
 49         </el-menu>
 50     </div>
 51 </template>
 52 
 53 <script>
 54 import bus from '../common/bus';
 55 export default {
 56     data() {
 57         return {
 58             collapse: false,
 59             items: [
 60                 {
 61                     icon: 'el-icon-s-home',
 62                     index: '/dashboard',
 63                     title: '系统首页'
 64                 },
 65             ]
 66         };
 67     },
 68     computed: {
 69         onRoutes() {
 70             return this.$route.path.replace('/', '');
 71         }
 72     },
 73     created() {
 74         // 通过 Event Bus 进行组件间通信,来折叠侧边栏
 75         bus.$on('collapse', msg => {
 76             this.collapse = msg;
 77             bus.$emit('collapse-content', msg);
 78         });
 79     }
 80 };
 81 </script>
 82 
 83 <style scoped>
 84 .sidebar {
 85     display: block;
 86     position: absolute;
 87     left: 0;
 88     top: 60px;
 89     bottom: 0;
 90     overflow-y: scroll;
 91 }
 92 .sidebar::-webkit-scrollbar {
 93      0;
 94 }
 95 .sidebar-el-menu:not(.el-menu--collapse) {
 96      250px;
 97 }
 98 .sidebar > ul {
 99     height: 100%;
100 }
101 </style>

4. 编写一个tab栏方便页面切换,在srccomponentscommon目录下创建Tabs.vue文件

  1 // srccomponentscommonTags.vue
  2 <template>
  3     <div class="tags" v-if="showTags">
  4         <ul>
  5             <li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index">
  6                 <router-link :to="item.path" class="tags-li-title">
  7                     {{item.title}}
  8                 </router-link>
  9                 <span class="tags-li-icon" @click="closeTags(index)"><i class="el-icon-close"></i></span>
 10             </li>
 11         </ul>
 12         <div class="tags-close-box">
 13             <el-dropdown @command="handleTags">
 14                 <el-button size="mini" type="primary">
 15                     标签选项<i class="el-icon-arrow-down el-icon--right"></i>
 16                 </el-button>
 17                 <el-dropdown-menu size="small" slot="dropdown">
 18                     <el-dropdown-item command="other">关闭其他</el-dropdown-item>
 19                     <el-dropdown-item command="all">关闭所有</el-dropdown-item>
 20                 </el-dropdown-menu>
 21             </el-dropdown>
 22         </div>
 23     </div>
 24 </template>
 25 
 26 <script>
 27     import bus from './bus';
 28     export default {
 29         data() {
 30             return {
 31                 tagsList: []
 32             }
 33         },
 34         methods: {
 35             isActive(path) {
 36                 return path === this.$route.fullPath;
 37             },
 38             // 关闭单个标签
 39             closeTags(index) {
 40                 const delItem = this.tagsList.splice(index, 1)[0];
 41                 const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
 42                 if (item) {
 43                     delItem.path === this.$route.fullPath && this.$router.push(item.path);
 44                 }else{
 45                     this.$router.push('/home');
 46                 }
 47             },
 48             // 关闭全部标签
 49             closeAll(){
 50                 this.tagsList = [];
 51                 this.$router.push('/home');
 52             },
 53             // 关闭其他标签
 54             closeOther(){
 55                 const curItem = this.tagsList.filter(item => {
 56                     return item.path === this.$route.fullPath;
 57                 })
 58                 this.tagsList = curItem;
 59             },
 60             // 设置标签
 61             setTags(route){
 62                 const isExist = this.tagsList.some(item => {
 63                     return item.path === route.fullPath;
 64                 })
 65                 if(!isExist){
 66                     if(this.tagsList.length >= 8){
 67                         this.tagsList.shift();
 68                     }
 69                     this.tagsList.push({
 70                         title: route.meta.title,
 71                         path: route.fullPath,
 72                         name: route.matched[1].components.default.name
 73                     })
 74                 }
 75                 bus.$emit('tags', this.tagsList);
 76             },
 77             handleTags(command){
 78                 command === 'other' ? this.closeOther() : this.closeAll();
 79             }
 80         },
 81         computed: {
 82             showTags() {
 83                 return this.tagsList.length > 0;
 84             }
 85         },
 86         watch:{
 87             $route(newValue, oldValue){
 88                 this.setTags(newValue);
 89             }
 90         },
 91         created(){
 92             this.setTags(this.$route);
 93             // 监听关闭当前页面的标签页
 94             bus.$on('close_current_tags', () => {
 95                 for (let i = 0, len = this.tagsList.length; i < len; i++) {
 96                     const item = this.tagsList[i];
 97                     if(item.path === this.$route.fullPath){
 98                         if(i < len - 1){
 99                             this.$router.push(this.tagsList[i+1].path);
100                         }else if(i > 0){
101                             this.$router.push(this.tagsList[i-1].path);
102                         }else{
103                             this.$router.push('/home');
104                         }
105                         this.tagsList.splice(i, 1);
106                         break;
107                     }
108                 }
109             })
110         }
111     }
112 
113 </script>
114 
115 
116 <style>
117     .tags {
118         position: relative;
119         height: 30px;
120         overflow: hidden;
121         background: #fff;
122         padding-right: 120px;
123         box-shadow: 0 5px 10px #ddd;
124         top: 0;
125     }
126 
127     .tags ul {
128         box-sizing: border-box;
129          100%;
130         height: 100%;
131     }
132 
133     .tags-li {
134         float: left;
135         margin: 3px 5px 2px 3px;
136         border-radius: 3px;
137         font-size: 12px;
138         overflow: hidden;
139         cursor: pointer;
140         height: 23px;
141         line-height: 23px;
142         border: 1px solid #e9eaec;
143         background: #fff;
144         padding: 0 5px 0 12px;
145         vertical-align: middle;
146         color: #666;
147         -webkit-transition: all .3s ease-in;
148         -moz-transition: all .3s ease-in;
149         transition: all .3s ease-in;
150     }
151 
152     .tags-li:not(.active):hover {
153         background: #f8f8f8;
154     }
155 
156     .tags-li.active {
157         color: #fff;
158     }
159 
160     .tags-li-title {
161         float: left;
162         max- 80px;
163         overflow: hidden;
164         white-space: nowrap;
165         text-overflow: ellipsis;
166         margin-right: 5px;
167         color: #666;
168     }
169 
170     .tags-li.active .tags-li-title {
171         color: #fff;
172     }
173 
174     .tags-close-box {
175         position: absolute;
176         right: 0;
177         top: 0;
178         box-sizing: border-box;
179         padding-top: 1px;
180         text-align: center;
181          110px;
182         height: 30px;
183         background: #fff;
184         box-shadow: -3px 0 15px 3px rgba(0, 0, 0, .1);
185         z-index: 10;
186     }
187 
188 </style>

5. 将这些组件拼到一块,在srccomponentscommon目录下创建Home.vue文件

 1 // srccomponentscommonHome.vue
 2 <template>
 3     <div class="wrapper">
 4         <v-head></v-head>
 5         <v-sidebar></v-sidebar>
 6         <div class="content-box" :class="{'content-collapse':collapse}">
 7             <v-tags></v-tags>
 8             <div class="content">
 9                 <transition name="move" mode="out-in">
10                     <keep-alive :include="tagsList">
11                         <router-view></router-view>
12                     </keep-alive>
13                 </transition>
14                 <el-backtop target=".content"></el-backtop>
15             </div>
16         </div>
17     </div>
18 </template>
19 
20 <script>
21 import vHead from './Header.vue';
22 import vSidebar from './Sidebar.vue';
23 import vTags from './Tags.vue';
24 import bus from './bus';
25 export default {
26     data() {
27         return {
28             tagsList: [],
29             collapse: false
30         };
31     },
32     components: {
33         vHead,
34         vSidebar,
35         vTags
36     },
37     created() {
38         bus.$on('collapse-content', msg => {
39             this.collapse = msg;
40         });
41 
42         // 只有在标签页列表里的页面才使用keep-alive,即关闭标签之后就不保存到内存中了。
43         bus.$on('tags', msg => {
44             let arr = [];
45             for (let i = 0, len = msg.length; i < len; i++) {
46                 msg[i].name && arr.push(msg[i].name);
47             }
48             this.tagsList = arr;
49         });
50     }
51 };
52 </script>

6. 编写系统首页,数据等项目完善后补全;在srccomponentspage下创建Dashboard.vue文件

  1 // srccomponentspageDashboard.vue
  2 <template>
  3     <div>
  4         <el-row :gutter="20">
  5             <el-col :span="8">
  6                 <el-card shadow="hover" style="height:252px;">
  7                     <div slot="header" class="clearfix">
  8                         <span>用例执行概况</span>
  9                     </div>
 10                     通过<el-progress :percentage=success_rate color="#42b983"></el-progress>
 11                     失败<el-progress :percentage=fail_rate color="#f56c6c"></el-progress>
 12                 </el-card>
 13             </el-col>
 14             <el-col :span="16">
 15                 <el-row :gutter="20" class="mgb20">
 16                     <el-col :span="8">
 17                         <el-card shadow="hover" :body-style="{padding: '0px'}">
 18                             <div class="grid-content grid-con-1">
 19                                 <i class="el-icon-s-cooperation grid-con-icon"></i>
 20                                 <div class="grid-cont-right">
 21                                     <div class="grid-num">{{ projects_count }}</div>
 22                                     <div>项目数量</div>
 23                                 </div>
 24                             </div>
 25                         </el-card>
 26                     </el-col>
 27                     <el-col :span="8">
 28                         <el-card shadow="hover" :body-style="{padding: '0px'}">
 29                             <div class="grid-content grid-con-2">
 30                                 <i class="el-icon-menu grid-con-icon"></i>
 31                                 <div class="grid-cont-right">
 32                                     <div class="grid-num">{{ modulars_count }}</div>
 33                                     <div>模块数量</div>
 34                                 </div>
 35                             </div>
 36                         </el-card>
 37                     </el-col>
 38                     <el-col :span="8">
 39                         <el-card shadow="hover" :body-style="{padding: '0px'}">
 40                             <div class="grid-content grid-con-2">
 41                                 <i class="el-icon-s-grid grid-con-icon"></i>
 42                                 <div class="grid-cont-right">
 43                                     <div class="grid-num">{{ testcases_count }}</div>
 44                                     <div>用例数量</div>
 45                                 </div>
 46                             </div>
 47                         </el-card>
 48                     </el-col>
 49                     <el-col :span="8">
 50                         <el-card shadow="hover" :body-style="{padding: '0px'}">
 51                             <div class="grid-content grid-con-2">
 52                                 <i class="el-icon-ship grid-con-icon"></i>
 53                                 <div class="grid-cont-right">
 54                                     <div class="grid-num">{{ envs_count }}</div>
 55                                     <div>运行环境数</div>
 56                                 </div>
 57                             </div>
 58                         </el-card>
 59                     </el-col>
 60                     <el-col :span="8">
 61                         <el-card shadow="hover" :body-style="{padding: '0px'}">
 62                             <div class="grid-content grid-con-2">
 63                                 <i class="el-icon-message grid-con-icon"></i>
 64                                 <div class="grid-cont-right">
 65                                     <div class="grid-num">{{ reports_count }}</div>
 66                                     <div>测试报告数</div>
 67                                 </div>
 68                             </div>
 69                         </el-card>
 70                     </el-col>
 71                 </el-row>
 72             </el-col>
 73         </el-row>
 74     </div>
 75 </template>
 76 
 77 <script>
 78 import bus from '../common/bus';
 79 export default {
 80     name: 'dashboard',
 81     data() {
 82         return {
 83             projects_count:999,
 84             modulars_count:999,
 85             testcases_count:999,
 86             envs_count:999,
 87             reports_count:999,
 88             success_rate:90,
 89             fail_rate:10,
 90         };
 91     },
 92 };
 93 </script>
 94 
 95 
 96 <style scoped>
 97 .el-row {
 98     margin-bottom: 20px;
 99 }
100 
101 .grid-content {
102     display: flex;
103     align-items: center;
104     height: 100px;
105 }
106 
107 .grid-cont-right {
108     flex: 1;
109     text-align: center;
110     font-size: 14px;
111     color: #999;
112 }
113 
114 .grid-num {
115     font-size: 30px;
116     font-weight: bold;
117 }
118 
119 .grid-con-icon {
120     font-size: 50px;
121      100px;
122     height: 100px;
123     text-align: center;
124     line-height: 100px;
125     color: #fff;
126 }
127 
128 .grid-con-1 .grid-con-icon {
129     background: rgb(45, 140, 240);
130 }
131 
132 .grid-con-1 .grid-num {
133     color: rgb(45, 140, 240);
134 }
135 
136 .grid-con-2 .grid-con-icon {
137     background: rgb(242, 94, 67);
138 }
139 
140 .grid-con-2 .grid-num {
141     color: rgb(242, 94, 67);
142 }
143 
144 </style>

二、编写组件路由

 1 // src
outerindex.js
 2 import Vue from 'vue'
 3 import Router from 'vue-router'
 4 
 5 Vue.use(Router)
 6 
 7 export default new Router({
 8   routes: [
 9     {
10       path: '/',
11       name: '/login',
12       component: () => import('../components/page/login.vue'),
13       meta: { title: '登录' }
14     },
15     {
16       path: '/home',
17       redirect: '/dashboard'
18     },
19     {
20       path: '/',
21       component: () => import('../components/common/Home.vue'),
22       meta: { title: '自述文件' },
23       children: [
24         {
25           // 系统主页
26           path: '/dashboard',
27           component: () => import('../components/page/Dashboard.vue'),
28           meta: { title: '系统首页' }
29         },
30       ]
31   }
32   ]
33 })

三、测试

GitHub持续更新:https://github.com/debugf/vueplatform

 

 转载请注明出处,商用请征得作者本人同意,谢谢!!!

原文地址:https://www.cnblogs.com/debugf/p/13433878.html