You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lichaojun 2ee0cac94c feat: 拖拽布局-列表页 1 month ago
..
css feat: 拖拽布局-列表页 1 month ago
image feat: 拖拽布局-当前选中焦点设置 1 month ago
js feat: 拖拽布局-列表页 1 month ago
layui feat: 拖拽布局-画布归为组件一样处理 1 month ago
GEMINI.md feat: 拖拽布局-优化问题 1 month ago
README.md feat: 拖拽布局-列表页 1 month ago
config.json feat: 拖拽布局-完整demo数据结构 2 months ago
data.json feat: 拖拽布局-列表页 1 month ago
index.html feat: 拖拽布局-优化问题 1 month ago
list.html feat: 拖拽布局-列表页 1 month ago

README.md

index.js核心逻辑

整个逻辑可以分为以下几个主要部分:

  1. 全局结构和初始化

代码被包裹在一个立即执行函数表达式 (function () { ... })(); 中,这样做可以创建一个独立的作用域,避免污染全局命名空间。

  • 核心对象:

    • main 和 welcome: 这两个对象分别存储了“主画布”和“欢迎画布”的状态,包括它们的 grid 实例和 initData布局数据
    • currentComponent: 一个全局变量,用于存放当前被选中的组件或画布对象。
  • init(type, self) 函数 (初始化函数): 这是整个应用启动的入口点。

    1. 加载数据: 尝试从 localStorage 中读取名为 ${type}Data (例如 mainData 或 welcomeData) 的数据。如果存在,就解析它;如果不存在,就创建一套默认的画布配置。
    2. 坐标转换: 在加载现有数据后,会调用 conputedInitData('init', self)这个关键函数会将存储的像素坐标x, y, w, h转换回 GridStack 能理解的栅格单位(行、列)。
    3. 初始化GridStack: 使用 GridStack.init() 来初始化一个网格布局的容器。GridStack.js 是一个强大的库,它负责处理组件的拖拽、缩放、碰撞检测等核心交互功能。这里配置了行高、边距、是否接受拖入组件等。
    4. 渲染初始组件: GridStack.init() 时传入的 children 数组(来自 initData会被自动渲染到画布上。
    5. 绑定事件:
      • 遍历所有已存在的组件,为它们调用 handleAddComponent 函数,以附加点击事件和设置初始视图。
      • 监听 grid 的 added 事件:当一个新组件被拖入时触发,同样调用 handleAddComponent。
      • 监听 grid 的 removed 事件:当一个组件被删除时触发 handleRemoveComponent。
  1. 组件处理逻辑
  • GridStack.setupDragIn(...): 这段代码定义了左侧“组件列表”中的可拖拽项。它为“图片”和“文本”这两种组件预设了默认的属性如尺寸、类型、名称、背景色等。当用户把它们拖到画布上时GridStack 就会使用这些预设值创建一个新的组件实例。

  • handleAddComponent(component, self) 函数:

    1. 分配ID: 如果组件没有ID则使用 generateUniqueId() 生成一个唯一ID。
    2. 更新视图: 调用 setComponentView(component),根据组件的属性(如背景色、图片路径、文字内容等)来渲染其实际外观。
    3. 绑定点击事件: 为每个组件的DOM元素添加一个点击事件监听器。
      • 当组件被点击时,首先会清除上一个选中组件的“聚焦样式”(通过 clearOldFocusStyle
      • 将 currentComponent 变量更新为当前点击的组件。
      • 调用 setCurrentComponentProps(currentComponent),将该组件的属性填充到右侧的“属性面板”中。
      • 调用 setCurrentFocusStyle(),为当前组件添加“聚焦样式”(例如,改变边框、背景或应用 transform: scale
  1. 属性面板交互

右侧的属性面板是用户配置组件和画布的地方。

  • setCurrentComponentProps(component) 函数: 这是一个核心的UI更新函数。

    • 它接收一个组件(或画布)对象。
    • 根据对象的类型和拥有的属性hasOwnProperty动态地显示或隐藏表单中的各个输入框如名称、背景色、图片URL、字体大小等
    • 将组件的当前值填充到对应的输入框中。例如,文本组件会显示字体大小、颜色等输入框,而图片组件则不会。
  • bindComponentEvents() 函数: 这个函数为属性面板中的所有输入框绑定了事件。

    • 它使用一个 elementMappings 对象来定义每个输入框ID、它应该监听的事件blur 或 change以及它对应的组件属性名。
    • 当用户修改了某个输入框的值并触发事件(如输入框失去焦点)时,会执行一个回调:
      1. 获取输入框的新值。
      2. 更新 currentComponent 对象上对应的属性。
      3. 调用 setComponentView(currentComponent, [property]),并传入被修改的属性名,从而即时更新画布上组件的视觉样式,实现“所见即所得”。
  1. 数据保存与坐标计算
  • handleSaveClick() 函数:

    • 当点击“保存”按钮时,它会分别调用 main.grid.save() 和 welcome.grid.save()。grid.save() 是 GridStack 的一个方法,它会返回一个描述所有子组件布局信息的数组(包含 x, y, w, h 等栅格单位的坐标)。
    • 将获取到的组件数组存回 main.initData.children 和 welcome.initData.children。
    • 调用 conputedInitData('save', ...)。
  • conputedInitData(type, self) 函数: 这个函数是数据持久化的关键。

    • 保存时 (type === 'save'): 它遍历所有组件,将 GridStack 提供的栅格坐标(如 x: 1, w: 2乘以一个固定的缩放因子如画布宽度 1920 / 12将其转换成像素单位的坐标。这样做是为了让保存的数据与具体的 GridStack 配置解耦。
    • 初始化时 (type === 'init'): 它执行相反的操作,将之前保存的像素坐标除以缩放因子,还原成 GridStack 能识别的栅格坐标。
  1. 其他功能
  • handleTabSwitch(): 处理“主画布”和“欢迎画布”之间的切换,通过控制 display 样式来显示或隐藏对应的画布。
  • setComponentView(): 使用了“策略模式”componentStrategies 对象),为不同的组件属性(如 background, image, fontSize定义了专门的DOM操作函数使代码更清晰、易于扩展。
  • 焦点样式: setCurrentFocusStyle 和 clearOldFocusStyle 两个函数配合,实现了选中组件时的高亮视觉效果。

总结

整个应用的运行流程如下:

  1. 启动: 页面加载后init() 函数被调用,分别初始化 main 和 welcome 两个画布。
  2. 加载/创建: init() 从 localStorage 加载布局数据,或创建新布局,并使用 GridStack.js 渲染出来。
  3. 交互:
    • 用户可以从左侧拖动新组件到画布上。
    • 用户可以点击画布上的任一组件,此时该组件被“激活”。
  4. 配置:
    • 组件被激活后,右侧属性面板会显示其所有可配置属性。
    • 用户在属性面板中修改值,所做的更改会实时反映在画布的组件上。
  5. 保存:
    • 用户点击“保存”按钮。
    • 当前两个画布的布局(使用栅格坐标)被 GridStack 导出。
    • 坐标被转换为像素单位。
    • 最终的布局数据包含像素坐标被序列化成JSON字符串存储到 localStorage 中。

下次用户打开页面时流程会从第1步重新开始但这次会从 localStorage 加载上次保存的数据,从而恢复之前的布局状态。

历史任务

  • 在index.js中conputedInitData函数当type为save的时候帮我处理带有下划线的数据数据结构参考data.json和下面示例(xxx为省略结果不代表真实值)
mainData = {
    xxx,
    children: [{
        "focusedStyle_background": "",
        "focusedStyle_border_width": 0,
        "focusedStyle_border_color": "",
        "focusedStyle_scale": 1,
        ...这里后续还有可能扩展(例如)
        "other_name": "",
    }]
}

最终转换为(最终转换的结果需要保留原址类似focusedStyle_background)
mainData = {
    xxx,
    children: [{
        focusedStyle: {
            background: xxx,
            border: {
                width: xxx,
                color: xxx
            },
            scale: xxx
        },
        ...这里后续还有扩展也保持一样的转换(例如)
        other: {
            name: xxx
        }
    }]
}

历史任务

  • 实现一个列表页列表页里面包含多个index.html生成的模板
  • 列表页面的js文件格式请参照index.js的写法包括使用jquery进行初始化等保持代码风格一致
  • 列表页面一个子项宽度为400px,从左到右依次排列,超出换行,并且保证换行后每行的每个子项左右间距相同
  • 子项高度根据内容自动撑开
  • 子项的数据参考data.json
子项布局参考

image  (占满,固定高度)

name + action btn action btn 点击后弹出选项 1. 复制 2. 删除 3. 编辑 4. 应用)

date
  • 子项数据直接使用data.json并复制内部子项10份更改id和name保证不重复

对话记录

本次会话总结

  • 任务: 在 js/index.jsconputedInitData 函数中,当 type 为 'save' 时,处理带有下划线的属性(例如 focusedStyle_background),将其转换为嵌套对象结构(例如 focusedStyle: { background: xxx }),并保留原始带下划线的属性。
  • 实现方式:
    1. 创建了一个名为 transformUnderscoreData 的辅助函数。该函数遍历给定对象的属性,如果属性名包含下划线,则根据下划线拆分属性名,并创建相应的嵌套对象。对于如 focusedStyle_border_widthfocusedStyle_border_color,会进一步嵌套到 focusedStyle.border 对象下。
    2. conputedInitData 函数的 if (type === 'save') 代码块中,对 initDataCopy.children 数组中的每个 item 调用 transformUnderscoreData 函数,将其返回的转换后的属性合并回 item 对象,从而实现数据结构的转换。
  • 文件改动:
    • js/index.js: 添加 transformUnderscoreData 辅助函数,并修改 conputedInitData 函数以调用此辅助函数。

本次会话总结

  • 任务: 根据用户反馈,优化 transformUnderscoreData 辅助函数,使其更加通用。
  • 优化点:
    • 旧的实现方式依赖硬编码的属性名(如 border)来创建嵌套对象。
    • 新的实现方式通过遍历由下划线分割的属性名部分,动态地创建任意层级的嵌套对象。例如,a_b_c 会被正确地转换为 { a: { b: { c: value } } }
  • 文件改动:
    • js/index.js: 更新了 transformUnderscoreData 函数的实现逻辑,使其更加灵活和通用。

本次会话总结

  • 任务: 优化 list.html 列表页的布局和交互功能。
  • 布局优化点:
    • 最后一行对齐问题: 解决了由于 justify-content: space-around 导致的最后一行项目居中或分散的问题,通过将 #list-paneljustify-content 改为 flex-start,确保最后一行从左到右对齐。
    • 子项宽度及排版: 将 .list-item 的固定宽度 400px 修改为 calc(25% - 7.5px)实现了每行4个子项的弹性布局并合理处理了子项间的 gap 间距。
    • 底部内边距缺失: 解决了 #list-panel 底部 padding 显示不全的问题,通过为 #list-panel 添加 width: 100%,确保其在 flex 父容器中正确撑开。
  • 交互优化点:
    • 模板名称显示: 修复了列表项中模板名称 (item.name) 未显示的问题,通过在 js/list.js 中动态生成的 HTML 结构中添加 <span class="list-item-name">${item.name}</span>
    • 操作按钮样式优化: 优化了点击“...”按钮弹出的操作菜单(.action-menu)的样式:
      • .action-menu 添加 min-width: 80px;,防止文字换行。
      • .action-menu button 添加 white-space: nowrap; 禁止文字换行,将 text-align 改为 center,并增加 transition 效果。
    • 操作按钮事件: 为操作菜单中的四个按钮(复制、删除、编辑、应用)添加了点击事件监听,点击时会在控制台打印相应的操作名称。
  • 文件改动:
    • css/list.css: 修改了 #list-panel.action-menu 的样式,以及 .list-item.action-menu button 的样式。
    • js/list.js: 修改了 listItem 的 HTML 模板以显示名称,并添加了四个操作按钮的点击事件监听器。