全栈开发前端代码:黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开,big-event

这篇具有很好参考价值的文章主要介绍了全栈开发前端代码:黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开,big-event。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录:希望对大家有帮助

①项目压缩包:

1.要启动的指令:可以参考下面的文件

①登录、注册页面

src\views\login\LoginPage.vue

src\api\user.js

src\stores\modules\user.js

src\stores\index.js

②首页实现的页面

src\views\layout\LayoutContainer.vue

src\api\user.js

user下面的小模块:实现改用户信息、图像、密码

src\components\PageContainer.vue

src\views\user\UserProfile.vue

src\views\user\UserAvatar.vue

src\views\user\UserPassword.vue

③文章的目录编辑:

src\components\PageContainer.vue

src\views\article\ArticleChannel.vue

src\views\article\components\ChannelEdit.vue

④文章的详情管理:

src\views\article\ArticleManage.vue

src\views\article\components\ChannelSelect.vue

src\views\article\components\ArticleEdit.vue

src\utils\format.js

⑤全局的路由:


①项目压缩包:

📎day13-大事件管理系统-Vue3-big-event-admin.zip

1.要启动的指令:可以参考下面的文件

📎day12~day13-大事件管理系统-new.pdf

📎11-day12-day14-大事件管理系统.zip

npm install -g pnpm

pnpm create vue

pnpm dlx husky-init && pnpm install

pnpm i lint-staged -D

2.自己原版的笔记:在语雀

https://www.yuque.com/wangruiquandayi3banrengongzhineng/bmgrcg/fgp0su8yx0bb5039?singleDoc# 《②big-event前端代码》# 《②big-event前端代码》

①登录、注册页面

黑马程序员大事件后台程序,javascript,前端,vue.js

src\views\login\LoginPage.vue

<script setup>
  import { userRegisterService, userLoginService } from '@/api/user.js'
  import { User, Lock } from '@element-plus/icons-vue'
  import { ref, watch } from 'vue'
  import { useUserStore } from '@/stores'
  import { useRouter } from 'vue-router'
  const isRegister = ref(false)

  //获取整个表单的信息,进行统一的校验
  //只有信息都输入正确后,才能进行注册的
  const form = ref()

  // 整个的用于提交的form数据对象
  const formModel = ref({
    username: '',
    password: '',
    repassword: ''
  })
  // 整个表单的校验规则
  // 1. 非空校验 required: true      message消息提示,  trigger 触发校验的时机 blur change
  // 2. 长度校验 min:xx, max: xx
  // 3. 正则校验 pattern: 正则规则    \S 非空字符
  // 4. 自定义校验 => 自己写逻辑校验 (校验函数)
  //    validator: (rule, value, callback)
  //    (1) rule  当前校验规则相关的信息
  //    (2) value 所校验的表单元素目前的表单值
  //    (3) callback 无论成功还是失败,都需要 callback 回调
  //        - callback() 校验成功
  //        - callback(new Error(错误信息)) 校验失败
  const rules = {
    username: [
      { required: true, message: '请输入用户名', trigger: 'blur' },
      { min: 5, max: 10, message: '用户名必须是 5-10位 的字符', trigger: 'blur' }
    ],
    password: [
      { required: true, message: '请输入密码', trigger: 'blur' },
      {
        pattern: /^\S{6,15}$/,
        message: '密码必须是 6-15位 的非空字符',
        trigger: 'blur'
      }
    ],
    repassword: [
      { required: true, message: '请输入密码', trigger: 'blur' },
      {
        pattern: /^\S{6,15}$/,
        message: '密码必须是 6-15位 的非空字符',
        trigger: 'blur'
      },
      {
        validator: (rule, value, callback) => {
          // 判断 value 和 当前 form 中收集的 password 是否一致
          if (value !== formModel.value.password) {
            callback(new Error('两次输入密码不一致'))
          } else {
            callback() // 就算校验成功,也需要callback
          }
        },
        trigger: 'blur'
      }
    ]
  }

  const register = async () => {
    // 注册成功之前,先进行校验,校验成功 → 请求,校验失败 → 自动提示
    //validate()是官网提供的方法,已经直接暴露,可以直接使用
    await form.value.validate()
    await userRegisterService(formModel.value)
    ElMessage.success('注册成功')
    isRegister.value = false
  }

  const userStore = useUserStore()
  const router = useRouter()

  const login = async () => {
    await form.value.validate()
    const res = await userLoginService(formModel.value)
    userStore.setToken(res.data.token)
    ElMessage.success('登录成功')
    router.push('/')
  }

  // 切换的时候,重置表单内容
  watch(isRegister, () => {
    formModel.value = {
      username: '',
      password: '',
      repassword: ''
    }
  })
</script>

<template>
  <!-- 
  1. 结构相关
  el-row表示一行,一行分成24份 
  el-col表示列  
  (1) :span="12"  代表在一行中,占12份 (50%)
  (2) :span="6"   表示在一行中,占6份  (25%)
  (3) :offset="3" 代表在一行中,左侧margin份数

  el-form 整个表单组件
  el-form-item 表单的一行 (一个表单域)
  el-input 表单元素(输入框)
  2. 校验相关
  (1) el-form => :model="ruleForm"      绑定的整个form的数据对象 { xxx, xxx, xxx }
  (2) el-form => :rules="rules"         绑定的整个rules规则对象  { xxx, xxx, xxx }
  (3) 表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form的子属性
  (4) el-form-item => prop配置生效的是哪个校验规则 (和rules中的字段要对应)
  -->
  <el-row class="login-page">
    <!--左边的大事件的图片-->
    <el-col :span="12" class="bg"></el-col>
    <el-col :span="6" :offset="3" class="form">
      <!-- 注册相关表单 -->
      <el-form
        :model="formModel"
        :rules="rules"
        ref="form"
        size="large"
        autocomplete="off"
        v-if="isRegister"
      >
        <el-form-item>
          <h1>注册</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input
            v-model="formModel.username"
            :prefix-icon="User"
            placeholder="请输入用户名"
          ></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="formModel.password"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></el-input>
        </el-form-item>
        <el-form-item prop="repassword">
          <el-input
            v-model="formModel.repassword"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入再次密码"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button
            @click="register"
            class="button"
            type="primary"
            auto-insert-space
          >
            注册
          </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
        :model="formModel"
        :rules="rules"
        ref="form"
        size="large"
        autocomplete="off"
        v-else
      >
        <el-form-item>
          <h1>登录</h1>
        </el-form-item>
        <el-form-item prop="username">
          <el-input
            v-model="formModel.username"
            :prefix-icon="User"
            placeholder="请输入用户名"
          ></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="formModel.password"
            name="password"
            :prefix-icon="Lock"
            type="password"
            placeholder="请输入密码"
          ></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
            @click="login"
            class="button"
            type="primary"
            auto-insert-space
            >登录</el-button
          >
        </el-form-item>
        <el-form-item class="flex">
          <el-link type="info" :underline="false" @click="isRegister = true">
            注册 →
          </el-link>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<style lang="scss" scoped>
.login-page {
  height: 100vh;
  background-color: #fff;
  .bg {
    background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
      url('@/assets/login_bg.jpg') no-repeat center / cover;
    border-radius: 0 20px 20px 0;
  }
  .form {
    display: flex;
    flex-direction: column;
    justify-content: center;
    user-select: none;
    .title {
      margin: 0 auto;
    }
    .button {
      width: 100%;
    }
    .flex {
      width: 100%;
      display: flex;
      justify-content: space-between;
    }
  }
}
</style>

src\api\user.js

import request from '@/utils/request'

// 注册接口
export const userRegisterService = ({ username, password, repassword }) =>
  request.post('/api/reg', { username, password, repassword })

// 登录接口
export const userLoginService = ({ username, password }) =>
  request.post('/api/login', { username, password })

// 获取用户基本信息
export const userGetInfoService = () => request.get('/my/userinfo')

export const userUpdateInfoService = ({ id, nickname, email }) =>
  request.put('/my/userinfo', { id, nickname, email })

export const userUploadAvatarService = (avatar) =>
  request.patch('/my/update/avatar', { avatar })

export const userUpdatePassService = ({ old_pwd, new_pwd, re_pwd }) =>
  request.patch('/my/updatepwd', { old_pwd, new_pwd, re_pwd })

src\stores\modules\user.js

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { userGetInfoService } from '../../api/user'

// 用户模块 token setToken removeToken
export const useUserStore = defineStore(
  'big-user',
  () => {
    const token = ref('')
    const setToken = (newToken) => {
      token.value = newToken
    }

    const removeToken = () => {
      token.value = ''
    }

    //当退出登录时,要进行置空
    const user = ref({})

    const getUser = async () => {
      const res = await userGetInfoService() // 请求获取数据
      user.value = res.data.data
    }

    const setUser = (obj) => {
      user.value = obj
    }

    return {
      token,
      setToken,
      removeToken,
      user,
      getUser,
      setUser
    }
  },
  {
    persist: true
  }
)

src\stores\index.js

import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(persist)

export default pinia

//实现了全部的输出,其他模块导入就方便了
export * from './modules/user'
export * from './modules/counter'

// import { useUserStore } from './modules/user'
// export { useUserStore }
// import { useCountStore } from './modules/counter'
// export { useCountStore }

②首页实现的页面

黑马程序员大事件后台程序,javascript,前端,vue.js

src\views\layout\LayoutContainer.vue

<script setup>
import {
  Management,
  Promotion,
  UserFilled,
  User,
  Crop,
  EditPen,
  SwitchButton,
  CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
import { useUserStore } from '@/stores'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()

// 开始就取得User的属性
onMounted(() => {
  userStore.getUser()
})

const handleCommand = async (key) => {
  if (key === 'logout') {
    // 退出操作
    await ElMessageBox.confirm('你确认要进行退出么', '温馨提示', {
      type: 'warning',
      confirmButtonText: '确认',
      cancelButtonText: '取消'
    })

    // 清除本地的数据 (token + user信息)
    userStore.removeToken() //移除key
    userStore.setUser({})
    router.push('/login')
  } else {
    // 跳转操作
    router.push(`/user/${key}`)
  }
}
</script>

<template>
  <!-- 
    el-menu 整个菜单组件
      :default-active="$route.path"  配置默认高亮的菜单项
      router  router选项开启,el-menu-item 的 index 就是点击跳转的路径

    el-menu-item 菜单项
      index="/article/channel" 配置的是访问的跳转路径,配合default-active的值,实现高亮
      如果路径一致,则要实现高亮
  -->
  <el-container class="layout-container">
    <!--左边导航栏的信息-->
    <el-aside width="200px">
      <div class="el-aside__logo"></div>
      <el-menu
        active-text-color="#ffd04b"
        background-color="#232323"
        :default-active="$route.path"
        text-color="#fff"
        router
      >
        <!--里面的模块分类-->
        <el-menu-item index="/article/channel">
          <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 index="/user">
          <!-- 多级菜单的标题 - 具名插槽 title -->
          <template #title>
            <el-icon><UserFilled /></el-icon>
            <span>个人中心</span>
          </template>

          <!--展开的内容 - 默认插槽 -->
          <el-menu-item index="/user/profile">
            <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>{{
            userStore.user.nickname || userStore.user.username
          }}</strong>
        </div>
        <el-dropdown placement="bottom-end" @command="handleCommand">
          <!-- 展示给用户,默认看到的 -->
          <span class="el-dropdown__box">
            <el-avatar :src="userStore.user.user_pic || 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>
        <router-view></router-view>
      </el-main>
      <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
    </el-container>
  </el-container>
</template>

<style lang="scss" scoped>
.layout-container {
  height: 100vh;
  .el-aside {
    background-color: #232323;
    &__logo {
      height: 120px;
      background: url('@/assets/logo.png') no-repeat center / 120px auto;
    }
    .el-menu {
      border-right: none;
    }
  }
  .el-header {
    background-color: #fff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .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>

src\api\user.js

  1. user作者路由的操作
import request from '@/utils/request'

// 注册接口
export const userRegisterService = ({ username, password, repassword }) =>
  request.post('/api/reg', { username, password, repassword })

// 登录接口
export const userLoginService = ({ username, password }) =>
  request.post('/api/login', { username, password })

// 获取用户基本信息→后端是存储到redis缓存的
export const userGetInfoService = () => request.get('/my/userinfo')

export const userUpdateInfoService = ({ id, nickname, email }) =>
  request.put('/my/userinfo', { id, nickname, email })

export const userUploadAvatarService = (avatar) =>
  request.patch('/my/update/avatar', { avatar })

export const userUpdatePassService = ({ old_pwd, new_pwd, re_pwd }) =>
  request.patch('/my/updatepwd', { old_pwd, new_pwd, re_pwd })

user下面的小模块:实现改用户信息、图像、密码

src\components\PageContainer.vue
  1. 设计插槽的方式:具名插槽、默认插槽
<script setup>
defineProps({
  title: {
    requiered: true,
    type: String
  }
})
</script>

<template>
  <el-card class="page-container">
    <template #header>
      <div class="header">
        <span>{{ title }}</span>

        <!--具名插槽:指定了一个名为 "extra" 的插槽,父组件可以通过这个名称将内容插入到这个插槽中-->
        <div class="extra">
          <slot name="extra"></slot>
        </div>
      </div>
    </template>
    <slot></slot>
  </el-card>
</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>
src\views\user\UserProfile.vue

黑马程序员大事件后台程序,javascript,前端,vue.js

<script setup>
import PageContainer from '@/components/PageContainer.vue'
import { useUserStore } from '@/stores'
import { ref } from 'vue'
import { userUpdateInfoService } from '@/api/user'

const formRef = ref()

// 快速从useUserStore里面解析出方法、属性
// getUser快速实现更新的动作
const {
  user: { username, nickname, email, id, getUser }
} = useUserStore()

// 基于上面的字段进行初始化了
const userInfo = ref({ username, nickname, email, id })

const rules = {
  nickname: [
    { required: true, message: '请输入用户昵称', trigger: 'blur' },
    {
      pattern: /^\S{2,10}$/,
      message: '昵称必须是2-10位的非空字符串',
      trigger: 'blur'
    }
  ],
  email: [
    { required: true, message: '请输入用户邮箱', trigger: 'blur' },
    { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
  ]
}

const onSubmit = async () => {
  // 进行全局的校验,
  const valid = await formRef.value.validate()
  if (valid) {
    await userUpdateInfoService(userInfo.value)

    //更新数据
    await getUser()
    ElMessage.success('修改成功')
  }
}
</script>

<template>
  <page-container title="基本资料">
    <el-row>
      <el-col :span="12">
        <el-form
          :model="userInfo"
          :rules="rules"
          ref="formRef"
          label-width="100px"
          size="large"
        >
          <el-form-item label="登录名称">
            <el-input v-model="userInfo.username" disabled></el-input>
          </el-form-item>

          <el-form-item label="用户昵称" prop="nickname">
            <el-input v-model="userInfo.nickname"></el-input>
          </el-form-item>

          <el-form-item label="用户邮箱" prop="email">
            <el-input v-model="userInfo.email"></el-input>
          </el-form-item>

          <el-form-item>
            <el-button @click="onSubmit" type="primary">提交修改</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </page-container>
</template>
src\views\user\UserAvatar.vue
  1. 学会了借用别人的功能: 借用了上面上传图片的功能

黑马程序员大事件后台程序,javascript,前端,vue.js

<script setup>
import PageContainer from '@/components/PageContainer.vue'
import { ref } from 'vue'
import { Plus, Upload } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores'
import { userUploadAvatarService } from '../../api/user'

const userStore = useUserStore()
const uploadRef = ref()
const imgUrl = ref(userStore.user.user_pic)

// 选择更新图片
const onUploadFile = (file) => {
  const reader = new FileReader()
  reader.readAsDataURL(file.raw)
  reader.onload = () => {
    imgUrl.value = reader.result
  }
}

// 上传图片进行更新
const onUpdateAvatar = async () => {
  await userUploadAvatarService(imgUrl.value)

  //马上更新信息→更新图像,不用再进行刷新
  await userStore.getUser()
  ElMessage({ type: 'success', message: '更换头像成功' })
}
</script>

<template>
  <page-container title="更换头像">
    <el-row>
      <el-col :span="12">
        <el-upload
          ref="uploadRef"
          class="avatar-uploader"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="onUploadFile"
        >
          <img v-if="imgUrl" :src="imgUrl" class="avatar" />
          <img v-else src="@/assets/avatar.jpg" width="278" />
        </el-upload>

        <br />
        <!--借用了+号上传图片,借用了别人的功能
        借用了上面上传图片的功能-->
        <el-button
          @click="uploadRef.$el.querySelector('input').click()"
          type="primary"
          :icon="Plus"
          size="large"
        >
          选择图片
        </el-button>
        <el-button
          @click="onUpdateAvatar"
          type="success"
          :icon="Upload"
          size="large"
        >
          上传头像
        </el-button>
      </el-col>
    </el-row>
  </page-container>
</template>

<style lang="scss" scoped>
.avatar-uploader {
  :deep() {
    .avatar {
      width: 278px;
      height: 278px;
      display: block;
    }
    .el-upload {
      border: 1px dashed var(--el-border-color);
      border-radius: 6px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: var(--el-transition-duration-fast);
    }
    .el-upload:hover {
      border-color: var(--el-color-primary);
    }
    .el-icon.avatar-uploader-icon {
      font-size: 28px;
      color: #8c939d;
      width: 278px;
      height: 278px;
      text-align: center;
    }
  }
}
</style>
src\views\user\UserPassword.vue

黑马程序员大事件后台程序,javascript,前端,vue.js

<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/stores'
import { userUpdatePassService } from '@/api/user'

const formRef = ref()
const router = useRoute()
const userStore = useUserStore()

const pwdForm = ref({
  old_pwd: '',
  new_pwd: '',
  re_pwd: ''
})

const checkOldSame = (rule, value, cb) => {
  if (value === pwdForm.value.old_pwd) {
    cb(new Error('原密码和新密码不能一样!'))
  } else {
    cb()
  }
}

const checkNewSame = (rule, value, cb) => {
  if (value !== pwdForm.value.new_pwd) {
    cb(new Error('新密码和确认再次输入的新密码不一样!'))
  } else {
    cb()
  }
}
const rules = {
  // 原密码
  old_pwd: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码长度必须是6-15位的非空字符串',
      trigger: 'blur'
    }
  ],
  // 新密码
  new_pwd: [
    { required: true, message: '请输入新密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码长度必须是6-15位的非空字符串',
      trigger: 'blur'
    },
    { validator: checkOldSame, trigger: 'blur' }
  ],
  // 确认新密码
  re_pwd: [
    { required: true, message: '请再次确认新密码', trigger: 'blur' },
    {
      pattern: /^\S{6,15}$/,
      message: '密码长度必须是6-15位的非空字符串',
      trigger: 'blur'
    },
    { validator: checkNewSame, trigger: 'blur' }
  ]
}

const onSubmit = async () => {
  const valid = await formRef.value.validate()
  if (valid) {
    await userUpdatePassService(pwdForm.value)
    ElMessage({ type: 'success', message: '更换密码成功' })
    userStore.setToken('')
    userStore.setUser({})
    router.push('/login')
  }
}
const onReset = () => {
  formRef.value.reseetFields()
}
</script>
<template>
  <page-container title="重置密码">
    <el-row>
      <el-col :span="12">
        <el-form
          :model="pwdForm"
          :rules="rules"
          ref="formRef"
          label-width="100px"
          size="large"
        >
          <el-form-item label="原密码" prop="old_pwd">
            <el-input v-model="pwdForm.old_pwd" type="password"></el-input>
          </el-form-item>
          <el-form-item label="新密码" prop="new_pwd">
            <el-input v-model="pwdForm.new_pwd" type="password"></el-input>
          </el-form-item>
          <el-form-item label="确认新密码" prop="re_pwd">
            <el-input v-model="pwdForm.re_pwd" type="password"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button @click="onSubmit" type="primary">修改密码</el-button>
            <el-button @click="onReset">重置</el-button>
          </el-form-item>
        </el-form>
      </el-col>
    </el-row>
  </page-container>
</template>

③文章的目录编辑:

黑马程序员大事件后台程序,javascript,前端,vue.js

src\components\PageContainer.vue

  1. 也是往里面进行填充的
<script setup>
defineProps({
  title: {
    requiered: true,
    type: String
  }
})
</script>

<template>
  <el-card class="page-container">
    <template #header>
      <div class="header">
        <span>{{ title }}</span>

        <!--具名插槽:指定了一个名为 "extra" 的插槽,父组件可以通过这个名称将内容插入到这个插槽中-->
        <div class="extra">
          <slot name="extra"></slot>
        </div>
      </div>
    </template>
    <slot></slot>
  </el-card>
</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>
src\views\article\ArticleChannel.vue
<script setup>
import { ref } from 'vue'
import { Edit, Delete } from '@element-plus/icons-vue'
import { artGetChannelsService, artDelChannelService } from '../../api/article'
import ChannelEdit from './components/ChannelEdit.vue'
const channelList = ref([])

// 是加载旋转的效果
const loading = ref(false)

//绑定了组件,可以获取里面的方法
const dialog = ref()

const getChannelList = async () => {
  loading.value = true
  const res = await artGetChannelsService()
  channelList.value = res.data.data
  loading.value = false
}

// 开始就调用获取数据
getChannelList()

// row是获取一行的数据
const onDelChannel = async (row) => {
  await ElMessageBox.confirm('你确认要删除该分类么', '温馨提示', {
    type: 'warning',
    confirmButtonText: '确认',
    cancelButtonText: '取消'
  })
  await artDelChannelService(row.id)
  ElMessage.success('删除成功')
  getChannelList()
}

// 编辑文章分类要进行回显
const onEditChannel = (row) => {
  dialog.value.open(row)
}

// 添加文章分类不需要→调用子组件的方法
const onAddChannel = () => {
  dialog.value.open({})
}

// 在成功后,
const onSuccess = () => {
  getChannelList()
}
</script>

<template>
  <page-container title="文章分类">
    <template #extra>
      <el-button @click="onAddChannel">添加分类</el-button>
    </template>

    <el-table v-loading="loading" :data="channelList" style="width: 100%">
      <el-table-column type="index" label="序号" width="100"></el-table-column>
      <el-table-column prop="cate_name" label="分类名称"></el-table-column>
      <el-table-column prop="cate_alias" label="分类别名"></el-table-column>
      <el-table-column label="操作" width="150">
        <!-- row 就是 channelList 的一项, $index 下标 -->
        <template #default="{ row, $index }">
          <el-button
            :icon="Edit"
            circle
            plain
            type="primary"
            @click="onEditChannel(row, $index)"
          ></el-button>
          <el-button
            :icon="Delete"
            circle
            plain
            type="danger"
            @click="onDelChannel(row, $index)"
          ></el-button>
        </template>
      </el-table-column>

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

    <channel-edit ref="dialog" @success="onSuccess"></channel-edit>
  </page-container>
</template>

<style lang="scss" scoped></style>
src\views\article\components\ChannelEdit.vue
  1. 进行文章分类的编辑→区分添加、编辑文章(编辑要进行回显)
<script setup>
import { ref } from 'vue'
import { artEditChannelService, artAddChannelService } from '@/api/article.js'

const dialogVisible = ref(false) //绑定窗口是否出现

const formRef = ref()

const formModel = ref({
  cate_name: '',
  cate_alias: '' //分类的别名
})
const rules = {
  cate_name: [
    { required: true, message: '请输入分类名称', trigger: 'blur' },
    {
      pattern: /^\S{1,10}$/,
      message: '分类名必须是 1-10 位的非空字符',
      trigger: 'blur'
    }
  ],
  cate_alias: [
    { required: true, message: '请输入分类别名', trigger: 'blur' },
    {
      pattern: /^[a-zA-Z0-9]{1,15}$/,
      message: '分类名必须是 1-15 位的字母或数字',
      trigger: 'blur'
    }
  ]
}

const emit = defineEmits(['success']) //给父组件的方法,实现更新

const onSubmit = async () => {
  // 先进行全局的校验
  await formRef.value.validate()

  //区分编辑、添加
  const isEdit = formModel.value.id

  if (isEdit) {
    await artEditChannelService(formModel.value)
    ElMessage.success('编辑成功')
  } else {
    await artAddChannelService(formModel.value)
    ElMessage.success('添加成功')
  }
  //关闭弹层、
  dialogVisible.value = false
  //进行子传父
  emit('success')
}

// 组件对外暴露一个方法 open,基于open传来的参数,区分添加还是编辑
// open({})  => 表单无需渲染,说明是添加
// open({ id, cate_name, ... })  => 表单需要渲染,说明是编辑
// open调用后,可以打开弹窗
const open = (row) => {
  dialogVisible.value = true
  // 使用了展开运算符,进行一一赋值
  formModel.value = { ...row } // 添加 → 重置了表单内容,编辑 → 存储了需要回显的数据
}

// 向外暴露方法
defineExpose({
  open
})
</script>

<template>
  <el-dialog
    v-model="dialogVisible"
    :title="formModel.id ? '编辑分类' : '添加分类'"
    width="30%"
  >
    <el-form
      ref="formRef"
      :model="formModel"
      :rules="rules"
      label-width="100px"
      style="padding-right: 30px"
    >
      <el-form-item label="分类名称" prop="cate_name">
        <el-input
          v-model="formModel.cate_name"
          placeholder="请输入分类名称"
        ></el-input>
      </el-form-item>

      <el-form-item label="分类别名" prop="cate_alias">
        <el-input
          v-model="formModel.cate_alias"
          placeholder="请输入分类别名"
        ></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="onSubmit"> 确认 </el-button>
      </span>
    </template>
  </el-dialog>
</template>

④文章的详情管理:

黑马程序员大事件后台程序,javascript,前端,vue.js

src\views\article\ArticleManage.vue

  1. 重点知识: <!--在vue2里面 v-model :value和@input的简写

在vue3里面 (v-model :modelValue)和@update:modelValue的简写

下面是要绑定的复选框,实现里面的选择的更新-->

<script setup>
import { Delete, Edit } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { formatTime } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'

import { artDelService, artGetListService } from '../../api/article'
import ArticleEdit from './components/ArticleEdit.vue'
import channelSelect from './components/ChannelSelect.vue'

// 假数据
// const articleList = ref([
//   {
//     id: 5961,
//     title: '新的文章啊',
//     pub_date: '2022-07-10 14:53:52.604',
//     state: '已发布',
//     cate_name: '体育'
//   },
//   {
//     id: 5962,
//     title: '新的文章啊',
//     pub_date: '2022-07-10 14:54:30.904',
//     state: null,
//     cate_name: '体育'
//   }
// ])

// const onEditArticle = (row) => {
//   console.log(row)
// }

const onDeleteArticle = async (row) => {
  await ElMessageBox.confirm('你确认删除文章吗?', '温馨提示', {
    type: 'warning',
    confirmButtonText: '确认',
    cancelButtonText: '取消'
  })
  await artDelService(row.id)
  ElMessage({ type: 'success', message: '删除成功了' })
  getArticleList()
}

const params = ref({
  pagenum: 1,
  pagesize: 5,
  cate_id: '', // 复选框绑定的默认值
  state: ''
})

const articleList = ref([])
const total = ref(0)
const loading = ref(false)

const getArticleList = async () => {
  loading.value = true
  const res = await artGetListService(params.value)
  articleList.value = res.data.data
  total.value = res.data.total
  console.log(res.data.data)
  loading.value = false //加载数据结束
}

// 开始进行初始化的数据
getArticleList()

// 处理分页的逻辑→每页数量发生改变
const onSizeChange = (size) => {
  // 只有是没页数变化了,那么原本正在访问的当前页面意义不大,数据大概率不再那一页了
  // 重新从第一页渲染即可
  params.value.pagenum = 1
  params.value.pagesize = size
  getArticleList()
}

// 处理到了第几页
const onCurrentChange = (page) => {
  params.value.pagenum = page

  // 基于最新页面,进行渲染数据
  getArticleList()
}

// 搜索逻辑=》按照最新的条件,重新检索,从第一页开始展示
const onSearch = () => {
  params.value.pagenum = 1 // 重置页面
  getArticleList()
}

// 重置逻辑→将筛选的条件清空,重新检索,从第一页开始展示
const onReset = () => {
  params.value.pagenum = 1
  params.value.cate_id = ''
  params.value.state = ''
  getArticleList()
}

// const visibleDrawer = ref(false)

// const onAddArticle = () => {
//   visibleDrawer.value = true
// }

// 封装抽屉
const articleEditRef = ref()

// 编辑新增逻辑
const onAddArticle = () => {
  articleEditRef.value.open({})
}

const onEditArticle = (row) => {
  articleEditRef.value.open(row)
}

// 父组件监听事件,重新渲染
const onSuccess = (type) => {
  if (type === 'add') {
    const lastPage = Math.ceil((total.value + 1) / params.value.pagesize)
    params.value.pagenum = lastPage
  }
  getArticleList()
}
</script>

<template>
  <page-container title="文章管理">
    <template #extra>
      <el-button type="primary" @click="onAddArticle">发布文章</el-button>
    </template>

    <el-form inline>
      <el-form-item label="文章分类:">
        <!--在vue2里面 v-model :value和@input的简写
            在vue3里面 (v-model :modelValue)和@update:modelValue的简写
            下面是要绑定的复选框,实现里面的选择的更新-->
        <channel-select width="100%" v-model="params.cate_id"></channel-select>
      </el-form-item>

      <el-form-item label="发布状态:">
        <el-select v-model="params.state">
          <el-option label="已发布" value="已发布"></el-option>
          <el-option label="草稿" value="草稿"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="onSearch" type="primary">搜索</el-button>
        <el-button @click="onReset">重置</el-button>
      </el-form-item>
    </el-form>

    <!--下面的部分-->
    <el-table :data="articleList" style="width: 100%" v-loading="loading">
      <el-table-column label="文章标题" width="400">
        <!--里面是默认的链接插槽,row是一行的数据-->
        <template #default="{ row }">
          <el-link type="primary" :underline="false">{{ row.title }}</el-link>
        </template>
      </el-table-column>

      <el-table-column label="分类" prop="cate_name"></el-table-column>
      <el-table-column label="发表时间" prop="pub_date">
        <template #default="{ row }">
          {{ formatTime(row.pub_data) }}
        </template>
      </el-table-column>
      <el-table-column label="状态" prop="state"></el-table-column>

      <el-table-column label="操作" width="100">
        <template #default="{ row }">
          <el-button
            :icon="Edit"
            circle
            plain
            type="primary"
            @click="onEditArticle(row)"
          ></el-button>
          <el-button
            :icon="Delete"
            circle
            plain
            type="danger"
            @click="onDeleteArticle(row)"
          ></el-button>
        </template>
      </el-table-column>

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

    <!--分页模块-->
    <el-pagination
      v-model:current-page="params.pagenum"
      v-model:page-size="params.pagesize"
      :page-sizes="[2, 3, 4, 5, 10]"
      layout="jumper, total, sizes, prev, pager, next"
      background
      :total="total"
      @size-change="onSizeChange"
      @current-change="onCurrentChange"
      style="margin-top: 20px; justify-content: flex-end"
    />
  </page-container>

  <!-- 准备抽屉容器
  <el-drawer v-model="visibleDrawer" title="大标题" direction="rtl" size="50%">
    <span>Hi there!</span>
  </el-drawer> -->

  <!-- 弹窗 -->
  <article-edit ref="articleEditRef" @success="onSuccess"></article-edit>
</template>
src\views\article\components\ChannelSelect.vue
  1. 选择复选框:vue3实现数据的双向绑定
<script setup>
import { artGetChannelsService } from '@/api/article'
import { ref } from 'vue'

// 父传子进行接收
defineProps({
  modelValue: {
    type: [Number, String]
  },
  width: {
    type: String
  }
})

// 子传父进行更新
const emit = defineEmits(['update:modelValue'])

const channelList = ref([])

const getChannelList = async () => {
  // 是获取文章的分类 channel
  const res = await artGetChannelsService()
  channelList.value = res.data.data
}

getChannelList()
</script>

<!--开始是没有选中的
    把$event里面选中的值,传给父类,实现复选框的更新→实现了双向的绑定-->
<template>
  <el-select
    :modelValue="modelValue"
    @update:modelValue="emit('update:modelValue', $event)"
    :style="{ width }"
  >
    <el-option
      v-for="channel in channelList"
      :key="channel.id"
      :label="channel.cate_name"
      :value="channel.id"
    ></el-option>
  </el-select>
</template>
src\views\article\components\ArticleEdit.vue

黑马程序员大事件后台程序,javascript,前端,vue.js文章来源地址https://www.toymoban.com/news/detail-801875.html

<script setup>
import { ref } from 'vue'
import ChannelSelect from './channelselect.vue'
import { Plus } from '@element-plus/icons-vue'

import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import {
  artGetDetailService,
  artPublishService,
  artEditService
} from '../../../api/article'
import { ElMessage } from 'element-plus'

import { baseURL } from '@/utils/request'
import axios from 'axios'

const visibleDrawer = ref(false)
const defaultForm = {
  title: '',
  cate_id: '',
  cover_img: '',
  content: '',
  state: ''
}

const formModel = ref({ ...defaultForm })
const formRef = ref()
const editorRef = ref()

// 将网络图片地址转换为File对象
async function imageUrlToFile(url, fileName) {
  try {
    // 第一步:使用axios获取网络图片数据
    const response = await axios.get(url, { responseType: 'arraybuffer' })
    const imageData = response.data

    // 第二步:将图片数据转换为Blob对象
    const blob = new Blob([imageData], {
      type: response.headers['content-type']
    })

    // 第三步:创建一个新的File对象
    const file = new File([blob], fileName, { type: blob.type })

    return file
  } catch (error) {
    console.error('将图片转换为File对象时发生错误:', error)
    throw error
  }
}

const imgUrl = ref('')

const open = async (row) => {
  visibleDrawer.value = true
  if (row.id) {
    console.log('编辑回显')
    const res = await artGetDetailService(row.id)
    formModel.value = res.data.data
    // 图片需要单独处理进行回显
    imgUrl.value = baseURL + formModel.value.cover_img

    // 注意:提交给后台,需要的数据格式,是file对象的格式的
    // 需要将网络图片的地址→转换成为file对象,存储起来,方便提交
    formModel.value.cover_img = await imageUrlToFile(
      imgUrl.value,
      formModel.value.cover_img
    )
  } else {
    console.log('添加功能')
    formModel.value = { ...defaultForm }
    imgUrl.value = ''
    editorRef.value.setHTML('') //进行清空
  }
}

defineExpose({
  open
})

// 图片上传逻辑
// onSelectFile是element提供的函数,改变的钩子,图片上传
const onUploadFile = (uploadFile) => {
  console.log(uploadFile)
  // 预览图片
  imgUrl.value = URL.createObjectURL(uploadFile.raw)
  // 立刻将图片对象转入提交
  formModel.value.cover_img = uploadFile.raw
}

const emit = defineEmits(['success'])

const onPublish = async (state) => {
  // 将已发布还是草稿状态,存入 state
  formModel.value.state = state

  // 转换 formData 数据
  const fd = new FormData()
  for (let key in formModel.value) {
    fd.append(key, formModel.value[key])
  }

  if (formModel.value.id) {
    if (formModel.value.id) {
      await artEditService(fd)
      ElMessage.success('编辑成功')
      visibleDrawer.value = false
      emit('success', 'edit')
    } else {
      // 添加请求
      await artPublishService(fd)
      ElMessage.success('添加成功')
      visibleDrawer.value = false
      emit('success', 'add')
    }
  }
}
</script>

<template>
  <el-drawer
    v-model="visibleDrawer"
    :title="formModel.id ? '编辑文章' : '添加文章'"
    direction="rtl"
    size="50%"
  >
    <!-- 发表文章表单 -->
    <el-form :model="formModel" ref="formRef" label-width="100px">
      <el-form-item label="文章标题" prop="title">
        <el-input v-model="formModel.title" placeholder="请输入标题"></el-input>
      </el-form-item>

      <el-form-item label="文章分类" prop="cate_id">
        <channel-select
          v-model="formModel.cate_id"
          width="100%"
        ></channel-select>
      </el-form-item>

      <!--此处不需要element-plus自动上传,不需要配置action参数
      只需要做前端的本地预览即可,无需提交前上传图片,以免取消,浪费空间
      语法:URL.createObjectURL(...)创建本地预览的地址,来预览-->
      <el-form-item label="文章封面" prop="cover_img">
        <el-upload
          class="avatar-uploader"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="onUploadFile"
        >
          <img v-if="imgUrl" :src="imgUrl" class="avatar" />
          <el-icon v-else class="avatar-uploader-icon">
            <Plus />
          </el-icon>
        </el-upload>
      </el-form-item>

      <el-form-item label="文章内容" prop="content">
        <div class="editor">
          <div class="editor">
            <quill-editor
              ref="editorRef"
              theme="snow"
              v-model:content="formModel.content"
              contentType="html"
            >
            </quill-editor>
          </div>
        </div>
      </el-form-item>

      <el-form-item>
        <el-button @click="onPublish('已发布')" type="primary">发布</el-button>
        <el-button @click="onPublish('草稿')" type="info">草稿</el-button>
      </el-form-item>
    </el-form>
  </el-drawer>
</template>

<style lang="scss" scoped>
.avatar-uploader {
  :deep() {
    .avatar {
      width: 178px;
      height: 178px;
      display: block;
    }

    .el-upload {
      border: 1px dashed var(--el-border-color);
      border-radius: 6px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: var(--el-transition-duration-fast);
    }

    .el-upload:hover {
      border-color: var(--el-color-primary);
    }

    .el-icon.avatar-uploader-icon {
      font-size: 28px;
      color: #8c939d;
      width: 178px;
      height: 178px;
      text-align: center;
    }
  }
}

.editor {
  width: 100%;

  :deep(.ql-editor) {
    min-height: 200px;
  }
}
</style>
src\utils\format.js
import { dayjs } from 'element-plus'

export const formatTime = (time) => dayjs(time).format('YYYY年MM月DD日')

⑤全局的路由:

import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores'

// createRouter 创建路由实例
// 配置 history 模式
// 1. history模式:createWebHistory     地址栏不带 #
// 2. hash模式:   createWebHashHistory 地址栏带 #
// console.log(import.meta.env.DEV)

// vite 中的环境变量 import.meta.env.BASE_URL  就是 vite.config.js 中的 base 配置项
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/login', component: () => import('@/views/login/LoginPage.vue') }, // 登录页
    {
      path: '/',
      component: () => import('@/views/layout/LayoutContainer.vue'),
      redirect: '/article/manage',
      children: [
        {
          path: '/article/manage',
          component: () => import('@/views/article/ArticleManage.vue')
        },
        {
          path: '/article/channel',
          component: () => import('@/views/article/ArticleChannel.vue')
        },
        {
          path: '/user/profile',
          component: () => import('@/views/user/UserProfile.vue')
        },
        {
          path: '/user/avatar',
          component: () => import('@/views/user/UserAvatar.vue')
        },
        {
          path: '/user/password',
          component: () => import('@/views/user/UserPassword.vue')
        }
      ]
    }
  ]
})

// 登录访问拦截 => 默认是直接放行的
// 根据返回值决定,是放行还是拦截
// 返回值:
// 1. undefined / true  直接放行
// 2. false 拦回from的地址页面
// 3. 具体路径 或 路径对象  拦截到对应的地址
//    '/login'   { name: 'login' }
router.beforeEach((to) => {
  // 如果没有token, 且访问的是非登录页,拦截到登录,其他情况正常放行
  const useStore = useUserStore()
  if (!useStore.token && to.path !== '/login') return '/login'
})

export default router

到了这里,关于全栈开发前端代码:黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开,big-event的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • CSS笔记(黑马程序员pink老师前端)定位

    定位可以让盒子自由的在某个盒子内移动位置或者固定在屏幕中某个位置,并且可以压住其他盒子。 定位 = 定位模式 + 边偏移 定位模式 说明 static 静态定位,按标准流特性摆放,没有边偏移,很少用 relative 相对定位,相对自身原有位置移动,原有位置继续占有(不脱标) absolute 绝

    2024年02月09日
    浏览(14)
  • CSS笔记(黑马程序员pink老师前端)浮动,清除浮动

    浮动可以改变标签的默认排列方式。浮动元素常与标准流的父元素搭配使用. 网页布局第一准则 :多个块级元素纵向排列找标准流,多个块级元素横向排列找浮动。 float属性用于创建浮动框,将其移动到一边,直到左边缘或右边缘触及包含块或另一个浮动框的边缘。 1.浮动元素

    2024年02月09日
    浏览(15)
  • 黑马程序员JavaWeb开发|Maven高级

    将项目按照功能拆分成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享。 注意:分模块开发需要先对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分。 继承:描述的是两个工程间的关系,与java中的继承相似,子工程

    2024年01月23日
    浏览(16)
  • 黑马程序员前端 Vue3 小兔鲜电商项目——(八)登录页面

    登录页面的主要功能就是表单校验和登录登出业务。 account password cdshi0080 123456 cdshi0081 123456 cdshi0082 123456 cdshi0083 123456 cdshi0084 123456 cdshi0085 123456 cdshi0086 123456 cdshi0087 123456 cdshi0088 123456 模版代码 在 srcviewsLoginindex.vue 中添加登录页代码: 配置路由跳转 修改 srcviewsLayoutcompon

    2024年02月10日
    浏览(16)
  • 黑马程序员前端 Vue3 小兔鲜电商项目——(七)详情页

    模板代码 创建 srcviewsDetailindex.vue 文件,添加以下代码: 配置路由 在 srcrouterindex.js 中添加对应路由【 /detail/{goodId} 】: 链接跳转 对 srcviewsHomecomponentsHomeNew.vue 文件及其他涉及商品信息的页面修改路由跳转: 封装接口 在 srcapisdetail.js 文件中封装接口用于获取商品信息

    2024年02月10日
    浏览(20)
  • 低代码开发会是前端程序员的下一个春天吗?

    最近前端的大环境不太行,之前身处在前端的自己薪资也越来越无望了,隐隐约约感觉前端做不下去了,2024前端找不到工作要转行吗? 但是别担心啊老铁们,前端技术精微渊深,除了基础的 HTML、CSS 和 JavaScript 技术外,还涉及前端框架、UI 库、自动化构建工具、代码管理工

    2024年02月19日
    浏览(11)
  • 黑马程序员前端 Vue3 小兔鲜电商项目——(二)初始化项目

    Vue3是Vue.js最新的主要版本,它已经于2020年9月18日发布。它提供了许多新功能和性能改进,这些改进使得Vue更易于使用和更具可扩展性。 以下是Vue3的一些主要特性: 更快的渲染:Vue3使用重写的响应式系统,它使用Proxy对象来解决Vue2中的性能瓶颈问题。这使得Vue3的渲染速度比

    2024年02月11日
    浏览(13)
  • 黑马程序员前端 Vue3 小兔鲜电商项目——(一)初始化项目

    Vue3是Vue.js最新的主要版本,它已经于2020年9月18日发布。它提供了许多新功能和性能改进,这些改进使得Vue更易于使用和更具可扩展性。 以下是Vue3的一些主要特性: 更快的渲染:Vue3使用重写的响应式系统,它使用Proxy对象来解决Vue2中的性能瓶颈问题。这使得Vue3的渲染速度比

    2024年02月15日
    浏览(17)
  • 《黑马程序员2023新版黑马程序员大数据入门到实战教程,大数据开发必会的Hadoop、Hive,云平台实战项目》学习笔记总目录

    本文是对《黑马程序员新版大数据入门到实战教程》所有知识点的笔记进行总结分类。 学习视频:黑马程序员新版大数据 学习时总结的学习笔记以及思维导图会在后续更新,请敬请期待。 前言:配置三台虚拟机,为集群做准备(该篇章请到原视频进行观看,不在文章内详细

    2024年02月03日
    浏览(17)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包