文章目录
前言
为自己搭建一个可以自我思考的平台,其核心为“心想事成”。
一、思考过程
此篇文章是将排序修改的接口以及页面开发!
二、完善
1、后端代码
(1)controller层
package com.etp.sustainable.controller.component;
import com.etp.sustainable.domain.req.SortQueryReq;
import com.etp.sustainable.domain.req.SortReq;
import com.etp.sustainable.service.SortService;
import com.etp.sustainable.util.R;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author ETP
* @since 2023/10/25 15:46
*/
@Tag(name="组件排序管理")
@RestController
@AllArgsConstructor
@RequestMapping("/sort")
public class SortController {
private final SortService sortService;
@GetMapping("/list")
public R list(SortQueryReq req) {
return R.ok(sortService.componentWithTypeList(req));
}
@PutMapping("/updateSort")
public R updateSort(@RequestBody List<SortReq> req) {
return R.ok(sortService.updateComponent(req));
}
}
(2)service层
package com.etp.sustainable.service;
import com.etp.sustainable.domain.req.SortQueryReq;
import com.etp.sustainable.domain.req.SortReq;
import com.etp.sustainable.domain.resp.SortResp;
import java.util.List;
/**
* @author ETP
* @since 2023/10/25 17:01
*/
public interface SortService {
/**
* 组件与组件类型集合
* @param req
* @return
*/
List<SortResp> componentWithTypeList(SortQueryReq req);
/**
* 组件与组件类型集合
* @param req
* @return
*/
boolean updateComponent(List<SortReq> req);
}
(3)serviceImpl层
package com.etp.sustainable.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.etp.sustainable.domain.component.Component;
import com.etp.sustainable.domain.component.ComponentType;
import com.etp.sustainable.domain.req.SortQueryReq;
import com.etp.sustainable.domain.req.SortReq;
import com.etp.sustainable.domain.resp.SortResp;
import com.etp.sustainable.mapper.component.ComponentMapper;
import com.etp.sustainable.mapper.component.ComponentTypeMapper;
import com.etp.sustainable.service.SortService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author ETP
* @since 2023/10/25 17:03
*/
@Service
@AllArgsConstructor
public class SortServiceImpl implements SortService {
private final ComponentTypeMapper componentTypeMapper;
private final ComponentMapper componentMapper;
@Override
public List<SortResp> componentWithTypeList(SortQueryReq req) {
List<SortResp> componentTypeList = componentTypeMapper.getComponentTypeList(req);
if (CollUtil.isEmpty(componentTypeList)) {
return new ArrayList<>();
}
List<Long> componentTypeIdList = componentTypeList.stream().map(SortResp::getId).distinct().toList();
req.setComponentTypeIdList(componentTypeIdList);
List<SortResp> componentList = componentMapper.getComponentList(req);
Map<Long, List<SortResp>> componentMap = componentList.stream().collect(Collectors.groupingBy(SortResp::getTypeId, LinkedHashMap::new, Collectors.toList()));
for (SortResp sortResp : componentTypeList) {
Long key = sortResp.getId();
List<SortResp> component = componentMap.getOrDefault(key, new ArrayList<>());
sortResp.setComponentList(component);
}
return componentTypeList;
}
@Override
public boolean updateComponent(List<SortReq> req) {
req.forEach(componentTypeReq -> {
ComponentType componentType = new ComponentType();
componentType.setId(componentTypeReq.getId());
componentType.setSort(componentTypeReq.getSort());
componentTypeMapper.updateById(componentType);
if (CollUtil.isNotEmpty(componentTypeReq.getComponentList())) {
componentTypeReq.getComponentList().forEach(componentReq -> {
Component component = new Component();
component.setId(componentReq.getId());
component.setSort(componentReq.getSort());
componentMapper.updateById(component);
});
}
});
return true;
}
}
2、上前端代码
(1)上sort页面
<template>
<el-space
fill
wrap
:fill-ratio="fillRatio"
:direction="direction"
style="width: 100%; margin-bottom: 15px;"
>
<div>
<div v-if="startDrag">
<el-button @click="dgStart">启动拖拽</el-button>
</div>
<div v-else>
<el-button :type="dragTypeFlag && dragComponentFlag ? 'primary' : ''" @click="allStart">自由拖拽</el-button>
<el-button :type="dragTypeFlag && !dragComponentFlag ? 'primary' : ''" @click="typeStart">类型拖拽</el-button>
<el-button :type="!dragTypeFlag && dragComponentFlag ? 'primary' : ''" @click="componentStart">组件拖拽</el-button>
</div>
</div>
</el-space>
<el-space
fill
wrap
:fill-ratio="fillRatio"
:direction="direction"
style="width: 100%; margin-bottom: 15px;"
>
<div v-if="changeDrag">
<el-button @click="submit">确认</el-button>
<el-button @click="cancel">取消</el-button>
</div>
</el-space>
<div class="component-with-type-box">
<transition-group name="drag" class="row-line">
<ul
v-for="(item, index) in componentTypeList"
class="component-type-box"
:draggable="dragFlag && dragTypeFlag"
@dragstart="dragstart(1, index, null)"
@dragenter="dragenterType($event, index, null)"
@dragover="dragover($event)"
@dragend="dragend()"
@mouseover="topicTypeHover($event)"
@mouseout="removeTopicTypeHover($event)"
:key="index"
>
<div class="component-type">
<div class="component-type-title">
{{ item.name }}
</div>
<span v-if="!startDrag && dragFlag && dragTypeFlag" class="drag-sort">拖拽换序</span>
</div>
<li
v-for="(component, i) in item.componentList"
class="component-type-info"
:draggable="dragFlag && dragComponentFlag"
@dragstart="dragstart(2, index, i)"
@dragenter="dragenter($event, index, i)"
@dragover="dragover($event)"
@dragend="dragend()"
@mouseover="addNumberHover($event)"
@mouseout="removeNumberHover($event)"
:key="i"
>
<el-image style="width: 100px; height: 100px" :src="component.imgUrl" draggable="false" :fit="'fill'" @click="bigImage(component.imgUrl)" />
{{ component ? component.name : "" }}
</li>
<div style="clear: both; height: 0"></div>
</ul>
</transition-group>
<el-image-viewer
style="z-index:1500"
v-if="showImageViewer"
@close="closeBigImage"
:url-list="imageData"/>
</div>
</template>
<script lang="ts" setup>
import {onMounted, ref, reactive, Ref} from 'vue'
import {cwtList, updateSort} from '@/api/sortApi'
import {canvasToImg} from '@/utils/drawCanvasToImg'
import ComponentCanvas from "@/views/component/ComponentCanvas.vue";
import {ElMessage} from "element-plus";
const fillRatio = ref(30)
const direction = ref('horizontal')
interface QueryParams {
name: string
}
const queryParams = reactive<QueryParams>({
name: '',
})
interface ComponentList {
id: number
imgUrl: string
name: string
sort: number
trajectory: string
typeId: number
}
interface ComponentTypeList {
id: number
imgUrl: string
name: string
sort: number
trajectory: string
typeId: number
componentList: Array<ComponentList>
}
const componentTypeList:Ref<ComponentTypeList[]> = ref([])
let canvasWidth = 100, canvasHeight = 100;
onMounted(() => {
cwtList(queryParams).then(res => {
if (res.code != 200) return
if (res.data && res.data.length > 0) {
res.data.forEach(item => {
if (item.componentList && item.componentList.length > 0) {
item.componentList.forEach(component => {
if (component.trajectory) {
component.trajectory = component.trajectory.split(",")
let imgUrl = canvasToImg(component.trajectory, canvasWidth, canvasHeight)
component.imgUrl = imgUrl
}
})
}
})
}
componentTypeList.value = res.data
})
})
let startDrag = ref(true);
const dgStart = () => {
startDrag.value = false;
allStart()
}
let changeDrag = ref(false);
const cancel = () => {
startDrag.value = true;
changeDrag.value = false;
dragTypeFlag.value = false;
dragComponentFlag.value = false;
}
let dragFlag = ref(false);
let dragTypeFlag = ref(false);
let dragComponentFlag = ref(false);
const allStart = () => {
dragFlag.value = true
dragTypeFlag.value = true
dragComponentFlag.value = true
}
const typeStart = () => {
dragFlag.value = true
dragTypeFlag.value = true
dragComponentFlag.value = false
}
const componentStart = () => {
dragFlag.value = true
dragTypeFlag.value = false
dragComponentFlag.value = true
}
const submit = () => {
interface ReqParam {
id: number,
sort: number,
}
interface ReqParams {
id: number,
sort: number,
componentList: Array<ReqParam>
}
let reqParams:ReqParams[] = []
let typeIndex = 1;
componentTypeList.value.forEach(componentType => {
let reqParam:ReqParams = {
id: componentType.id,
sort: typeIndex,
componentList: [],
}
typeIndex++;
if (componentType.componentList && componentType.componentList.length > 0) {
let componentIndex = 1;
componentType.componentList.forEach(component => {
let componentParam:ReqParam = {
id: component.id,
sort: componentIndex,
}
componentIndex++;
reqParam.componentList.push(componentParam)
})
}
reqParams.push(reqParam)
})
updateSort(reqParams).then(res => {
if (res.code != 200) {
ElMessage.success("请求错误")
return
}
ElMessage.success("修改成功")
startDrag.value = true;
dragFlag.value = false
changeDrag.value = false
dragTypeFlag.value = false
dragComponentFlag.value = false
})
}
const dragType = ref(0)
const typeDefaultIndex = ref(0)
const typeIndex = ref(0)
const dragDefaultIndex = ref(0)
const dragIndex = ref(0)
const updateLi = ref(false)
interface TypeInfo {
id: number
imgUrl: string
name: string
sort: number
trajectory: string
typeId: number
}
const typeInfo = ref({})
const moveInfo = ref({})
const showImageViewer = ref(false)
interface ImageData {
imgUrl: string;
}
const imageData: Ref<ImageData[]> = ref([])
const bigImage = (imgUrl: any) => {
imageData.value.push(imgUrl)
showImageViewer.value = true
}
const closeBigImage = () => {
showImageViewer.value = false
}
/**
* 拖拽开始
* @param type 类型:1-组件类型,2-组件
* @param index 组件类型下标
* @param i 组件下标
*/
const dragstart = (type, index, i) => {
if (dragType.value !== 0) {
if (dragType.value === 1 && dragFlag.value && dragTypeFlag.value) {
changeDrag.value = true
typeDefaultIndex.value = index;
typeIndex.value = index;
typeInfo.value = JSON.parse(JSON.stringify(componentTypeList.value[index]));
} else if (dragType.value === 2 && dragFlag.value && dragComponentFlag.value) {
} else {
dragType.value = 0
}
} else {
dragType.value = type
if (dragType.value === 1 && dragFlag.value && dragTypeFlag.value) {
changeDrag.value = true
typeDefaultIndex.value = index;
typeIndex.value = index;
typeInfo.value = JSON.parse(JSON.stringify(componentTypeList.value[index]));
} else if (dragType.value === 2 && dragFlag.value && dragComponentFlag.value) {
changeDrag.value = true
updateLi.value = true
typeDefaultIndex.value = index;
typeIndex.value = index;
dragDefaultIndex.value = i;
dragIndex.value = i;
componentTypeList.value[index].componentList[i].sort = 0;
moveInfo.value = componentTypeList.value[index].componentList[i];
removeNumberHover(event);
} else {
dragType.value = 0
}
}
}
// 拖拽停留位置 --- 增加拖拽效果
const dragover = (event: any) => {
if (dragType.value === 1 && dragFlag.value && dragTypeFlag.value) {
event.preventDefault();
} else if (dragType.value === 2) {
event.preventDefault();
}
}
// 拖拽鼠标释放
const dragenter = (event: any, index: number, i: number) => {
event.preventDefault();
if (dragType.value === 1 && dragFlag.value && dragTypeFlag.value) {
componentTypeList.value.splice(typeIndex.value, 1);
componentTypeList.value.splice(index, 0, typeInfo.value);
// 排序变化后目标对象的索引变成源对象的索引
typeIndex.value = index;
} else if (dragType.value === 2) {
// 避免源对象触发自身的dragenter事件
// 是否跨区域,启示集合下标和移动至集合下标位置不同
if (typeIndex.value !== index) {
componentTypeList.value[typeIndex.value].componentList.splice(dragIndex.value, 1);
componentTypeList.value[index].componentList.splice(i, 0, moveInfo.value);
// 排序变化后目标对象的索引变成源对象的索引
} else if (dragIndex.value !== i) {
componentTypeList.value[typeIndex.value].componentList.splice(dragIndex.value, 1);
componentTypeList.value[typeIndex.value].componentList.splice(i, 0, moveInfo.value);
// 排序变化后目标对象的索引变成源对象的索引
}
typeIndex.value = index;
dragIndex.value = i;
}
}
// 跨类型拖拽
const dragenterType = (event: any, index: number) => {
event.preventDefault();
if (dragType.value === 1 && dragFlag.value && dragTypeFlag.value) {
// 避免源对象触发自身的dragenter事件
// 是否跨区域,启示集合下标和移动至集合下标位置不同
componentTypeList.value.splice(typeIndex.value, 1);
componentTypeList.value.splice(index, 0, typeInfo.value);
// 排序变化后目标对象的索引变成源对象的索引
typeIndex.value = index;
} else if (dragType.value === 2 && dragFlag.value && dragComponentFlag.value) {
// 避免源对象触发自身的dragenter事件
// 是否跨区域,启示集合下标和移动至集合下标位置不同
if (typeIndex.value !== index) {
componentTypeList.value[typeIndex.value].componentList.splice(dragIndex.value, 1);
componentTypeList.value[index].componentList.splice(componentTypeList.value[index].componentList.length, 0, moveInfo.value);
// 排序变化后目标对象的索引变成源对象的索引
typeIndex.value = index;
dragIndex.value = componentTypeList.value[index].componentList.length - 1;
}
}
}
// 拖拽结束
const dragend = () => {
if (dragType.value === 1) {
componentTypeList.value.forEach((item, index) => {
index++;
item.sort = index
/*item.componentList.forEach((component, i) => {
i++;
component.sort = i;
});*/
});
} else {
componentTypeList.value.forEach((item, index) => {
index++;
item.sort = index
/*item.componentList.forEach((component, i) => {
i++;
component.sort = i;
});*/
});
}
dragType.value = 0
}
// 组件鼠标经过时添加样式
const addNumberHover = (event:any) => {
event.currentTarget.className = "component-type-info-hover";
}
// 组件鼠标经过后移除样式
const removeNumberHover = (event:any) => {
event.currentTarget.className = "component-type-info";
}
// 组件类型经过时添加样式
const topicTypeHover = (event:any) => {
event.currentTarget.className = "component-type-box-hover";
}
// 组件类型经过后移除样式
const removeTopicTypeHover = (event:any) => {
event.currentTarget.className = "component-type-box";
}
</script>
<style lang="scss" scoped>
$ratio: 0.85;
.component-with-type-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #78edaf;
.component-type-box {
min-width: 300px;
list-style: none;
padding: 10px;
border: 1px solid #78edaf;
.component-type {
height: 40px;
line-height: 40px;
margin-bottom: 10px;
display: flex;
align-items: center;
.component-type-title {
display: flex;
.item_nameBox {
width: 80%;
box-sizing: border-box;
//padding: 15px * $ratio 0 15px * $ratio;
// display: flex;
// flex-direction: column;
.item_title {
}
}
}
.drag-sort {
display: none;
}
}
}
.component-type-box-hover {
min-width: 300px;
list-style: none;
padding: 10px;
border: 1px solid #78edaf;
background: #F8FAFF;
cursor: pointer;
.component-type {
height: 40px;
line-height: 40px;
margin-bottom: 10px;
display: flex;
align-items: center;
.component-type-title {
width: 85%;
display: flex;
.item_nameBox {
width: 80%;
box-sizing: border-box;
//padding: 15px * $ratio 0 15px * $ratio;
// display: flex;
// flex-direction: column;
.item_title {
}
}
.item_title {
display: flex;
}
i {
color: #78edaf
}
}
.drag-sort {
display: inline-block;
width: 60px;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #989898;
line-height: 20px;
}
}
}
.drag-move {
transition: transform 0.3s;
}
.component-type-info {
cursor: pointer;
line-height: 27px;
background: #ffffff;
border-radius: 6px;
border: 1px solid #78edaf;
color: #78edaf;
float: left;
text-align: center;
margin-right: 20px;
margin-bottom: 20px;
display: flex;
flex-direction: column;
::v-deep .el-image {
border-radius: 6px;
}
}
.component-type-info-hover {
cursor: pointer;
line-height: 27px;
border-radius: 6px;
border: 1px solid #78edaf;
float: left;
text-align: center;
margin-right: 20px;
margin-bottom: 20px;
background: #78edaf;
color: #ffffff;
display: flex;
flex-direction: column;
::v-deep .el-image {
border-radius: 6px;
}
}
}
</style>
(2)我猜可能看了就想得到,使用到了canvas转图片工具类
export const canvasToImg = (trajectoryArray: Array<string>, canvasWidth, canvasHeight) => {
let canvas = document.createElement("canvas")
if (canvas instanceof HTMLCanvasElement) {
let ctx = canvas.getContext('2d');
let xMax = -Number.MAX_VALUE, xMin = Number.MAX_VALUE, yMax = -Number.MAX_VALUE, yMin = Number.MAX_VALUE
trajectoryArray.forEach((trajectory, i) => {
if (i % 2 === 0) {
if (xMax < parseInt(trajectory)) {
xMax = parseInt(trajectory)
}
if (xMin > parseInt(trajectory)) {
xMin = parseInt(trajectory)
}
} else {
if (yMax < parseInt(trajectory)) {
yMax = parseInt(trajectory)
}
if (yMin > parseInt(trajectory)) {
yMin = parseInt(trajectory)
}
}
})
let width = xMax - xMin, height = yMax - yMin
// 设置Canvas尺寸
canvas.width = 100;
canvas.height = 100;
return getDots(canvas, ctx, trajectoryArray, canvasWidth, canvasHeight, width, height, xMin, yMin)
}
}
const getDots = (canvas: any, ctx: any, arr: any, canvasWidth: any, canvasHeight: any, width: any, height: any, xMin: any, yMin: any) => {
let scaleX = 1;
// 计算偏移量
let offsetX = 0;
let offsetY = 0;
if (width > canvasWidth) {
scaleX = canvasWidth / width
} else {
offsetX = (canvasWidth - width) / 2
if (offsetX < 0) {
offsetX = 0
}
}
let scaleY = 1
if (height > canvasHeight) {
scaleY = canvasHeight / height
} else {
offsetY = (canvasHeight - height) / 2
if (offsetY < 0) {
offsetY = 0
}
}
let scale = Math.min(scaleY, scaleX)
interface Dot {
x: number;
y: number;
}
let dots: Dot[] = [];
for (let i = 0; i < arr.length; i++) {
let dot = {
x: 0,
y: 0
}
dot.x = (arr[i] - xMin) * scale + offsetX
i++
if (i <= arr.length - 1) {
dot.y = (arr[i] - yMin) * scale + offsetY
dots.push(dot)
}
}
return initCanvas(canvas, ctx, dots)
}
const initCanvas = (canvas: any, ctx: any, dots: any) => {
ctx.beginPath()
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < dots.length; i++) {
// 开始一条路径
ctx.beginPath();
// 设置开始位置
ctx.moveTo(dots[i].x, dots[i].y);
// 设置线条的宽度
ctx.lineWidth = 1
// 设置结束位置
if (dots[i+1]) {
ctx.lineTo(dots[i+1].x, dots[i+1].y)
}
// 设置颜色
ctx.strokeStyle = '#666666';
// 开始绘制
ctx.stroke()
}
// 保存到画布上
ctx.fill()
return canvas.toDataURL('image/jpeg', 1.0);
}
(3)上api.js代码
import axios from '../utils/http'
// 分页查询组件页码
export function cwtList(params) {
return axios('/sort/list', { params }, "get");
}
// 分页查询组件页码
export function updateSort(dataForm) {
return axios('/sort/updateSort', { dataForm }, "put");
}
三、总结
排序界面也就开发完成啦!