Compare commits
2 Commits
44f817eced
...
3dbfff5f41
| Author | SHA1 | Date | |
|---|---|---|---|
| 3dbfff5f41 | |||
| f04385a306 |
@@ -0,0 +1,10 @@
|
|||||||
|
package com.corewing.app.dto.biz.tutorial;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BatchTutorialDeleteRequest {
|
||||||
|
private List<Long> ids;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.corewing.app.dto.biz.tutorial;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CategoryBatchDeleteRequest {
|
||||||
|
private List<Long> ids;
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.corewing.app.entity;
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
@@ -62,12 +60,20 @@ public class Tutorial implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
private Date updateTime;
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教程分类id
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Long categoryId;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.corewing.app.entity;
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,11 +64,16 @@ public class TutorialCategory implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
private Date createTime;
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新时间
|
* 更新时间
|
||||||
*/
|
*/
|
||||||
private Date updateTime;
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
public static final String typeCategory = "category";
|
||||||
|
public static final String typeTag = "tag";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package com.corewing.app.modules.admin.biz;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.biz.tutorial.CategoryBatchDeleteRequest;
|
||||||
|
import com.corewing.app.entity.Tutorial;
|
||||||
|
import com.corewing.app.entity.TutorialCategory;
|
||||||
|
import com.corewing.app.service.TutorialCategoryService;
|
||||||
|
import com.corewing.app.service.TutorialService;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教程指南
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/biz/tutorial")
|
||||||
|
public class BizTutorialController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TutorialCategoryService tutorialCategoryService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TutorialService tutorialService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教程分页
|
||||||
|
* @param tutorial
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/page")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<Page<Tutorial>> page(Tutorial tutorial) {
|
||||||
|
return Result.success(tutorialService.page(tutorial));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增教程
|
||||||
|
* @param tutorial
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/save")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> save(@RequestBody Tutorial tutorial) {
|
||||||
|
return Result.isBool(tutorialService.save(tutorial));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新教程
|
||||||
|
* @param tutorial
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> update(@RequestBody Tutorial tutorial) {
|
||||||
|
return Result.isBool(tutorialService.updateById(tutorial));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除教程
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/delete")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> delete(Long id) {
|
||||||
|
return Result.isBool(tutorialService.remove(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教程管理首页
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/index")
|
||||||
|
public String index() {
|
||||||
|
return "admin/biz/tutorial/index";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教程分类分页
|
||||||
|
* @param tutorialCategory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/category/page")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<Page<TutorialCategory>> categoryPage(TutorialCategory tutorialCategory) {
|
||||||
|
return Result.success(tutorialCategoryService.page(tutorialCategory));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增教程分类
|
||||||
|
* @param tutorialCategory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/category/save")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> saveCategory(@RequestBody TutorialCategory tutorialCategory) {
|
||||||
|
tutorialCategory.setType(TutorialCategory.typeCategory);
|
||||||
|
return Result.isBool(tutorialCategoryService.save(tutorialCategory));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新教程分类
|
||||||
|
* @param tutorialCategory
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/category/update")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> updateCategory(@RequestBody TutorialCategory tutorialCategory) {
|
||||||
|
return Result.isBool(tutorialCategoryService.updateById(tutorialCategory));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除教程分类
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/category/delete")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> deleteCategory(Long id) {
|
||||||
|
return Result.isBool(tutorialCategoryService.removeById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除教程分类
|
||||||
|
* @param categoryBatchDeleteRequest
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/category/batchDelete")
|
||||||
|
@ResponseBody
|
||||||
|
public Result<String> batchDeleteCategory(@RequestBody CategoryBatchDeleteRequest categoryBatchDeleteRequest) {
|
||||||
|
return Result.isBool(tutorialCategoryService.removeBatchByIds(categoryBatchDeleteRequest.getIds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.corewing.app.modules.admin.sys;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 固件管理
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/sys/firmware")
|
|
||||||
public class SysFirmwareController {
|
|
||||||
|
|
||||||
@GetMapping("/index")
|
|
||||||
public String index() {
|
|
||||||
return "admin/sys/firmware/index";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.corewing.app.service;
|
package com.corewing.app.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.corewing.app.entity.TutorialCategory;
|
import com.corewing.app.entity.TutorialCategory;
|
||||||
|
|
||||||
public interface TutorialCategoryService extends IService<TutorialCategory> {
|
public interface TutorialCategoryService extends IService<TutorialCategory> {
|
||||||
|
|
||||||
|
Page<TutorialCategory> page(TutorialCategory tutorialCategory);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,16 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.corewing.app.entity.Tutorial;
|
import com.corewing.app.entity.Tutorial;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
public interface TutorialService extends IService<Tutorial> {
|
public interface TutorialService extends IService<Tutorial> {
|
||||||
IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang);
|
IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang);
|
||||||
|
|
||||||
|
Page<Tutorial> page(Tutorial tutorial);
|
||||||
|
|
||||||
|
boolean save(Tutorial tutorial);
|
||||||
|
|
||||||
|
boolean update(Tutorial tutorial);
|
||||||
|
|
||||||
|
boolean remove(Long id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
package com.corewing.app.service.impl;
|
package com.corewing.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.corewing.app.common.page.PageContext;
|
||||||
import com.corewing.app.entity.TutorialCategory;
|
import com.corewing.app.entity.TutorialCategory;
|
||||||
import com.corewing.app.mapper.TutorialCategoryMapper;
|
import com.corewing.app.mapper.TutorialCategoryMapper;
|
||||||
import com.corewing.app.service.TutorialCategoryService;
|
import com.corewing.app.service.TutorialCategoryService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TutorialCategoryServiceImpl extends ServiceImpl<TutorialCategoryMapper, TutorialCategory> implements TutorialCategoryService {
|
public class TutorialCategoryServiceImpl extends ServiceImpl<TutorialCategoryMapper, TutorialCategory> implements TutorialCategoryService {
|
||||||
|
|
||||||
|
public Page<TutorialCategory> page(TutorialCategory tutorialCategory){
|
||||||
|
Page<TutorialCategory> page = PageContext.getPage(TutorialCategory.class);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<TutorialCategory> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.like(StringUtils.hasText(tutorialCategory.getCategoryTitle()), TutorialCategory::getCategoryTitle, tutorialCategory.getCategoryTitle());
|
||||||
|
return page(page, queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,69 @@
|
|||||||
package com.corewing.app.service.impl;
|
package com.corewing.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.corewing.app.common.page.PageContext;
|
||||||
import com.corewing.app.entity.Tutorial;
|
import com.corewing.app.entity.Tutorial;
|
||||||
|
import com.corewing.app.entity.TutorialCategoryRelation;
|
||||||
import com.corewing.app.mapper.TutorialMapper;
|
import com.corewing.app.mapper.TutorialMapper;
|
||||||
|
import com.corewing.app.service.TutorialCategoryRelationService;
|
||||||
import com.corewing.app.service.TutorialService;
|
import com.corewing.app.service.TutorialService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TutorialServiceImpl extends ServiceImpl<TutorialMapper, Tutorial> implements TutorialService {
|
public class TutorialServiceImpl extends ServiceImpl<TutorialMapper, Tutorial> implements TutorialService {
|
||||||
|
|
||||||
private final TutorialMapper tutorialMapper;
|
@Resource
|
||||||
|
private TutorialMapper tutorialMapper;
|
||||||
|
|
||||||
public TutorialServiceImpl(TutorialMapper tutorialMapper) {
|
@Resource
|
||||||
this.tutorialMapper = tutorialMapper;
|
private TutorialCategoryRelationService tutorialCategoryRelationService;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang) {
|
public IPage<Tutorial> pageList(Page<Tutorial> page, int categoryId, String tutorialTitle, String lang) {
|
||||||
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
|
return tutorialMapper.pageList(page, categoryId, tutorialTitle, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<Tutorial> page(Tutorial tutorial) {
|
||||||
|
Page<Tutorial> page = PageContext.getPage(Tutorial.class);
|
||||||
|
LambdaQueryWrapper<Tutorial> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.like(StringUtils.hasText(tutorial.getTutorialTitle()), Tutorial::getTutorialTitle, tutorial.getTutorialTitle());
|
||||||
|
return page(page, queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public boolean save(Tutorial tutorial) {
|
||||||
|
super.save(tutorial);
|
||||||
|
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
|
||||||
|
tutorialCategoryRelation.setTutorialId(tutorial.getId());
|
||||||
|
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
|
||||||
|
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public boolean update(Tutorial tutorial) {
|
||||||
|
super.updateById(tutorial);
|
||||||
|
tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
|
||||||
|
TutorialCategoryRelation tutorialCategoryRelation = new TutorialCategoryRelation();
|
||||||
|
tutorialCategoryRelation.setTutorialId(tutorial.getId());
|
||||||
|
tutorialCategoryRelation.setCategoryId(tutorial.getCategoryId());
|
||||||
|
return tutorialCategoryRelationService.save(tutorialCategoryRelation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public boolean remove(Long id) {
|
||||||
|
Tutorial tutorial = getById(id);
|
||||||
|
removeById(id);
|
||||||
|
return tutorialCategoryRelationService.remove(new LambdaQueryWrapper<TutorialCategoryRelation>().eq(TutorialCategoryRelation::getCategoryId, tutorial.getCategoryId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,3 +115,10 @@ body {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ellipsis-single {
|
||||||
|
white-space: nowrap; /* 禁止换行 */
|
||||||
|
overflow: hidden; /* 隐藏超出部分 */
|
||||||
|
text-overflow: ellipsis; /* 显示省略号 */
|
||||||
|
max-width: 200px; /* 自定义最大宽度(根据需求调整,如 150px/300px) */
|
||||||
|
}
|
||||||
|
|||||||
915
src/main/resources/templates/admin/biz/tutorial/index.html
Normal file
915
src/main/resources/templates/admin/biz/tutorial/index.html
Normal file
@@ -0,0 +1,915 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>教程管理</title>
|
||||||
|
<!-- 外部引入库文件 -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
<!-- 谷歌 Material Icons 库 -->
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<!-- 引入Pickr颜色选择库样式 -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css">
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/wangeditor@4.7.15/dist/css/wangEditor.min.css">
|
||||||
|
<!-- 外部引入自定义样式 -->
|
||||||
|
<link rel="stylesheet" href="/assets/css/table.css">
|
||||||
|
<!-- 颜色选择器适配样式 -->
|
||||||
|
<style>
|
||||||
|
.pickr-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pickr .pcr-app {
|
||||||
|
margin-top: 5px;
|
||||||
|
z-index: 1060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pickr .pcr-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pickr .pcr-interaction input.pcr-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis-single {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body id="app">
|
||||||
|
<div class="main-container row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="table-container">
|
||||||
|
<h4 class="mb-3">分类管理</h4>
|
||||||
|
|
||||||
|
<div class="search-bar">
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
名称
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="categorySearchParams.categoryTitle"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="请输入名称"
|
||||||
|
@keyup.enter="fetchCategoryData()"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" @click="fetchCategoryData()">
|
||||||
|
<i class="bi bi-search me-1"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" @click="categoryResetSearch()">
|
||||||
|
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<button class="btn btn-info" @click="categoryOpenAddModal()">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>名称</th>
|
||||||
|
<th>颜色</th>
|
||||||
|
<th style="width: 120px;">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- 加载中状态 -->
|
||||||
|
<tr v-if="categoryLoading">
|
||||||
|
<td colspan="8" class="p-0">
|
||||||
|
<div class="loading-container">
|
||||||
|
<div class="spinner-border text-primary" role="status"
|
||||||
|
style="width: 2rem; height: 2rem;">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 无数据状态 -->
|
||||||
|
<tr v-else-if="categoryTableData.length === 0">
|
||||||
|
<td colspan="8" class="p-0">
|
||||||
|
<div class="no-data">
|
||||||
|
<i class="bi bi-folder-x"></i>
|
||||||
|
<h5>暂无匹配数据</h5>
|
||||||
|
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 数据列表 -->
|
||||||
|
<tr v-else v-for="(item, index) in categoryTableData" :key="item.id">
|
||||||
|
<td>{{ item.categoryTitle }}</td>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
style="width: 40px; height: 30px; border: 1px solid #ddd; border-radius: 4px;"
|
||||||
|
:style="{ backgroundColor: item.color || '#ffffff' }"
|
||||||
|
></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-1">
|
||||||
|
<button class="btn btn-sm btn-primary" @click="categoryQueryData(item)">
|
||||||
|
<i class="bi bi-box-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-primary" @click="categoryOpenEditModal(item)">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" @click="categoryHandleDelete(item.id)">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div class="page-info">
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination pagination-sm mb-0">
|
||||||
|
<li class="page-item" :class="{ disabled: categoryPageNum === 1 }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(categoryPageNum - 1)">上一页</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="page === categoryPageNum ? 'active' : ''"
|
||||||
|
v-for="page in getPageList(categoryPageNum, categoryTotalPages, categoryPageRange)"
|
||||||
|
:key="page">
|
||||||
|
<a class="page-link" href="#" @click.prevent="categoryChangePage(page)">{{ page }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="{ disabled: categoryPageNum === totalPages }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(categoryPageNum + 1)">下一页</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="table-container">
|
||||||
|
<h4 class="mb-3">教程管理</h4>
|
||||||
|
|
||||||
|
<div class="search-bar">
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
名称
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
v-model="searchParams.tutorialTitle"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="请输入名称"
|
||||||
|
@keyup.enter="fetchData()"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-item">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
类型
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
v-model="searchParams.status"
|
||||||
|
class="form-select"
|
||||||
|
@change="fetchData()"
|
||||||
|
>
|
||||||
|
<option value="">全部状态</option>
|
||||||
|
<option value="1">启用</option>
|
||||||
|
<option value="2">关闭</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" @click="fetchData()">
|
||||||
|
<i class="bi bi-search me-1"></i> 搜索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" @click="resetSearch()">
|
||||||
|
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<button class="btn btn-info" @click="openAddModal()">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-striped table-hover table-bordered ">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>名称</th>
|
||||||
|
<th>描述</th>
|
||||||
|
<th>推荐</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>查看次数</th>
|
||||||
|
<th style="width: 120px;">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- 加载中状态 -->
|
||||||
|
<tr v-if="loading">
|
||||||
|
<td colspan="8" class="p-0">
|
||||||
|
<div class="loading-container">
|
||||||
|
<div class="spinner-border text-primary" role="status"
|
||||||
|
style="width: 2rem; height: 2rem;">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 无数据状态 -->
|
||||||
|
<tr v-else-if="tableData.length === 0">
|
||||||
|
<td colspan="8" class="p-0">
|
||||||
|
<div class="no-data">
|
||||||
|
<i class="bi bi-folder-x"></i>
|
||||||
|
<h5>暂无匹配数据</h5>
|
||||||
|
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- 数据列表 -->
|
||||||
|
<tr v-else v-for="(item, index) in tableData" :key="item.id">
|
||||||
|
<td>{{ item.tutorialTitle }}</td>
|
||||||
|
<td class="ellipsis-single">{{ item.description }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge" :class="item.recommendStatus === 1 ? 'bg-success' : 'bg-info'">
|
||||||
|
{{ item.recommendStatus === 1 ? '推荐' : '不推荐' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge" :class="item.status === 1 ? 'bg-success' : 'bg-danger'">
|
||||||
|
{{ item.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.viewCount }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-1">
|
||||||
|
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div class="page-info">
|
||||||
|
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination pagination-sm mb-0">
|
||||||
|
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="page === pageNum ? 'active' : ''"
|
||||||
|
v-for="page in getPageList(pageNum, totalPages, pageRange)"
|
||||||
|
:key="page">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||||
|
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分类 新增 or 编辑 -->
|
||||||
|
<div class="modal fade" id="addOrEditCategoryModel" tabindex="-1" aria-labelledby="addOrEditCategoryModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="addOrEditCategoryModalLabel">{{ categoryAddOrEditTitle }}</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">图标</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" v-model="categoryAddOrEditDto.icon"
|
||||||
|
placeholder="请输入图标">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">颜色</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div id="colorPickerContainer" style="border: 1px #dee2e6 solid"></div>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
v-model="categoryAddOrEditDto.color"
|
||||||
|
id="categoryColorValue"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">分类名称</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" v-model="categoryAddOrEditDto.categoryTitle"
|
||||||
|
placeholder="请输入分类名称">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">描述</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea class="form-control" v-model="categoryAddOrEditDto.description"
|
||||||
|
placeholder="请输入描述"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">是否置首</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-control" v-model="categoryAddOrEditDto.firstStatus">
|
||||||
|
<option value=0>不置首</option>
|
||||||
|
<option value=1>置首</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">状态</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-control" v-model="categoryAddOrEditDto.status">
|
||||||
|
<option value=1>启用</option>
|
||||||
|
<option value=2>关闭</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-3 col-form-label">语言</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-control" v-model="categoryAddOrEditDto.lang">
|
||||||
|
<option value='zh'>中文</option>
|
||||||
|
<option value='en'>英文</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click.prevent="categorySaveEntity">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 新增 or 编辑 -->
|
||||||
|
<div class="modal fade" id="addOrEditModel" tabindex="-1" aria-labelledby="addOrEditModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="addOrEditModalLabel">{{ addOrEditTitle }}</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">教程分类</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-control" v-model="addOrEditDto.categoryId">
|
||||||
|
<option v-for="(item, index) in categoryTableData" :value="item.id">{{item.categoryTitle}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">教程名称</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" v-model="addOrEditDto.tutorialTitle"
|
||||||
|
placeholder="请输入教程名称">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">教程描述</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" v-model="addOrEditDto.description"
|
||||||
|
placeholder="请输入教程描述">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">教程详情</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<!-- 富文本编辑器容器 -->
|
||||||
|
<div id="tutorialContentEditor" style="border: 1px solid #dee2e6; border-radius: 0.375rem;"></div>
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<div class="invalid-feedback" id="contentError" style="display: none; margin-top: 0.25rem;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">是否推荐</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-control" v-model="addOrEditDto.recommendStatus">
|
||||||
|
<option value=0>不推荐</option>
|
||||||
|
<option value=1>推荐</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">状态</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-control" v-model="addOrEditDto.status">
|
||||||
|
<option value=1>启用</option>
|
||||||
|
<option value=2>关闭</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label class="col-sm-2 col-form-label">语言</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="form-control" v-model="addOrEditDto.lang">
|
||||||
|
<option value='zh'>中文</option>
|
||||||
|
<option value='en'>英文</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click.prevent="saveEntity">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 外部引入库文件 -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- 引入Pickr颜色选择库核心JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/wangeditor@4.7.15/dist/wangEditor.min.js"></script>
|
||||||
|
<!-- 外部引入模拟请求文件 -->
|
||||||
|
<script src="/assets/js/axiosRequest.js"></script>
|
||||||
|
<script src="/assets/js/message.js"></script>
|
||||||
|
<script src="/assets/js/confirmModal.js"></script>
|
||||||
|
|
||||||
|
<!-- 页面核心逻辑 -->
|
||||||
|
<script>
|
||||||
|
const {createApp} = Vue;
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tableData: [], // 表格数据源
|
||||||
|
loading: false, // 加载状态
|
||||||
|
searchParams: {
|
||||||
|
tutorialTitle: '',
|
||||||
|
status: '',
|
||||||
|
},
|
||||||
|
// 分页参数
|
||||||
|
pageNum: 1, // 当前页码
|
||||||
|
pageSize: 10, // 每页条数
|
||||||
|
total: 0, // 总数据量
|
||||||
|
totalPages: 0, // 总页数
|
||||||
|
pageRange: 5,
|
||||||
|
modalInstances: {},
|
||||||
|
editor: null, // WangEditor 实例
|
||||||
|
addOrEditTitle: '',
|
||||||
|
addOrEditDto: {
|
||||||
|
id: null,
|
||||||
|
tutorialTitle: null,
|
||||||
|
description: null,
|
||||||
|
content: null,
|
||||||
|
status: 1,
|
||||||
|
recommendStatus: 1,
|
||||||
|
lang: 'zh',
|
||||||
|
},
|
||||||
|
// 教程分类
|
||||||
|
categoryTableData: [], // 表格数据源
|
||||||
|
categoryLoading: false, // 加载状态
|
||||||
|
categorySearchParams: {
|
||||||
|
categoryTitle: '',
|
||||||
|
status: '',
|
||||||
|
},
|
||||||
|
// 分页参数
|
||||||
|
categoryPageNum: 1, // 当前页码
|
||||||
|
categoryPageSize: 10, // 每页条数
|
||||||
|
categoryTotal: 0, // 总数据量
|
||||||
|
categoryTotalPages: 0, // 总页数
|
||||||
|
categoryPageRange: 5,
|
||||||
|
categoryAddOrEditTitle: '',
|
||||||
|
categoryAddOrEditDto: {
|
||||||
|
id: null,
|
||||||
|
icon: null,
|
||||||
|
color: '#ffffff', // 默认白色
|
||||||
|
categoryTitle: null,
|
||||||
|
description: null,
|
||||||
|
firstStatus: 0,
|
||||||
|
status: 1,
|
||||||
|
lang: 'zh',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPageList(currentPage, totalPages, pageRange) {
|
||||||
|
const list = [];
|
||||||
|
if (totalPages === 0) return list;
|
||||||
|
|
||||||
|
if (totalPages <= pageRange) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
list.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let start = Math.max(1, currentPage - 2);
|
||||||
|
let end = Math.min(totalPages, currentPage + 2);
|
||||||
|
|
||||||
|
if (end - start < pageRange - 1) {
|
||||||
|
if (start === 1) {
|
||||||
|
end = pageRange;
|
||||||
|
} else if (end === totalPages) {
|
||||||
|
start = totalPages - pageRange + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
list.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
// 教程分类
|
||||||
|
async fetchCategoryData() {
|
||||||
|
this.categoryLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await request.get('/biz/tutorial/category/page', {
|
||||||
|
...this.categorySearchParams,
|
||||||
|
current: this.categoryPageNum,
|
||||||
|
size: this.categoryPageSize
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.categoryTableData = response.data.records;
|
||||||
|
this.categoryTotal = response.data.total;
|
||||||
|
this.categoryTotalPages = response.data.pages;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.categoryTableData = [];
|
||||||
|
this.categoryTotal = 0;
|
||||||
|
this.categoryTotalPages = 0;
|
||||||
|
} finally {
|
||||||
|
this.categoryLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 切换页码
|
||||||
|
categoryChangePage(page) {
|
||||||
|
if (page < 1 || page > this.categoryTotalPages || page === this.categoryPageNum) return;
|
||||||
|
this.categoryPageNum = page;
|
||||||
|
this.fetchCategoryData();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
categoryResetSearch() {
|
||||||
|
this.categorySearchParams = {
|
||||||
|
categoryTitle: '',
|
||||||
|
};
|
||||||
|
this.categoryPageNum = 1;
|
||||||
|
this.fetchCategoryData();
|
||||||
|
},
|
||||||
|
// 单个删除
|
||||||
|
async categoryHandleDelete(id) {
|
||||||
|
showConfirmModal({
|
||||||
|
title: '删除确认',
|
||||||
|
content: '确定要删除这条数据吗?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
const response = await request.delete('/biz/tutorial/category/delete', {id});
|
||||||
|
if (response.code === 200) {
|
||||||
|
$message.success("删除成功");
|
||||||
|
this.fetchCategoryData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
$message.error('删除失败,请重试!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
categoryQueryData() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
// 打开新增模态框
|
||||||
|
categoryOpenAddModal() {
|
||||||
|
this.categoryAddOrEditTitle = '新增分类';
|
||||||
|
this.categoryClearForm(); // 重置表单默认值
|
||||||
|
this.modalInstances['addOrEditCategoryModel'].show();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 打开编辑模态框
|
||||||
|
categoryOpenEditModal(item) {
|
||||||
|
this.categoryAddOrEditTitle = '编辑分类';
|
||||||
|
this.categoryAddOrEditDto = {...item}; // 深拷贝避免引用问题
|
||||||
|
this.modalInstances['addOrEditCategoryModel'].show();
|
||||||
|
},
|
||||||
|
categorySaveEntity() {
|
||||||
|
let url = (this.categoryAddOrEditDto.id === null) ? '/biz/tutorial/category/save' : '/biz/tutorial/category/update';
|
||||||
|
request.post(url, this.categoryAddOrEditDto)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
$message.success('保存成功!', 500);
|
||||||
|
this.fetchCategoryData();
|
||||||
|
this.modalInstances['addOrEditCategoryModel'].hide();
|
||||||
|
} else {
|
||||||
|
$message.error('保存失败!', 500);
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
$message.error('网络错误,请重试!');
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 清空表单数据
|
||||||
|
categoryClearForm() {
|
||||||
|
this.categoryAddOrEditDto = {
|
||||||
|
id: null,
|
||||||
|
icon: null,
|
||||||
|
color: '#ffffff',
|
||||||
|
categoryTitle: null,
|
||||||
|
description: '',
|
||||||
|
type: 'category',
|
||||||
|
firstStatus: 0,
|
||||||
|
status: 1,
|
||||||
|
lang: 'zh',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// =========================== 教程 =========================
|
||||||
|
|
||||||
|
async fetchData() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await request.get('/biz/tutorial/page', {
|
||||||
|
...this.searchParams,
|
||||||
|
current: this.pageNum,
|
||||||
|
size: this.pageSize
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.tableData = response.data.records;
|
||||||
|
this.total = response.data.total;
|
||||||
|
this.totalPages = response.data.pages;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据失败:', error);
|
||||||
|
this.tableData = [];
|
||||||
|
this.total = 0;
|
||||||
|
this.totalPages = 0;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 切换页码
|
||||||
|
changePage(page) {
|
||||||
|
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||||
|
this.pageNum = page;
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
resetSearch() {
|
||||||
|
this.searchParams = {
|
||||||
|
firmwareName: '',
|
||||||
|
firmwareType: '',
|
||||||
|
};
|
||||||
|
this.pageNum = 1;
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
// 单个删除
|
||||||
|
async handleDelete(id) {
|
||||||
|
showConfirmModal({
|
||||||
|
title: '删除确认',
|
||||||
|
content: '确定要删除这条数据吗?',
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
const response = await request.delete('/biz/tutorial/delete', {id});
|
||||||
|
if (response.code === 200) {
|
||||||
|
$message.success("删除成功");
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error);
|
||||||
|
$message.error('删除失败,请重试!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 打开新增模态框
|
||||||
|
openAddModal() {
|
||||||
|
this.addOrEditTitle = '新增';
|
||||||
|
this.clearForm();
|
||||||
|
this.modalInstances['addOrEditModel'].show();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 打开编辑模态框
|
||||||
|
openEditModal(item) {
|
||||||
|
this.addOrEditTitle = '编辑';
|
||||||
|
this.addOrEditDto = {...item};
|
||||||
|
// 延迟回显富文本内容
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.editor) this.editor.txt.html(item.content || '');
|
||||||
|
}, 300);
|
||||||
|
this.modalInstances['addOrEditModel'].show();
|
||||||
|
},
|
||||||
|
saveEntity() {
|
||||||
|
let url = (this.addOrEditDto.id === null) ? '/biz/tutorial/save' : '/biz/tutorial/update';
|
||||||
|
request.post(url, this.addOrEditDto)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
$message.success('保存成功!', 500);
|
||||||
|
this.fetchData();
|
||||||
|
this.modalInstances['addOrEditModel'].hide();
|
||||||
|
} else {
|
||||||
|
$message.error('保存失败!', 500);
|
||||||
|
}
|
||||||
|
this.clearForm();
|
||||||
|
}).catch(() => {
|
||||||
|
$message.error('网络错误,请重试!');
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 清空表单数据
|
||||||
|
clearForm() {
|
||||||
|
this.addOrEditDto = {
|
||||||
|
id: null,
|
||||||
|
tutorialTitle: null,
|
||||||
|
description: null,
|
||||||
|
content: '',
|
||||||
|
status: 1,
|
||||||
|
recommendStatus: 1,
|
||||||
|
lang: 'zh',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
createColorPicker() {
|
||||||
|
const container = document.getElementById('colorPickerContainer');
|
||||||
|
container.innerHTML = '<div id="categoryColorPicker" class="pickr-container"></div>';
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
return Pickr.create({
|
||||||
|
el: '#categoryColorPicker',
|
||||||
|
theme: 'nano',
|
||||||
|
default: this.categoryAddOrEditDto.color || '#dee2e6',
|
||||||
|
swatches: [
|
||||||
|
'#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff',
|
||||||
|
'#ffff00', '#ff7f50', '#98fb98', '#87ceeb', '#dda0dd'
|
||||||
|
],
|
||||||
|
components: {
|
||||||
|
preview: true,
|
||||||
|
opacity: false,
|
||||||
|
hue: true,
|
||||||
|
interaction: {
|
||||||
|
hex: true,
|
||||||
|
rgba: false,
|
||||||
|
hsla: false,
|
||||||
|
hsva: false,
|
||||||
|
cmyk: false,
|
||||||
|
input: true,
|
||||||
|
clear: true,
|
||||||
|
save: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 初始化 WangEditor
|
||||||
|
initWangEditor() {
|
||||||
|
const _this = this;
|
||||||
|
// 获取编辑器容器
|
||||||
|
const editorDom = document.getElementById('tutorialContentEditor');
|
||||||
|
// 创建编辑器实例
|
||||||
|
this.editor = new wangEditor(editorDom);
|
||||||
|
|
||||||
|
// 编辑器配置
|
||||||
|
this.editor.config.height = 400; // 高度
|
||||||
|
this.editor.config.zIndex = 1050; // 层级(确保在模态框上方)
|
||||||
|
this.editor.config.uploadImgShowBase64 = true; // 图片以 Base64 格式保存(无需后端接口)
|
||||||
|
this.editor.config.menus = [
|
||||||
|
'head', // 标题
|
||||||
|
'bold', // 粗体
|
||||||
|
'italic', // 斜体
|
||||||
|
'underline', // 下划线
|
||||||
|
'strikeThrough', // 删除线
|
||||||
|
'foreColor', // 文字颜色
|
||||||
|
'backColor', // 背景颜色
|
||||||
|
'link', // 插入链接
|
||||||
|
'list', // 列表
|
||||||
|
'justify', // 对齐方式
|
||||||
|
'image', // 插入图片
|
||||||
|
'table', // 表格
|
||||||
|
'code', // 插入代码
|
||||||
|
'undo', // 撤销
|
||||||
|
'redo' // 重做
|
||||||
|
];
|
||||||
|
|
||||||
|
// 内容变化时同步到 Vue 数据
|
||||||
|
this.editor.config.onchange = function (html) {
|
||||||
|
_this.addOrEditDto.content = html;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建编辑器
|
||||||
|
this.editor.create();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchData();
|
||||||
|
this.fetchCategoryData();
|
||||||
|
|
||||||
|
// 初始化模态框
|
||||||
|
const modalIds = ['addOrEditModel', 'addOrEditCategoryModel'];
|
||||||
|
modalIds.forEach(id => {
|
||||||
|
const modalElement = document.getElementById(id);
|
||||||
|
if (modalElement) {
|
||||||
|
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
|
||||||
|
backdrop: 'static',
|
||||||
|
keyboard: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修复颜色选择器多次触发问题
|
||||||
|
const categoryModal = document.getElementById('addOrEditCategoryModel');
|
||||||
|
let pickrInstance = null; // 用局部变量存储实例,避免全局污染
|
||||||
|
|
||||||
|
categoryModal.addEventListener('shown.bs.modal', () => {
|
||||||
|
if (pickrInstance) {
|
||||||
|
pickrInstance.destroy();
|
||||||
|
}
|
||||||
|
pickrInstance = this.createColorPicker();
|
||||||
|
|
||||||
|
pickrInstance.on('change', (color) => {
|
||||||
|
this.categoryAddOrEditDto.color = color.toHEXA().toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
pickrInstance.on('save', (color) => {
|
||||||
|
this.categoryAddOrEditDto.color = color.toHEXA().toString();
|
||||||
|
pickrInstance.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
pickrInstance.on('clear', () => {
|
||||||
|
this.categoryAddOrEditDto.color = '#dee2e6';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模态框关闭时:彻底清理实例和DOM
|
||||||
|
categoryModal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
if (pickrInstance) {
|
||||||
|
pickrInstance.destroy();
|
||||||
|
pickrInstance = null; // 重置实例
|
||||||
|
}
|
||||||
|
// 清空容器,确保下次打开是全新DOM
|
||||||
|
document.getElementById('colorPickerContainer').innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化 WangEditor(延迟初始化,确保DOM加载完成)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initWangEditor();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// 监听模态框关闭事件,避免内存泄漏
|
||||||
|
const modal = document.getElementById('addOrEditModel');
|
||||||
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.txt.clear(); // 清空内容
|
||||||
|
document.getElementById('tutorialContentEditor').style.borderColor = '#dee2e6';
|
||||||
|
document.getElementById('contentError').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).mount('#app');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user