Java 小项目开发日记 06(Vue3 前端开发)

Java 小项目开发日记 06(Vue3 前端开发)

在这里插入图片描述

一、环境准备

1.1 创建vue工程(big-event-admin)

npm init vue@latest

cd big-event-admin
npm install

1.2 安装插件

1. 安装element-plus

cnpm i element-plus --save

2. 安装axios

  cnpm i axios

3. 安装sass依赖

   cnpm i sass -D

4. 安装路由

cnpm install vue-router

5. 安装 pinia 插件

Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态

cnpm install pinia

6. 安装 persist 插件

默认情况下,由于pinia是内存存储,当你刷新页面的时候pinia中的数据会丢失,可以借助于persist插件解决这个问题,persist插件支持将pinia中的数据持久化到sessionStorage和localStorage中

cnpm install pinia-persistedstate-plugin

二、vite.config.js

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  // 配置代理
  server: {
    proxy: {
      '/api': { // 获取请求中带 /api 的请求
        target: 'http://localhost:8080',  // 后台服务器的源
        changeOrigin: true,   // 修改源
        rewrite: (path) => path.replace(/^\/api/, "")   //  /api 替换为空字符串
      }
    }
  }
})

三、main.js

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from '@/router';
import {createPinia} from 'pinia'
import { createPersistedState } from 'pinia-persistedstate-plugin';
// 创建 app 实例
const app = createApp(App);

// 创建 pinia 实例
const pinia = createPinia();

// 创建 pinia 插件
const persist = createPersistedState()
// 使用 pinia 插件
pinia.use(persist)

// 挂载 ElementPlus
app.use(router).use(pinia).use(ElementPlus);
app.mount('#app');

四、 src目录

api

article.js

// 导入 request.js 请求工具
import request from '@/utils/request.js';

// 获取登录用户的全部文章
export const getMySelfAllCategoryService = () => {
    return request.get('/category');
}

// 添加文章分类
export const addCategoryService = (categoryData) => {
    return request.post('/category', categoryData);
}

user.js

// 导入 request.js 请求工具
import request from '@/utils/request.js';

// 注册
export const userRegisterService = (registerData) => {
    const params = new URLSearchParams();
    for (let key in registerData) {
        params.append(key, registerData[key]);
    }
    return request.post('/user/register', params);
}
// 登录
export const userLoginService = (loginData) => {
    const params = new URLSearchParams();
    for (let key in loginData) {
        params.append(key, loginData[key]);
    }
    return request.post('/user/login', params);
}

router

index.js

// 导入 vue-router
import { createRouter, createWebHistory } from 'vue-router'

// 导入组件
import Login from "@/views/login/Login.vue";
import Main from "@/views/main/Main.vue";
import ArticleCategory from "@/views/article/ArticleCategory.vue";
import ArticleManage from "@/views/article/ArticleManage.vue";
import UserAvatar from "@/views/user/UserAvatar.vue";
import UserInfo from "@/views/user/UserInfo.vue";
import UserResetPassword from "@/views/user/UserResetPassword.vue";

// 定义路由关系
const routes = [
    { path: '/login', component: Login },
    {
        path: '/', component: Main,
        // 子路由
        children: [
            {path: '/article/category', component: ArticleCategory},
            {path: '/article/manage', component: ArticleManage},
            {path: '/user/info', component: UserInfo},
            {path: '/user/avatar', component: UserAvatar},
            {path: '/user/password', component: UserResetPassword},
        ],
        redirect: '/article/category'

    },
]

// 创建路由
const router = createRouter({
    history: createWebHistory(),
    routes: routes
});

export default router;

store

token.js

import { defineStore } from 'pinia';
import { ref } from 'vue'

/**
 * @description:
 * @param {*} token 名字需要唯一性
 * @param {*} 函数  函数的内部可以定义状态的所有内容
 * @return {*}
 */
export const useTokenStore = defineStore('token', () => {
    // 描述 token
    const token = ref('');

    // 定义修改 token 的方法
    const setToken = (newToken) => {
        token.value = newToken;
    }

    // 定义移除 token 的方法
    const removeToken = () => {
        token.value = '';
    }

    return { token, setToken, removeToken }
},
    {
        persist: true
    });

utils

request.js

/*
 * @Date: 2024-03-03 16:45:08
 * @LastEditors: zhong
 * @LastEditTime: 2024-03-04 20:04:17
 * @FilePath: \big-event-vue\big-event-admin\src\utils\request.js
 */
// 导入 axios 依赖
import router from '@/router';
import { useTokenStore } from '@/store/token.js';
import axios from 'axios';
import { ElMessage } from 'element-plus'
// 定义baseUrl
const baseURL = '/api';
// 创建实例
const instance = axios.create({
    baseURL: baseURL,
});

// 添加请求拦截
instance.interceptors.request.use(
    (config) => {
        // 请求成功操作
        let tokenStore = useTokenStore();
        if (tokenStore.token) {
            config.headers.Authorization = tokenStore.token;
        }
        return config;
    },
    (err) => {
        // 请求错误操作
        return Promise.reject(err);
    }
)

// 添加响应拦截器
instance.interceptors.response.use(
    result => {
        if (result.data.code === 0) {
            return result.data;
        }
        ElMessage.error(result.data.message ? result.data.message : "服务异常");
        return Promise.reject(result.data);
    },
    err => {
        // 如果响应状态码为 401 代表未登录 跳转至首页  路由守卫
        if (err.response.status === 401) {
            ElMessage.error("请先登录!");
            router.push('/login');
        } else {
            ElMessage.error("服务异常");
        }
        return Promise.reject(err);
    }
)

export default instance;

五、views

article

ArticleCategory.vue

<!--
 * @Date: 2024-03-04 16:14:29
 * @LastEditors: zhong
 * @LastEditTime: 2024-03-04 20:06:54
 * @FilePath: \big-event-vue\big-event-admin\src\views\article\ArticleCategory.vue
-->

<script setup>
import {
    Edit,
    Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getMySelfAllCategoryService, addCategoryService } from "@/api/article.js"

const categorys = ref([])

// 获取数据
const articleCategoryList = async () => {
    let data = await getMySelfAllCategoryService();
    categorys.value = data.data;
}
articleCategoryList();

//控制添加分类弹窗
const dialogVisible = ref(false)

//添加分类数据模型
const categoryModel = ref({
    categoryName: '',
    categoryAlias: ''
})
//添加分类表单校验
const rules = {
    categoryName: [
        { required: true, message: '请输入分类名称', trigger: 'blur' },
    ],
    categoryAlias: [
        { required: true, message: '请输入分类别名', trigger: 'blur' },
    ]
}

// 添加文章分类
const addCategory = async () => {
    // 调用接口
    let result = await addCategoryService(categoryModel.value)
    ElMessage.success(result.message ? result.message : "添加成功")
    // 重新加载分类数据
    articleCategoryList();
    // 关闭弹窗
    dialogVisible.value = false;

}
</script>

<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章分类</span>
                <div class="extra">
                    <el-button type="primary" @click="dialogVisible = true">添加分类</el-button>
                </div>
            </div>
        </template>
        <el-table :data="categorys" style="width: 100%">
            <el-table-column label="序号" width="100" type="index"> </el-table-column>
            <el-table-column label="分类名称" prop="categoryName"></el-table-column>
            <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
            <el-table-column label="操作" width="100">

                <template #default="{ row }">
                    <el-button :icon="Edit" circle plain type="primary"></el-button>
                    <el-button :icon="Delete" circle plain type="danger"></el-button>
                </template>
            </el-table-column>

            <template #empty>
                <el-empty description="没有数据" />
            </template>
        </el-table>
    </el-card>

    <!-- 添加分类弹窗 -->
    <el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
        <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
            <el-form-item label="分类名称" prop="categoryName">
                <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
            </el-form-item>
            <el-form-item label="分类别名" prop="categoryAlias">
                <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
            </el-form-item>
        </el-form>

        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogVisible = false">取消</el-button>
                <el-button type="primary" @click="addCategory()"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>
</template>

<style lang="scss" scoped>
.page-container {
    min-height: 100%;
    box-sizing: border-box;

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}
</style>

ArticleManage.vue

<template>
    文章管理
</template>

login

Login.vue

<script setup>
import { User, Lock } from "@element-plus/icons-vue";
import { ElMessage } from 'element-plus'
import { ref, reactive } from "vue";
import { useTokenStore } from '@/store/token.js'
import { userRegisterService, userLoginService } from "@/api/user.js";
// 导入并创建路由
import { useRouter } from "vue-router";
const router = useRouter();

//控制注册与登录表单的显示, 默认显示注册
const isRegister = ref(false);
// 清空数据模型
const clearDataAll = () => {
  // 设置延时 清空太快了不习惯
  setTimeout(() => {
    registerData.value = {
      username: "",
      password: "",
      rePassword: ""
    }
  }, 1000)
}

const registerData = ref({
  username: "",
  password: "",
  rePassword: ""
})

// 密码重复校验规则
const checkRePassword = (rule, value, callback) => {
  if (value === '') {
    callback(new Error('请再次确认密码'))
  } else if (value !== registerData.value.password) {
    callback(new Error('两次密码不一致'))
  } else {
    callback()
  }
}

// 表单校验规则
const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 4, max: 12, message: '请输入 4-12 长度的用户名', trigger: 'blur' },
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, max: 12, message: '请输入 6-12 长度的密码', trigger: 'blur' },
  ],
  rePassword: [
    { validator: checkRePassword, trigger: 'blur' },
    { min: 6, max: 12, message: '请输入 6-12 长度的密码', trigger: 'blur' },
  ]
}
// token 获取 token
const tokenStore = useTokenStore();

// 登录
const login = async () => {
  // 调用登录请求
  let result = await userLoginService(registerData.value);
  ElMessage.success(result.message ? result.message : "登录成功");
  // 登录成功存储 token
  tokenStore.setToken(result.data);
  // 跳转到首页
  router.push("/");
}
// 注册
const register = async () => {
  // 调用注册请求
  let result = await userRegisterService(registerData.value)
  ElMessage.success(result.message ? result.message : "注册成功");
}

</script>

<template>
  <el-row class="login-page">
    <el-col :span="12" class="bg"></el-col>

    <el-col :span="6" :offset="3" class="form">
      <div class="name">
        <h1>欢迎访问后台</h1><h1>管理系统</h1>
      </div>

      <!-- 注册表单 -->
      <el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules">
        <el-form-item>
          <h1>注册</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
        </el-form-item>
        <el-form-item prop="rePassword">
          <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"
            v-model="registerData.rePassword"></el-input>
        </el-form-item>
        <!-- 注册按钮 -->
        <el-form-item>
          <el-button class="button" type="primary" auto-insert-space @click="register(); clearDataAll()">
            注册
          </el-button>
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = false">
            ← 返回
          </el-link>
        </el-form-item>
      </el-form>
      <!-- 登录表单 -->
      <el-form ref="form" size="large" autocomplete="off" :model="registerData" :rules="rules" v-else>
        <el-form-item>
          <h1>登录</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"
            v-model="registerData.password"></el-input>
        </el-form-item>
        <el-form-item class="flex">
          <div class="flex">
            <el-checkbox>记住我</el-checkbox>
            <el-link type="primary" :underline="false">忘记密码?</el-link>
          </div>
        </el-form-item>
        <!-- 登录按钮 -->
        <el-form-item>
          <el-button class="button" type="primary" auto-insert-space @click="login(); clearDataAll()">登录</el-button>
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = true; clearDataAll()">
            注册 →
          </el-link>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<style lang="scss" scoped>
/* 样式 */
.login-page {
  height: 100vh;
  background-color: #fff;
  background-color: #eaecf5;

  .bg {
    background: url("@/assets/back-jk.jpg") no-repeat center / cover;
    border-radius: 0 20px 20px 0;
  }

  .form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    user-select: none;

    .name {
      text-align: center;
      font-size: 26px;
    }

    .title {
      margin: 0 auto;
    }

    .button {
      width: 100%;
    }

    .flex {
      width: 100%;
      display: flex;
      justify-content: space-between;
    }
  }
}
</style>

main

Main.vue

<script setup>
import {
    Management,
    Promotion,
    UserFilled,
    User,
    Crop,
    EditPen,
    SwitchButton,
    CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
</script>

<template>
    <el-container class="layout-container">
        <!-- 左侧菜单 -->
        <el-aside width="300px">
            <div class="el-aside__logo"></div>
            <el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff" router>
                <el-menu-item index="/article/category">
                    <el-icon>
                        <Management />
                    </el-icon>
                    <span>文章分类</span>
                </el-menu-item>
                <el-menu-item index="/article/manage">
                    <el-icon>
                        <Promotion />
                    </el-icon>
                    <span>文章管理</span>
                </el-menu-item>
                <el-sub-menu class="sub_menu">
                    <template #title>
                        <el-icon>
                            <UserFilled />
                        </el-icon>
                        <span>个人中心</span>
                    </template>
                    <el-menu-item index="/user/info">
                        <el-icon>
                            <User />
                        </el-icon>
                        <span>基本资料</span>
                    </el-menu-item>
                    <el-menu-item index="/user/avatar">
                        <el-icon>
                            <Crop />
                        </el-icon>
                        <span>更换头像</span>
                    </el-menu-item>
                    <el-menu-item index="/user/password">
                        <el-icon>
                            <EditPen />
                        </el-icon>
                        <span>重置密码</span>
                    </el-menu-item>
                </el-sub-menu>
            </el-menu>
        </el-aside>
        <!-- 右侧主区域 -->
        <el-container>
            <!-- 头部区域 -->
            <el-header>
                <div>云尚校园:<strong>小钟</strong></div>
                <el-dropdown placement="bottom-end">
                    <span class="el-dropdown__box">
                        <el-avatar :src="avatar" />
                        <el-icon>
                            <CaretBottom />
                        </el-icon>
                    </span>

                    <template #dropdown>
                        <el-dropdown-menu>
                            <el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item>
                            <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
                            <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
                            <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
                        </el-dropdown-menu>
                    </template>
                </el-dropdown>
            </el-header>
            <!-- 中间区域 -->
            <el-main>
                <div style="height: 570px;border: 1px solid red;">
                    <router-view></router-view>
                </div>
            </el-main>
            <!-- 底部区域 -->
            <el-footer>云尚校园后台管理系统 ©2024 Created by 小钟</el-footer>
        </el-container>
    </el-container>
</template>

<style lang="scss" scoped>
span {
    font-size: 20px;
}

.el-icon svg {
    height: 3em;
    width: 3em;

}

.layout-container {
    height: 100vh;

    .el-aside {
        background-color: #232323;
        margin-top: 20px;


        &__logo {
            height: 120px;
            margin-top: 20px;
            background: url('@/assets/back-jk.jpg') no-repeat center / 120px auto;
        }

        .el-menu {
            justify-content: center;
            border-right: none;

            .el-menu-item {
                align-items: center;
                justify-content: center;

            }

            .sub_menu {
                margin-left: 25%;
            }

        }


    }

    .el-header {
        background-color: #fff;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding-top: 20px;

        .el-dropdown__box {
            display: flex;
            align-items: center;

            .el-icon {
                color: #999;
                margin-left: 10px;
            }

            &:active,
            &:focus {
                outline: none;
            }
        }
    }

    .el-footer {
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 14px;
        color: #666;
    }
}
</style>

user

UserAvatar.vue

<template>
    更换头像
</template>

UserInfo.vue

<template>
	基本资料
</template>

UserResetPassword.vue

<template>
    重置密码
</template>

App.vue

<script setup>

</script>

<template>
    <router-view></router-view>
</template>

<style scoped></style>

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
个人健康信息管理小程序是一个基于SpringBoot后端框架、微信小程序前端和Vue技术栈开发的综合性健康管理解决方案。该程序旨在为用户提供一个方便、高效的健康数据追踪和分析工具,帮助用户更好地理解自己的健康状况,并采取相应的健康维护措施。 技术栈概览: 后端:SpringBoot - 作为主要后端框架,负责处理业务逻辑、数据库交互以及API的提供。 前端:微信小程序 - 利用微信平台的便捷性,用户可以直接在微信内访问小程序,无需额外安装应用。 前端:Vue.js - 用于构建用户界面,以其组件化和响应式特点提升了开发效率和用户体验。 数据库:可能采用MySQL或NoSQL数据库如MongoDB存储用户健康数据和相关信息。 功能概述: 用户注册与登录:通过微信授权机制进行快速登录,保证用户身份的唯一性和安全性。 健康数据记录:用户可以输入或同步各种健康数据,例如体重、血压、血糖、步数等。 数据分析:系统会根据用户的健康数据提供图表化的展示,帮助用户了解自己的健康趋势。 健康建议:根据分析结果,小程序会给出个性化的健康建议和改善方案。 饮食日记:用户可以记录每天的饮食情况,系统可能会提供食物热量计算和营养建议。 运动计划:用户可以设定运动目标,跟踪运动进度,并根据个人情况调整运动计划。 提醒功能:设置提醒用户按时记录健康数据、服药或者进行体检等。 数据备份与同步:确保用户更换设备后数据不会丢失,实现数据的云同步。 社区交流:提供平台让用户可以互相交流健康心得和经验。 总之,这个个人健康信息管理小程序整合了现代Web开发中的流行技术和工具,提供了一个全面而细致的健康信息管理体验。它不仅能够让用户轻松地管理和监控自己的健康数据,还提供了交流互动的平台,增加了用户的使用动力和参与感。通过这些功能,小程序能够帮助用户建立健康意识,促进健康生活方式的形成。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

她似晚风般温柔789

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值