一、项目先容
1、技能栈
- vue3+vite+vue-router
- pinia
- element-plus
- axios
- mock
- Echarts
2、业务功能
3、应用场景
- 举行后台管理项目的
- 根据不同用户的权限授予不同的功能
4、项目源码
xuyuan-upward 盼望每位大佬获取时候点个小小的赞!
二、项目实战
2.1、项目初始化
1、构建项目
- # npm 7+,需要添加额外的 --:
- npm create vite@latest my-vue-app -- --template vue
复制代码 2、安装成功后举行安装依靠
3、修改路径替换符
- import { defineConfig } from 'vite'
- import vue from '@vitejs/plugin-vue'
- // https://vite.dev/config/
- export default defineConfig({
- plugins: [vue()],
- /* 添加的是别名 */
- resolve: {
- alias: {
- '@': '/src',
- },
- },
- })
复制代码 4、引入element-plus依靠,axios,router
安装依靠内容过于简单,请自行取相关内容官网查看文档举行安装
element-plus官网
axios官网
router
2.2、项目实战
1、引入router配置
- import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
- const routes = [
- {
- path: '/',
- name: 'main',
- component: () => import('@/views/RootView.vue'),
- redirect: '/home',
- /* 访问 /home 时的行为
- 当你访问 /home 时,会发生以下情况:
- 匹配父路由:
- 首先匹配到 path: '/' 的路由配置。
- 由于 Main.vue 是父组件,它会被渲染。
- 匹配子路由:
- 在 Main.vue 内部的 <router-view> 中,会匹配到 path: '/home' 的子路由。
- 因此,Home.vue 会在 Main.vue 的 <router-view> 中渲染。 */
- children: [
- {
- path: '/home',
- name: 'home',
- component: () => import('@/views/MainView.vue')
- },
- ]
- },
- ]
- const router = createRouter({
- history: createWebHistory(),
- routes
- })
- export default router
复制代码 接着还需要导入main.js一些配置文件
- import { createApp } from 'vue'
- import App from './App.vue'
- // 导入全局样式
- import "@/assets/less/index.less"
- import router from '@/router'
- import ElementPlus from 'element-plus'
- // 导入element-plus组件的全局样式
- import 'element-plus/dist/index.css'
- import * as ElementPlusIconsVue from '@element-plus/icons-vue'
- import { createPinia } from 'pinia'
- import { useAllDataStore } from '@/stores'
- /* 引入mock */
- import "@/apis/mock.js"
- /* 引入apis 管理请求接口 */
- import apis from "@/apis/apis.js"
- const pinia = createPinia()
- const app = createApp(App)
- /* 定义全局配置使用 */
- app.config.globalProperties.$apis = apis
- app.use(ElementPlus)
- app.use(pinia)
- // localStorage.removeItem("store")
- const store = useAllDataStore()
- store.addRoutes(router, "refresh")
- app.use(router)
- /*function isRoute(to) {
- let routes = router.getRoutes()
- console.log("routes", routes);
- let resFil = routes.filter(item =>
- /* 相当于return */
- item.path === to.path
- )
- /* let resFil = routes.filter(item => {
- 相当于一段代码,只有return为true时候才会保留对应的数据
- item.path === to.path}
-
- ) */
- return resFil
- }
- /*
- router.beforeEach((to, from, next) => {
- console.log("store.state.token", store.state.token);
- if (!store.state.token && to.path !== '/login') {
- console.log("to.path1", to.path);
- next({ name: 'login' })
- }
- if (store.state.token && to.path === '/login') {
- console.log("to.path2", to.path);
- next({ name: 'home' })
- }
- if (store.state.token && to.path !== '/login') {
- console.log("to.path3", to.path);
- console.log("isRoute", isRoute(to));
- if (isRoute(to).length === 0) {
- console.log("to.path3", to.path);
- next({ name: '404' })
- }
- }
- console.log("to.path4", to.path);
- next()
- })
- */
- for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
- app.component(key, component)
- }
- app.mount('#app')
复制代码 2、App.vue组件引入
- <script setup>
- </script>
- <template>
- <div class="app">
- <router-view />
- </div>
- </template>
- <style >
- #app,
- .app {
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- </style>
复制代码 3、创建RootView根路由组件
- <template>
- <div class="common-layout">
- <el-container class="main-container">
- <div class="aside-container">
- <CommonAside />
- </div>
- <el-container class="right-container">
- <el-header style="height: 40px;">
- <CommonHeader />
- </el-header>
- <el-divider style="margin: 5px 0;" />
- <CommonTab />
- <el-main>
- <router-view />
- </el-main>
- <el-footer>
- <Footer />
- </el-footer>
- </el-container>
- </el-container>
- </div>
- </template>
- <script setup>
- import { reactive, toRefs, onMounted, nextTick } from 'vue'
- import CommonAside from '@/components/CommonAside.vue';
- import CommonHeader from '@/components/CommonHeader.vue';
- import CommonTab from '@/components/CommonTab.vue';
- import Footer from '@/components/Footer.vue';
- </script>
- <style lang='less' scoped>
- .common-layout,
- .main-container,
- right-container {
- height: 100%;
- background-color: #f0f2f5;
- .el-main {
- padding: 8px;
- }
- }
- </style>
复制代码 4、依次创建
三个普通组件 CommonAside CommonHeader Footer 以及每个页面的路由组件 router-view
- <template>
- <!-- default-active通常与对应的index相关 -->
- <el-aside :width="width">
- <el-menu text-color="#fff" background-color="#545c64" :collapse="isCollapse" :collapse-transition="false"
- :default-active="activeMenu"
- class="el-menu-vertical-demo">
- <div class="title">
- <i class="iconfont icon-quanpingtai"> </i>
- <h4 v-show="!isCollapse">许苑后台管理</h4>
- </div>
- <!-- 没有子菜单 -->
- <el-menu-item v-for="item in noChildren" :key="item.path" :index="item.path" @click="handleMenu(item)">
- <component :is="item.icon" class="icon" />
- <span style="margin-left: 10px">{{ item.label }}</span>
- </el-menu-item>
- <!-- 有子菜单 -->
- <el-sub-menu v-for="item in hasChildren" :key="item.path" :index="item.path">
- <template #title>
- <component :is="item.icon" class="icon" />
- <span style="margin-left: 10px">{{ item.label }}</span>
- </template>
- <el-menu-item-group>
- <el-menu-item v-for="(subItem) in item.children" :index="subItem.path" :key="subItem.path">
- <component :is="subItem.icon" class="icon" />
- <span>{{ subItem.label }}</span>
- </el-menu-item>
- </el-menu-item-group>
- </el-sub-menu>
- </el-menu>
- </el-aside>
- </template>
- <script setup>
- import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
- import { useRouter, useRoute } from 'vue-router'
- import { useAllDataStore } from '@/stores'
- const store = useAllDataStore()
- const router = useRouter()
- const route = useRoute()
- // 测试数据,初始化,刚开始可以使用,后续通过配置路由权限获取
- /* const list = ref([
- {
- path: '/home',
- name: 'home',
- label: '首页',
- icon: 'house',
- url: 'Home'
- },
- {
- path: '/mall',
- name: 'mall',
- label: '商品管理',
- icon: 'ShoppingBag',
- url: 'Mall'
- },
- {
- path: '/user',
- name: 'user',
- label: '用户管理',
- icon: 'user',
- url: 'User'
- },
- {
- path: 'other',
- label: '其他',
- icon: 'location',
- children: [
- {
- path: '/page1',
- name: 'page1',
- label: '页面1',
- icon: 'setting',
- url: 'Page1'
- },
- {
- path: '/page2',
- name: 'page2',
- label: '页面2',
- icon: 'setting',
- url: 'Page2'
- }
- ]
- }
- ])
- */
- const list = computed(() => store.state.menuList)
- const noChildren = computed(() => list.value.filter(item => !item.children))
- const hasChildren = computed(() => list.value.filter(item => item.children))
- const width = computed(() => store.state.isCollapse ? '60px' : '200px')
- // 涉及组件之间的传递 => 使用pinia进行各组件之间的传递
- const isCollapse = computed(() => store.state.isCollapse)
- const activeMenu = computed(() => route.path)
- const handleMenu = (item) => {
- if (item.children) {
- return
- }
- router.push(item.path)
- store.selectMenu(item)
- }
- </script>
- <style lang='less' scoped>
- .icon {
- width: 18px;
- height: 18px;
- margin-right: 5px;
- }
- .el-aside {
- background-color: #545c64;
- height: 100vh;
- .el-menu {
- border-right: none;
- .title {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- h4 {
- color: #fff;
- font-size: 17px;
- margin: 20px;
- font-weight: 500px;
- text-align: center;
- }
- }
- }
- </style>
复制代码
- <template>
- <div class="header">
- <div class="l-header">
- <el-button size="small" @click="store.state.isCollapse = !store.state.isCollapse">
- <el-icon>
- <Menu />
- </el-icon>
- </el-button>
-
- </div>
- <div class="r-header">
- <el-dropdown>
- <span class="el-dropdown-link">
- <img src="@/assets/images/xuyuan.jpg" alt="" class="r-header-avatar">
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item>个人中心</el-dropdown-item>
- <el-dropdown-item @click="handlerLogout">退出登录</el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, toRefs, onMounted, nextTick, computed } from 'vue'
- import { useAllDataStore } from '@/stores'
- import { ArrowRight } from '@element-plus/icons-vue'
- import { useRouter } from 'vue-router'
- const store = useAllDataStore()
- const router = useRouter()
- let currentPath = computed(() => {
- console.log("store.state.currentMenu", store.state.currentMenu);
- return store.state.currentMenu;
- })
- const handlerLogout = () => {
- store.clean()
- router.push('/login')
- }
- </script>
- <style lang='less' scoped>
- .header {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .l-header {
- display: flex;
- justify-content: center;
- align-items: center;
- .el-button {
- margin-right: 15px;
- }
- }
- }
- .r-header-avatar {
- width: 30px;
- height: 30px;
- border-radius: 50%;
- margin-right: 10px;
- }
- </style>
- <!-- -- 面包屑功能 <el-breadcrumb :separator-icon="ArrowRight">
-
- <el-breadcrumb-item :to=" '/' ">首页</el-breadcrumb-item>
- <el-breadcrumb-item v-if="currentPath" :to="currentPath.path" @cl>{{ currentPath.label
- }}
- </el-breadcrumb-item>
- </el-breadcrumb> -->
复制代码
- <template>
- <div class="footer">
- <a href="https://github.com/xuyuan-upward" class="toLearn">
- <span> <i class="iconfont icon-github"></i>站长: 许苑向上</span>
- </a>
- <a href="https://blog.csdn.net/a147775" class="toLearn">
- <span> <i class="iconfont icon-bokeyuan"></i>博客: xuyuan-upward</span>
- </a>
- <a href="https://user.qzone.qq.com/2517115657/main" class="toLearn">
- <span> <i class="iconfont icon-shouye"></i>联系方式: 许苑向上</span>
- </a>
- </div>
- </template>
- <script setup>
- import { ref, reactive, toRefs, onMounted, nextTick } from 'vue'
- </script>
- <style lang='less' scoped>
- .toGithub {
- text-decoration: none;
- font-size: 14px;
- font-weight: bold;
- padding: 10px 0;
- display: block;
- text-align: center;
- border-radius: 5px;
- transition: all 0.3s ease-in-out;
- }
- .iconfont {
- margin-right: 5px;
- }
- .footer {
- height: 100%;
- display: flex;
- justify-content: center;
- a {
- margin-right: 40px;
- color: #00000073;
- span {
- line-height: 60px;
- }
- }
- }
- </style>
复制代码
- <template>
- <div class="home">
- <el-row>
- <!-- 左侧 -->
- <el-col :span="7">
- <div class="l-user">
- <el-card style="max-width: 480px" shadow="hover" class="user-info">
- <div class="user">
- <img src="@/assets/images/xuyuan.jpg" alt=""
- style="width: 100px;height: 100px;border-radius: 50%;margin-right: 10px;">
- <div class="userInfo">
- <h>admin</h>
- <p style="margin-top: 20px; color: #999;">超级管理员</p>
- </div>
- </div>
- <el-divider />
- <div class="login-info">
- <p>上次登录时间:<span>2024-11-18 1:00:00</span></p>
- <p style="margin-top: 10px;">上次登录地点:<span>广西</span></p>
- </div>
- </el-card>
- <el-card style="max-width: 480px" shadow="hover" class="user-table">
- <el-table :data="tableData" style="width: 100%">
- <!-- 遍历val是值 key是键 -->
- <el-table-column v-for="(val, key) in tableLabel" :key="key" :prop="key" :label="val">
- </el-table-column>
- </el-table>
- </el-card>
- </div>
- </el-col>
- <!-- 右侧 -->
- <el-col :span="17">
- <div class="r-echart">
- <div class="top">
- <el-card v-for="(item) in counterData" :key="item.name"
- :body-style="{ padding: '20px', display: 'flex' }" shadow="hover">
- <component :is="item.icon" class="icons" :style="{ background: item.color }" />
- <div class="detail">
- <p class="num">¥{{ item.value }}</p>
- <p class="txt">¥{{ item.name }}</p>
- </div>
- </el-card>
- </div>
- <div class="bottom">
- <!-- 三个图表容器 -->
- <div class="echart-top">
- <el-card shadow="hover">
- <div ref="echart" style="height: 220px;"></div>
- </el-card>
- </div>
- <div class="echart-bottom">
- <el-card shadow="hover">
- <div ref="userEchart" style="height: 140px"></div>
- </el-card>
- <el-card shadow="hover">
- <div ref="videoEchart" style="height: 140px"></div>
- </el-card>
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- </div>
- </template>
- <script setup>
- import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
- import * as echarts from 'echarts';
- //这个tableData是假数据,等会我们使用axios请求mock数据
- const { proxy } = getCurrentInstance()
- const tableData = ref([])
- const counterData = ref([])
- //observer 接收观察器实例对象
- const observer = ref(null)
- //这个是折线图和柱状图 两个图表共用的公共配置
- const xOptions = reactive({
- // 图例文字颜色
- textStyle: {
- color: "#333",
- },
- legend: {},
- grid: {
- left: "20%",
- },
- // 提示框
- tooltip: {
- trigger: "axis",
- },
- xAxis: {
- type: "category", // 类目轴
- data: [],
- axisLine: {
- lineStyle: {
- color: "#17b3a3",
- },
- },
- axisLabel: {
- interval: 0,
- color: "#333",
- },
- },
- yAxis: [
- {
- type: "value",
- axisLine: {
- lineStyle: {
- color: "#17b3a3",
- },
- },
- },
- ],
- color: ["#2ec7c9", "#b6a2de", "#5ab1ef", "#ffb980", "#d87a80", "#8d98b3"],
- series: [],
- })
- const pieOptions = reactive({
- tooltip: {
- trigger: "item",
- },
- legend: {},
- color: [
- "#0f78f4",
- "#dd536b",
- "#9462e5",
- "#a6a6a6",
- "#e1bb22",
- "#39c362",
- "#3ed1cf",
- ],
- series: []
- })
- const getTableData = async () => {
- const data = await proxy.$apis.getTableData()
- console.log("home,tableData获取到的数据:", data);
- tableData.value = data.tableData
- }
- const getCounterData = async () => {
- const data = await proxy.$apis.getCounterData()
- console.log("home,counterData获取到的数据:", data);
- counterData.value = data
- }
- const getChartData = async () => {
- // 获取图标信息 ,解构
- const { orderData, userData, videoData } = await proxy.$apis.getChartData()
- console.log("home,orderData获取到的数据:", orderData);
- //对第一个图表的xAxis和series赋值
- xOptions.xAxis.data = orderData.date
- xOptions.series = Object.keys(orderData.data[0]).map(val => {
- return {
- name: val,
- data: orderData.data.map(item => item[val]),
- type: "line"
- }
- }
- )
- //one echarts.init方法初始化ECharts实例,需要传入dom对象
- const OneEcharts = echarts.init(proxy.$refs["echart"])
- //setOption方法应用配置对象
- OneEcharts.setOption(xOptions)
- //对第二个图表的xAxis和series赋值
- xOptions.xAxis.data = userData.map((item) => item.date)
- xOptions.series = [
- {
- name: "新增用户",
- data: userData.map((item) => item.new),
- type: "bar",
- },
- {
- name: "活跃用户",
- data: userData.map((item) => item.active),
- type: "bar",
- }
- ]
- //two
- const TwoEcharts = echarts.init(proxy.$refs["userEchart"])
- TwoEcharts.setOption(xOptions)
- //对第三个图表的series赋值
- pieOptions.series = [
- {
- data: videoData,
- type: "pie",
- },
- ]
- //three
- const ThreeEcharts = echarts.init(proxy.$refs["videoEchart"])
- ThreeEcharts.setOption(pieOptions);
- //ResizeObserver 如果监视的容器大小变化,如果改变会执行传递的回调
- observer.value = new ResizeObserver(entries => {
- OneEcharts.resize()
- TwoEcharts.resize()
- ThreeEcharts.resize()
- })
- //如果这个容器存在
- if (proxy.$refs["echart"]) {
- //则调用监视器的observe方法,监视这个容器的大小
- observer.value.observe(proxy.$refs["echart"]);
- }
- }
- onMounted(() => {
- getTableData()
- getCounterData()
- getChartData()
- console.log(proxy);
- })
- const tableLabel = ref({
- name: "课程",
- todayBuy: "今日购买",
- monthBuy: "本月购买",
- totalBuy: "总购买",
- })
- </script>
- <style lang='less' scoped>
- .home {
- height: 100%;
- overflow: hidden;
- .l-user {
- .user-info {
- .user {
- display: flex;
- align-items: center;
- .userInfo {
- margin-left: 30px;
- }
- }
- .login-info {
- p {
- font-size: 14px;
- color: #999;
- span {
- color: #666;
- margin-left: 30px;
- }
- }
- }
- }
- .user-table {
- margin-top: 50px;
- }
- }
- .r-echart {
- .top {
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- .el-card {
- width: 30%;
- margin-bottom: 10px;
- margin-left: 20px;
- }
- .icons {
- width: 50px;
- height: 50px;
- border-radius: 50%;
- margin-right: 20px;
- }
- .detail {
- display: flex;
- flex-direction: column;
- justify-content: center;
- .num {
- margin-bottom: 10px;
- }
- }
- }
- .bottom {
- margin-left: 20px;
- .echart-top {
- margin-bottom: 20px;
- }
- .echart-bottom {
- display: flex;
- justify-content: space-between;
- align-items: center;
- .el-card {
- width: 48%;
- }
- }
- }
- }
- }
- </style>
复制代码
- <template>
- <div class="user">
- <div class="user-head">
- <el-button type="primary" @click="handleAdd">新增</el-button>
- <el-form :inline="true" :model="formData">
- <el-form-item label="请输入">
- <el-input placeholder="请输入姓名" v-model="formData.keyWord"></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handlerSearch">搜索</el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="user-table">
- <el-dialog v-model="dialogVisible" :title="action == 'add' ? '新增用户' : '编辑用户'" width="35%"
- :before-close="handleClose">
- <!--需要注意的是设置了:inline="true",
- 会对el-select的样式造成影响,我们通过给他设置一个class=select-
- 在css进行处理-->
- <el-form :inline="true" :model="formUser" :rules="rules" ref="userForm">
- <el-row>
- <el-col :span="12">
- <el-form-item label="姓名" prop="name">
- <el-input v-model="formUser.name" placeholder="请输入姓名" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="年龄" prop="age">
- <el-input v-model.number="formUser.age" placeholder="请输入年龄" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-col :span="12">
- <!-- 使用:inline="true"会对select造成影响,此时长度应该设置最大 -->
- <el-form-item label="性别" prop="sex" style="width: 80%;">
- <el-select v-model="formUser.sex" placeholder="请选择" class="select-clean">
- <el-option label="男" :value="1" />
- <!-- 注意这里的 :value 表示绑定一个表达式即所谓的"1" 其实代表的是number类型1 -->
- <el-option label="女" :value="0" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="出生日期" prop="birth">
- <el-date-picker v-model="formUser.birth" type="date" placeholder="请输入"
- style="width: 100%" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row>
- <el-form-item label="地址" prop="addr">
- <el-input v-model="formUser.addr" placeholder="请输入地址" />
- </el-form-item>
- </el-row>
- <el-row style="justify-content: flex-end">
- <el-form-item>
- <el-button type="primary" @click="handleCancel">取消</el-button>
- <el-button type="primary" @click="onSubmit">确定</el-button>
- </el-form-item>
- </el-row>
- </el-form>
- </el-dialog>
- <el-table :data="tableData" style="width: 100%">
- <el-table-column v-for="item in tableLabel" :key="item.prop" :prop="item.prop" :label="item.label"
- :width="item.width" />
- <el-table-column fixed="right" label="Operations" min-width="120">
- <template #="scoped">
- <el-button type="primary" size="small" @click="onEdit(scoped.row)">
- 编辑
- </el-button>
- <el-button type="danger" size="small" @click="onDelete(scoped.row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-pagination layout="prev, pager, next" :total="config.total" @current-change="handlerChangePage"
- class="page" />
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, toRefs, onMounted, nextTick, getCurrentInstance } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- const { proxy } = getCurrentInstance()
- const tableData = ref([])
- const tableLabel = reactive([
- {
- prop: "name",
- label: "姓名",
- },
- {
- prop: "age",
- label: "年龄",
- },
- {
- prop: "sex",
- label: "性别",
- },
- {
- prop: "birth",
- label: "出生日期",
- width: 200,
- },
- {
- prop: "addr",
- label: "地址",
- width: 400,
- },
- ])
- const config = reactive({
- name: "",
- total: 0,
- }
- )
- const formData = reactive({
- keyWord: ""
- })
- const dialogVisible = ref(false)
- const action = ref("add")
- const formUser = ref({
- sex: 0,
- })
- //表单校验规则
- const rules = reactive({
- name: [{ required: true, message: "姓名是必填项", trigger: "blur" }],
- age: [
- { required: true, message: "年龄是必填项", trigger: "blur" },
- { type: "number", message: "年龄必须是数字" },
- ],
- sex: [{ required: true, message: "性别是必选项", trigger: "change" }],
- birth: [{ required: true, message: "出生日期是必选项" }],
- addr: [{ required: true, message: '地址是必填项' }]
- })
- const handlerSearch = () => {
- console.log("搜索", formData.keyWord);
- config.name = formData.keyWord
- // console.log("搜索", searchText);
- getUserData(config)
- }
- const getUserData = async (query) => {
- const data = await proxy.$apis.getUserData(query)
- console.log("UserView的数据", data);
- config.total = data.count
- tableData.value = data.list.map((item) => {
- return {
- ...item,
- sex: item.sex === 1 ? '女' : '男'
- }
- })
- }
- onMounted(() => {
- getUserData()
- })
- const handlerChangePage = (value) => {
- console.log("当前页码", value);
- config.page = value
- getUserData(config)
- }
- const onDelete = (row) => {
- console.log("删除", row);
- ElMessageBox.confirm(
- '你确定要删除吗?',
- '删除提示',
- {
- confirmButtonText: '确定删除',
- cancelButtonText: '取消',
- type: 'danger ',
- }
- )
- .then(() => {
- proxy.$apis.deleteUser({ id: row.id })
- ElMessage({
- type: 'success',
- message: '删除成功',
- })
- getUserData()
- })
- .catch(() => {
- ElMessage({
- type: 'info',
- message: '取消删除',
- })
- })
- }
- const onEdit = (row) => {
- console.log("编辑", row);
- action.value = "edit"
- dialogVisible.value = true
- /* nextTick 确保在 DOM 更新完成之后再执行回调函数
- 也就是编辑表单
- */
- nextTick(() => {
- formUser.value = {
- ...row,
- }
- }
- )
- /* formUser.value = {
- ...row,
- } */
- }
- //这个方法之前定义过
- const handleAdd = () => {
- action.value = "add"
- //打开对话窗
- dialogVisible.value = true
- }
- //对话框右上角的关闭事件
- const handleClose = () => {
- //获取到表单dom,执行resetFields重置表单
- //关闭对话框
- dialogVisible.value = false
- proxy.$refs["userForm"].resetFields()
- }
- //对话框右下角的取消事件
- const handleCancel = () => {
- dialogVisible.value = false
- proxy.$refs["userForm"].resetFields()
- }
- //格式化日期,格式化为:1997-01-02这种
- const timeFormat = (time) => {
- var time = new Date(time);
- var year = time.getFullYear();
- var month = time.getMonth() + 1;
- var date = time.getDate();
- function add(m) {
- return m < 10 ? "0" + m : m;
- }
- return year + "-" + add(month) + "-" + add(date);
- }
- const onSubmit = async () => {
- // 获取表单数据
- console.log("添加的xxx", formUser.value);
- // 先进行校验
- proxy.$refs["userForm"].validate(async (validate) => {
- if (validate) {
- let res = null;
- //这里无论是新增或者是编辑,我们都要对这个日期进行一个格式化
- //如果不是1997-01-02这种格式,使用timeFormat方法进行格式化
- formUser.birth = /^\d{4}-\d{2}-\d{2}$/.test(formUser.birth)
- ? formUser.birth
- : timeFormat(formUser.birth)
- // 提交表单时候,还需要判断是add or edit
- if (action.value === "add") {
- res = await proxy.$apis.addUser(formUser.value)
- } else {
- res = await proxy.$apis.editUser(formUser.value)
- }
- if (res) {
- ElMessage({
- type: 'success',
- message: action.value === "add" ? '添加成功' : "编辑成功",
- })
- dialogVisible.value = false
- proxy.$refs["userForm"].resetFields()
- // 刷新页面数据
- getUserData()
- }
- }
- else {
- ElMessage({
- type: 'error',
- message: "请输入正确内容",
- })
- }
- })
- // 校验通过,执行添加操作
- proxy.$apis.addUser(formUser.value)
- }
- </script>
- <style lang='less' scoped>
- .user {
- height: 100%;
- .user-head {
- display: flex;
- justify-content: space-between;
- }
- .user-table {
- height: 540px;
- position: relative;
- .page {
- position: absolute;
- bottom: 50px;
- right: 50px;
- }
- }
- }
- </style>
复制代码 5、每个组件之间需要共享配置导入pinia配置举行信息共享和传递。
pinia.js
- import { defineStore } from 'pinia'
- import { ref, watch } from 'vue'
- function initData() {
- return {
- isCollapse: false,
- tags: [
- {
- path: '/home',
- name: 'home',
- label: '首页',
- icon: 'hone'
- }
- ],
- currentMenu: null,
- /* 展示菜单列表的数组 */
- menuList: [],
- token: null,
- routerList: [],
- }
- }
- export const useAllDataStore = defineStore('allData', () => {
- // 全部数据的获取和修改
- const state = ref(initData())
- // 进行数据持久化
- watch(state, newObj => {
- if (!newObj.token) return
- localStorage.setItem('store', JSON.stringify(newObj))
- }, {
- deep: true,
- })
- function selectMenu(val) {
- if (val.name === 'home') {
- state.value.currentMenu = null
- }
- else {
- state.value.currentMenu = val
- let index = state.value.tags.findIndex(item => item.name === val.name)
- index === -1 ? state.value.tags.push(val) : ""
- }
- }
- function deleteMenu(tag) {
- let index = state.value.tags.findIndex(item => item.name == tag.name)
- // 将当前tags切除
- state.value.tags.splice(index, 1);
- }
- function updateMenuList(val) {
- // 将当前tags切除
- state.value.menuList = val;
- }
- function clean() {
- // 将所有路由移除
- state.value.routerList.forEach(item => {
- if (item) item();
- state.value = initData();
- // 删除本地的缓存
- localStorage.removeItem('store')
- })
- }
- function addRoutes(router, type) {
- // 刷新页面时候
- if (type === 'refresh') {
- if (JSON.parse(localStorage.getItem('store'))) {
- state.value = JSON.parse(localStorage.getItem('store'))
- //
- state.value.routerList = []
- }
- else {
- return;
- }
- }
- // 将当前tags切除
- const menu = state.value.menuList;
- console.log("menu", menu);
- /* 执行该代码后 import.meta.glob可能返回的是这样的对象
- '@/views/Home.vue': () => import('@/views/Home.vue'),
- '@/views/About.vue': () => import('@/views/About.vue'),
- '@/views/User/Profile.vue': () => import('@/views/User/Profile.vue')
- */
- const module = import.meta.glob('../views/*.vue')
- console.log("module", module);
- const routeArr = []
- menu.forEach(item => {
- if (item.children) {
- item.children.forEach(child => {
- let url = `../views/${child.url}.vue`
- console.log("url", url);
- child.component = module[url]
- console.log("child.component", child.component);
- routeArr.push(...item.children)
- })
- }
- else {
- let url = `../views/${item.url}.vue`
- console.log("url", url);
- item.component = module[url]
- console.log("item.component", item.component);
- routeArr.push(item)
- }
- routeArr.forEach(item => {
- state.value.routerList.push(router.addRoute("main", item));
- })
- })
- console.log("state.value.routerList", state.value.routerList);
- console.log("state.value.routeArr", routeArr);
- }
- return {
- /* 其实是直接返回的是state.value */
- state,
- selectMenu,
- deleteMenu,
- updateMenuList,
- addRoutes,
- clean,
- }
- })
复制代码 5、举行对应各个环境的配置环境设置
config.js
- // 用于获取对应的环境变量
- const env = process.env.NODE_ENV || "prod";
- const EnvConfig = {
- development: {
- baseURL: "/api",
- mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
- },
- test: {
- baseURL: "//test.xuyuan.com/api",
- mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
- },
- prod: {
- baseURL: "//xuyuan.com/api",
- mockApi: "https://mock.apipark.cn/m1/4068509-0-default/api"
- },
- }
- export default {
- env,
- /* 将其重新解构成一个对象,并将其合并到默认配置中 */
- ...EnvConfig[env],
- isMock:false,
- };
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |