diff --git a/src/components/Atom/index.jsx b/src/components/Atom/index.jsx new file mode 100644 index 0000000..52dd7be --- /dev/null +++ b/src/components/Atom/index.jsx @@ -0,0 +1,50 @@ +// Nie 2023/2/8 元素添加迭代递归渲染 + +import {useEffect, useState} from "react"; +import css from './index.module.scss'; +import {defaultStore} from "@/store/index.js"; + +export default function Atom(props) { + const AtomList = props.AtomList; + const [boxState, setBoxState] = useState(new Array(AtomList.length).fill(false)); + // 打开关闭标签 + function handleChangeBoxState(index){ + const temporaryState = new Array(boxState.length).fill(false); + if(!boxState[index]){ + temporaryState[index] = true; + } + setBoxState(temporaryState) + } + // 添加元素 + function handleAddElement(item){ + defaultStore.setNewElementIdentify(item.identify) + } + useEffect(() => { + if(props.state){ + setBoxState(new Array(boxState.length).fill(false)) + } + }, [props.state]) + return AtomList.map((item, index) => { + if (item.children && Array.isArray(item.children)) { + return
+
handleChangeBoxState(index)}>{item.name}
+
+
{item.describe}
+
{Atom({AtomList: item.children, state: boxState[index]})}
+
+
+ } else { + return
+
+
handleChangeBoxState(index)}>{item.name}
+
+
handleAddElement(item)}>+
+
+
+
+
{item.describe}
+
+
+ } + }) +} \ No newline at end of file diff --git a/src/components/Atom/index.module.scss b/src/components/Atom/index.module.scss new file mode 100644 index 0000000..85babe0 --- /dev/null +++ b/src/components/Atom/index.module.scss @@ -0,0 +1,84 @@ +@import '@/assets/default.scss'; + +$open: open; +$close: close; + +.atomBox { + position: relative; + margin: 0.5rem 0; + padding-left: 0.5rem; + + + .atomTitle { + @include font-serif; + font-weight: 600; + cursor: pointer; + display: flex; + + & > div.text { + position: relative; + white-space: nowrap; + flex: 1; + } + + & > div.cmd { + position: relative; + flex-shrink: 0; + width: 40px; + @include font-mono; + font-size: 30px; + } + } + + .atomSon { + position: relative; + overflow: hidden; + //padding: 1rem 0; + box-sizing: border-box; + max-height: 0; + animation: close linear 300ms; + + & > div.atomDescribe { + position: relative; + white-space: nowrap; + font-size: 14px; + padding-left: 1.2rem; + color: #666; + + &:before { + content: ""; + position: absolute; + left: 0.7rem; + width: 0.3rem; + height: 100%; + background: #1a1a1a; + } + } + } + + .atomBoxOpen { + animation: open ease-in-out 300ms forwards; + + &:hover { + overflow: overlay; + } + } +} + +@keyframes open { + from { + max-height: 0; + } + to { + max-height: 300px; + } +} + +@keyframes close { + to { + max-height: 0; + } + from { + max-height: 300px; + } +} \ No newline at end of file diff --git a/src/components/DirectoryStructureTree/index.jsx b/src/components/DirectoryStructureTree/index.jsx index 640b145..5174bac 100644 --- a/src/components/DirectoryStructureTree/index.jsx +++ b/src/components/DirectoryStructureTree/index.jsx @@ -1,3 +1,4 @@ +// Nie 2023/2/8 import css from './index.module.scss' export default function DirectoryStructureTree(props){ return props.tree.map((item, index) => { diff --git a/src/config/sys22.js b/src/config/sys22.js index 76a2222..d909d08 100644 --- a/src/config/sys22.js +++ b/src/config/sys22.js @@ -1,4 +1,7 @@ import icon from '../assets/react.svg' + + + export const config = { projectName:'西安升帮联创技术服务有限公司', icon, diff --git a/src/store/defaultStore.js b/src/store/defaultStore.js index 2fda191..ee3aa9d 100644 --- a/src/store/defaultStore.js +++ b/src/store/defaultStore.js @@ -1,6 +1,6 @@ -import { observable, action, computed, makeObservable} from "mobx"; +import {observable, action, computed, makeObservable} from "mobx"; import {config} from "@/config/sys22"; - +import {NE} from "@/tools/index.js"; class DefaultStore { // 系统配置 @@ -9,36 +9,50 @@ class DefaultStore { // 目录结构树 directoryStructureTree = [ { - name:'菜单1', - icon:'', - children:[ + name: '菜单1', + icon: '', + children: [ { - name:'子菜单' + name: '子菜单' } ] }, { - name:'菜单2', - icon:'', + name: '菜单2', + icon: '', }, ]; + + // 元素结构参考 + Atom = new NE(); + // 新元素转存媒介 + newElementIdentify = null; + constructor() { // mobx6 和以前版本这是最大的区别 makeObservable(this, { config: observable, directoryStructureTree: observable, + Atom: observable, + newElementIdentify: observable, + setNewElementIdentify: action, setName: action, titleName: computed }); - } + setName(v) { console.log('触发action'); this.name = v; } - get titleName(){ - return this.name+'___111'; + get titleName() { + return this.name + '___111'; + } + + // 在页面添加新元素 + setNewElementIdentify(identify) { + this.newElementIdentify = identify } } diff --git a/src/tools/index.js b/src/tools/index.js index e69de29..a262cef 100644 --- a/src/tools/index.js +++ b/src/tools/index.js @@ -0,0 +1,230 @@ +// 从元素模板中获取指定元素 +export function getTargetElement(identify, AtomTemplate){ + let element = null; + for(let i in AtomTemplate){ + if(AtomTemplate[i].identify == identify){ + element = AtomTemplate[i]; + break + }else if(Array.isArray(AtomTemplate[i].children)){ + const s = getTargetElement(identify, AtomTemplate[i].children); + if(s) element = s + } + } + return element +} + + +// 框架基础属性 +const style1 = [ + 'background', + 'backdrop-filter', + 'position', + 'top', + 'bottom', + 'left', + 'right', + 'width', + 'height', + 'margin', + 'padding', + 'border', + 'border-radius', + 'href',// 跳转 + 'overflow', + '全剧中', +] +// 文字属性 +const style2 = [ + 'color', 'font-size', 'font-weight', '是否换行', '下划线', '首行缩进', 'line-height', 'text-align' +] + + +// 元素原型 +export class NE { + constructor() { + return [ + { + name: '框架',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "此类元素为页面提供骨架",// 描述 + identify: 'frame',// 标识 + config: {}, + depth: '', + children: [ + { + name: '单元素结构',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "此结构仅可容纳一个子元素",// 描述 + identify: 'singleFrame',// 标识 + config: { + childrenLength: 1, + childrenType: 'element', + middle: '中间件', + style: [...style1], + styleLimit: {}, + event: {} + }, + depth: '', + childElement: [] + }, { + name: '横向多元素结构',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "此结构可在横向上排列多个子元素",// 描述 + identify: 'transverseFrame',// 标识 + config: { + childrenLength: 'n', + childrenType: 'element', + middle: '中间件', + style: [...style1, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + childElement: [] + }, { + name: '纵向多元素结构',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "此结构可在纵向上排列多个子元素",// 描述 + identify: 'longitudinalFrame',// 标识 + config: { + childrenLength: 'n', + childrenType: 'element', + middle: '中间件', + style: [...style1, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + childElement: [] + } + ] + }, + { + name: '内元素',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "",// 描述 + identify: 'innerElement',// 标识 + config: {}, + depth: '', + children: [ + { + name: '行内文本',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "",// 描述 + identify: 'span',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style2], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '块文本',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "",// 描述 + identify: 'blockText',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style2, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '图像',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "",// 描述 + identify: 'image',// 标识 + config: { + childrenType: 'images', + middle: '中间件', + style: [...style1, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '视频',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "",// 描述 + identify: 'video',// 标识 + config: { + childrenType: 'video', + middle: '中间件', + style: [...style2, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + } + ] + }, + { + name: '输入元素',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "信息录入元素",// 描述 + identify: 'input',// 标识 + config: {}, + depth: '', + children: [ + { + name: '基本输入',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "单行文本输入",// 描述 + identify: 'input-text',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style2], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '块输入',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "多行文本输入",// 描述 + identify: 'input-textarea',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style2, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '代码块',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "等宽字体的代码多行输入",// 描述 + identify: 'input-code',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style1, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + }, { + name: '等待开发',// 元素名称 + // dir:true,// 是否为叶子节点 + describe: "可拓展的输入选项",// 描述 + identify: 'nomal',// 标识 + config: { + childrenType: 'text', + middle: '中间件', + style: [...style2, 'flex'], + styleLimit: {}, + event: {} + }, + depth: '', + } + ] + }, + ]; + } +} \ No newline at end of file diff --git a/src/view/EditPageNew/index.jsx b/src/view/EditPageNew/index.jsx index 269e17f..9f164c1 100644 --- a/src/view/EditPageNew/index.jsx +++ b/src/view/EditPageNew/index.jsx @@ -1,16 +1,20 @@ -import {useState, useEffect} from 'react' -import {defaultStore} from '@/store/index' +import {useState, useEffect} from 'react'; +import {defaultStore} from '@/store/index'; +import {getTargetElement, NE} from "@/tools/index.js"; import DirectoryStructureTree from "@/components/DirectoryStructureTree/index.jsx"; import Modal from '@/components/Modal'; import Button from "@/components/Button/index.jsx"; +import Atom from "@/components/Atom/index.jsx"; + import css from './index.module.scss' import svgLolipop from '@/assets/lolipop.svg' import svgHelp from '@/assets/help.svg' import svgArrow from '@/assets/arrow.svg' +import {autorun} from "mobx"; export default function EditPageNew() { // 大纲 - const [thumbnailList, setThumbnailList] = useState([{name: '大纲', choose: false}]); + const [thumbnailList, setThumbnailList] = useState([]); // 添加大纲页面状态 const [addThumbnailState, setAddThumbnailState] = useState(false); // 大纲名称 @@ -18,47 +22,96 @@ export default function EditPageNew() { // 选择大纲的box样式 const moleculeBoxChoose = [css.moleculeBoxChoose, css.moleculeBox].join(' '); - // 关闭大纲添加页面 + // 当下页面列表 + const [nowContentList, setNowContentList] = useState({}); + // 当下的页面内容 + const [nowContent, setNowContent] = useState({}); + // 当下的选中元素 + const [nowElement, setNowElement] = useState(null); + + // 属性页开关 + const [attributeSwitch, setAttributeSwitch] = useState(false); + // 属性页标签位置 + const [attrLabelPosition, setAttrLabelPosition] = useState(0); + + + + useEffect(() => { + // console.log(thumbnailList) + // console.log(defaultStore.Atom) + }, [thumbnailList]) + + // 添加新元素 + useEffect(() => { + const handleNewElementChange = autorun(() => { + // 判断子元素是否为空 + if(!defaultStore.newElementIdentify)return; + // 判断是否允许添加子元素 + // 判断是否有选中元素 + if(!nowElement){ + alert('未选中元素,无法添加。') + defaultStore.setNewElementIdentify(null); + return; + } + // 判断元素是否支持添加Element子元素 + if(nowElement.config.childrenType != 'element'){ + alert('此元素不支持添加子元素。'); + defaultStore.setNewElementIdentify(null); + return; + } + // 判断是否能容纳新元素 + if(nowElement.config.childrenLength == 1 && nowElement.childElement.length != 0){ + alert('此元素无法容纳更多元素'); + defaultStore.setNewElementIdentify(null); + return; + } + const newElement = getTargetElement(defaultStore.newElementIdentify, new NE()) + nowElement.childElement.push(newElement); + setNowElement(nowElement) + defaultStore.setNewElementIdentify(null); + }) + return () => { + handleNewElementChange() + } + }, [nowElement]) + + // 关闭大纲添加的页面 function closeThumbnailModal() { setNewThumbnailName('') setAddThumbnailState(false) } - - // 确认添加大纲 + // 确认添加新的大纲元素 function addThumbnail() { - thumbnailList.push({ - name: newThumbnailName - }) + const newThumbnail = {name: newThumbnailName, id:Math.random(), choose:false} + // 大纲页面添加新元素 + thumbnailList.push(newThumbnail); + // 内容列表添加新元素(同时生成根元素) + const newElement = getTargetElement('singleFrame' ,new NE()); + nowContentList[newThumbnail.id] = newElement + setNowContentList({...nowContentList}) setThumbnailList(thumbnailList) closeThumbnailModal() } - // 选择大纲 + // 选择大纲作为编辑项 function chooseThumbnail(index) { thumbnailList.forEach(item => { item.choose = false; }) - thumbnailList[index].choose = true + thumbnailList[index].choose = true; setThumbnailList([...thumbnailList]) - setNowContent(thumbnailList[index]) + setNowContent(nowContentList[thumbnailList[index].id]); + if(nowContent.identify = 'singleFrame' && nowContentList[thumbnailList[index].id].childElement.length == 0){ + setNowElement(nowContentList[thumbnailList[index].id]); + } } - - useEffect(() => { - console.log(thumbnailList) - }, [thumbnailList]) - - // 当下的页面内容 - const [nowContent, setNowContent] = useState({}) - // 属性页开关 - const [attributeSwitch, setAttributeSwitch] = useState(false); - // 属性页标签位置 - const [attrLabelPosition, setAttrLabelPosition] = useState(0); // 切换属性标签 - function handleChangeAttrLabelPosition(index){ + function handleChangeAttrLabelPosition(index) { setAttrLabelPosition(index) } + return
@@ -146,18 +199,50 @@ export default function EditPageNew() {
-
+
handleChangeAttrLabelPosition(0)}>元素结构树
handleChangeAttrLabelPosition(1)}>添加元素
handleChangeAttrLabelPosition(2)}>元素属性
-
-
+
+ {/* 页面节点树*/} +
+
根节点
+
+
-
+ {/*新增元素*/} +
+ +
-
+ {/*元素属性*/} +
+
+
样式属性
+
+
+
父元素相关属性
+
+
+
+
元素属性
+
+
+
+
+
+
事件
+
+
点击事件
+
+
+
+
数据关系
+
+
diff --git a/src/view/EditPageNew/index.module.scss b/src/view/EditPageNew/index.module.scss index 8424ae8..46d8b65 100644 --- a/src/view/EditPageNew/index.module.scss +++ b/src/view/EditPageNew/index.module.scss @@ -334,6 +334,7 @@ display: flex; align-items: center; justify-content: center; + box-shadow: -3px 3px 10px -3px #33333333; & > img { width: 25px; @@ -356,6 +357,7 @@ display: flex; align-items: center; justify-content: center; + box-shadow: -3px 3px 10px -3px #33333333; & > img { width: 25px; @@ -432,7 +434,7 @@ & > div.linner { flex-shrink: 1 !important; background: #fff; - //width: 1px; + width: 2px; height: 100%; border-radius: 10px; }