vue后台框架开发1

概述

一直以来,我都想做一套属于自己的后台管理框架,那么,今天,2023/5/27日,jkw框架就应运而生了。此框架,前端我用vue做,后端测试接口用的java技术。来看看今天都干了什么吧!

项目搭建

创建项目【项目名为vue-jkw】

npm init vue@latest

创建项目时依赖选用


            这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具。你将会看到一些诸如 TypeScript 和测试支持之类的可选功能提示
            ✔ Project name: … (项目名,不要存在大写)
            ✔ Add TypeScript? … No / Yes(no,直接回车)
            ✔ Add JSX Support? … No / Yes(no,直接回车)
            ✔ Add Vue Router for Single Page Application development? … No / Yes 集成路由
            ✔ Add Pinia for state management? … No / Yes(no,直接回车)Pinia
            ✔ Add Vitest for Unit testing? … No / Yes(no,直接回车)
            ✔ Add Cypress for both Unit and End-to-End testing? … No / Yes(no,直接回车)
            ✔ Add ESLint for code quality? … No / Yes(no,直接回车)
            ✔ Add Prettier for code formatting? … No / Yes(no,直接回车)  

安装依赖

cd vue-jkw
npm install

启动开发服务器(项目目录)

npm run dev

项目效果【2023/5/27】

登录页面

首页 

用户管理界面

对话框

代码公开

 登录页面的代码

<template>
    <div class="login-container">
        <el-form class="user" size="large" :model="user">
            <div class="title-container">
                <h3 class="title">LOGIN</h3>
            </div>
            <div class="input">
                <el-form-item prop="username">
                    <el-input :prefix-icon="User"  type="text" v-model="user.username" placeholder="请输入用户名"></el-input>
                </el-form-item>
                <el-form-item prop="password">
                    <el-input :prefix-icon="Lock" type="password" v-model="user.password" placeholder="请输入密码"></el-input>
                </el-form-item>
            </div>
            <!--由于el-button是放在表单里,默认是会实现表单的跳转,需要用@click.native.prevent阻止-->
            <el-button style="width:100%;margin-bottom:30px;"  round   type="primary"
                @click.native.prevent="handleLogin">登录</el-button>
        </el-form>
    </div>
</template>

<script setup>
import { reactive } from 'vue';
import { useRouter } from "vue-router"//router
import axios from "@/utils/request.js"//引入封装的axios
import { useLoginStore } from "@/stores/loginStore.js"//stores
import { User, Lock } from '@element-plus/icons-vue'//输入框引入字体图标,使用:prefix-icon="User"
const loginStore = useLoginStore()
//获取路由对象
const router = useRouter()
//声明用户信息
const user = reactive({
    username: "",
    password: ""
})
//登录点击事件
const handleLogin = () => {
    axios.post('admin/login', {
        username: user.username,
        password: user.password
    }).then(res => {
        if (res.data.code === 200) {
            loginStore.username = res.data.data.username;
            loginStore.islogin = true
            router.push("/")
        } else {
            ElMessage.error(res.data.message)
        }
    })


}
</script>
<style scoped>
.login-container {
    /*在App.vue中设置公共样式:顶级容器body,html,#app的高度为100%,使其背景布满全屏 */
    width: 100%;
    height: 730px;
    /* border-radius: 15px; */
    
    background-image: linear-gradient(135deg, #EE9AE5 10%, #5961F9 100%) !important;
}

.user {
    /**相对定位 */
    position: relative;
    width: 400px;
    /*设置内边距:上 左右 下 */
    padding: 160px 35px 0;
    /*居中:外边距上下-0  左右自动居中*/
    margin: 0 auto;

}

.title-container .title {
    font-family:楷体;
    font-size: 40px;
    font-weight: 900;
    color: #fff;
    text-align: center;
    margin: 20px;
}

</style>

侧边栏代码

<template>
    <!--添加动画效果:设置内部样式-->
    <div class="slider-navs" :style="{ width: menuStore.isCollapse ? '64px' : '210px' }">
        <div v-if="menuStore.toggleStore" class="logo">{{ menuStore.isCollapse ? 'JKW' : "J K W" }}</div>
        <!--background-color:背景颜色(只有菜单这块,菜单占多少背景占多少) 
            text-color:文本颜色 
            active-text-color:选中后文本颜色
            :default-active="active":点击高亮(与index=""搭配,在script设置默认访问/)
            router:可以把点击高亮里的index的地址变为路由地址,进行路由访问
            :collapse:是否开启折叠面板
            unique-opened 是否只保持一个子菜单的展开
            点击的是el-sub-menu,所以el-sub-menu 的唯一性是必须的,设置index属性来保持唯一性
        -->
        <el-menu background-color="rgba(255,255,255,.1)" text-color="#ffff" active-text-color="orange"
            :default-active="active" router class="el-menu-vertical-demo" :collapse="menuStore.isCollapse" unique-opened>
            <el-menu-item index="/home/index">
                <el-icon>
                    <HomeFilled />
                </el-icon>
                <span>首页</span>
            </el-menu-item>
            <el-sub-menu index="1">
                <template #title>
                    <el-icon>
                        <Tools />
                    </el-icon>
                    <span>系统</span>
                </template>
                <el-menu-item index="/sys/admin">
                    <el-icon>
                        <User />
                    </el-icon>
                    <span>用户</span>
                </el-menu-item>
                <el-menu-item index="/sys/role">
                    <el-icon>
                        <CopyDocument />
                    </el-icon>
                    <span>角色</span>
                </el-menu-item>
                <el-menu-item index="/sys/permission">
                    <el-icon>
                        <Lock />
                    </el-icon>
                    <span>权限</span>
                </el-menu-item>
            </el-sub-menu>


        </el-menu>
    </div>
</template>
<script setup>
import { ref } from 'vue';
import { useMenuStore } from "@/stores/menuStore.js";//stores
//解决页面刷新后 菜单高亮与面包屑不匹配
const menuStore = useMenuStore()
const active = ref("/")
if (localStorage.getItem("active")) {
    active.value = localStorage.getItem("active")
}
</script>
<style scoped>
.slider-navs {
    background-image: linear-gradient(135deg, #EE9AE5 10%, #5961F9 100%) !important;
    /*固定在左侧(position: fixed生成绝对定位的元素, 相对于浏览器窗口进行定位) */
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    /*width: 210px;*/
  
    /*设置动画过渡 和<el-menu>内部的一样 */
    transition: 0.3s ease-in;
}

.el-menu-vertical-demo {
    background-image: linear-gradient(to bottom right, rgb(114, 135, 254), rgb(130, 88, 186));
  
}

.logo {
    background-image: linear-gradient(120deg, #00e4d0, #5983e8) !important;
    /*需要在App.vue设置公共样式,把element menu的右边框去掉,否则对不齐 */
    width: 100%;
    height: 60px;
    background-color: #364e6d;
    font-size: 25px;
    color: #fff;
    text-align: center;
    /*左右居中 */
    line-height: 60px;
    /*上下居中:和高度保持一致 */
}

.icon {
    width: 16px;
    height: 16px;
    margin-right: 5px;
    /*让图标和文字有空隙 */
}
</style>

头部导航栏代码

<template>
    <div class="nav">
        <div class="toggle-menu">
            <div class="toggle-menu-toggle">
                <!--折叠菜单 图标-->
                <el-icon v-if="menuStore.isCollapse" class="icon" @click="openMenu(false)">
                    <Expand />
                </el-icon>
                <el-icon v-else class="icon" @click="closeMenu(true)">
                    <Fold />
                </el-icon>
            </div>
        </div>
        <div class="toggle-menu-breadcrumb">
            <el-breadcrumb separator="/">
                <el-breadcrumb-item>{{ $t("message.navs") }}</el-breadcrumb-item>
                <el-breadcrumb-item>{{ menuStore.breadcrumb }}</el-breadcrumb-item>
            </el-breadcrumb>
        </div>
        <div class="lang">
            <el-dropdown>
                <span class="el-dropdown-link">
                    语言
                    <el-icon class="el-icon--right">
                        <arrow-down />
                    </el-icon>
                </span>
                <template #dropdown>
                    <el-dropdown-item @click="changeLang('zh')">
                        简体中文
                    </el-dropdown-item>
                    <el-dropdown-item @click="changeLang('en')">
                        English
                    </el-dropdown-item>
                </template>
            </el-dropdown>
        </div>
        <div class="login" v-if="!loginStore.islogin">
            <el-button type="success" round plain @click="loginHandler">立即登录</el-button>
        </div>
        <div class="user" v-if="loginStore.islogin">
            <el-dropdown>
                <span class="el-dropdown-link">
                    <el-icon>
                        <Avatar />
                    </el-icon>
                    <!--动态显示用户名-->
                    {{ loginStore.username }}
                    <el-icon class="el-icon--right">
                        <arrow-down />
                    </el-icon>
                </span>
                <template #dropdown>
                    <el-dropdown-menu>
                        <el-dropdown-item>
                            <el-icon>
                                <UserFilled />
                            </el-icon>
                            <router-link to="/home/center">个人中心</router-link>
                        </el-dropdown-item>
                        <el-dropdown-item @click="logoutHandler">
                            <el-icon>
                                <SwitchButton />
                            </el-icon>退出登录
                        </el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
        </div>
    </div>
</template>
<script setup>
import { useLoginStore } from "@/stores/loginStore.js"//stores
import { useMenuStore } from "@/stores/menuStore.js"
import { useRouter } from "vue-router"//router
import axios from "@/utils/request.js"
const loginStore = useLoginStore()
const router = useRouter()
const menuStore = useMenuStore()
//登录
const loginHandler = () => {
    router.push("/home/login")
}
/*退出登录*/
const logoutHandler = () => {
    axios.get('/admin/logout').then(res => {
        console.log(res.data)
        if (res.data.code == 200) {
            //把仓库中登录信息清空,并返回登录页
            loginStore.islogin = false
            loginStore.username = ""
            router.push("/")
        } else {
            ElMessage.error(res.data.message)
        }
    }).catch(error => {
        console.log(error);
    })
}
/*关闭左侧菜单栏 */
const closeMenu = (flag) => {
    //点击后把isCollapse值赋予true,关闭左侧菜单
    menuStore.isCollapse = flag
}
/*打开菜单栏 */
const openMenu = (flag) => {
    //点击后把isCollapse值赋予false,关闭左侧菜单
    menuStore.isCollapse = flag
}
//切换语言
const changeLang = (lang) => {
    //console.log(lang);
    localStorage.setItem("lang", lang)
    //语言切换刷新ui
    location.reload();
}
</script>
<style scoped>
.nav {
    background-image: linear-gradient(120deg, #00e4d0, #5983e8) !important;
    width: 100%;
    background-color: #fff;
    /*height:和左侧菜单的logo高度一致 */
    height: 60px;
    /*最下面阴影效果 */
    box-shadow: 0 1px 3px 0 rgb(0 0 0/ 12%), 0 0 3px 0 rgb(0 0 0 /4%);
}

.toggle-menu {
    /*图标居中 */
    padding-top: 17.5px;
    padding-left: 10px;
}

.icon {
    /*改变图标大小 */
    font-size: 25px;
}

.toggle-menu-toggle {
    /*向左浮动,水平结构 */
    float: left;
}

.toggle-menu-breadcrumb {
    /*向左浮动,水平结构 */
    float: left;
    /*面包屑居中 */
    line-height: 60px;
    margin-top: 6px;
    margin-left: 20px;
}

.login {


    position: absolute;
    right: 40px;
    top: 15px;
}

.user {
    /*下拉框大小 */
    font-size: 15px;
    /*下拉框放到左边 */
    position: absolute;
    right: 40px;
    /*下拉框居中*/
    top: 23px;
}

.lang {
    float: right;
    margin-right: 180px;
    margin-top: 5px;
}
</style>

echarts数据公开

/**
 * echarts图标库
 */
import * as echarts from "echarts"//基本图标
export default {
    /**echarts挂载到vue全局 */
    install: app => {
        //折线图  
        app.config.globalProperties.$line = (element, data) => {
            var myChart = echarts.init(document.getElementById(element))
            const colors = ['#5470C6', '#EE6666'];
            const option = {
              color: colors,
              tooltip: {
                trigger: 'none',
                axisPointer: {
                  type: 'cross'
                }
              },
              legend: {},
              grid: {
                top: 70,
                bottom: 50
              },
              xAxis: [
                {
                  type: 'category',
                  axisTick: {
                    alignWithLabel: true
                  },
                  axisLine: {
                    onZero: false,
                    lineStyle: {
                      color: colors[1]
                    }
                  },
                  axisPointer: {
                    label: {
                      formatter: function (params) {
                        return (
                          'Precipitation  ' +
                          params.value +
                          (params.seriesData.length ? ':' + params.seriesData[0].data : '')
                        );
                      }
                    }
                  },
                  // prettier-ignore
                  data: ['2016-1', '2016-2', '2016-3', '2016-4', '2016-5', '2016-6', '2016-7', '2016-8', '2016-9', '2016-10', '2016-11', '2016-12']
                },
                {
                  type: 'category',
                  axisTick: {
                    alignWithLabel: true
                  },
                  axisLine: {
                    onZero: false,
                    lineStyle: {
                      color: colors[0]
                    }
                  },
                  axisPointer: {
                    label: {
                      formatter: function (params) {
                        return (
                          'Precipitation  ' +
                          params.value +
                          (params.seriesData.length ? ':' + params.seriesData[0].data : '')
                        );
                      }
                    }
                  },
                  // prettier-ignore
                  data: ['2015-1', '2015-2', '2015-3', '2015-4', '2015-5', '2015-6', '2015-7', '2015-8', '2015-9', '2015-10', '2015-11', '2015-12']
                }
              ],
              yAxis: [
                {
                  type: 'value'
                }
              ],
              series: [
                {
                  name: 'Precipitation(2015)',
                  type: 'line',
                  xAxisIndex: 1,
                  smooth: true,
                  emphasis: {
                    focus: 'series'
                  },
                  data: [
                    2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3
                  ]
                },
                {
                  name: 'Precipitation(2016)',
                  type: 'line',
                  smooth: true,
                  emphasis: {
                    focus: 'series'
                  },
                  data: [
                    3.9, 5.9, 11.1, 18.7, 48.3, 69.2, 231.6, 46.6, 55.4, 18.4, 10.3, 0.7
                  ]
                }
              ]
            };
              
            myChart.setOption(option)
        },
        //雷达图
        app.config.globalProperties.$radar = (element, data) => {
        var myChart = echarts.init(document.getElementById(element))
        const option = {
                    legend: {
                        data: ['Allocated Budget', 'Actual Spending']
                    },
                    radar: {
                        // shape: 'circle',
                        indicator: [
                            { name: 'Sales', max: 6500 },
                            { name: 'Administration', max: 16000 },
                            { name: 'Information Technology', max: 30000 },
                            { name: 'Customer Support', max: 38000 },
                            { name: 'Development', max: 52000 },
                            { name: 'Marketing', max: 25000 }
                        ]
                    },
                    series: [
                        {
                            name: 'Budget vs spending',
                            type: 'radar',
                            data: [
                                {
                                    value: [4200, 3000, 20000, 35000, 50000, 18000],
                                    name: 'Allocated Budget'
                                },
                                {
                                    value: [5000, 14000, 28000, 26000, 42000, 21000],
                                    name: 'Actual Spending'
                                }
                            ]
                        }
                    ]
                }
            myChart.setOption(option)
        },
        //饼状图
        app.config.globalProperties.$rose = (element, data) => {
        var myChart = echarts.init(document.getElementById(element))
        const option = {
            tooltip: {
              trigger: 'item'
            },
            legend: {
              top: '5%',
              left: 'center'
            },
            series: [
              {
                name: 'Access From',
                type: 'pie',
                radius: ['40%', '70%'],
                avoidLabelOverlap: false,
                itemStyle: {
                  borderRadius: 10,
                  borderColor: '#fff',
                  borderWidth: 2
                },
                label: {
                  show: false,
                  position: 'center'
                },
                emphasis: {
                  label: {
                    show: true,
                    fontSize: 40,
                    fontWeight: 'bold'
                  }
                },
                labelLine: {
                  show: false
                },
                data: [
                  { value: 1048, name: 'Search Engine' },
                  { value: 735, name: 'Direct' },
                  { value: 580, name: 'Email' },
                  { value: 484, name: 'Union Ads' },
                  { value: 300, name: 'Video Ads' }
                ]
              }
            ]
          }
        myChart.setOption(option)

        },
        //柱状图
        app.config.globalProperties.$bar = (element, data) => {
            var myChart = echarts.init(document.getElementById(element))
            const option = {
                xAxis: {
                  type: 'category',
                  data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
                },
                yAxis: {
                  type: 'value'
                },
                series: [
                  {
                    data: [120, 200, 150, 80, 70, 110, 130],
                    type: 'bar'
                  }
                ]
              }
            myChart.setOption(option)    

        }
    }
}

pinia代码

import { defineStore } from "pinia"
  
export const useLoginStore = defineStore("login", {
    state: () => {
        return {
            username: "",//用户名
            islogin:false//是否登录
        }
    },
    //本地持久化(把数据存储到浏览器本地)
    persist: {
        enabled: true,//是否开启持久化
        strategies: [
            {
                key: 'login', //自定义Key值,存储到本地时的key
                storage: localStorage, // 选择存储方式:本地存储
            },
        ],
    }
}) 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月木@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值