仿LeekCode前端答题页面
1. LeekCode页面效果
2. 效果展示
3. 主要思路
- 数学思维
- Pinia持久化存储布局数据结构
- N叉树的遍历和递归处理
- 前端拖拽API以及DOM操作
- Vue组件
4. 核心代码
<!-- @author 葡萄w -->
<!-- 仿力扣答题页面 -->
<!-- 原创 -->
<template>
<div id="show" class="showDom"/>
<div id="#flexShadow" style="padding: 2px" class="shadow"/>
</template>
<script setup>
// ===================================== 引入子组件,根据obj生成dom
import {createApp, onMounted} from "vue";
import test from "@/ViewsFront/questionDetail/child/test.vue";
import {flexBox, flexPageStore} from "@/stores/flexPageStore.ts";
import {storeToRefs} from "pinia";
const {flexMemo, childBoxsType} = storeToRefs(flexPageStore());
const obj = flexMemo.value
const flexType = childBoxsType.value
const domMinWidth = 39
const domMinHeight = 39
const navbarHeight = 60
let show;
// ======================================= 绑定 XY 动态布局
function getDomPercentageAndPx(str) {
try {
return str.match(/(\d+(\.\d+)?)|(\.\d+)/g).map(match => parseFloat(match))
}catch (e) {
return [100,0]
}
}
const handleResize = (pn) => {
const nodes = pn.childNodes;
const n = nodes.length
const boxCnt = (n + 1) / 2
const resizeCnt = n - boxCnt
const pd = (n - 1) * p / (n + 1)
for (let i = 1; i < n; i+=2) {
const ln = nodes[i].previousElementSibling
const nn = nodes[i]
const rn = nodes[i].nextElementSibling
if(pn.dataset.childType === '0') {
ln.style.width = `calc(${getDomPercentageAndPx(ln.style.width)[0]}% - ${pd}px)`
rn.style.width = `calc(${getDomPercentageAndPx(rn.style.width)[0]}% - ${pd}px)`
nn.onmousedown = e => {
const startX = e.clientX;
const tl = ln.offsetWidth;
const tr = rn.offsetWidth;
document.onmousemove = e => {
const endX = e.clientX;
const resizeSum = resizeCnt * nn.offsetWidth
const maxWidth = pn.clientWidth - resizeSum
let leftSize = tl + endX - startX
let rightSize = tr - (endX - startX)
if (leftSize < domMinWidth) {
rightSize = leftSize + rightSize - domMinWidth
leftSize = domMinWidth;
}
if (rightSize < domMinWidth) {
leftSize = leftSize + rightSize - domMinWidth
rightSize = domMinWidth;
}
ln.style.width =
`calc( ${(leftSize / maxWidth) * 100}% - ${pd}px )`;
rn.style.width =
`calc( ${(rightSize / maxWidth) * 100}% - ${pd}px )`;
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
nn.releaseCapture && nn.releaseCapture();
memoDomToObj()
};
nn.setCapture && nn.setCapture();
return false;
};
}
if(pn.dataset.childType === '1') {// 纵向排布
ln.style.height = `calc(${getDomPercentageAndPx(ln.style.height)[0]}% - ${pd}px)`
rn.style.height = `calc(${getDomPercentageAndPx(rn.style.height)[0]}% - ${pd}px)`
nn.onmousedown = e => {
const startY = e.clientY;
const tt = ln.offsetHeight;
const tb = rn.offsetHeight;
document.onmousemove = e => {
const endY = e.clientY;
const resizeSum = resizeCnt * nn.offsetHeight
const maxHeight = pn.clientHeight - resizeSum
let topSize = tt + endY - startY
let bomSize = tb - (endY - startY)
if (topSize < domMinHeight) {
bomSize = topSize + bomSize - domMinHeight
topSize = domMinHeight;
}
if (bomSize < domMinHeight) {
topSize = topSize + bomSize - domMinHeight
bomSize = domMinHeight;
}
ln.style.height =
`calc( ${(topSize / maxHeight) * 100}% - ${pd}px )`;
rn.style.height =
`calc( ${(bomSize / maxHeight) * 100}% - ${pd}px )`;
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
nn.releaseCapture && nn.releaseCapture();
memoDomToObj()
};
nn.setCapture && nn.setCapture();
return false;
};
}
}
}
// ======================================= 解析box,滚动条,组件,数据结构
const p = 3
function getBoxDom (type, Size) {
const dom = document.createElement('div');
if (type === 1) dom.style.height = `calc(${Size}% - 0px)`;
else dom.style.width = `calc(${Size}% - 0px)`;
return dom
}
function getResizeDom(type) {
const dom = document.createElement('div');
dom.dataset.domType = `${type}`
dom.classList.add(type === 1 ? "resizeY" : "resizeX");
return dom
}
function getComponent(titles) {
const componentId = Math.random()
const elementApp = createApp(test,
{'message':'生成组件','number':componentId ,'cardTitles': titles});
const tempDiv = document.createElement('div');
elementApp.mount(tempDiv);
tempDiv.style.width = "calc(100% - 0px)";
tempDiv.style.height = "calc(100% - 0px)";
tempDiv.classList.add("innerCard")
tempDiv.dataset.drop = "true"
tempDiv.dataset.propsid = `${componentId}`
return tempDiv
}
function parseObj (parent, obj, childBoxsType) {
if(obj.content === null){
childBoxsType = Number(childBoxsType)
if (childBoxsType === 0) parent.style.display = 'flex'
const n = obj.childBoxs.length
for (let i = 0; i < n; i++) {
if (i > 0) parent.appendChild(getResizeDom(childBoxsType))
const box = getBoxDom(childBoxsType, obj.childBoxs[i].size)
parseObj(box, obj.childBoxs[i], !childBoxsType)
parent.appendChild(box)
}
parent.dataset.childType = `${childBoxsType}`
handleResize(parent)
}else{
parent.dataset.domType = "-1"
parent.classList.add("box")
parent.appendChild(getComponent(obj.content));
}
}
function handlerTree(node) {
console.log("处理含childType的节点",node)
handleResize(node)
const childes = node.childNodes
const n = childes.length
for (let i = 0; i < n; i++) {
if(childes[i].dataset.childType !== undefined)
handlerTree(childes[i])
}
}
function memoDomToObj() {
const res = []
const node = show.firstChild
dfs(node, res)
childBoxsType.value = node.dataset.childType
flexMemo.value = res[0]
}
function dfs(node, val) {
if(node.dataset.childType !== undefined) {
const tmp = new flexBox(null, parseFloat(getDomPercentageAndPx(node.dataset.childType === '0' ? node.style.height : node.style.width)[0]), [])
val.push(tmp)
const childes = node.childNodes
const n = childes.length
for (let i = 0; i < n; i++) {
dfs(childes[i], tmp.childBoxs)
}
}else if(node.dataset.domType === '-1'){
const titles = document.getElementById(`card${node.firstChild.dataset.propsid}`).childNodes;
const n = titles.length
const tmp = []
for (let i = 0; i < n; i++) {
const title = titles[i]
if(title.dataset?.title) {
tmp.push(title.dataset.title)
}
}
val.push(new flexBox(tmp, parseFloat(getDomPercentageAndPx(node.parentNode.dataset.childType === '0' ? node.style.width : node.style.height)[0]), []))
}
}
// ======================================= 定义拖拽api的实现
let x,y;// 鼠标位置
let source = null;// 拿起来的dom
let cloneSource = null// 拖拽渲染的cloneDom
let type = 0// 拖拽类型
let shadow;// 初始化与template中的shadow绑定
// ======================================= node操作获取drop和box以及对应下标
function getDropNode(node) {
while(node) {
if(node.dataset?.drop) {
return node;
}
node = node.parentNode
}
}
function getBoxNode(node) {
while(node) {
if(node.dataset?.domType === "-1") {
return node;
}
node = node.parentNode
}
}
function atDomIndex(dom) {
return Array.prototype.indexOf.call(dom.parentNode.children, dom);
}
// ================================================ 判断方位的工具函数
function hereLog(dropTo, val) {
let rect = dropTo.getBoundingClientRect();
const w = rect.width, h = rect.height;
const sx = rect.left, sy = rect.top;
if (type !== val) {
if(val === 1){
shadow.style.display = "block"
shadow.style.left = `${sx}px`
shadow.style.top = `${sy - navbarHeight}px`
shadow.style.height = `${h/2}px`
shadow.style.width = `${w}px`
}else if(val === 2){
shadow.style.display = "block"
shadow.style.left = `${sx+w/2}px`
shadow.style.top = `${sy - navbarHeight}px`
shadow.style.height = `${h}px`
shadow.style.width = `${w/2}px`
}else if(val === 3){
shadow.style.display = "block"
shadow.style.left = `${sx}px`
shadow.style.top = `${sy+h/2 - navbarHeight}px`
shadow.style.height = `${h/2}px`
shadow.style.width = `${w}px`
}else if(val === 4){
shadow.style.display = "block"
shadow.style.left = `${sx}px`
shadow.style.top = `${sy - navbarHeight}px`
shadow.style.height = `${h}px`
shadow.style.width = `${w/2}px`
}else if(val === 5){
shadow.style.display = "block"
shadow.style.left = `${sx}px`
shadow.style.top = `${sy - navbarHeight}px`
shadow.style.height = `${h}px`
shadow.style.width = `${w}px`
}
type = val
}
}
function getTitleWhere(onTitle) {
if (onTitle.id.slice(0,4) !== 'card') {
let idx = atDomIndex(onTitle) + 1
const tmp = onTitle.getBoundingClientRect();
const w = tmp.width, h = tmp.height;
const sx = tmp.left, sy = tmp.top;
const cx = tmp.width / 2 + tmp.left;
idx += (x > cx)
// 渲染shadow
if(type !== -idx) {
if(x < cx) {
shadow.style.display = "block"
shadow.style.left = `${sx - 1}px`
shadow.style.top = `${sy + 0.2 * h - navbarHeight}px`
shadow.style.height = `${h * 0.6}px`
shadow.style.width = `1px`
}else {
shadow.style.display = "block"
shadow.style.left = `${sx + w - 1}px`
shadow.style.top = `${sy + 0.2 * h - navbarHeight}px`
shadow.style.height = `${h * 0.6}px`
shadow.style.width = `1px`
}
type = -idx
}
}else {
const n = onTitle.childNodes.length;
const tmp = onTitle.childNodes[n-1].getBoundingClientRect();
const w = tmp.width, h = tmp.height;
const sx = tmp.left, sy = tmp.top;
if(type !== -(n+1)) {
shadow.style.display = "block"
shadow.style.left = `${sx + w - 1}px`
shadow.style.top = `${sy + 0.2 * h - navbarHeight}px`
shadow.style.height = `${h * 0.6}px`
shadow.style.width = `1px`
type = -(n+1)
}
}
}
function getCardWhere(dropTo) {
let rect = dropTo.getBoundingClientRect();
const w = rect.width, h = rect.height;
const o1x = rect.left, o1y = rect.top;
const o5x = rect.left + w / 4, o5y = rect.top + h / 4;
const o6x = rect.left + w * 3 / 4, o6y = rect.top + h * 3 / 4;
const cx = o1x + w / 2, cy = o1y + h / 2;
if(x >= o5x && x <= o6x && y >= o5y && y <= o6y) // 判断5方位
hereLog(dropTo, 5)
else if(x >= o5x && x <= o6x) // 判断8 2方位
hereLog(dropTo, y <= cy ? 1 : 3)
else if(y >= o5y && y <= o6y) // 判断4 6方位
hereLog(dropTo, x <= cx ? 4 : 2)
else if(x < cx && y < cy) // 1
hereLog(dropTo, y < h * (x - o1x) / w + o1y ? 1 : 4)
else if(x > cx && y < cy) // 3
hereLog(dropTo, x < w * (o1y + h - y) / h + o1x ? 1 : 2)
else if(x < cx && y > cy) // 7
hereLog(dropTo, x < w * (o1y + h - y) / h + o1x ? 4 : 3)
else if(x > cx && y > cy) // 9
hereLog(dropTo, y < h * (x - o1x) / w + o1y ? 2 : 3)
else
hereLog(dropTo, 0)
}
// ================================================ 钩子函数
onMounted(() => {
document.addEventListener("drag", function (event) {
x = event.clientX;
y = event.clientY;
});
// 初始化所有页面组件
// initMemo()
shadow = document.getElementById('#flexShadow')
// 向展示盒show添入解析完的数据
show = document.getElementById('show');
const parent = document.createElement('div');
parent.dataset.ancestor = "true"
parent.dataset.childType = `${flexType}`
parent.style.width = "calc(100% - 0px)";
parent.style.height = "calc(100% - 0px)";
parseObj(parent, obj, flexType);
show.appendChild(parent);
// 抓起
show.ondragstart = (e) => {
source = e.target
// 防卡边界
if(cloneSource) document.body.removeChild(cloneSource);
cloneSource = null
// 去除默认效果
cloneSource = source.cloneNode(true)
cloneSource.draggable = false
const img = new Image();
img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E";
e.dataTransfer.setDragImage(img, 0, 0);
document.body.appendChild(cloneSource);
}
// 拖动
show.ondragover = (e) => {
// 添加试图效果
cloneSource.style = 'position:fixed;left:0;top:0;z-index:999;pointer-events:none;transform:translate3d( ' + x + 'px ,' + y + 'px,0);'
e.preventDefault()
const dropTo = getDropNode(e.target)
if (dropTo) {
if (dropTo.id.slice(0,4) === "card") {
getTitleWhere(e.target)
}else {
getCardWhere(dropTo)
}
}else {
type = 0
}
}
// 放下
show.ondrop = (e) => {
try {
const comeParent = source.parentNode
const comeTitle = source.textContent
const comeParentId = source.parentNode.dataset.propsid
const comeShow = document.getElementById(`content${comeParentId}`);
const comeShowTitle = comeShow.childNodes[0].id.slice(14)
const comeIsShow = comeTitle === comeShowTitle
const isAllMove = comeParent.childNodes.length === 1
console.log('所选组件span',source)
console.log('所选组件', comeTitle)
console.log('所选目录',comeParent)
console.log('所选目录ID',comeParentId)
console.log('所选目录展示盒',comeShow)
console.log('所选目录展示盒组件',comeShowTitle)
console.log('所选组件是否被展示',comeIsShow)
console.log('是否要全部挪走',isAllMove)
const dom = getDropNode(e.target);
const dropToId = dom.dataset.propsid
const selfFun = comeParentId === dropToId
console.log('自环操作',selfFun)
let deleteBox = null,
downDeepBox = getBoxNode(source),
nextClick = null,
cutDownDeep = null;
if(!selfFun && isAllMove) {
deleteBox = downDeepBox;
}
downDeepBox = downDeepBox.parentNode;
// 获取被移走区域的下一个要显示的组件
// 必须先获取再执行,否则在自环情况下
// 会导致source获取的是组件的信息,此处执行click使组件回到memo,source无法冒泡到card上
if(comeIsShow && !selfFun && !isAllMove && type !== 0) {
const spans = comeParent.childNodes
const idx = atDomIndex(source)
nextClick = spans[(idx - 1 >= 0 ? idx - 1 : idx + 1)]
}
; if(type < 0){ // 插入逻辑
const dropTitleList = dom.childNodes;
dom.insertBefore(source, dropTitleList[-type - 1])
source.click()
}
if(type === 1){
// 先获取父级盒子
const pn = getBoxNode(e.target).parentNode
// 获取放下的目标盒子
const rightDom = dom.parentNode
// 重点:若父盒子为祖父盒子,情况为仅有一个盒子,此时上部需要竖向排列
if(pn.dataset.ancestor && pn.childNodes.length === 1) {
// 竖向排列
pn.dataset.childType = '1'
pn.style.display = ''
// 仅有一个盒子,竖向排布将长宽调换
rightDom.style.height = `calc(${getDomPercentageAndPx(rightDom.style.width)[0]}% - 0px)`
rightDom.style.width = ""
}
// 得到父盒子的排布规则
const parentChildType = pn.dataset.childType
// 横向排布情况:横向盒子的上部操作为将该盒子拉入一个竖向排布的布局,该盒子在上,dropTo盒子在下
// 此时还未添加盒子,仅仅是将dropTo盒子创建父级
if(parentChildType === '0') {
const tmp = getDomPercentageAndPx(rightDom.style.width)[0]
const parentBox = getBoxDom(0, tmp)
parentBox.dataset.childType = '1'
rightDom.before(parentBox)
parentBox.appendChild(rightDom)
rightDom.style.width = ""
rightDom.style.height = `calc(100% - 0px)`
cutDownDeep = parentBox
}
// 横向排布已经将dropTo盒子拉入创建好的父级盒子中
// 竖向排布直接向dropTo盒子上方插入即可
const tmp = getDomPercentageAndPx(rightDom.style.height)[0]
// 自己单独一个挪到自己上方的情况,
if(selfFun && isAllMove) {
const otherDom = rightDom?.previousElementSibling?.previousElementSibling
?? rightDom?.nextElementSibling?.nextElementSibling
otherDom.style.height = `calc(${getDomPercentageAndPx(otherDom.style.height)[0] + tmp/2}% - 0px)`
rightDom.style.height = `calc(${tmp/2}% - 0px)`
}else{
const leftDom = getBoxDom(1, tmp/2)
rightDom.style.height = `calc(${tmp/2}% - 0px)`
leftDom.dataset.domType = "-1"
leftDom.classList.add("box")
rightDom.before(leftDom)
leftDom.appendChild(getComponent([source.textContent]))
rightDom.before(getResizeDom(1))
source.remove()
document.getElementById(`card${rightDom.firstChild.dataset.propsid}`)?.firstChild?.click()
}
}
if(type === 2){
console.log('处理右方')
const pn = getBoxNode(e.target).parentNode
const leftDom = dom.parentNode
if(pn.dataset.ancestor && pn.childNodes.length === 1) {
// 横向排列
pn.dataset.childType = '0'
pn.style.display = 'flex'
// 仅有一个盒子,横向排布将长宽调换
leftDom.style.width = `calc(${getDomPercentageAndPx(leftDom.style.height)[0]}% - 0px)`
leftDom.style.height = ""
}
// 得到父盒子的排布规则
const parentChildType = pn.dataset.childType
// 竖向排布情况:横向盒子的右部操作为将该盒子拉入一个横向排布的布局,该盒子在左,dropTo盒子在右
// 此时还未添加盒子,仅仅是将dropTo盒子创建父级
if(parentChildType === '1') {
const tmp = getDomPercentageAndPx(leftDom.style.height)[0]
const parentBox = getBoxDom(1, tmp)
parentBox.dataset.childType = '0'
leftDom.before(parentBox)
parentBox.appendChild(leftDom)
parentBox.style.display = `flex`
leftDom.style.height = ""
leftDom.style.width = `calc(100% - 0px)`
cutDownDeep = parentBox
}
const tmp = getDomPercentageAndPx(leftDom.style.width)[0]
// 自己单独一个挪到自己上方的情况,
if(selfFun && isAllMove) {
const otherDom = leftDom?.previousElementSibling?.previousElementSibling
?? leftDom?.nextElementSibling?.nextElementSibling
otherDom.style.width = `calc(${getDomPercentageAndPx(otherDom.style.width)[0] + tmp/2}% - 0px)`
leftDom.style.width = `calc(${tmp/2}% - 0px)`
}else{
const rightDom = getBoxDom(0, tmp/2)
leftDom.style.width = `calc(${tmp/2}% - 0px)`
rightDom.dataset.domType = "-1"
rightDom.classList.add("box")
leftDom.after(rightDom)
rightDom.appendChild(getComponent([source.textContent]))
leftDom.after(getResizeDom(0))
source.remove()
document.getElementById(`card${leftDom.firstChild.dataset.propsid}`)?.firstChild?.click()
}
}
if(type === 3){
const pn = getBoxNode(e.target).parentNode
const leftDom = dom.parentNode
if(pn.dataset.ancestor && pn.childNodes.length === 1) {
pn.dataset.childType = '1'
pn.style.display = ''
leftDom.style.height = `calc(${getDomPercentageAndPx(leftDom.style.width)[0]}% - 0px)`
leftDom.style.width = ""
}
const parentChildType = pn.dataset.childType
if(parentChildType === '0') {
const tmp = getDomPercentageAndPx(leftDom.style.width)[0]
const parentBox = getBoxDom(0, tmp)
parentBox.dataset.childType = '1'
leftDom.before(parentBox)
parentBox.appendChild(leftDom)
leftDom.style.width = ""
leftDom.style.height = `calc(100% - 0px)`
cutDownDeep = parentBox
}
// 横向排布已经将dropTo盒子拉入创建好的父级盒子中
// 竖向排布直接向dropTo盒子上方插入即可
const tmp = getDomPercentageAndPx(leftDom.style.height)[0]
if(selfFun && isAllMove) {
const otherDom = leftDom?.previousElementSibling?.previousElementSibling
?? leftDom?.nextElementSibling?.nextElementSibling
otherDom.style.height = `calc(${getDomPercentageAndPx(otherDom.style.height)[0] + tmp/2}% - 0px)`
leftDom.style.height = `calc(${tmp/2}% - 0px)`
}else{
const rightDom = getBoxDom(1, tmp/2)
leftDom.style.height = `calc(${tmp/2}% - 0px)`
rightDom.dataset.domType = "-1"
rightDom.classList.add("box")
leftDom.after(rightDom)
rightDom.appendChild(getComponent([source.textContent]))
leftDom.after(getResizeDom(1))
source.remove()
document.getElementById(`card${leftDom.firstChild.dataset.propsid}`)?.firstChild?.click()
}
}
if(type === 4){
console.log('处理左方')
// 先获取父级盒子
const pn = getBoxNode(e.target).parentNode
// 获取放下的目标盒子
const rightDom = dom.parentNode
// 重点:若父盒子为祖父盒子,情况为仅有一个盒子,此时需要横向排列
if(pn.dataset.ancestor && pn.childNodes.length === 1) {
// 横向排列
pn.dataset.childType = '0'
pn.style.display = 'flex'
// 仅有一个盒子,横向排布将长宽调换
rightDom.style.width = `calc(${getDomPercentageAndPx(rightDom.style.height)[0]}% - 0px)`
rightDom.style.height = ""
}
// 得到父盒子的排布规则
const parentChildType = pn.dataset.childType
// 竖向排布情况:横向盒子的右部操作为将该盒子拉入一个横向排布的布局,该盒子在左,dropTo盒子在右
// 此时还未添加盒子,仅仅是将dropTo盒子创建父级
if(parentChildType === '1') {
console.log("sfsafasfafaf")
const tmp = getDomPercentageAndPx(rightDom.style.height)[0]
const parentBox = getBoxDom(1, tmp)
parentBox.dataset.childType = '0'
rightDom.before(parentBox)
parentBox.appendChild(rightDom)
parentBox.style.display = `flex`
rightDom.style.height = ""
rightDom.style.width = `calc(100% - 0px)`
cutDownDeep = parentBox
}
const tmp = getDomPercentageAndPx(rightDom.style.width)[0]
// 自己单独一个挪到自己上方的情况,
if(selfFun && isAllMove) {
const otherDom = rightDom?.previousElementSibling?.previousElementSibling
?? rightDom?.nextElementSibling?.nextElementSibling
otherDom.style.width = `calc(${getDomPercentageAndPx(otherDom.style.width)[0] + tmp/2}% - 0px)`
rightDom.style.width = `calc(${tmp/2}% - 0px)`
}else{
const leftDom = getBoxDom(0, tmp/2)
rightDom.style.width = `calc(${tmp/2}% - 0px)`
leftDom.dataset.domType = "-1"
leftDom.classList.add("box")
rightDom.before(leftDom)
leftDom.appendChild(getComponent([source.textContent]))
rightDom.before(getResizeDom(0))
source.remove()
document.getElementById(`card${rightDom.firstChild.dataset.propsid}`)?.firstChild?.click()
}
}
if(type === 5 && !selfFun){
document
.getElementById(`card${getBoxNode(e.target).firstChild.dataset.propsid}`)
.appendChild(source)
source.click()
}
// 切换显示
nextClick?.click()
// ======= 删除空Box逻辑
if(deleteBox && type !== 0) {
const boxParent = deleteBox.parentNode
const childType = boxParent.dataset.childType;
const nodes = deleteBox.parentNode.childNodes
const idx = Array.prototype.indexOf.call(boxParent.children, deleteBox)
const leftDom = idx - 2 >= 0 ? nodes[idx - 2] : null
const rightDom = idx + 2 < nodes.length ? nodes[idx + 2] : null
// 得到%
const a = getDomPercentageAndPx(childType === "0" ? deleteBox.style.width:deleteBox.style.height)[0]
// 将剩余组件移动到缓存区
const component = document.getElementById(`content${comeParentId}`).firstChild;
if (component) {
document.getElementById('componentMemo').appendChild(component);
}
// 注意顺序
boxParent.removeChild(nodes[(idx - 1 >= 0 ? idx - 1 : idx + 1)])
boxParent.removeChild(deleteBox)
const n = nodes.length
const pd = (n-1) * p / (n+1)
if(leftDom && rightDom) {
const ls = getDomPercentageAndPx(childType === "0" ? leftDom.style.width:leftDom.style.height)[0]
const rs = getDomPercentageAndPx(childType === "0" ? rightDom.style.width:rightDom.style.height)[0]
if(childType === "0"){
leftDom.style.width = `calc(${ls + a/2}% - ${pd}px)`;
rightDom.style.width = `calc(${rs + a/2}% - ${pd}px)`;
}else {
leftDom.style.height = `calc(${ls + a/2}% - ${pd}px)`;
rightDom.style.height = `calc(${rs + a/2}% - ${pd}px)`;
}
}else if(leftDom) {
const ls = getDomPercentageAndPx(childType === "0" ? leftDom.style.width:leftDom.style.height)[0]
if(childType === "0"){
leftDom.style.width = `calc(${ls + a}% - ${pd}px)`;
}else {
leftDom.style.height = `calc(${ls + a}% - ${pd}px)`;
}
}else if(rightDom) {
const rs = getDomPercentageAndPx(childType === "0" ? rightDom.style.width:rightDom.style.height)[0]
if(childType === "0") rightDom.style.width = `calc(${rs + a}% - ${pd}px)`;
else rightDom.style.height = `calc(${rs + a}% - ${pd}px)`;
}
// handleResize(boxParent)
}
// ======= 降级逻辑1(两个元素移除一个导致的降级)
if(downDeepBox.childNodes.length === 1 && downDeepBox.parentNode.id !== 'show') {
const fa = downDeepBox
const ch = downDeepBox.firstChild
console.log('触发降级', fa)
// downDeepBox.firstChild.style.height = downDeepBox.height
// downDeepBox.firstChild.style.width = downDeepBox.width
// const faLevelType = fa.parentNode.dataset.childType
ch.style.width = fa.style.width
ch.style.height = fa.style.height
// ch.style.display = `${faLevelType === '0' ? '':'flex' }`
fa.parentNode.insertBefore(ch, fa)
fa.remove()
// handleResize(ch.parentNode)
}
// ======= 降级逻辑2(分裂时生成的元素与父元素同类型导致的降级)
if(cutDownDeep && (cutDownDeep.dataset.childType === cutDownDeep.parentNode.dataset.childType)) {
console.log("降级逻辑2")
const boxType = cutDownDeep.dataset.childType
const nodes = cutDownDeep.childNodes
const n = nodes.length
const boxSize = getDomPercentageAndPx(boxType === '0'? cutDownDeep.style.width : cutDownDeep.style.height)[0];
// 降级box 占父级boxSize%
for (let i=n-1;i>=0;i--) {
const child = nodes[i]
if(child.dataset.domType === '-1') {
const childSize = getDomPercentageAndPx(boxType === '0'? child.style.width : child.style.height)[0];
if(boxType === '0') {
child.style.width = `calc(${boxSize * childSize / 100}% - 0px)`
}else {
child.style.height = `calc(${boxSize * childSize / 100}% - 0px)`
}
}
cutDownDeep.after(child)
}
cutDownDeep.remove()
}
}catch (e) {
console.log(e)
}finally {
type = 0
shadow.style.display = "none"
shadow.style.height = `0px`
shadow.style.width = `0px`
source = null
document.body.removeChild(cloneSource);
cloneSource = null
handlerTree(show.firstChild)
memoDomToObj()
}
}
})
</script>
<style lang="less">
.showDom {
width: 100vw;
height: calc(100vh - var(--navbar-height));
padding: 0.5vh 0.5vw;
overflow: hidden;
}
.box {
padding: 1px;
overflow: hidden;
}
.innerCard {
border-radius: 0.5vh;
border: 1px solid oklch(var(--bc) / 0.3);
overflow: hidden;
}
.resizeX {
position: relative;
width: 3px;
cursor: col-resize;
&:hover {
background-color: #45a3ff;
}
}
.resizeY {
position: relative;
height: 3px;
cursor: row-resize;
&:hover {
background-color: #45a3ff;
}
}
.shadow {
position: absolute;
pointer-events: none;
top: 50vh;
left: 50vw;
width: 0;
height: 0;
display: none;
border-radius: 10px;
background-color: rgba(46, 227, 158, 0.2);
border: 1px solid rgb(0, 122, 255);
z-index: 1000;
transition: top 0.3s,left 0.3s, width 0.3s, height 0.3s;
}
.flexTitleList{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.flexTitleListLimit{
flex-direction: column;
height: auto;
}
</style>
// function traverseDOM(element) {
// if (!element ) return;
// const nodes = Array.from(element.childNodes).filter(node => {
// return node.dataset?.domtype !== undefined
// });
// if (nodes.length === 0) return;
// const parentDom = element
// let idx = 0;
// while(idx < nodes.length) {
// while(idx < nodes.length && nodes[idx].dataset.domtype !== '0' && nodes[idx].dataset.domtype !== '1') idx += 1;
// if (idx < nodes.length) {
// if (nodes[idx].dataset.domtype === '0')
// handleResizeX(parentDom,nodes[idx-1],nodes[idx],nodes[idx+1])
// if (nodes[idx].dataset.domtype === '1')
// handleResizeY(parentDom,nodes[idx-1],nodes[idx],nodes[idx+1])
// }
// idx+=1;
// }
// nodes.forEach(function(child) {
// traverseDOM(child);
// });
// }
5. 父级组件
<template>
<question-detail-flex/>
<div id="componentMemo" style="display: none">
<div id="moveComponent-题目正文" class="questionComponentLayout">
<question-content v-if="res" :body="res"/>
</div>
<div id="moveComponent-评论" class="questionComponentLayout">
<common/>
</div>
<div id="moveComponent-代码编辑器" class="questionComponentLayout" style="overflow: hidden;">
<code-editor
:value="value"
:language="coder"
:handle-change="value_change"
:language-change="language_change"
/>
</div>
<div id="moveComponent-提交记录" class="questionComponentLayout">
<commit-list v-if="res" :question-id="res.id"/>
</div>
<div id="moveComponent-样例测试" class="questionComponentLayout">
<question-test
v-if="res"
:question-id="res.id"
:eg="res.eg === null ? [] : res.eg"
:execute="questionJudge"
v-model:language="coder.value"
v-model:code="codeStr"
/>
</div>
</div>
</template>
6. 持久化仓库
import {ref} from 'vue'
import {defineStore} from 'pinia'
import {MessageType, openView} from "@/tools/notyfTool";
class flexBox {
content: Array<string> | null;
size: number;
childBoxs: Array<flexBox>;
constructor(content: any, size: number, childBoxs: Array<flexBox>) {
this.content = content;
this.size = size;
this.childBoxs = childBoxs
}
}
export {flexBox}
export const flexPageStore = defineStore('flexPage', () => {
const defaultFlex =
new flexBox(
null, 100,
[
new flexBox(['题目正文', '提交记录'], 35, []),
new flexBox(null, 65, [new flexBox(['代码编辑器'], 60, []), new flexBox(['样例测试','评论'], 40, [])]),
]
)
const flexMemo = ref(defaultFlex)
const childBoxsType = ref(0)
function reset() {
flexMemo.value = defaultFlex
openView(MessageType.Info, "重置布局成功")
}
return {flexMemo, childBoxsType, reset}
}, {
persist: true
})
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 葡萄w
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果