搭建自己的前后台学习平台一 前端实现

40 阅读3分钟

一.技术架构

  1. vscode+vite+vue3+js
  2. pinia做状态管理
  3. route4做路由管理
  4. axios网络封装

二.业务描述

本套页面是一个简单的课程管理页面,包括注册登录,课程的查询、修改、删除、增加功能

1.用户界面

注册

登录

3.课程管理

查询

增加

删除

修改

三.项目介绍

1.新建项目

npm create vite

设置项目名称为MySchdeule

2.配置依赖

1.npm install vue-router --save 下载路由

2.npm install pinia --save 使用pina

3.npm install axios --save 使用网络框架

3.路由工具封装

//配置一个myrouter.js文件,用来管理路由,路由的跳转等功能。
import { createRouter, createWebHashHistory } from 'vue-router'
// import Login from '../pages/Login.vue'
import Register from '../pages/Register.vue'
import ScheduleList from '../pages/ScheduleList.vue'


//路由配置 需要补充具体的路由
const routes = [
    {
        path: '/',
        redirect: '/login'
    },
    {
        path: '/register',
        name: 'Register',
        component: Register
    },
    {
        path: '/login',
        name: 'Login',
        // component: Login 这样写会报错 因为Login是一个异步组件,所以需要使用() => import('../pages/Login.vue')的方式导入组件
        component: () => import('../pages/Login.vue')
    },
    {
        path: '/scheduleList',
        name: 'ScheduleList',
        component: ScheduleList
    }

]

const myRouter = createRouter({
    history: createWebHashHistory(),
    routes
})



//添加路由守卫
myRouter.beforeEach((to, from, next) => {
    console.log("from",from.path)
    console.log("to",to.path)
    next()
})

export default myRouter

4.网络工具封装

// 网络请求工具,并且配置请求和响应拦截器,请求头,超时时间等。
import axios from 'axios'
const myRequest = axios.create({
  baseURL: 'http://localhost:8080/',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})
// 请求拦截器
myRequest.interceptors.request.use(config => {
    // 在发送请求之前做些什么
    return config
}, error => {
    // 对请求错误做些什么
    console.log("请求错误", error)
    return Promise.reject(error)
})
// 响应拦截器
myRequest.interceptors.response.use(response => {
    // 对响应数据做点什么
    console.log("响应数据", response)
    return response
}, error => { 
    console.log("响应错误", error)
    // 对响应错误做点什么
    return Promise.reject(error)
})


export default myRequest 

// 这样就可以使用axios来进行网络请求了

3.pinia封装

//配置一个pinia实例
import { createPinia } from 'pinia'

const MyPinia = createPinia()

export default MyPinia
//user的状态管理 
import { defineStore } from 'pinia'
import myRouter from './myrouter.js'

export const userStore = defineStore('user', {
  state: () => ({
    name: '',
    uid: -1,
  }),
  getters: {
    getName() {
      if (this.name === '') {
        return sessionStorage.getItem('name') //存储到session中关闭浏览器后或新开后无效
      }
      return this.name
    },
    getUid() {
      if (this.uid === -1) {
        return sessionStorage.getItem('uid')
      }
      return this.uid
    },

  },
  actions: {
    setName(name) {
      this.name = name
    },
    setUid(uid) {
      this.uid = uid
    },
    loginSuccess(name, uid) {
      console.log("login", `$name: ${name}, $uid: ${uid}`)
      this.name = name
      this.uid = uid
      sessionStorage.setItem('name', name)
      sessionStorage.setItem('uid', uid)
      //  myRouter.push('/scheduleList')
    },
    logout() {
      this.$reset() //清空pinia
      sessionStorage.removeItem('name')
      sessionStorage.removeItem('uid')
    }
  }
})

5.用户界面编写

5.1 公共头部组件

5.1.1 未登录

5.1.2 已登录

5.1.3 业务逻辑代码
<template>


    <div class="header">
        <h1 style="color: #42b983;">欢迎使用学习计划管理系统</h1>

        <div class="header-detail">
            <div v-if="isNotLogin && isLoginOrRegisterShow">
                <!-- <button>登录</button> -->
                <!-- <button>注册</button> -->
                <router-link class="router-link-style" to="/login">登录</router-link>
                <router-link class="router-link-style" to="/register">注册</router-link>
            </div>
            <div v-else>
                <span>欢迎您,{{ user.getName }}</span>
                <router-link class="router-link-style" to="/login" @click="logout">退出</router-link>
            </div>
        </div>
    </div>
</template>

<script setup>

import { computed } from 'vue';
import { userStore } from '../utils/userstore';
import myRouter from '../utils/myrouter';
let user = userStore();
//判断pinia状态或内存状态来显示登录或注册按钮
const isNotLogin = computed(() => {
    const name = user.getName;
    console.log("computed name:", name);
    if (name == '' || name == null) {
        return true
    }
    return false;
})
//根据当前页面路径 判断是否要显示登录或注册按钮
const isLoginOrRegisterShow = computed(() => {
    console.log("computed path--->>>>:", myRouter.currentRoute.value.path);
    return myRouter.currentRoute.value.path == '/login' || myRouter.currentRoute.value.path == '/register'
});

const logout = () => {
    user.logout();
}
</script>

<style scoped>
/* 设置上下排列 */
.header {
    display: flex;
    flex-direction: column;
    /* 垂直轴居中 */
    align-items: center;
    height: 130px;
    width: 100%;
    background-color: #fff;
    padding: 0 20px;
}

.header-detail {
    width: 100%;
    display: flex;
    margin-right: 100px;
    /* 主轴靠右 */
    justify-content: end;
    /* 左右排列 */
    flex-direction: row;
}

.header-detail button {
    margin-right: 5px;
    padding: 10px 10px;
    /* 帮我设置下边框 选中时边框颜色 */
    border: 1px solid #42b983;
    border-radius: 4px;
    background-color: #42b983;
    color: #fff;
    cursor: pointer;
}

.router-link-style {
    margin-left: 20px;
    margin-right: 20px;
    padding: 10px 20px;
    /* 帮我设置下边框 选中时边框颜色 */
    border: 1px solid #42b983;
    border-radius: 4px;
    background-color: #42b983;
    color: #fff;
    cursor: pointer;

}


/* .header-detail button:hover {
    background-color: #87de94;
    color: #42b983;
    
    border: 1px solid #42b983;
} */
</style>

5.2登录界面

5.2.1 页面展示

5.2.2 业务逻辑代码
<template>
    <div class="login">
        <h1>用户登录</h1>

        <input class="input-style" type="text" placeholder="用户名" v-model="username">
        <input class="input-style" type="password" placeholder="密码" v-model="password">
        <button class="btn-style" @click="login">登录</button>
    </div>

</template>

<script setup>
import { ref } from 'vue'
import { userStore } from '../utils/userstore'
import myRouter from '../utils/myrouter'
import myRequest from '../utils/myrequest'
const user = userStore()
const username = ref('')
const password = ref('')

const login =  () => {
    // try {
    //     console.log("login", username.value)
    //     myRouter.push('/scheduleList')

    // } catch (err) {
    //     console.log(err)
    // }
    //下面检查下用户名和密码是否正确
    //如果正确,则登录成功,跳转到列表页面
    //如果错误,则提示错误信息
    //这里只是简单模拟一下,实际项目中,应该调用后端接口进行验证
    if (username.value === '' && password.value === '') {
        alert('用户名或密码不能为空')
        return
    }
    myRequest.post('user/login', {
        username: username.value,
        password: password.value
    }).then(res => {
        if (res.status === 200) {
            console.log("code", res.data.code)
            if (res.data.code === 200) {
                // alert('登录成功')
                console.log("user", user.name)
                const {username,uid} =res.data.data
                user.loginSuccess(username,uid)
                myRouter.push('/scheduleList')
             
            } else {
                alert(res.data.message)
            }
        } else {
            alert('用户名或密码错误')
        }
    }).catch(err => {
        console.log(err)
    })
}





</script>
<style scoped>
h1 {
    color: #42b983;
}

.login {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.input-style {
    border: 1px solid #42b983;
    margin: 5px;
    border-radius: 5px;
    width: 200px;
    height: 40px;
}

.btn-style {
    background-color: #42b983;
    color: white;
    border: none;
    border-radius: 5px;
    width: 200px;
    height: 40px;
    margin-top: 10px;
}
</style>

5.3 注册界面

5.3.1 页面展示

5.3.2 业务逻辑代码
<template>
    <div class="register">
        <h1>用户注册</h1>

        <div>
            <span class="span-centered">用户名</span>
            <input class="input-style" type="text" placeholder="用户名" v-model="username">
        </div>
        <div>
            <span class="span-centered">密码</span>
            <input class="input-style" type="password" placeholder="密码" v-model="password">
        </div>
        <div>
            <span class="span-centered">确认密码</span>
            <input class="input-style" type="password" placeholder="确认密码" v-model="confirmPassword">
        </div>
        <div class="btn-container-style">
            <button class="btn-style" @click="register">注册</button>
            <button class="btn-style" @click="login">去登录</button>
        </div>

    </div>
</template>

<script setup>


import { ref } from 'vue'
import myRequest from '../utils/myrequest'
import  myRouter from '../utils/myrouter'

const username = ref('')
const password = ref('')
const confirmPassword = ref('')

const register = () => {
    if (username.value === '') {
        alert('用户名不能为空')
        return
    }
    if (password.value === '') {
        alert('密码不能为空')
        return
    }
    if (password.value !== confirmPassword.value) {
        alert('两次输入的密码不一致')
        return
    }
    // 注册逻辑
    myRequest.post('user/register', {
        username: username.value,
        password: password.value
    }).then(res => {
        console.log(res)
        if (res.data.code === 200) {
            alert('注册成功')
        } else {
            alert(`注册失败  ${res.data.message}`)
        }

    }).catch(err => {
        console.log(err)
        alert(`注册失败${err.message}`)
    })

}
const login = () => {
    // 跳转登录页面
    myRouter.push('/login')
}


</script>

<style scoped>
h1 {
    color: #42b983;
}


.span-centered {
    font-size: 16px;
    display: inline-block;
    /* 或者使用 display: block; 这样就可以给span设置宽度了 */
    width: 80px;
    /* 设置您想要的宽度 */
    height: 40px;
    /* 设置您想要的高度 */
    text-align: right;
    /* 水平居中文本 */
    line-height: 40px;
    /* 垂直居中文本,值应与高度相同 */
}

.register {
    width: 100%;
    height: 100%;
    display: flex;
    /*垂直 */
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.input-style {
    border: 1px solid #42b983;
    margin: 5px;
    border-radius: 5px;
    width: 200px;
    height: 40px;
}

.btn-container-style {
    display: flex;
    justify-content: right;
    flex-direction: row;
    align-items: right;
    width: 260px;
}

.btn-style {
    background-color: #42b983;
    color: white;
    border: none;
    margin-right: 10px;
    width: 80px;
    border-radius: 5px;
    height: 40px;
    margin-top: 10px;
}
</style>

6.课程管理界面编写

6.1 页面展示

6.2 业务逻辑代码

<template>
    <div>

        <table class="tableStyle">

            <thead>
                <tr>
                    <th>内容</th>
                    <th>进度</th>
                    <th>操作</th>
                </tr>
            </thead>
            <!-- 设置行数据 -->
            <tbody>
                <tr v-for="(item, index) in scheduleList" :key="index">
                    <td>
                        {{ index + 1 }}.
                        <input type="text" v-model="item.content">

                    </td>
                    <td>
                        <input type="radio" value="1" v-model="item.state"> 已完成
                        <input type="radio" value="0" v-model="item.state"> 未完成

                    </td>
                    <td>
                        <button @click="handleEdit(item)">修改</button>
                        <button @click="handleDelete(item)">删除</button>
                    </td>
                </tr>
                  <tr >
                    <td colspan="4">
                        <button class="btn1" @click="addItem()">新增日程</button>
                    </td>

                </tr>
            </tbody>
        </table>
    </div>
</template>
<script setup>

import { onMounted, ref } from 'vue';
import myRequest from '../utils/myrequest';
import { userStore } from '../utils/userstore';

let user = userStore();

let scheduleList = ref([]);

// 编辑任务
async function handleEdit(item) {
    console.log('编辑任务:', item);
    let result = await myRequest.post('/schedule/updateSchedule', {
        uid: user.getUid,
        sid: item.sid,
        content: item.content,
        state: item.state
    }).then(res => {
        if (res.data.code == 200) {
            console.log('编辑任务成功:', res);
            alert('编辑任务成功');
            getScheduleList();
        } else {
            alert(`编辑任务失败${res.data.message}`);
            console.log('编辑任务失败:', res);
        }
        return "success"
    }).catch(err => {
        console.log('编辑任务失败:', err);
    });
    console.log('handleEdit:', result);
}
// 删除任务
async function handleDelete(item) {
    console.log('删除任务:', item);
    await myRequest.request({
        method: 'post',
        url: '/schedule/deleteSchedule',
        params: {
            sid: item.sid
        }
    }).then(res => {
        if (res.data.code == 200) {
            console.log('删除任务成功:', res);
            alert('删除任务成功');
            getScheduleList();
        } else {
            alert(`删除任务失败${res.data.message}`);
            console.log('删除任务失败:', res);
        }
        return "success"
    }).catch(err => {
        console.log('删除任务失败:', err);
    });
}

const getScheduleList = async() => {
   await myRequest.request({
        url: 'schedule/querySchedule',
        method: 'post',
        params: {
            uid: user.getUid
        }
    }).then(res => {
        console.log('请求数据成功--->>:', res.data.data.scheduleList);
        scheduleList.value = res.data.data.scheduleList;
        console.log('scheduleList', scheduleList);
    }).catch(err => {
        console.log('请求数据失败:', err);
    });

}

// 新增任务
async function addItem() {
    console.log('新增任务');
    let newItem = {
        content: '',
        state: 0,
        sid: -1
    };
    await myRequest.request({
        method: 'post',
        url: '/schedule/addSchedule',
        data: {
            uid: user.getUid,
            content: newItem.content,
            state: newItem.state
        }
    }).then(res => {
        if (res.data.code == 200) {
            console.log('新增任务成功:', res);
            alert('新增任务成功');
            getScheduleList();
        } else {
            alert(`新增任务失败${res.data.message}`);
            console.log('新增任务失败:', res);
        }
        return "success"
    }).catch(err => {
        console.log('新增任务失败:', err);
    });
}

// 页面加载完成后,请求数据 
onMounted(() => {
    console.log('页面加载完成');
    // 页面加载完成后,请求数据 
    getScheduleList();
});

</script>

<style scoped>
.tableStyle {
    margin: 30px auto;
    border-collapse: collapse;
    width: 60%;
    text-align: center;
}

/* 设置表格单元格 */
.tableStyle td {
    text-align: center;
    border: 1px solid #ddd;
    padding: 8px;
    width: 33%;
}

.tableStyle input {
    /* width: 80%; */
    padding: 5px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin-bottom: 10px;
}

/* 设置表头单元格 */
.tableStyle th {
    border: 1px solid #ddd;
    background-color: #f2f2f2;
    color: #333;
}

/* 设置按钮样式 */
.tableStyle button {
    margin-left: 10px;
    padding: 5px 10px;
    border: none;
    border-radius: 5px;
    background-color: #4CAF50;
    color: white;
    cursor: pointer;
}
</style>

四. 项目总结

课程管理系统前端页面是一个简单的入门项目,主要是为了熟悉vue3的路由、网络、状态管理以及选项式编程习惯,同时使用了html/css简单的网页布局。