• Spring Boot + vue-element 开发个人博客项目实战教程(二十五、项目完善及扩展(前端部分))


    作者简介:码上言


    代表教程:Spring Boot + vue-element 开发个人博客项目实战教程


    专栏内容:零基础学Java个人博客系统


    我的文档网站:http://xyhwh-nav.cn/

    👦 学习讨论群:530826149

    后端代码gitee地址:https://gitee.com/whxyh/personal_blog

    前端代码gitee地址:https://gitee.com/whxyh/personal_vue

    项目部署视频

    https://www.bilibili.com/video/BV1sg4y1A7Kv/?vd_source=dc7bf298d3c608d281c16239b3f5167b

    前言

    这一篇将是我们项目开发的最后一篇文章了,到这里该和大家说再见了,这个项目从开始写到现在刚好一年了,有时间就写写,中间断了好几个月的时间,看到好多人说对他们很有用,我写的也就变成了有意义的事情,希望大家都不忘初心,牢记使命,认真的学习技术和好好地生活。

    正如士兵突击中的许三多说的,好好活,就是做有意义的事,做有意义的事情,就是好好活着。

    预告

    这里给大家预告一下,这个教程已经接近尾声了,看到大家给我的好多反馈,说的学到了很多东西,我感觉到十分的欣慰,感觉自己的付出没有白费,有的小伙伴说比某些机构的课程学的还多,我感觉只要好好地学习,都是可以的,哪怕你只是拿这个项目去最毕设或者其他的之类的,反正技多不压身,这个也涵盖了好多的东西,基本的入门也可以了。

    我在想要不要再搞一个大一点的项目,将知识点再扩大一些,比如现在没有权限的操作、我们登录再加上短信验证码、文件的上传和解析、redis的实际运用等操作、自动化部署代码、原型设计、日志记录、小程序学习等新的技术,会更加完善做项目的流程和规范,基本上达到全栈的技术

    我再考虑是不是要开通付费的专栏,大家可以根据自身的需求来学习,感觉自己需要学习就来学习,不需要看这个教程可以了就不要订阅。我感觉肯定会比花上万的去培训要实在的多,大家自己衡量。

    欢迎大家给我提改进意见或者要加入什么技术,我尽量用项目来整合这些技术加入实际的应用。感谢各位!

    1、项目完善

    1.1、文章创建完后在编辑标签没有展示

    这是上一篇的bug,我在测试文章添加后,然后再点击编辑后,发现标签的值并没有,我查看了接口返回的数据为空,发现是后端添加文章存入缓存的问题,这里修改一下,只要加一行代码即可,大家可以提前想一下。

    先分析一下我们调用的方法findById。

     @Override
        public ArticleVO findById(Integer articleId) {
            Article article = articleMap.get(articleId);
            。。。。。。
    
    • 1
    • 2
    • 3
    • 4

    只看这一句即可,文章的数据来自map中,然而再添加文章的时候,map只存的是页面传来的数据,并没有将标签的数据给map,所以查出来的话肯定没有标签数据。

    可以在文章添加的实现类中直接加上一下这行代码,在我们添加文章之后,在调用init重新加载一下缓存里的数据

    this.init();
    
    • 1

    完整代码:

    @Override
        public void insertOrUpdateArticle(ArticleInsertBO bo) {
            //分类添加
            Category category = saveCategory(bo);
            Article article = BeanUtil.copyProperties(bo, Article.class);
            if (category != null) {
                article.setCategoryId(category.getCategoryId());
            }
            String username = (String) SecurityUtils.getSubject().getPrincipal();
            User user = userService.getUserByUserName(username);
            article.setUserId(user.getId());
            article.setAuthor(user.getUserName());
            article.setViews(0L);
            article.setTotalWords(WordCountUtil.wordCount(bo.getContent()));
            if (bo.getId() != null) {
                articleMapper.updateArticle(article);
            } else {
                articleMapper.createArticle(article);
            }
            articleMap.put(article.getId(), article);
            //添加文章标签
            saveTags(bo, article.getId());
            this.init();
            //添加文章发送邮箱提醒
            try {
                String content = "【{0}】您好:\n" +
                        "您已成功发布了标题为: {1} 的文章 \n" +
                        "请注意查收!\n";
                MailInfo build = MailInfo.builder()
                        .receiveMail(user.getEmail())
                        .content(MessageFormat.format(content, user.getUserName(), article.getTitle()))
                        .title("文章发布")
                        .build();
                SendMailConfig.sendMail(build);
            } catch (Exception e) {
                log.error("邮件发送失败{}", e.getMessage());
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    1.2、文章字数统计实现

    我们在文章的数据表中预留了一个文章的字数,一开始的时候我直接赋值的是0,现在我们要把字数统计给加上,所以需要写一个字数统计的工具类。

    package com.blog.personalblog.util;
    
    /**
     * @author: SuperMan
     * @create: 2022-10-14
     **/
    public class WordCountUtil {
    
        /**
         * 统计字数, 空格不统计
         * @param string
         * @return
         */
        public static long wordCount(String string) {
            if (string == null) {
                return 0;
            }
            long letterCount = 0L;
            long numCount = 0L;
            long otherCount = 0L;
    
            String str = string.trim();
            char[] chr = str.toCharArray();
            for(int i = 0; i < chr.length;i++){
                if(Character.isLetter(chr[i])){
                    letterCount++;
                } else if(Character.isDigit(chr[i])){
                    numCount ++;
                } else{
                    otherCount ++;
                }
            }
            return letterCount + numCount + otherCount;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    还是在添加的方法中来统计文章字数。将原来的**article.setTotalWords(0L)**改成以下代码:

     article.setTotalWords(WordCountUtil.wordCount(bo.getContent()));
    
    • 1

    查看页面效果:
    在这里插入图片描述

    1.3、发布文章新建分类修改

    我在测试的时候,发布文章没有选择从数据库查出来的分类,而是自己创建的一个分类,点击发布会报错。

    首先定位到代码错误的信息。

       private Category saveCategory(ArticleInsertBO bo) {
            if (StrUtil.isEmpty(bo.getCategoryName())) {
                return null;
            }
            Category category = categoryService.getCategoryByName(bo.getCategoryName());
            if (category == null && !ArticleArtStatusEnum.DRAFT.getStatus().equals(bo.getArtStatus())) {
                category.setCategoryName(bo.getCategoryName());
                categoryService.saveCategory(category);
            }
            return category;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Category category = categoryService.getCategoryByName(bo.getCategoryName());

    这一句我们拿前端传过来的分类名去查找,然后没有找到,我们又将前端的值赋给了它,就报错了。

    我们拿到的category是一个null,而null对象在堆中会被java的垃圾回收机制回收。所以这里赋值直接报错了,所以我们再重新new一个分类对象即可。

     private Category saveCategory(ArticleInsertBO bo) {
            if (StrUtil.isEmpty(bo.getCategoryName())) {
                return null;
            }
            Category category = categoryService.getCategoryByName(bo.getCategoryName());
            Category newCategory = new Category();
            if (category == null && !ArticleArtStatusEnum.DRAFT.getStatus().equals(bo.getArtStatus())) {
                newCategory.setCategoryName(bo.getCategoryName());
                categoryService.saveCategory(newCategory);
                return newCategory;
            }
            return category;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.4、邮件发送配置修改

    这里我把邮箱的配置进行了修改,原来配置的邮箱信息是在代码里配置的,维护不太方便,我把它提到了配置文件中了。以后修改邮箱信息直接修改配置文件,就不需要找代码了。打开application.yml

    send:
      mail:
        host: # 邮件服务器的SMTP地址
        port: # 邮件服务器的SMTP端口
        from: # 发件人
        pass: # 密码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后修改代码,打开SendMailConfig.java,将配置信息引入进来。

        @Value("${send.mail.host}")
        private String host;
    
        @Value("${send.mail.port}")
        private Integer port;
    
        @Value("${send.mail.from}")
        private String from;
    
        @Value("${send.mail.pass}")
        private String pass;
    
    public void sendMail(MailInfo mailInfo) {
            try {
                MailAccount account = new MailAccount();
                //邮件服务器的SMTP地址
                account.setHost(host);
                //邮件服务器的SMTP端口
                account.setPort(port);
                //发件人
                account.setFrom(from);
                //密码
                account.setPass(pass);
                //使用SSL安全连接
                account.setSslEnable(false);
                MailUtil.send(account, mailInfo.getReceiveMail(),
                        mailInfo.getTitle(), mailInfo.getContent(), false);
                log.info("邮件发送成功!");
            } catch (Exception e) {
                log.error("邮件发送失败" + JSONUtil.toJsonStr(mailInfo));
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    添加完之后,再去测试下。

    1.5、登录页改造升级

    我们现在的登录页面非常的原始,不太好看,俗话说人靠衣服马靠鞍,我们也将登录的入口进行改造,后端的逻辑不用动,我们只改前端代码即可。

    <style rel="stylesheet/scss" lang="scss">
    $bg:#889aa4;
    $light_gray:#eaeaea;
    
    /* reset element-ui css */
    .login-container {
      .el-input {
        display: inline-block;
        height: 47px;
        width: 85%;
        input {
          background: transparent;
          border: 0px;
          -webkit-appearance: none;
          border-radius: 0px;
          padding: 12px 5px 12px 15px;
          color: black;
          height: 47px;
          &:-webkit-autofill {
            -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
            -webkit-text-fill-color: black !important;
          }
        }
      }
      .el-form-item {
        border: 1px solid rgba(255, 255, 255, 0.1);
        background: rgba(0, 0, 0, 0.1);
        border-radius: 5px;
        color: #454545;
      }
    }
    
    </style>
    
    <style rel="stylesheet/scss" lang="scss" scoped>
    $bg:#889aa4;
    $dark_gray:#889aa4;
    $light_gray:#eee;
    .login-container {
      position: fixed;
      height: 100%;
      width: 100%;
      background:url("../../assets/bj.jpg");
      position:fixed;
      background-size:100% 100%;
      // background-image: "../../assets/bg.jpg";
      .login-form {
        position: absolute;
        left: 0;
        right: 0;
        width: 520px;
        max-width: 100%;
        padding: 35px 35px 15px 35px;
        margin: 120px auto;
      }
      .tips {
        font-size: 14px;
        color: #fff;
        margin-bottom: 10px;
        span {
          &:first-of-type {
            margin-right: 16px;
          }
        }
      }
      .svg-container {
        padding: 6px 5px 6px 15px;
        color: $dark_gray;
        vertical-align: middle;
        width: 30px;
        display: inline-block;
      }
      .title {
        font-size: 28px;
        font-weight: 400;
        margin: 0px auto 40px auto;
        text-align: center;
        font-weight: bold;
      }
      .show-pwd {
        position: absolute;
        right: 10px;
        top: 7px;
        font-size: 16px;
        color: $dark_gray;
        cursor: pointer;
        user-select: none;
      }
      
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    这里只修改了一些样式和添加了一个背景图片。也算是有点样子了。
    在这里插入图片描述

    2、首页功能前端开发

    这一块也就是对应的我们的首页,刚一进来就能直观看到的,我们尽量做的美观一点,逼格高一点。

    首先我们首页的顶部先放四个导航菜单,用来展示我们的一些重要的数据。

    下面的开发我先写前端页面布局完成之后,再去写后端的代码

    2.1、顶部导航

    这里使用了vue-element-admin的首页的功能。打开我们的前端项目,然后找到/views/dashboard

    然后我们引入一个组件,这个导航菜单已经封装成了一个组件。

    dashboard文件下新建一个components文件夹,然后创建一个文件PanelGroup.vue

    <template>
      <el-row :gutter="40" class="panel-group">
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-people">
              <svg-icon icon-class="peoples" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                文章数量
              </div>
              <count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-message">
              <svg-icon icon-class="message" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                分类数量
              </div>
              <count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-money">
              <svg-icon icon-class="money" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                标签数量
              </div>
              <count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-shopping">
              <svg-icon icon-class="shopping" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                用户数量
              </div>
              <count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
            </div>
          </div>
        </el-col>
      </el-row>
    </template>
    
    <script>
    import CountTo from 'vue-count-to'
    
    export default {
      components: {
        CountTo
      },
      methods: {
        handleSetLineChartData(type) {
          this.$emit('handleSetLineChartData', type)
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    .panel-group {
      margin-top: 18px;
    
      .card-panel-col {
        margin-bottom: 32px;
      }
    
      .card-panel {
        height: 108px;
        cursor: pointer;
        font-size: 12px;
        position: relative;
        overflow: hidden;
        color: #666;
        background: #fff;
        box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
        border-color: rgba(0, 0, 0, .05);
    
        &:hover {
          .card-panel-icon-wrapper {
            color: #fff;
          }
    
          .icon-people {
            background: #40c9c6;
          }
    
          .icon-message {
            background: #36a3f7;
          }
    
          .icon-money {
            background: #f4516c;
          }
    
          .icon-shopping {
            background: #34bfa3
          }
        }
    
        .icon-people {
          color: #40c9c6;
        }
    
        .icon-message {
          color: #36a3f7;
        }
    
        .icon-money {
          color: #f4516c;
        }
    
        .icon-shopping {
          color: #34bfa3
        }
    
        .card-panel-icon-wrapper {
          float: left;
          margin: 14px 0 0 14px;
          padding: 16px;
          transition: all 0.38s ease-out;
          border-radius: 6px;
        }
    
        .card-panel-icon {
          float: left;
          font-size: 48px;
        }
    
        .card-panel-description {
          float: right;
          font-weight: bold;
          margin: 26px;
          margin-left: 0px;
    
          .card-panel-text {
            line-height: 18px;
            color: rgba(0, 0, 0, 0.45);
            font-size: 16px;
            margin-bottom: 12px;
          }
    
          .card-panel-num {
            font-size: 20px;
          }
        }
      }
    }
    
    @media (max-width:550px) {
      .card-panel-description {
        display: none;
      }
    
      .card-panel-icon-wrapper {
        float: none !important;
        width: 100%;
        height: 100%;
        margin: 0 !important;
    
        .svg-icon {
          display: block;
          margin: 14px auto !important;
          float: none !important;
        }
      }
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182

    然后去dashboard目录下的index.vue中引入该组件。

    import PanelGroup from './components/PanelGroup'
    
    export default {
      name: 'Dashboard',
      components: {
        PanelGroup
      },
      computed: {
        ...mapGetters([
          'name',
          'roles'
        ])
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这时控制台会报一个错误

    此时我们要执行:npm install --save vue-count-to即可。

    执行完之后,我们在引入该组件。

    <panel-group ></panel-group>
    
    • 1

    此时页面上就已经有数据了,我们进行改造一下页面。
    在这里插入图片描述

    可以看到上边还缺少图标和描述之类的,这个是在组件里修改,打开PanelGroup.vue修改。

    <template>
      <el-row :gutter="40" class="panel-group">
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-people">
              <svg-icon icon-class="documentation" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                文章数量
              </div>
              <count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-message">
              <svg-icon icon-class="component" class-name="card-panel-icon"/>
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                分类数量
              </div>
              <count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-money">
              <svg-icon icon-class="icon" class-name="card-panel-icon" />
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                标签数量
              </div>
              <count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
            </div>
          </div>
        </el-col>
        <el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
          <div class="card-panel">
            <div class="card-panel-icon-wrapper icon-shopping">
              <svg-icon icon-class="people" class-name="card-panel-icon"/>
            </div>
            <div class="card-panel-description">
              <div class="card-panel-text">
                用户数量
              </div>
              <count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
            </div>
          </div>
        </el-col>
      </el-row>
    </template>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    图标的话,可以去官网上查找或者从这里查找,然后将图标下载放到自己项目的svg目录下即可。https://panjiachen.github.io/vue-element-admin/#/icon/index

    改造完之后是这样的页面

    在这里插入图片描述

    2.2、ECharts入门

    这里先介绍一下echarts

    官网:https://echarts.apache.org/zh/index.html

    什么是echarts?

    它是一个基于 JavaScript 的开源可视化图表库,可以用于我们对数据分析的可视化展示,是我们的数据在图表中清晰可见,一般领导比较喜欢看这种分析的图表。

    具体的如何使用这里不再一一讲述了,可以查看官方给的文档,有快速上手的教程可以学习。

    2.3、数据图表

    这里我们先安装一下echarts图表库。使用以下命令

    npm install echarts --save
    
    • 1

    之后我们在创建的components组件文件中新建一个放图表的文件,现在是一个图表对应一个文件,新建一个BarChart.vue文件,这个放我们的柱状图,打开文件,先写一下存放图表的的容器,并设置一下高和宽。

    <template>
      <div :class="className" :style="{height:height,width:width}" />
    </template>
    
    • 1
    • 2
    • 3

    紧接着要去写一下图表的代码,先引入echarts文件

    import * as echarts from 'echarts'
    require('echarts/theme/macarons') // echarts theme
    
    • 1
    • 2

    然后设置一下容器的高和宽

    export default {
      props: {
        className: {
          type: String,
          default: 'chart'
        },
        width: {
          type: String,
          default: '100%'
        },
        height: {
          type: String,
          default: '300px'
        }
      },
     data() {
        return {
          chart: null
        }
      },
      mounted() {
        this.$nextTick(() => {
          this.initChart()
        })
      },
      beforeDestroy() {
        if (!this.chart) {
          return
        }
        this.chart.dispose()
        this.chart = null
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    然后就可以通过 echarts.init方法初始化一个 echarts 实例并通过 setOption 方法生成一个简单的柱状图

    methods: {
        initChart() {
          this.chart = echarts.init(this.$el, 'macarons')
    
          this.chart.setOption({
            title: {
              text: '发文数量'
            },
            tooltip: {
              trigger: 'axis',
              axisPointer: {
                type: 'shadow'
              }
            },
            grid: {
              left: '3%',
              right: '4%',
              bottom: '3%',
              containLabel: true
            },
            xAxis: [
              {
                type: 'category',
                data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
                axisTick: {
                  alignWithLabel: true
                }
              }
            ],
            yAxis: [
              {
                type: 'value'
              }
            ],
            series: [
              {
                name: 'Direct',
                type: 'bar',
                barWidth: '60%',
                data: [10, 52, 200, 334, 390, 330, 220]
              }
            ]
            
          })
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    这里的代码大家可以去echarts官网的实例中去查找。

    然后再去我们的主页将这个组件引进来。

    import BarChart from './components/BarChart'
    
    components: {
        PanelGroup,
        BarChart,
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    页面代码:

    	<el-row :gutter="32" class="row-chart">
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <bar-chart />
            </div>
          </el-col>
        </el-row>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后再设置一下css样式

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后我们打开页面,查看一下图表有没有渲染出来
    在这里插入图片描述

    此时就渲染出来了,一个我们会了,我们再添加两个图表分别统计分类和访问量。

    和之前的那个图表一样,我这里不再一一讲述,只把代码给大家展现出来。

    在components文件夹中新建一个PieChart.vue

    <template>
      <div :class="className" :style="{height:height,width:width}" />
    </template>
    
    <script>
    import * as echarts from 'echarts'
    require('echarts/theme/macarons') // echarts theme
    
    
    export default {
      props: {
        className: {
          type: String,
          default: 'chart'
        },
        width: {
          type: String,
          default: '100%'
        },
        height: {
          type: String,
          default: '300px'
        }
      },
      data() {
        return {
          chart: null
        }
      },
      mounted() {
        this.$nextTick(() => {
          this.initChart()
        })
      },
      beforeDestroy() {
        if (!this.chart) {
          return
        }
        this.chart.dispose()
        this.chart = null
      },
      methods: {
        initChart() {
          this.chart = echarts.init(this.$el, 'macarons')
    
          this.chart.setOption({
            title: {
                text: '分类占比',
                left: 'left'
            },
            tooltip: {
                trigger: 'item'
            },
            series: [
                {
                name: 'Access From',
                type: 'pie',
                radius: '50%',
                data: [
                    { value: 1048, name: 'Search Engine' },
                    { value: 735, name: 'Direct' },
                    { value: 580, name: 'Email' },
                    { value: 484, name: 'Union Ads' },
                    { value: 300, name: 'Video Ads' }
                ],
                emphasis: {
                    itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
                }
            ]
            
          })
        }
      }
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    再新建一个LineChart.vue

    <template>
      <div :class="className" :style="{height:height,width:width}" />
    </template>
    
    <script>
    import * as echarts from 'echarts'
    require('echarts/theme/macarons') // echarts theme
    
    
    export default {
      props: {
        className: {
          type: String,
          default: 'chart'
        },
        width: {
          type: String,
          default: '100%'
        },
        height: {
          type: String,
          default: '300px'
        }
      },
      data() {
        return {
          chart: null
        }
      },
      mounted() {
        this.$nextTick(() => {
          this.initChart()
        })
      },
      beforeDestroy() {
        if (!this.chart) {
          return
        }
        this.chart.dispose()
        this.chart = null
      },
      methods: {
        initChart() {
          this.chart = echarts.init(this.$el, 'macarons')
    
          this.chart.setOption({
           title: {
              text: '访问量'
           },
            xAxis: {
                type: 'category',
                data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                data: [820, 932, 901, 934, 1290, 1330, 1320],
                type: 'line',
                smooth: true
                }
            ]
            
          })
        }
      }
    }
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    再去主页将这两个引入。

    index.vue完整代码如下

    <template>
      <div class="dashboard-container">
        <panel-group ></panel-group>
    
        <!-- 数据分析 -->
        <el-row :gutter="32" class="row-chart">
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <bar-chart />
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <pie-chart />
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
              <line-chart />
            </div>
          </el-col>
        </el-row>
     
      </div>
      
    </template>
    
    <script>
    
    import PanelGroup from './components/PanelGroup'
    import BarChart from './components/BarChart'
    import PieChart from './components/PieChart'
    import LineChart from './components/LineChart'
    
    export default {
      name: 'Dashboard',
      components: {
        PanelGroup,
        BarChart,
        PieChart,
        LineChart
      },
      
      data() {
        return {
        }
      },
      methods: {
       
      },
    
    }
    </script>
    
    <style rel="stylesheet/scss" lang="scss" scoped>
    
    .dashboard {
      &-container {
        padding: 32px;
        background-color: #f0f2f5;
      }
    }
    .chart-wrapper {
      background: #fff;
      padding: 16px 16px 0;
      margin-bottom: 32px;
    }
    .row-chart{
      margin-top: 30px;
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    然后我们看一下效果
    在这里插入图片描述

    是不是感觉还挺哇塞的,其实那种看着非常高大上的大屏展示就是这种画出来的,我后边应该会写一篇如何制作大屏的页面的文章,大家可以等待一下。

    现在我们的首页是不是有点样子了,越来越完善了,是有点系统的样子了。

    接下来我们再美化一下首页,再添加一点小功能。

    2.4、添加日历

    大家可以参考这个文章添加日历,以下就是我参照实现的,稍微做了修改。

    参考文章:vue日历插件vue-calendar

    1. 首先安装一下日历的组件
    npm i vue-calendar-component --save
    
    • 1

    如果安装失败,可以试试以下的命令

    cnpm i vue-calendar-component --save
    
    • 1
    1. 引入组件

    在我们的首页引入一下日历的组件

    import Calendar from 'vue-calendar-component';
    
    • 1

    然后写绘制日历的代码

    	 <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <div class="con">
                <div class="now-data-myself">
                  <div class="now-data-myself-time">{{ date }}</div>
                  <div class="now-data-myself-week">{{ week }}</div>
                </div>
                <Calendar
                  v-on:choseDay="clickDay"
                  v-on:changeMonth="changeDate"
                  v-on:isToday="clickToday"
                ></Calendar>
              </div>
            </div>
          </el-col>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    components: {
        PanelGroup,
        BarChart,
        PieChart,
        LineChart,
        Calendar
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    data() {
        return {
         date: "",
         week: "",
        }
    },
    created() {
        var now = new Date();
        this.date = now.getDate();//得到日期
        var day = now.getDay();//得到周几
        var arr_week = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");
        this.week = arr_week[day];
    },
    
    methods: {
       clickDay(data) {},
       changeDate(data) {},
       clickToday(data) {}
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    CSS样式:

    .now-data-myself {
      width: 40%;
      position: absolute;
      border-right: 1px solid rgba(227, 227, 227, 0.6);
    }
    .con {
      position: relative;
      max-width: 400px;
      margin: auto;
    }
    .con .wh_content_all {
      background: transparent !important; 
    }
    .wh_top_changge li {
      color: #F56C6C !important;
      font-size: 15px !important;
    }
    .wh_content_item, .wh_content_item_tag {
      color: #303133 !important;
    }
    .wh_content_item .wh_isToday {
      background: #00d985  !important;
      color: #fff  !important;
    }
    .wh_content_item .wh_chose_day {
      background: #409EFF  !important;
      color: #ffff  !important;
    }
    .wh_item_date:hover {
        background: rgb(217, 236, 255) !important;
        border-radius: 100px !important;
        color: rgb(102, 177, 255)  !important;
    }
    .wh_jiantou1[data-v-2ebcbc83] {
        border-top: 2px solid #909399;
        border-left: 2px solid #909399;
        width: 7px;
        height: 7px;
    }
    .wh_jiantou2[data-v-2ebcbc83] {
        border-top: 2px solid #909399;
        border-right: 2px solid #909399;
        width: 7px;
        height: 7px;
    }
    .wh_top_tag[data-v-2ebcbc83] {
      color: #409EFF;
      border-top: 1px solid rgba(227, 227, 227, 0.6);
      border-bottom: 1px solid rgba(227, 227, 227, 0.6);
    }
    .wh_container[data-v-2ebcbc83] {
        max-width: 400px;
    }
    .wh_top_changge[data-v-2ebcbc83] {
        display: flex;
        width: 50%;
        margin-left: 43%;
    }
    .now-data-myself-time {
      color: #F56C6C;
      font-size: 28px;
      margin-left:60px;
      height: 33px;
      font-family: "Helvetica Neue";
    }
    .now-data-myself-week {
      margin-left:60px;
      font-size: 10px;
      color: #909399;
    }
    .wh_top_changge .wh_content_li[data-v-2ebcbc83] {
      font-family: Helvetica;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    这里我遇到了一个坑,修改的日历的样式没有效果,最终看到css样式的地方加了scoped

    <style rel="stylesheet/scss" lang="scss" scoped>
    
    • 1

    我们把这个去掉即可,想知道什么原因的可以去学习一下。

    然后看一下我们的页面。
    在这里插入图片描述

    2.5、词云

    接下来我们来写一下这个词云,这个主要是美化我们的页面,做一些特效使用,也多学一些常用的小功能。

    我这个是在网上找了一个词云的代码,我将它封装成了一个组件,直接在首页引用即可。

    在components文件夹下面新建一个WordCloud.vue文件

    <template>
      <section class="cloud-bed">
        <div class="cloud-box">
          <span
            v-for="(item, index) in dataList"
            :key="index"
            @click="getDataInfo(item)"
            :style="{color:item.color,background:item.bgColor}"
          >
            {{ item.name }}
          </span>
        </div>
      </section>
    </template>
     
    <script>
      export default {
        name: "word-cloud",
        data() {
          return {
            timer: 10, // 球体转动速率
            radius: 0, // 词云球体面积大小
            dtr: Math.PI/180, //鼠标滑过球体转动速度
            active: false, // 默认加载是否开启转动
            lasta: 0, // 上下转动
            lastb: 0.5, // 左右转动
            distr: true,
            tspeed: 1, // 鼠标移动上去时球体转动
            mouseX: 0,
            mouseY: 0,
            tagAttrList: [],
            tagContent: null,
            cloudContent: null,
            sinA: '',
            cosA: '',
            sinB: '',
            cosB: '',
            sinC: '',
            cosC: '',
            dataList: [
              {
                name: '页面卡顿\白屏',
                value: '1',
                bgColor:'rgb(57, 193, 207,0.12)',
                color:'#39c1cf',
              },
              {
                name: '闪退',
                value: '8',
                bgColor:'rgb(66, 105, 245,0.12)',
                color:'#4269f5',
              },
              {
                name: '登录问题',
                value: '9',
                bgColor:'rgb(184, 107, 215,0.12)',
                color:'#b86bd7',
              },
              {
                name: '功能bug',
                value: '3',
                bgColor:'rgb(243, 84, 83,0.12)',
                color:'#f35453',
              },
              {
                name: '无法收到短信',
                value: '6',
                bgColor:'rgb(250, 116, 20,0.12)',
                color:'#FA7414',
              },
              {
                name: '人脸/指纹认证失败',
                value: '10',
                bgColor:'rgb(255, 171, 30,0.12)',
                color:'#FFAB1E',
              },
              {
                name: '功能建议',
                value: '2',
                bgColor:'rgb(136, 104, 217,0.12)',
                color:'#8868D9',
              },
              {
                name: 'UI/UX',
                value: '5',
                bgColor:'rgb(42, 184, 230,0.12)',
                color:'#2AB8E6',
              },
              {
                name: '导航性',
                value: '7',
                bgColor:'rgb(117, 133, 162,0.12)',
                color:'#7585A2',
              },
            ]
          }
        },
        mounted () {
          this.$nextTick(() => {
            this.radius = document.querySelector('.cloud-box').offsetWidth / 2
            this.initWordCloud()
          })
        },
        beforeDestroy () {
          clearInterval(this.timer)
        },
        methods:{
          // 获取点击文本信息
          getDataInfo (item) {
            console.log(item, 'item')
          },
          initWordCloud () {
            this.cloudContent = document.querySelector('.cloud-box');
            this.tagContent = this.cloudContent.getElementsByTagName('span');
            for (let i = 0; i < this.tagContent.length; i++) {
              let tagObj = {};
              tagObj.offsetWidth = this.tagContent[i].offsetWidth;
              tagObj.offsetHeight = this.tagContent[i].offsetHeight;
              this.tagAttrList.push(tagObj);
            }
            this.sineCosine(0, 0, 0);
            this.positionAll();
            this.cloudContent.onmouseover = () => {
              this.active=true;
            };
            this.cloudContent.onmouseout = () => {
              this.active=false;
            };
            this.cloudContent.onmousemove = (ev) => {
              let oEvent = window.event || ev;
              this.mouseX = oEvent.clientX - (this.cloudContent.offsetLeft + this.cloudContent.offsetWidth/2);
              this.mouseY = oEvent.clientY - (this.cloudContent.offsetTop + this.cloudContent.offsetHeight/2);
              this.mouseX/= 5;
              this.mouseY/= 5;
            };
    	      setInterval(this.update, this.timer);
          },
          positionAll () {
            let phi = 0;
            let theta = 0;
            let max = this.tagAttrList.length;
            let aTmp = [];
            let oFragment = document.createDocumentFragment();
            //随机排序
            for (let i=0; i < this.tagContent.length; i++) {
              aTmp.push(this.tagContent[i]);
            }
            aTmp.sort(() => {
              return Math.random() < 0.5 ? 1 : -1;
            });
            for (let i = 0; i < aTmp.length; i++) {
              oFragment.appendChild(aTmp[i]);
            }
            this.cloudContent.appendChild(oFragment);
            for(let i = 1; i < max + 1; i++){
              if (this.distr) {
                phi = Math.acos(-1 + (2 * i - 1) / max);
                theta = Math.sqrt(max * Math.PI) * phi;
              } else {
                phi = Math.random() * (Math.PI);
                theta = Math.random() * (2 * Math.PI);
              }
              //坐标变换
              this.tagAttrList[i-1].cx = this.radius * Math.cos(theta) * Math.sin(phi);
              this.tagAttrList[i-1].cy = this.radius * Math.sin(theta) * Math.sin(phi);
              this.tagAttrList[i-1].cz = this.radius * Math.cos(phi);
              this.tagContent[i-1].style.left = this.tagAttrList[i-1].cx + this.cloudContent.offsetWidth / 2 - this.tagAttrList[i-1].offsetWidth / 2 + 'px';
              this.tagContent[i-1].style.top = this.tagAttrList[i-1].cy + this.cloudContent.offsetHeight / 2 - this.tagAttrList[i-1].offsetHeight / 2 + 'px';
            }
          },
          update () {
            let angleBasicA;
            let angleBasicB;
     
            if (this.active) {
              angleBasicA = (-Math.min(Math.max(-this.mouseY, -200 ), 200) / this.radius) * this.tspeed;
              angleBasicB = (Math.min(Math.max(-this.mouseX, -200 ), 200) / this.radius) * this.tspeed;
            } else {
              angleBasicA = this.lasta * 0.98;
              angleBasicB = this.lastb * 0.98;
            }
     
            //默认转动是后是否需要停下
            // lasta=a;
            // lastb=b;
     
            // if(Math.abs(a)<=0.01 && Math.abs(b)<=0.01)
            // {
            // return;
            // }
            this.sineCosine(angleBasicA, angleBasicB, 0);
            for(let j = 0; j < this.tagAttrList.length; j++) {
              let rx1 = this.tagAttrList[j].cx;
              let ry1 = this.tagAttrList[j].cy * this.cosA + this.tagAttrList[j].cz * (-this.sinA);
              let rz1 = this.tagAttrList[j].cy * this.sinA + this.tagAttrList[j].cz * this.cosA;
     
              let rx2 = rx1 * this.cosB + rz1 * this.sinB;
              let ry2 = ry1;
              let rz2 = rx1 * (-this.sinB) + rz1 * this.cosB;
     
              let rx3 = rx2 * this.cosC + ry2 * (-this.sinC);
              let ry3 = rx2 * this.sinC + ry2 * this.cosC;
              let rz3 = rz2;
              this.tagAttrList[j].cx = rx3;
              this.tagAttrList[j].cy = ry3;
              this.tagAttrList[j].cz = rz3;
     
              let per = 350 / (350 + rz3);
     
              this.tagAttrList[j].x = rx3 * per - 2;
              this.tagAttrList[j].y = ry3 * per;
              this.tagAttrList[j].scale = per;
              this.tagAttrList[j].alpha = per;
     
              this.tagAttrList[j].alpha = (this.tagAttrList[j].alpha - 0.6) * (10/6);
            }
            this.doPosition();
            this.depthSort();
          },
          doPosition() {
            let len = this.cloudContent.offsetWidth/2;
            let height = this.cloudContent.offsetHeight/2;
            for (let i=0;i < this.tagAttrList.length;i++) {
              this.tagContent[i].style.left = this.tagAttrList[i].cx + len - this.tagAttrList[i].offsetWidth/2 + 'px';
              this.tagContent[i].style.top = this.tagAttrList[i].cy + height - this.tagAttrList[i].offsetHeight/2 + 'px';
              // this.tagContent[i].style.fontSize = Math.ceil(12 * this.tagAttrList[i].scale/2) + 8 + 'px';
              this.tagContent[i].style.fontSize = Math.ceil(12 * this.tagAttrList[i].scale/2) +2 + 'px';
              this.tagContent[i].style.filter = "alpha(opacity="+100 * this.tagAttrList[i].alpha+")";
              this.tagContent[i].style.opacity = this.tagAttrList[i].alpha;
            }
          },
          depthSort(){
            let aTmp = [];
            for (let i = 0; i < this.tagContent.length; i++) {
              aTmp.push(this.tagContent[i]);
            }
            aTmp.sort((item1, item2) => item2.cz - item1.cz);
            for (let i = 0; i < aTmp.length; i++) {
              aTmp[i].style.zIndex=i;
            }
          },
          sineCosine (a, b, c) {
            this.sinA = Math.sin(a * this.dtr);
            this.cosA = Math.cos(a * this.dtr);
            this.sinB = Math.sin(b * this.dtr);
            this.cosB = Math.cos(b * this.dtr);
            this.sinC = Math.sin(c * this.dtr);
            this.cosC = Math.cos(c * this.dtr);
          }
        }
      };
    </script>
     
     
    <style scoped>
    .cloud-bed {
      width: 250px;
      height: 270px;
      margin: auto;
    }
    .cloud-box{
        position:relative;
        margin:20px auto 0px;
        width: 100%;
        height: 100%;
        background:	#00000000;
      }
     .cloud-box span{
          position: absolute;
          padding: 3px 6px;
          top: 0px;
          font-weight: bold;
          text-decoration:none;
          left:0px;
          background-image: linear-gradient(to bottom, red, #fff);
          background-clip: text;
          color: transparent;
          width: 50px;
          height: 50px;
          border-radius: 50%;
          text-align: center;
    
          display: flex;
          align-items: center;
          justify-content: center;
    
          /* line-height: 50px;
          overflow:hidden;
          white-space: nowrap;
          text-overflow: ellipsis; */
        }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293

    然后在首页中将组件引进来,和之前的图表引入一样。

    <el-col :xs="24" :sm="24" :lg="8">
        <div class="chart-wrapper">
           <div class="e-title">文章标签统计</div>
           <word-cloud />
        </div>
    </el-col>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    引入组件

    import WordCloud from './components/WordCloud.vue'
    
    components: {
        PanelGroup,
        BarChart,
        PieChart,
        LineChart,
        Calendar,
        WordCloud
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后运行一下项目,我们看一下页面

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YYhodFpn-1668411950943)(../../.vuepress/public/img/mdimg/image-20221114140055628.png)]

    看着是不是还挺高大上的。这个功能模块添加完成了,最后一个我们写一下公告的展示

    2.6、公告

    在首页添加了公告的信息,方便我们及时的查看,但只展示最近发的前四条公告,其余的还是要去公告列表中去查看,这个就比较简单了,我们直接引用element-ui的组件即可。

    我选择了折叠面板来实现,感觉还挺符合这个通知公告的功能实现。

    <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
              <div class="e-title">最新公告</div>
              <el-collapse v-model="activeName" accordion>
                <el-collapse-item title="一致性 Consistency" name="1">
                  <div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div>
                  <div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div>
                </el-collapse-item>
                <el-collapse-item title="反馈 Feedback" name="2">
                  <div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div>
                  <div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
                </el-collapse-item>
                <el-collapse-item title="效率 Efficiency" name="3">
                  <div>简化流程:设计简洁直观的操作流程;</div>
                  <div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div>
                  <div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div>
                </el-collapse-item>
                <el-collapse-item title="可控 Controllability" name="4">
                  <div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div>
                  <div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div>
                </el-collapse-item>
          </el-collapse>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    里面的内容暂时先不修改,我们下一篇处理后端的接口,再统一对接后端接口数据。

    好啦,首页的功能基本上都写完了,我让大家看一下完整的页面效果

    在这里插入图片描述

    这个是不是很哇塞,感觉很beautiful。

    首页完整代码如下:

    <template>
      <div class="dashboard-container">
        <panel-group ></panel-group>
    
        <!-- 数据分析 -->
        <el-row :gutter="32" class="row-chart">
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <bar-chart />
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <pie-chart />
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
              <line-chart />
            </div>
          </el-col>
        </el-row>
    
        <!-- 功能 -->
        <el-row :gutter="32" class="row-chart">
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
             <div class="e-title">文章标签统计</div>
             <word-cloud />
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
              <div class="e-title">最新公告</div>
              <el-collapse v-model="activeName" accordion>
                <el-collapse-item title="一致性 Consistency" name="1">
                  <div>与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;</div>
                  <div>在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。</div>
                </el-collapse-item>
                <el-collapse-item title="反馈 Feedback" name="2">
                  <div>控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;</div>
                  <div>页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。</div>
                </el-collapse-item>
                <el-collapse-item title="效率 Efficiency" name="3">
                  <div>简化流程:设计简洁直观的操作流程;</div>
                  <div>清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;</div>
                  <div>帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。</div>
                </el-collapse-item>
                <el-collapse-item title="可控 Controllability" name="4">
                  <div>用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;</div>
                  <div>结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。</div>
                </el-collapse-item>
              </el-collapse>
              
            </div>
          </el-col>
          <el-col :xs="24" :sm="24" :lg="8">
            <div class="chart-wrapper">
               <div class="con">
                <div class="now-data-myself">
                  <div class="now-data-myself-time">{{ date }}</div>
                  <div class="now-data-myself-week">{{ week }}</div>
                </div>
                <Calendar
                  v-on:choseDay="clickDay"
                  v-on:changeMonth="changeDate"
                  v-on:isToday="clickToday"
                ></Calendar>
              </div>
            </div>
          </el-col>
        </el-row>
     
      </div>
      
    </template>
    
    <script>
    
    import PanelGroup from './components/PanelGroup'
    import BarChart from './components/BarChart'
    import PieChart from './components/PieChart'
    import LineChart from './components/LineChart'
    import Calendar from 'vue-calendar-component'
    import WordCloud from './components/WordCloud.vue'
    
    
    export default {
      name: 'Dashboard',
      components: {
        PanelGroup,
        BarChart,
        PieChart,
        LineChart,
        Calendar,
        WordCloud
      },
      
      data() {
        return {
         date: "",
         week: "",
         activeName: '1'
        }
      },
       created() {
        var now = new Date();
        this.date = now.getDate();//得到日期
        var day = now.getDay();//得到周几
        var arr_week = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六");
        this.week = arr_week[day];
      },
    
      methods: {
        clickDay(data) {},
        changeDate(data) {},
        clickToday(data) {}
    
      },
      
    
    }
    </script>
    
    <style rel="stylesheet/scss" lang="scss">
    
    .dashboard {
      &-container {
        padding: 32px;
        background-color: #f0f2f5;
      }
    }
    .chart-wrapper {
      background: #fff;
      padding: 16px 16px 0;
      margin-bottom: 32px;
    }
    .row-chart{
      margin-top: 30px;
    }
    .now-data-myself {
      width: 40%;
      position: absolute;
      border-right: 1px solid rgba(227, 227, 227, 0.6);
    }
    .con {
      position: relative;
      max-width: 400px;
      margin: auto;
    }
    .con .wh_content_all {
      background: transparent !important; 
    }
    .wh_top_changge li {
      color: #F56C6C !important;
      font-size: 15px !important;
    }
    .wh_content_item, .wh_content_item_tag {
      color: #303133 !important;
    }
    .wh_content_item .wh_isToday {
      background: #00d985  !important;
      color: #fff  !important;
    }
    .wh_content_item .wh_chose_day {
      background: #409EFF  !important;
      color: #ffff  !important;
    }
    .wh_item_date:hover {
        background: rgb(217, 236, 255) !important;
        border-radius: 100px !important;
        color: rgb(102, 177, 255)  !important;
    }
    .wh_jiantou1[data-v-2ebcbc83] {
        border-top: 2px solid #909399;
        border-left: 2px solid #909399;
        width: 7px;
        height: 7px;
    }
    .wh_jiantou2[data-v-2ebcbc83] {
        border-top: 2px solid #909399;
        border-right: 2px solid #909399;
        width: 7px;
        height: 7px;
    }
    .wh_top_tag[data-v-2ebcbc83] {
      color: #409EFF;
      border-top: 1px solid rgba(227, 227, 227, 0.6);
      border-bottom: 1px solid rgba(227, 227, 227, 0.6);
    }
    .wh_container[data-v-2ebcbc83] {
        max-width: 400px;
    }
    .wh_top_changge[data-v-2ebcbc83] {
        display: flex;
        width: 50%;
        margin-left: 43%;
    }
    .now-data-myself-time {
      color: #F56C6C;
      font-size: 28px;
      margin-left:60px;
      height: 33px;
      font-family: "Helvetica Neue";
    }
    .now-data-myself-week {
      margin-left:60px;
      font-size: 10px;
      color: #909399;
    }
    .wh_top_changge .wh_content_li[data-v-2ebcbc83] {
      font-family: Helvetica;
    }
    
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216

    好啦,下一篇会写后端的接口,完成数据的渲染就结束了,我上次发起的投票关于上线发布的事情,我后边可能不会更新那一篇了,但是应该会放在下一个专栏里,但是有些好学的小伙伴想学习可以去下面的公众号找我,我可以给你提供思路和部署的方法,我这里就不再以文章的形式写出来了,一篇文章我要写好久的,熬好几夜才搞完,希望大家理解。多多给我点点赞,推荐一下。

    最后最后,希望大家再评论区给我留点意见和要搞的技术,要不然我光写大家只看我写的,对提升帮助比较小。感谢大家!

    在这里插入图片描述

    上一篇:Spring Boot + vue-element 开发个人博客项目实战教程(二十四、文章管理页面开发(3))

    下一篇:Spring Boot + vue-element 开发个人博客项目实战教程(二十六、项目完善及扩展(后端部分))

  • 相关阅读:
    14.10 Socket 套接字选择通信
    网络编程_bind函数
    如何封装安全的go
    Flutter学习-导航和路由
    无法安装64位版本的office,因为在您的PC上找到以下32位程序
    Python导入matplotlib库时出错:“找不到模块PIL”
    Information Sciences 2022 | 利用图嵌入和图神经网络实现社交网络中的影响力最大化
    java毕业设计基于Bootstrap的家具商城系统设计mybatis+源码+调试部署+系统+数据库+lw
    ECharts数据可视化项目【5】
    MNE系列教程2——MNE中的Raw及其基本用法
  • 原文地址:https://blog.csdn.net/m0_37779600/article/details/127848946