面向对象综合设计课设

前端设计流程图:

process.png

1. 环境搭建

  1. Node.js官网安装Node.js
  2. 安装Vue 2,使用npm安装
npm install vue@^2
  1. 安装命令行工具Vue Cli
npm install -g @vue/cli
  1. 打开Vue可视化管理工具界面,新建Vue项目:
vue ui
  1. 在项目根目录下安装element-ui
npm install element-ui --save
  1. 同样的,安装axios
cnpm install axios --save
  1. 安装MarkdownIt和md样式
npm install markdown-it --save
npm install github-markdown-css
  1. 安装mavon-editor
npm install mavon-editor --save

2. 配置文件

  1. main.js

将上述第三方库导入main.js中

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Element from 'element-ui'
import axios from 'axios'
import mavonEditor from 'mavon-editor';

import 'element-ui/lib/theme-chalk/index.css'
import 'mavon-editor/dist/css/index.css'
import 'github-markdown-css/github-markdown.css'

Vue.use(Element)
Vue.use(mavonEditor)

Vue.config.productionTip = false
Vue.prototype.$axios = axios

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')
  1. router.js

按照项目功能要求配置路由文件

import Vue from 'vue'
import VueRouter from 'vue-router'
import Blogs from '../views/Blogs.vue'
import BlogEdit from '../views/BlogEdit.vue'
import Login from '../views/Login.vue'
import BlogDetail from '../views/BlogDetail.vue'

Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'Index',
        redirect: {name: "Blogs"}
    },
    {
        path: '/blogs',
        name: 'Blogs',
        component: Blogs
    },
    {
        path: '/login',
        name: 'Login',
        component: Login
    },
    {
        path: '/blog/add',
        name: 'BlogAdd',
        component: BlogEdit,
        meta: {
            requireAuth: true,
        }
    },
    {
        path: '/blog/:blogId',
        name: 'BlogDetail',
        component: BlogDetail
    },
    {
        path: '/blog/:blogId/edit',
        name: 'BlogEdit',
        component: BlogEdit,
        meta: {
            requireAuth: true,
        }
    },
]

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

export default router
  1. App.vue
    配置页面背景色
body {
  background: linear-gradient(to right, #65CBF7, #B3A5FC);
  width: 100vw;
  height: 100vh;
  margin: 0;
}
  1. store.js
    全局配置存储
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
    state: {
        token: '',
        userInfo: JSON.parse(sessionStorage.getItem("userInfo"))
    },
    mutations: {
        SET_TOKEN: (state, token) => {
            state.token = token
            localStorage.setItem("token", token)
        },
        SET_USERINFO: (state, userInfo) => {
            state.userInfo = userInfo
            sessionStorage.setItem("userInfo", JSON.stringify(userInfo))
        },
        REMOVE_INFO: (state) => {
            localStorage.setItem("token", '')
            sessionStorage.setItem("userInfo", JSON.stringify(''))
            state.userInfo = {}
        }
    },
    getters: {
        getUser: state => {
            return state.userInfo
        }
    },
    actions: {},
    modules: {}
})

3. 设计登录组件

HTML+CSS设计样式,配合ElementUI设计登录组件

<template>
  <div>
    <Header></Header>
    <div class="box">
      <div class="left">
        <img src="./img/Login.jpg" alt="">
      </div>
      <div class="right">
        <h1>Login</h1>
        <el-main>
          <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px"
                   class="demo-ruleForm">
            <el-form-item label="用户名" prop="username">
              <el-input type="text" maxlength="12" v-model="ruleForm.username"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
              <el-button @click="resetForm('ruleForm')">重置</el-button>
            </el-form-item>
          </el-form>
        </el-main>
      </div>
    </div>
  </div>
</template>

CSS:

body {
  background: linear-gradient(to right, #65CBF7, #B3A5FC);
  width: 100vw;
  height: 100vh;
  margin: 0;
}

.box {
  width: 60%;
  height: 450px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, .8);
  display: flex;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.left {
  width: 65%;
}

.left > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.right {
  width: 35%;
  height: 100%;
  background-color: #fff;
  box-sizing: border-box;
  padding: 0 20px;
}

h1 {
  text-align: center;
  padding-top: 45px;
  margin-top: 0;
}

实现submitForm通过Axios提交表单至后端进行登录验证

import Header from "../components/Header";

export default {
  name: 'Login',
  components: {Header},
  data() {
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请输入密码'));
      } else {
        callback();
      }
    };
    return {
      ruleForm: {
        password: '',
        username: ''
      },
      rules: {
        password: [
          {validator: validatePass, trigger: 'blur', required: true}
        ],
        username: [
          {required: true, message: '请输入用户名', trigger: 'blur'},
          {min: 3, max: 12, message: '长度在 3 到 12 个字符', trigger: 'blur'}
        ]
      }
    };
  },
  methods: {
    submitForm(formName) {
      const _this = this
      this.$refs[formName].validate((valid) => {
        if (valid) {
          // 提交逻辑
          this.$axios.post('http://localhost:8081/login', this.ruleForm).then((res) => {
            const token = res.headers['authorization']
            _this.$store.commit('SET_TOKEN', token)
            _this.$store.commit('SET_USERINFO', res.data.data)
            _this.$router.push("/blogs")
          })
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }
  },
}

4. 设计头部导航栏

HTML+CSS:

<template>
  <div>
    <header>
      <nav>
        <div class="logo-contain">
          <img class="blogLogo" src="../views/img/LOGO.jpg" alt="">
          <div class="navText">YUDATI</div>
        </div>
        <div class="menu">
          <div class="menu-item">
            <router-link to="/blogs">Home</router-link>
          </div>
          <div class="divider">|</div>

          <div v-show="!hasLogin" class="menu-item">
            <router-link to="/login">Login</router-link>
          </div>

          <div v-show="hasLogin" class="menu-item">
            <router-link to="/blog/add">AddBlog</router-link>
          </div>
          <div v-show="hasLogin" class="divider">|</div>
          <div v-show="hasLogin" class="menu-item">
            <a @click="logout">Logout</a>
          </div>
        </div>
      </nav>
    </header>
  </div>
</template>

CSS:

header {
  margin: 0 auto;
  width: 100vw;
  background: #ffffff;
}

nav {
  margin-bottom: 20px;
  display: flex;
  align-items: center;
  justify-content: space-between; /* Adjust spacing between logo and menu items */
  padding: 10px 20px; /* Add padding to nav container */
}

.menu {
  display: flex;
  align-items: center;
}

.logo-contain {
  display: flex;
  align-items: center;
}

.menu-item {
  color: #ffffff;
  padding: 10px 20px;
  position: relative;
  text-align: center;
  border-bottom: 3px solid transparent;
  display: flex;
  transition: 0.4s;
}

.menu-item.active,
.menu-item:hover {
  background-color: #ffffff;
  border-bottom-color: #7f2978;
}

.menu-item a {
  font-size: 22px;
  color: #7f2978;
  text-decoration: none;
}

.block {
  position: relative;
  /* font-weight: bold; */
  font-size: 22px;
  color: #7f2978;
  text-decoration: none;
}

.navText {
  /* margin-right: 500px; */
  position: relative;
  font-weight: bold;
  font-size: 30px;
  color: #65CBF7;
  text-decoration: none;
}

.blogLogo {
  position: relative;
  width: 50px;
  height: 50px;
  border-radius: 50px;
}

实现Logout登出,在全局配置中清空userInfo,created初始化导航栏监控用户信息:

export default {
  name: "Header",
  data() {
    return {
      hasLogin: false,
      user: {
        username: '',
        avatar: ''
      },
      blogs: {},
      currentPage: 1,
      total: 0
    }
  },

  methods: {
    logout() {
      const _this = this;
      this.hasLogin = false;
      this.$axios.get('http://localhost:8081/logout', {
        headers: {
          "Authorization": localStorage.getItem("token")
        }
      }).then((res) => {
        _this.$store.commit('REMOVE_INFO')
        _this.$router.push('/login')
      });
    }
  },
  created() {
    if (this.$store.getters.getUser.username) {
      this.user.username = this.$store.getters.getUser.username
      this.user.avatar = this.$store.getters.getUser.avatar
      this.hasLogin = true
    }
  }
}

5. 设计博客主页

ElementUI时间线组件创建博客列表:

<template>
  <div>
    <Header></Header>
    <div class="block">
      <el-timeline>
        <el-timeline-item class="summary" v-bind:timestamp="blog.created" placement="top" v-for="blog in blogs">
          <el-card>
            <router-link :to="{name: 'BlogDetail', params: {blogId: blog.id}}"><h2 class="mtitle">{{ blog.title }}</h2>
            </router-link>
            <p class="describe">{{ blog.description }}</p>
          </el-card>
        </el-timeline-item>
      </el-timeline>
    </div>
    <el-pagination class="mpage"
                   background
                   layout="prev, pager, next"
                   :current-page=currentPage
                   :page-size=pageSize
                   @current-change=page
                   :total="total">
    </el-pagination>
  </div>
</template>

CSS:

.block {
  margin: 0 auto;
  max-width: 1080px;
}

.summary::v-deep .el-timeline-item__timestamp {
  font-family: 'Consola', sans-serif;
  font-size: 16px;
  color: #ffffff !important;
}

.mpage {
  margin: 0 auto;
  text-align: center;
}

.describe {
  font: 'Consolas';
}

.mtitle {
  margin: 0px auto;
  font-size: 20px;
  font: 'Consola';
  font-weight: bolder;
  text-decoration: none !important;
}

page()通过Axios从后端获取页面信息并展示:

import Header from "@/components/Header";

export default {
  name: "Blogs",
  components: {Header},
  data() {
    return {
      blogs: {},
      currentPage: 1,
      total: 0,
      pageSize: 5
    }
  },
  methods: {
    page(currentPage) {
      const _this = this
      this.$axios.get('http://localhost:8081/blogs?currentPage=' + currentPage).then((res) => {
        console.log(res.data.data.records)
        _this.blogs = res.data.data.records
        _this.currentPage = res.data.data.current
        _this.total = res.data.data.total
        _this.pageSize = res.data.data.size
      })
    }
  },
  mounted() {
    this.page(1);
  }
}

6. 设计博客内容详情页

HTML+CSS

<template>
  <div>
    <Header></Header>
    <div class="box">
      <div class="mblog">
        <h2>{{ blog.title }}</h2>
        <router-link :to="{name: 'BlogEdit', params: {blogId: blog.id}}">
          <el-link icon="el-icon-edit" v-if="ownBlog">编辑</el-link>
        </router-link>
        <el-divider></el-divider>
        <div class="content markdown-body" v-html="blog.content"></div>
      </div>
    </div>
  </div>
</template>

CSS:

.box {
  background-color: aliceblue;
  margin-top: 20px;
  margin-left: 15%;
  width: 70%;
  min-height: 700px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, .8);
  display: flex;
}

.mblog {
  margin: 30px;
}

.mtitle {
  margin: 10px;
  text-align: center;
}

getBlog()获取博客信息,MarkdownIt渲染

import 'github-markdown-css/github-markdown.css' // 然后添加样式markdown-body
import Markdown from '@/components/Markdown'
import Header from "@/components/Header";

export default {
  name: "BlogDetail",
  components: {
    Header, Markdown,
  },
  data() {
    return {
      blog: {
        userId: null,
        title: "",
        description: "",
        content: ""
      },
      ownBlog: false
    }
  },
  methods: {
    getBlog() {
      const blogId = this.$route.params.blogId
      const _this = this
      this.$axios.get('/blog/' + blogId).then((res) => {
        console.log(res)
        console.log(res.data.data)
        _this.blog = res.data.data
        var MarkdownIt = require('markdown-it'),
            md = new MarkdownIt();
        var result = md.render(_this.blog.content);
        _this.blog.content = result
        // 判断是否是自己的文章,能否编辑
        _this.ownBlog = (_this.blog.userId === _this.$store.getters.getUser.id)
      });
    }
  },
  created() {
    this.getBlog()
  }
}

7. 设计博客编辑页

ElementUI表单组件+mavon-editor构成组件:

<template>
  <div>
    <Header></Header>
    <div class="box">
      <div class="m-content">
        <el-form ref="editForm" status-icon :model="editForm" :rules="rules" label-width="80px">
          <el-form-item label="标题" prop="title">
            <el-input v-model="editForm.title"></el-input>
          </el-form-item>
          <el-form-item label="摘要" prop="description">
            <el-input type="textarea" v-model="editForm.description"></el-input>
          </el-form-item>
          <el-form-item label="内容" prop="content">
            <mavon-editor v-model="editForm.content"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm()">Submit</el-button>
            <router-link to="/">
              <el-button>Cancel</el-button>
            </router-link>
          </el-form-item>
        </el-form>
      </div>
    </div>
  </div>
</template>

CSS:

.box {
  background-color: aliceblue;
  margin-top: 20px;
  margin-left: 12.5%;
  width: 75%;
  min-height: 700px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, .8);
  display: flex;
}

.m-content {
  margin: 30px;
  margin-right: 60px;
}

submitForm提交表单至后端

import Header from "@/components/Header";

export default {
  name: "BlogEdit",
  components: {Header},
  data() {
    return {
      editForm: {
        id: null,
        title: '',
        description: '',
        content: ''
      },
      rules: {
        title: [
          {required: true, message: '请输入标题', trigger: 'blur'},
          {min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur'}
        ],
        description: [
          {required: true, message: '请输入摘要', trigger: 'blur'}
        ]
      }
    }
  },
  created() {
    const blogId = this.$route.params.blogId
    const _this = this
    if (blogId) {
      this.$axios.get('/blog/' + blogId).then((res) => {
        const blog = res.data.data
        _this.editForm.id = blog.id
        _this.editForm.title = blog.title
        _this.editForm.description = blog.description
        _this.editForm.content = blog.content
      });
    }
  },
  methods: {
    submitForm() {
      const _this = this
      this.$refs.editForm.validate((valid) => {
        if (valid) {
          this.$axios.post('/blog/edit', this.editForm, {
            headers: {
              "Authorization": localStorage.getItem("token")
            }
          }).then((res) => {
            _this.$alert('操作成功', '提示', {
              confirmButtonText: '确定',
              callback: action => {
                _this.$router.push("/blogs")
              }
            });
          });
        } else {
          console.log('error submit!!');
          return false;
        }
      })
    }
  }
}

8. 配置拦截

aixos.js:

import axios from 'axios'
import Element from "element-ui";
import store from "./store";
import router from "./router";

axios.defaults.baseURL = 'http://localhost:8081'
axios.interceptors.request.use(config => {
    console.log("前置拦截")
    // 可以统一设置请求头
    return config
})
axios.interceptors.response.use(response => {
        const res = response.data;
        console.log("后置拦截")
        // 当结果的code是否为200的情况
        if (res.code === 200) {
            return response
        } else {
            // 弹窗异常信息
            Element.Message({
                message: response.data.msg,
                type: 'error',
                duration: 2 * 1000
            })
            // 直接拒绝往下面返回结果信息
            return Promise.reject(response.data.msg)
        }
    },
    error => {
        console.log('err' + error)// for debug
        if (error.response.data) {
            error.message = error.response.data.msg
        }
        // 根据请求状态觉得是否登录或者提示其他
        if (error.response.status === 401) {
            store.commit('REMOVE_INFO');
            router.push({
                path: '/login'
            });
            error.message = '请重新登录';
        }
        if (error.response.status === 403) {
            error.message = '权限不足,无法访问';
        }
        Element.Message({
            message: error.message,
            type: 'error',
            duration: 3 * 1000
        })
        return Promise.reject(error)
    })

permission.js:

import router from "./router";

router.beforeEach((to, from, next) => {

    if (to.matched.some(record => record.meta.requireAuth)) {
        const token = localStorage.getItem("token");
        console.log("--------------" + token);

        if (token) {
            if (to.path == '/login') {

            } else {
                next();
            }
        } else {
            next({
                path: '/login'
            })
        }
    } else {
        next()
    }
})

在main.js中导入

import "./axios"
import "./permission.js"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值