使用SpringBoot+Vue3开发项目(2)---- 设计文章分类的相关接口及页面

news2024/9/24 3:25:10

目录

 一.所用技术栈:

二.后端开发:

1.文章分类列表渲染:

 2.新增文章分类:

 3.编辑文章分类:

4.删除文章分类 :

5.完整三层架构后端代码:

(1)Controller层:

 (2)Service层:

(3)Mapper层:

三.前端开发:

1.路由的使用:

(1)Vue Router的使用:

(2)使用vue-router来实现登录页面与主页面展示效果:

 ①创建 index.js :

②在 main.js 导入创建的路由器:

(3)子路由的使用:

①添加五个组件

②配置子路由:

③在主页面的展示区声明router-view标签:

④给菜单项设置index属性,设置点击后的路由地址:

2.对于文章分类的前端开发:

(1)导入ArticleCategory.vue文件:

(2)文章分类的前端相关请求开发:

①Pinia状态管理库:

 1.在main.js文件导入pinia:

2.定义store状态:

②文章分类列表渲染:

③添加请求拦截器:

④使用拦截器来对未登录统一处理:

(3)新增文章分类:

(4)编辑文章分类与删除:


引言:上篇文章实现了用户的注册以及登录的接口及页面的开发,我们本篇博客讲述设计文章分类的相关接口及页面 =>

 一.所用技术栈:

  1. Vue3
  2. pinia
  3. axios
  4. element-plus
  5. router(路由)
  6. SpringBoot
  7. JWT

二.后端开发:

我们整个布局是下面的样子:

需要我们开发四个接口:

  • 文章分类列表渲染
  • 新增文章分类
  • 编辑文章分类
  • 删除文章分类 

因为这四个接口是基本的增删改查操作,所以这里不过多强调代码如何写,这里介绍一下参数校验的注解:

①在pojo类内属性上加的注解:

  1.  @NotNull:值不能为空
  2. @NotEmpty:值不能为空并且不能为空串
  3. @Email:格式是email邮箱格式
  4. @Patten(regexp="正则表达式"):判断格式

上面这四个需要在Controller层内参数前加上@Validated注解才能使用。

  1. @JsonIgnore:返回响应数据会忽略该属性(eg:可以在密码上加来加密)
  2. @JsonFormat(patten="yyyy-MM-dd HH:mm:ss"):时间格式

上面这两个注解使用时可不用加@Validated注解 。

②在Controller层内参数前加的注解:

  1. @URL:参数需要是地址格式

 而基于上面还是有些不够便捷,我们提出了分组校验的概念,在@NotNull@NotEmpty这两个注解前可加上(groups="接口的字节码")来限定哪些方法会使用,随后我们就再Controller层的参数前注解@Validated(接口的字节码),这样就可以实现分组校验。

如果我们同一个校验项属于多个分组,这样会很麻烦,这个时候我们就可以借助默认分组来处理   =>

若某个校验项没有指定分组,默认属于Default分组,分组之间可以继承,A extends B,那么A中可以拥有B中所有的校验项,所以在@Validated()括号内写继承Default的接口字节码就可以使用默认的校验项

@Data
public class Category {
//    @NotEmpty(groups = {Add.class,Update.class})
    @NotEmpty
    private String categoryName;//分类名称
//    @NotEmpty(groups = {Add.class,Update.class})
    @NotEmpty
    private String categoryAlias;//分类别名

    //若某个校验项没有指定分组,默认属于Default分组
    //分组之间可以继承,A extends B,那么A中可以拥有B中所有的校验项

    public interface Add extends Default {}
    public interface Update extends Default {}
}

基于上面的注解功能,我们在pojo包下的Category类属性前加上完整注解代码:

@Data
public class Category {
    @NotNull(groups = Update.class)
    private Integer id;//主键ID

//    @NotEmpty(groups = {Add.class,Update.class})
    @NotEmpty
    private String categoryName;//分类名称
//    @NotEmpty(groups = {Add.class,Update.class})
    @NotEmpty
    private String categoryAlias;//分类别名

    private Integer createUser;//创建人ID

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;//创建时间
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;//更新时间

    //若某个校验项没有指定分组,默认属于Default分组
    //分组之间可以继承,A extends B,那么A中可以拥有B中所有的校验项

    public interface Add extends Default {}
    public interface Update extends Default {}
}

介绍完这个注解开发,我们就可以开发后端接口。 

1.文章分类列表渲染:

 根据文档要求,我们需要返回一个列表,每个元素是一个Category类封装的数据。 

 2.新增文章分类:

这里新增数据就可以使用@Validated注解来判断数据

根据文档要求,我们只需根据用户 id 保存两个数据值,为了在我们JWT令牌中拿到用户id,我们就会使用ThreadLocal来开辟线程空间存储JWT令牌,如果想使用就可以在线程空间获取JWT令牌中的用户id。

 如果不了解ThreadLocal请查看下面博客:使用ThreadLocal来存取单线程内的数据-CSDN博客

Map<String,Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get("id");
category.setCreateUser(userId);

 3.编辑文章分类:

 这里更新数据就可以使用@Validated注解来判断数据。

4.删除文章分类 :

删除就没什么好说的了,这里就不过多强调。

5.完整三层架构后端代码:

(1)Controller层:

@RestController
@RequestMapping("/category")
public class CateController {

    @Autowired
    private CategoryService categoryService;

    @PostMapping
    public Result add(@RequestBody @Validated(Category.Add.class) Category category){
        categoryService.add(category);
        return Result.success();
    }

    @GetMapping
    public Result<List<Category>> list(){
        List<Category> list = categoryService.list();
        return Result.success(list);
    }

    @PutMapping
    public Result update(@RequestBody @Validated(Category.Update.class) Category category){
        categoryService.update(category);
        return Result.success();
    }

    @DeleteMapping
    public Result delete(Integer id){
        categoryService.deleteById(id);
        return Result.success();
    }
}

 (2)Service层:

@Service
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    public void add(Category category) {
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());

        Map<String,Object> map = ThreadLocalUtil.get();
        Integer userId = (Integer) map.get("id");
        category.setCreateUser(userId);
        categoryMapper.add(category);
    }

    @Override
    public List<Category> list() {
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer userId = (Integer) map.get("id");
        return categoryMapper.list(userId);

    }

    @Override
    public Category findById(Integer id) {
        return categoryMapper.findById(id);
    }

    @Override
    public void update(Category category) {
        category.setUpdateTime(LocalDateTime.now());
        categoryMapper.update(category);
    }

    @Override
    public void deleteById(Integer id) {
        categoryMapper.deleteById(id);
    }
}

(3)Mapper层:

@Mapper
public interface CategoryMapper {

    @Insert("insert into category(category_name,category_alias,create_user,create_time,update_time)" +
            " values (#{categoryName},#{categoryAlias},#{createUser},#{createTime},#{updateTime})")
    void add(Category category);

    @Select("select * from category where create_user = #{userId}")
    List<Category> list(Integer userId);

    @Select("select * from category where id = #{id}")
    Category findById(Integer id);

    @Update("update category set category_name = #{categoryName} , category_alias = #{categoryAlias} , update_time = #{updateTime} where id = #{id}")
    void update(Category category);

    @Delete("delete from category where id = #{id}")
    void deleteById(Integer id);
}

三.前端开发:

1.路由的使用:

路由指的是根据不同的访问路径,展示不同组件的内容。

Vue Router 是 Vue.js 的官方路由。

(1)Vue Router的使用:

  1. 安装vue-router: cnpm install vue-router@4
  2. 在src/router/index.js中创建路由器,并导出
  3. 在vue应用实例中使用vue-router
  4. 声明router-view标签,展示组件内容

(2)使用vue-router来实现登录页面与主页面展示效果:

 ①创建 index.js :
//导入vue-router
import { createRouter , createWebHistory } from "vue-router";

//导入vue组件
import LoginVue from '@/views/Login.vue';
import LayoutVue from "@/views/Layout.vue";

//定义路由关系
const routes = [
    {path:'/login',component: LoginVue},
    {
        path:'/',  //设置访问路径
        component: LayoutVue,  //设置访问路径对应的访问组件
    }
]

//创建路由器
const router = createRouter({
    history: createWebHistory(), //路由模式
    routes: routes  //路由关系
})

//导出路由器
export default router;
②在 main.js 导入创建的路由器:
import './assets/main.scss'

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
//导入创建的路由器
//index.js可以省略不写,会默认导入该文件
import router from '@/router'

const app = createApp(App);
//将router传递至App.vue
app.use(router)
app.use(ElementPlus);
app.mount('#app')

③在App.vue声明标签:

<script setup>

</script>

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

<style>

</style>

这样就可以实现在同一页面显示不同组件。但是这样如果我们登录成功后,不会直接跳转主页面,那么这个时候我们需要通过路由来完成跳转主页面。

//导入路由器
import { useRouter } from 'vue-router';
const router = useRouter();
//通过路由跳转首页
router.push('跳转路径');

所以在login.vue文件内进行操作:

//导入路由器
import { useRouter } from 'vue-router';
const router = useRouter();
//表单数据校验
const login = async()=>{
    let result = await userLoginService(registerData.value);
    ElMessage.success(result.msg ? result.msg : '登录成功');
    //通过路由跳转首页
    router.push('/');
}

(3)子路由的使用:

为了在我们主页面下展示区点击按钮展示不同的子组件,我们就引入了子路由的知识:

上图是我们的每一级路由关系,我们想将五个vue文件在主页面Layout.vue中的展示区展示,就需要配置子路由 =>

①添加五个组件
②配置子路由:

在内部先定义路由关系,然后创建路由器,最后导出(暴露)路由器。

在路由关系内主页面内部设置 children 属性来声明五个子路由,并且为了不让主页面'/'为空,我们使用 redirect 来将 '/' 地址重定向为 '/article/manage' =>

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

//导入vue组件
import LoginVue from '@/views/Login.vue';
import LayoutVue from "@/views/Layout.vue";
import ArticleCategoryVue from '@/views/article/ArticleCategory.vue'
import ArticleManageVue from '@/views/article/ArticleManage.vue'
import UserAvatarVue from '@/views/user/UserAvatar.vue'
import UserInfoVue from '@/views/user/UserInfo.vue'
import UserResetPasswordVue from '@/views/user/UserResetPassword.vue'

//定义路由关系
const routes = [
    {path:'/login',component: LoginVue},
    {
        path:'/',
        component: LayoutVue,
        redirect: '/article/manage',  //重定向
        //子路由
        children:[
            {path:'/article/category',component: ArticleCategoryVue},
            {path:'/article/manage',component: ArticleManageVue},
            {path:'/user/avatar',component: UserAvatarVue},
            {path:'/user/info',component: UserInfoVue},
            {path:'/user/resetPassword',component: UserResetPasswordVue},
        ]
    }
]

//创建路由器
const router = createRouter({
    history: createWebHistory(), //路由模式
    routes: routes  //路由关系
})

//导出路由器
export default router;
③在主页面的展示区声明router-view标签:
            <!-- 中间区域 -->
            <el-main>
                <!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">
                    内容展示区
                </div> -->
                <!-- 路由 -->
                <router-view></router-view>
            </el-main>
④给菜单项设置index属性,设置点击后的路由地址:

这样设置后,当我们点击文字时候就可以自动在我们设置的标签router-view展示区来展示index='地址'中的地址。

<!-- 左侧菜单 -->
<el-aside width="200px">
    <div class="el-aside__logo"></div>
    <el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"
        router>
        <el-menu-item index="/article/category">
            <el-icon>
                <Management />
            </el-icon>
            <span>文章分类</span>
        </el-menu-item>
        <el-menu-item index="/article/manage">
            <el-icon>
                <Promotion />
            </el-icon>
            <span>文章管理</span>
        </el-menu-item>
        <el-sub-menu >
            <template #title>
                <el-icon>
                    <UserFilled />
                </el-icon>
                <span>个人中心</span>
            </template>
            <el-menu-item index="/user/info">
                <el-icon>
                    <User />
                </el-icon>
                <span>基本资料</span>
            </el-menu-item>
            <el-menu-item index="/user/avatar">
                <el-icon>
                    <Crop />
                </el-icon>
                <span>更换头像</span>
            </el-menu-item>
            <el-menu-item index="/user/resetPassword">
                <el-icon>
                    <EditPen />
                </el-icon>
                <span>重置密码</span>
            </el-menu-item>
        </el-sub-menu>
    </el-menu>
</el-aside>

所以这样我们就分别对五个vue组件开发就可以了。

2.对于文章分类的前端开发:

(1)导入ArticleCategory.vue文件:

<script setup>
import {
    Edit,
    Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = ref([
    {
        "id": 3,
        "categoryName": "美食",
        "categoryAlias": "my",
        "createTime": "2023-09-02 12:06:59",
        "updateTime": "2023-09-02 12:06:59"
    },
    {
        "id": 4,
        "categoryName": "娱乐",
        "categoryAlias": "yl",
        "createTime": "2023-09-02 12:08:16",
        "updateTime": "2023-09-02 12:08:16"
    },
    {
        "id": 5,
        "categoryName": "军事",
        "categoryAlias": "js",
        "createTime": "2023-09-02 12:08:33",
        "updateTime": "2023-09-02 12:08:33"
    }
])
</script>
<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章分类</span>
                <div class="extra">
                    <el-button type="primary">添加分类</el-button>
                </div>
            </div>
        </template>
        <el-table :data="categorys" style="width: 100%">
            <el-table-column label="序号" width="100" type="index"> </el-table-column>
            <el-table-column label="分类名称" prop="categoryName"></el-table-column>
            <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
            <el-table-column label="操作" width="100">
                <template #default="{ row }">
                    <el-button :icon="Edit" circle plain type="primary" ></el-button>
                    <el-button :icon="Delete" circle plain type="danger"></el-button>
                </template>
            </el-table-column>
            <template #empty>
                <el-empty description="没有数据" />
            </template>
        </el-table>
    </el-card>
</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>

我们通过定义响应式数据categorys来动态响应文章分类数据。

设置 :data="categorys" 将categorys数据绑定 table 表格,并 prop 属性来分别把属性绑定到  column 上。

(2)文章分类的前端相关请求开发:

  • 文章分类列表渲染
  • 新增文章分类
  • 编辑文章分类
  • 删除文章分类 

我们根据上面的四个内容开发 =>

一般我们都会再 js 文件内定义函数然后再vue组件进行使用请求函数。 

①Pinia状态管理库:

在article.js文件定义请求函数:

 为了传递JWT令牌,我们就会利用Pinia状态管理库,它允许跨组件或页面共享状态。

使用Pinia步骤:

  1. 安装pinia:cnpm install pinia
  2. 在vue应用实例中使用pinia
  3. 在src/stores/token.js中定义store
  4. 在组件中使用store 
 1.在main.js文件导入pinia:

这里因为Pinia是默认内存存储,刷新浏览器会丢失数据,我们使用pinia内的Persist插件就可以将Pinia中的数据持久化存储。

为了使用persist,我们需要安装persist:cnpm install pinia-persistedstate-plugin,然后再pinia中使用persist,并且需要再main.js导入一下。

import './assets/main.scss'

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from '@/router'
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-persistedstate-plugin'

const app = createApp(App);
//创建createPinia()函数实例
const pinia = createPinia();
const persist = createPersistedState();
pinia.use(persist)
app.use(pinia)
app.use(router)
app.use(ElementPlus);
app.mount('#app')
2.定义store状态:

其实无外乎就是使用defineStore()方法,在方法内部分为两个参数来写 :

第一个参数:名字,确保唯一性。
第二个参数:函数,其内部可以定义状态的所有内容,其内部先创建一个响应式数据,然后设置获取数据以及删除数据的方法,最后返回数据以及方法。

加上Persist插件就可以将Pinia中的数据持久化存储。

//定义store
import { defineStore } from "pinia";
import {ref} from 'vue'
/*
  第一个参数:名字,确保唯一性
  第二个参数:函数,其内部可以定义状态的所有内容
  返回值:函数
*/
export const useTokenStore = defineStore('token',()=>{
    //定义状态内容

    //1.定义响应式变量
    const token = ref('');

    //2.定义函数来修改token值
    const setToken = (newToken)=>{
        token.value = newToken;
    }

    //3.定义函数来移除token值
    const removeToken = ()=>{
        token.value = '';
    }

    return {
        token,setToken,removeToken
    }
},{
    persist: true  //因为Pinia是默认内存存储,刷新浏览器会丢失数据,使用Persist插件就可以将Pinia中的数据持久化存储
}
);

这样我们就可以调用定义的useTokenStore来使用pinia了。

②文章分类列表渲染:

我们首先再Login.vue文件中把得到的token存储到pinia中:

//导入store状态
import { useTokenStore } from '@/stores/token.js';
//导入路由器
import { useRouter } from 'vue-router';
const tokenstore = useTokenStore();
const router = useRouter();
//表单数据校验
const login = async()=>{
    let result = await userLoginService(registerData.value);
    // alert(result.msg ? result.msg : '登录成功'); 
    ElMessage.success(result.msg ? result.msg : '登录成功');
    //将得到的token存储到pinia中
    tokenstore.setToken(result.data);
    //通过路由跳转首页
    router.push('/');
}

然后再article.js中定义请求函数:

import request from '@/utils/request.js'
import { useTokenStore } from '@/stores/token.js';

export const ArticleCategoryListService = ()=>{
    const tokenStore = useTokenStore();
    //在pinia中定义的响应式数据不需要加.value才能使用数据
    return request.get('/category',{headers:{'Authorization':tokenStore.token}});
} 

但是这样我们需要将剩下的请求函数都要传递JWT令牌,代码会很繁琐,这个时候我们就可以添加请求拦截器来使用回调函数来发送。

③添加请求拦截器:

在request.js文件中添加请求拦截器:

import { useTokenStore } from '@/stores/token.js';
//添加请求拦截器
instance.interceptors.request.use(
    (config)=>{
        //请求前的回调
        const tokenStore = useTokenStore();
        if(tokenStore.token){
            //通过config调用headers获取请求头,在调用Authorization将JWT令牌存放到内部以此来添加统一的请求头
            config.headers.Authorization = tokenStore.token;
        }
        return config;
    },
    (err)=>{
        //请求错误的回调
        Promise.reject(err);//异步的状态转化成失败的状态
    }
)
export default instance;

修改article.js文件的请求函数:

import request from '@/utils/request.js'

export const ArticleCategoryListService = ()=>{
    return request.get('/category');
} 
④使用拦截器来对未登录统一处理:

为了将没有登录的用户直接打开主界面,随后浏览器直接能跳转到登录页面,我们就可以添加响应拦截器来对未登录统一处理:

在request.js文件中必须通过下面的方式才能导入router进行使用 =>
import router from '@/router';

// import { useRouter } from 'vue-router';
// const router = useRouter();
//在request.js文件中必须通过下面的方式才能导入router进行使用
import router from '@/router'
//添加响应拦截器
instance.interceptors.response.use(
    result=>{
        //判断业务状态码
        if(result.data.code === 0){
            return result.data;
        }
        //操作失败
        // alert(result.data.message ? result.data.message : '服务异常')
        ElMessage.error(result.data.message ? result.data.message : '服务异常')
        //异步操作的状态转换为失败
        return Promise.reject(result.data);
    },
    err=>{
        //判断响应状态码401
        if(err.response.status === 401){
            ElMessage.error('请先登录');
            //使用路由跳转登录页面
            router.push('/login');
        }else{
            ElMessage.error('服务异常');
        }
        return Promise.reject(err);//异步的状态转化成失败的状态
    }
)
export default instance;

(3)新增文章分类:

我们为了实现上面操作,在Article.vue添加组件:

<!-- 添加分类弹窗 -->
<el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
    <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
        <el-form-item label="分类名称" prop="categoryName">
            <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
        </el-form-item>
        <el-form-item label="分类别名" prop="categoryAlias">
            <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
        </el-form-item>
    </el-form>
    <template #footer>
        <span class="dialog-footer">
            <el-button @click="dialogVisible = false">取消</el-button>
            <el-button type="primary"> 确认 </el-button>
        </span>
    </template>
</el-dialog>

随后我们通过设置响应式数据来控制添加分类弹窗 =>

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

随后调用点击方法:

//添加分类数据模型
const categoryModel = ref({
    categoryName: '',
    categoryAlias: ''
})
//添加分类表单校验
const rules = {
    categoryName: [
        { required: true, message: '请输入分类名称', trigger: 'blur' },
    ],
    categoryAlias: [
        { required: true, message: '请输入分类别名', trigger: 'blur' },
    ]
}
//添加表单
import { ElMessage } from 'element-plus';
const addCategory = async ()=>{
    //调用接口
    let result = await ArticleCategoryAddService(categoryModel.value);
    ElMessage.success(result.message ? result.message : '添加成功');

    //获取文章分类的函数
    articleCategoryList();
    //让添加分类弹窗消失
    dialogVisible = false;
    //添加后值消失
    categoryModel = ref({
        categoryName: '',
        categoryAlias: ''
    })
}

 在article.js中:

import request from '@/utils/request.js'

export const ArticleCategoryAddService = (categoryData)=>{
    return request.post('/category',categoryData);
}

(4)编辑文章分类与删除:

在组件内:

我们复用添加的弹框给编辑弹框,在下面点击事件使用三目运算符进行处理。 

<script setup>
import {
    Edit,
    Delete
} from '@element-plus/icons-vue'
import { ref } from 'vue'
const categorys = ref([]);
//声明异步函数
import { ArticleCategoryListService,ArticleCategoryAddService,ArticleCategoryUpdateService,ArticleCategoryDeleteService } from '@/api/article.js';
const articleCategoryList = async ()=>{
    let result = await ArticleCategoryListService();
    categorys.value = result.data;
}
articleCategoryList();
//控制添加分类弹窗
const dialogVisible = ref(false)

//添加分类数据模型
const categoryModel = ref({
    categoryName: '',
    categoryAlias: ''
})
//添加分类表单校验
const rules = {
    categoryName: [
        { required: true, message: '请输入分类名称', trigger: 'blur' },
    ],
    categoryAlias: [
        { required: true, message: '请输入分类别名', trigger: 'blur' },
    ]
}
//添加表单
import { ElMessage } from 'element-plus';
const addCategory = async ()=>{
    //调用接口
    let result = await ArticleCategoryAddService(categoryModel.value);
    ElMessage.success(result.message ? result.message : '添加成功');

    //获取文章分类的函数
    articleCategoryList();
    //让添加分类弹窗消失
    dialogVisible = false;
    //添加后值消失
    categoryModel = ref({
        categoryName: '',
        categoryAlias: ''
    })
}
//定义变量来控制标题
const title = ref('');
//展示编辑弹窗
const showDialog = (row)=>{
    dialogVisible.value = true;
    title.value ='编辑分类';
    //数据拷贝
    categoryModel.value.categoryName = row.categoryName;
    categoryModel.value.categoryAlias = row.categoryAlias;
    //扩展id属性,将来需要传递给后台完成分类的修改
    categoryModel.value.id = row.id;
}
//编辑分类
const updateCategory = ()=>{
    let result  = ArticleCategoryUpdateService(categoryModel.value);
    ElMessage.success(result.message ? result.message : '修改成功');
    articleCategoryList();
    dialogVisible.value = false;
}
//清空
const clearData = ()=>{
    categoryModel.value.categoryName = '';
    categoryModel.value.categoryAlias = '';
}
//删除分类
import { ElMessageBox } from 'element-plus'
const deleteCategory = (row)=>{
    //提示用户
    ElMessageBox.confirm(
    '确认要删除该分类信息吗?',
    '温馨提示',
    {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning',
      center: true,
    }
  )
    .then(async () => {
      let result = await ArticleCategoryDeleteService(row.id); 
      ElMessage({
        type: 'success',
        message: '删除成功',
      })
      articleCategoryList();
    })
    .catch(() => {
      ElMessage({
        type: 'info',
        message: '取消删除',
      })
    })
}
</script>
<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章分类</span>
                <div class="extra">
                    <el-button type="primary" @click="dialogVisible = true;title = '添加分类';clearData()">添加分类</el-button>
                </div>
            </div>
        </template>
        <el-table :data="categorys" style="width: 100%">
            <el-table-column label="序号" width="100" type="index"> </el-table-column>
            <el-table-column label="分类名称" prop="categoryName"></el-table-column>
            <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
            <el-table-column label="操作" width="100">
                <template #default="{ row }">
                    <el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)" ></el-button>
                    <el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button>
                </template>
            </el-table-column>
            <template #empty>
                <el-empty description="没有数据" />
            </template>
        </el-table>
        <!-- 添加分类弹窗 -->
        <el-dialog v-model="dialogVisible" :title="title" width="30%">
            <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
                <el-form-item label="分类名称" prop="categoryName">
                    <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
                </el-form-item>
                <el-form-item label="分类别名" prop="categoryAlias">
                    <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
                </el-form-item>
            </el-form>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="title ==='添加分类' ? addCategory() : updateCategory()"> 确认 </el-button>
                </span>
            </template>
        </el-dialog>
    </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>

在article.js完整代码:

import request from '@/utils/request.js'
// import { useTokenStore } from '@/stores/token.js';

export const ArticleCategoryListService = ()=>{
    //在请求拦截器添加了JWT令牌到请求头中
    return request.get('/category');
} 

export const ArticleCategoryAddService = (categoryData)=>{
    return request.post('/category',categoryData);
}

//文章分类编辑
export const ArticleCategoryUpdateService = (categoryData)=>{
    return request.put('/category',categoryData);
}

//文章分类删除
export const ArticleCategoryDeleteService = (id)=>{
    return request.delete('/category?id='+id)
}

好了,今天的内容就到这里了,下期继续更新大事件项目前后端开发,感谢收看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1981970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习大数据DAY31 Python基础语法4和基于Python中的MySQL 编程

目录 Python 库 模块 time&datetime 库 连接 MySQL 操作 结构操作 数据增删改操作 数据查询操作 上机练习 7 面向对象 OOP 封装 继承 三层架构---面向对象思想模型层 数据层 业务逻辑显示层 上机练习 8 三层架构开发豆瓣网 关于我对 AI 写代码的看法&#xf…

大模型技术在企业应用中的实践与优化

【导读】大模型技术更新层出不穷&#xff0c;但对于众多企业及开发者而言&#xff0c;更为关键的命题则是如何进行应用落地&#xff0c;实现真正的智能化转型。本文系统且深入地探讨了大模型在企业应用中的关键环节和技术要点。从构建高质量的专属数据集、选择适宜的微调策略&a…

天线增益测试方法之射频器件S参数测试软件

天线增益的精确测量对于优化无线信号传输至关重要。NSAT-1000射频器件S参数测试软件作为针对S参数的测试设备&#xff0c;大幅提高了测试精度和效率。本文将为大家介绍该软件在天线增益测试方面的具体操作流程。 一、准备工作 在测试天线增益之前&#xff0c;需要准备好测试软件…

【启明智显分享】Model3A 7寸TFT触摸彩屏智能电压力锅解决方案

随着智能家居市场的快速发展&#xff0c;电压力锅作为厨房电器的代表之一&#xff0c;正逐步向智能化、高端化转型。为了进一步提升用户体验&#xff0c;增强产品竞争力&#xff0c;我们推出基于Model3A 7寸触摸彩屏电压力锅解决方案。该方案旨在通过Model3A芯片的强大性能与7寸…

24/8/5算法笔记 BGD,SGD,MGD梯度下降

今日对比不同梯度下降的代码 1.BGD大批量梯度下降(一元一次&#xff09; 首先导入库 import numpy as npimport matplotlib.pyplot as plt 随机生成线性回归函数 Xnp.random.rand(100,1)w,bnp.random.randint(1,10,size2)#增加噪声&#xff0c;更像真实数据 #numoy广播机制…

mysql的安装与基本操作

1、centos7 中安装 mysql 8.x&#xff08;1&#xff09;下载安装包 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar&#xff08;2&#xff09;解压 tar -xf mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar&#xff08;3&…

PXE实验-使用kickstart批量自动部署操作系统

实验准备&#xff1a;rhel7.9具备图形界面的虚拟机&#xff0c;虚拟机网络配置可用&#xff0c;VMware 中NAT的DHCP功能关闭&#xff0c;虚拟机中yum源已配置好 1.在虚拟机中安装kickstart并且启动图形制作工具 yum install system-config-kickstart.noarch -y system-config…

【第13章】Spring Cloud之Gateway全局异常处理

文章目录 前言一、异常处理1. 响应实体类2. 异常处理类 二、单元测试1. 无可用路由2. 服务不可用 总结 前言 网关作为我们对外服务的入口起着至关重要的作用&#xff0c;我们必须保证网关服务的稳定性&#xff0c;下面来为网关服务增加异常处理机制。 一、异常处理 1. 响应实…

动态规划.

目录 &#xff08;一&#xff09;递归到动规的一般转化方法 &#xff08;二&#xff09;动规解题的一般思路 1. 将原问题分解为子问题 2. 确定状态 3. 确定一些初始状态&#xff08;边界状态&#xff09;的值 4. 确定状态转移方程 &#xff08;三&#xff09;能用动规解…

小程序 发布流程

1&#xff1a; 点击HbuilderX 菜单栏上的 发行> 小程序-微信&#xff08;适用于uni-app&#xff09; 2: 第二步&#xff1a; 需要再弹出框中填写发布系小程序的名称和AppId 之后&#xff0c; 点击发行按钮。 3&#xff1a;在Hbuilder 的控制台中 查看小程序发布编译的进度。…

VMware17下载与安装

1.下载 通过百度网盘分享的文件&#xff1a;VMware17 链接&#xff1a;https://pan.baidu.com/s/1gCine3d3Rp_l3NYAu5-ojg 提取码&#xff1a;ek25 --来自百度网盘超级会员V3的分享 2.安装

k8s(六)---pod

六、pod&#xff08;k8s中最小的调度单元&#xff09; pod中可以有一个或多个容器 1、官网 2、简介 Pod是k8s中最小的调度单元、Pod具有命名空间隔离性 3、如何创建一个Pod资源&#xff08;主要两种方式&#xff09; 1&#xff09;kubctl run ①kubectl run nginx–imagereg…

k8s(七)---标签

一、标签&#xff08;适用于资源定位&#xff09; label是一对key和value,创建标签后&#xff0c;方便对资源进行分组管理。 1.帮助 kubectl label --help 2.打标签 pod 针对于pod打标签 key是env&#xff0c;value是test kubectl label po nginx envtest 给pod打标签 3.查看 k…

Qcustomplot绘制实时动态曲线??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

uviewPlus 组件库的使用

文章目录 1、 1、 全局引入样式文件 &#xff08;该语句是文档中提及但是不存在的语句&#xff09;

mysql的安装配置与基础用户使用

第五周 周一 早 mysql安装配置 1.官网下载或者wget [rootmysql ~]# ls anaconda-ks.cfg initserver.sh mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar mysql-community-client-8.0.33-1.el7.x86_64.rpm mysql-community-client-plugins-8.0.33-1.el7.x86_64.rpm mysql-c…

Dockerfile 容器镜像制作 私有仓库

Dockerfile 概述 制作镜像 FROM CMD # ENTRYPOINT 与 CMD 执行方式为 ${ENTRYPOINT} ${-${CMD}} apache 镜像 nginx 镜像 php-fpm 镜像 docker 私有仓库

单位工作邮箱如何实现快速开通

单位工作邮箱如何实现快速开通&#xff1f;单位工作邮箱快速开通需分析需求、选合适服务商、备材料、注册验证配置MX记录、创账户。开通前需测试邮件收发、功能及安全&#xff0c;确保稳定运行。本文将详细介绍单位工作邮箱的前期准备以及快速开通的流程。 一、需求分析与规划…

有了谷歌账号在登录游戏或者新APP、新设备时,要求在手机上点击通知和数字,怎么办?

有的朋友可能遇到过&#xff0c;自己注册或购买了谷歌账号以后&#xff0c;在自己的手机上可以正常登录&#xff0c;也完成了相关的设置&#xff0c;看起来一切都很完美&#xff0c;可以愉快地玩耍了。 但是&#xff0c;随后要登录一个游戏的时候&#xff08;或者登录一个新的…

[Web安全架构] HTTP协议

文章目录 前言1. HTTP1 . 1 协议特点1 . 2 URL1 . 3 Request请求报文1 . 3 .1 请求行1 . 3 .2 请求头1 . 3 .3 请求正文1 . 3 .4 常见传参方式 1 . 4 Response响应报文1 . 4 .1 响应行1 . 4 .2 响应头1 . 4 .3 响应正文 2. Web会话2 .1 Cookie2 .2 Session2 .3 固定会话攻击 前…