中后台开发笔记(一)

165 阅读4分钟

大佬地址:juejin.cn/user/317584…

中后台开发笔记

安装脚手架

  • ...

拉取manager模板

  • ...
  • 配置代理(解决是否允许跨域访问的问题)
//在vite.config.js文件下配置
// 配置前端服务地址和端口
  server: {
    host: "0.0.0.0",
    port: 5173,
    // 是否开启 https
    https: false,

    //让开发服务器帮我们做代理
    proxy:{
      // http://localhost:5173/api
      "/api":{
        target:"http://localhost:8173",//API服务地址
        changeOrigin:true,//开启跨域
        rewrite:(path)=>path.replace(/^\/api/,"")
      }
    }
  },

拉取server数据库模板

  • 下载源代码
   git clone https://gitee.com/steveouyang/mongo-expresser.git
  • 安装依赖
cd mongo-expresser
npm install
  • 修改配置文件
{
    "dbName": "mydb",//自动生成的数据库名称
    "port": 8002,//项目运行端口
    "jwtSecret": "jinwandalaohu",//jwtToken秘钥
    "tokenAge": "3600s",//登录token有效期

    // 模块接口
    "routes": [
        {
            "name": "film", //模块名称电影 = 数据库中的collection名称 = RESTful风格的接口前缀

            // 接口中间件配置
            "middlewares": {
                "create": ["adminCheck"], // 为单个添加接口【POST:/film/0】配置管理员校验中间件
                "createMany": ["adminCheck"], // 为批量添加接口【POST:/film/-1】配置管理员校验中间件
                "retrieve": [], // 详情查询接口【GET:/film/:id】无需登录鉴权
                "retrieveMany": [], // 批量查询接口【GET:/film/0】无需登录鉴权
                "update": ["adminCheck"], // 为数据更新接口【PUT:/film/:id】配置管理员校验中间件
                "delete": ["adminCheck"] // 为数据删除接口【DELETE:/film/:id】配置管理员校验中间件
            }
        },

        {
            "name": "city", //模块名称城市 = 数据库中的collection名称 = RESTful风格的接口前缀

            // 接口中间件配置
            "middlewares": {
                "middlewares": {
                    "create": ["adminCheck"], // 为单个添加接口【POST:/city/0】配置管理员校验中间件
                    "createMany": ["adminCheck"], // 为批量添加接口【POST:/city/-1】配置管理员校验中间件
                    "retrieve": ["loginCheck"], // 详情查询接口【GET:/city/:id】配置登录校验中间件
                    "retrieveMany": ["loginCheck"], // 批量查询接口【GET:/city/0】配置登录校验中间件
                    "update": ["adminCheck"], // 为数据更新接口【PUT:/city/:id】配置管理员校验中间件
                    "delete": ["adminCheck"] // 为数据删除接口【DELETE:/city/:id】配置管理员校验中间件
                }
            }
        }
    ]
}
  • 运行项目
npm run start
现在所有的工程模块已经具备增删改查功能!!!

调试阶段(可借助mongodb+postman进行调试)

静态资源模块:
GET:baseurl/<public下的文件目录>
文件上传服务
POST:baseurl/file/upload
-- 上传文件页面
baseurl/page/file_upload.html
必不可少的用户管理模块(带权限校验功能)
--用户注册
POST:baseurl/user/register + form或json格式请求体

--用户登录
POST:baseurl/user/login + form/json格式的请求体
业务模块

此处以音乐模块为例:

-- 添加单个音乐
POST:baseurl/music/0 + form或json格式的请求体

-- 批量添加
POST:baseurl/music/-1 + json数组格式的请求体

-- 删除音乐
DELETE:baseurl/music/:id

-- 修改音乐
PUT:baseurl/music/:id + form或json格式的请求体

-- 查询音乐详情
GET:baseurl/music/:id

-- 查询所有音乐
GET:baseurl/music/0
使用身份鉴权
  • 用户模块默认检测三个字段:username,password,admin
  • 后续通过admin字段的true/false判断是否是管理员身份登录
  • 用户登陆成功后的返回信息里会携带jwtToken,其密钥即为配置文件中配置的密钥
  • 对后续接口的返回需要在请求头当中携带token
    headers:{
        "authorization":登录时发放的token值
    }

使用ElementPlus(vue3)进行样式布局 ps:(vue2:Element ui)

  • 基本使用步骤:
//在 main.js 里面引入 elementplus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
  • Icon图标比较特殊,需要在用到组件内,再次声明
import {
  ArrowLeftBold,
  SwitchButton,
} from "@element-plus/icons-vue";

部署路由

在router/index.js
 routes: [
     //配置首页路由,一般使用同步路由
    {
      path: '/',
      name: 'home',
      component: HomeView
    },

    //其余组件路由,使用异步路由
    /* 用户管理 */
    {
      path: '/user',
      name: 'user',
      component: () => import('../views/UserList.vue')
    },

    /* 404 */
    {
      path:"/:pm(.*)*",
      name:"notfound",
      component: () => import('@views/NotFound.vue'),
    }
  ]
<el-menu-item index="4">
          <el-icon><User /></el-icon>
          <RouterLink class="tab" to="/user">用户管理</RouterLink>
</el-menu-item>

//必须要有routerView,这是作为路由的出口,没有无法显示路由
//在APP.vue当中部署的routerView,一级组件(一级路由)都会显示在routerView,部署二级路由,routerView应当部署在自己一级路由下
//这里使用 keepAlive + 动态路由,因为路由出口里面就是装载当前显示路由(组件)
//配合着tabs,使用时,也可以这样;无需像使用button切换路由那样,引入且手动切换组件名
<router-view v-slot="{ Component }">
        <transition name="slide-fade" mode="out-in">
          <keep-alive>
            <component :is="Component" />
          </keep-alive>
        </transition>
</router-view>

组件封装

  • MyPageHeader:@在中后台管理项目当中,页头都大差不差,为了以后我们开发方便,所有封装了这样一个组件;
  • 实现思路:先把基础样式写好=>进行抽取=>根据业务需求,看哪些数据是需要动态传入=>采用prop+emit+v-model+expose(组件暴露出API供父组件自行使用)=>还可有预留插槽给父组件,自行配置内容;最终这个组件复用程度非常高
  • 模板配置(支持父组件prop传入数据,子组件发送自定义事件,还可以对子组件添加相应的style,以及子组件自己的插槽的默认内容和默认方法)
<template>
    <div class="page-header">
        <div class="left" :style="leftStyle">
            <el-icon class="left-icon" size="23" @click="$router.back">
                <ArrowLeftBold />
            </el-icon>
        </div>
        <div class="middle" :style="titleStyle">{{ title }}</div>
        <div class="right">
            <p class="page-header-right-img-box" :style="imgStyle"><img :src="imgUrl" alt="" class="page-header-right-img">
            </p>
            <span class="page-header-right-welcome">欢迎回来:</span>
            <span class="page-header-right-host" v-OvertToCovert:mouseover="'host-mouseover'">{{host}}</span>
        </div>
        <div class="quit" :style="quitStyle">
            <slot name="right-icon">
                <el-icon size="25" class="quit-icon" @click="emit('onRightClick')">
                    <SwitchButton />
                </el-icon>
            </slot>

        </div>
    </div>
</template>
  • 完整代码
<template>
    <div class="page-header">
        <div class="left" :style="leftStyle">
            <el-icon class="left-icon" size="23" @click="$router.back">
                <ArrowLeftBold />
            </el-icon>
        </div>
        <div class="middle" :style="titleStyle">{{ title }}</div>
        <div class="right">
            <p class="page-header-right-img-box" :style="imgStyle"><img :src="imgUrl" alt="" class="page-header-right-img">
            </p>
            <span class="page-header-right-welcome">欢迎回来:</span>
            <span class="page-header-right-host" v-OvertToCovert:mouseover="'host-mouseover'">{{host}}</span>
        </div>
        <div class="quit" :style="quitStyle">
            <slot name="right-icon">
                <el-icon size="25" class="quit-icon" @click="emit('onRightClick')">
                    <SwitchButton />
                </el-icon>
            </slot>

        </div>
    </div>
</template>

<script setup>
import {
  ArrowLeftBold,
  SwitchButton
} from '@element-plus/icons-vue'
import { defineEmits, defineProps } from "vue"
const {title,host,imgUrl,leftStyle,titleStyle,quitStyle,imgStyle } = defineProps({
    title: {
        type: String,
        default: "欢迎光临卖座电影管理后台"
    },
    host: {
        type: String,
        default: "铁锤"
    },
    imgUrl:{
        type:String,
        default:"../../public/imgs/own.jpg"
    },
    leftStyle:{
        type:Object,
        default:{}
    },
    titleStyle:{
        type:Object,
        default:{}
    },
    imgStyle:{
        type:Object,
        default:{}
    },
    quitStyle:{
        type:Object,
        default:{}
    }
})

const emit = defineEmits({
    onRightClick:null,
    onLeftClick:null
})
</script>

<style lang="scss" scoped>
@import "@assets/variable.scss";
@import "@assets/mixin.scss";
.page-header {
  padding: 0 20px;
  line-height: 50px;
  background-color: blueviolet;
  display: flex;
  height: 50px;

  .left {
    margin-top: 10px;
    position: relative;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background-color: $grayc;

    .left-icon {
      color: white;
      top: 4px;
      left: 3px;
      position: absolute;
      text-align: center;
      line-height: 35px;
    }
  }

  .middle {
    width: 250px;
    // font-size: 18px;
    color: white;
    margin-left: 30px;
  }

  .right {
    position: relative;
    display: flex;
    justify-content: right;
    flex: 1;

    .page-header-right-img-box {
      position: relative;
      top: 11px;
      right: 140px;
      width: 30px;
      height: 30px;
      background-color: $grayc;
      border-radius: 50%;

      .page-header-right-img {
        position: absolute;  
        // top: -1px;
        width: 30px;
        border-radius: 50%;
      }
    }

    .page-header-right-welcome {
      position: absolute;
      right: 68px;
      font-size: 14px;  
      color: white;
    }

    .page-header-right-host {
      position: absolute; 
      left: 560px;
      font-size: 14px;
      color: rgb(240, 240, 98);
    }
    .host-mouseover{
      color: aqua;
      cursor: pointer;
    }
  }

  .quit {
    .quit-icon{
      margin-top: 12px;
      color: white;
    }
  }
}
</style>
  • 实现效果图

image.png

知识点拾遗

  • 回忆: input的v-model
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

//语法糖
<input v-model="searchText" />
  • 组件通信:prop-down , event-up , provide + inject , expose , v-model , store(中央数据仓库)
  • 组件之间的v-model:本质上就是,父组件给子组件(prop)传递一个数据,子组件接收并改变这个数据时,及时的传递给父组件(emit),且携带着变化的数据
  • 默认情况下,prop名字为 modelValue , emit名字为 update:modelValue
  • 单个v-model绑定:prop => modelValue | emit => update:modelValue
  • 多个v-model绑定:prop => v-model:参数名 | emit => update:参数名
//父组件
<father>
  <CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"/>
  
  //语法糖
  <CustomInput v-model="searchText" />

</father>
//子组件
//子组件在内部需要声明 prop + emit(且emit发送自定义事件时,携带最新的数据)
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>


//父组件
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

//子组件
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>