feat: 拖拽布局

master
LCJ-MinYa 4 weeks ago
parent 7c9d012f7e
commit ab4de42fdc

@ -0,0 +1,45 @@
## 项目概述
帮我实现一个纯静态的只包含html和css的页面不需要js代码的实现分为左中右布局其中中间布局内容不需要你添加我自己已有代码实现
## 技术栈
- **html5**
- **css3**
- **javascript**
## DOM结构
```html
<div id="app">
<!-- 组件列表 -->
<div id="components-panel"></div>
<!-- 画布容器 -->
<div id="canvas-panel"></div>
<!-- 当前选中组件属性 -->
<div id="props-panel"></div>
</div>
```
## 组件列表
* 组件列表分为两栏布局,当分辨率过低时只支持一栏布局
* 组件UI展示需要有代表性能让用户清晰知道当前组件类型
* 组件列表
- 轮播图
- 搜索
- 热门频道
- 直播
- 推荐内容
- 快捷入口
## 组件属性
* 以表单形式展示
* 表单字段为以下字段:
- 名称
- 图片
- 描述
- 跳转链接
## 要求
* 页面自适应不需要兼容移动端左边1份右边1份中间占5份并且占满剩余全部无论左中右内部内容超出页面高度都只在内部滚动
* 页面滚动条样式美化
* 页面布局要求美观大方,间距合理,色彩风格一致
* 不使用第三方UI库
* css相关代码请放在css/index.css文件中

@ -0,0 +1 @@
.grid-stack{position:relative}.grid-stack-rtl{direction:ltr}.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack-placeholder>.placeholder-content{background-color:rgba(0,0,0,.1);margin:0;position:absolute;width:auto;z-index:0!important}.grid-stack>.grid-stack-item{position:absolute;padding:0;top:0;left:0;width:var(--gs-column-width);height:var(--gs-cell-height)}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item.size-to-content:not(.size-to-content-max)>.grid-stack-item-content{overflow-y:hidden}.grid-stack>.grid-stack-item>.grid-stack-item-content,.grid-stack>.grid-stack-placeholder>.placeholder-content{top:var(--gs-item-margin-top);right:var(--gs-item-margin-right);bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack-item>.ui-resizable-ne,.grid-stack-item>.ui-resizable-nw,.grid-stack-item>.ui-resizable-se,.grid-stack-item>.ui-resizable-sw{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="%23666" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 20 20"><path d="m10 3 2 2H8l2-2v14l-2-2h4l-2 2"/></svg>');background-repeat:no-repeat;background-position:center}.grid-stack-item>.ui-resizable-ne{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-sw{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-nw{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-se{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:var(--gs-item-margin-top);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:var(--gs-item-margin-top);left:25px;right:25px}.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:var(--gs-item-margin-top);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px;right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:var(--gs-item-margin-bottom);right:25px}.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px;left:var(--gs-item-margin-left)}.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack-item.ui-draggable-dragging{will-change:left,top}.grid-stack-item.ui-resizable-resizing{will-change:width,height}.ui-draggable-dragging,.ui-resizable-resizing{z-index:10000}.ui-draggable-dragging>.grid-stack-item-content,.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px rgba(0,0,0,.2);opacity:.8}.grid-stack-animate,.grid-stack-animate .grid-stack-item{transition:left .3s,top .3s,height .3s,width .3s}.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack-animate .grid-stack-item.ui-resizable-resizing{transition:left 0s,top 0s,height 0s,width 0s}.grid-stack>.grid-stack-item[gs-y="0"]{top:0}.grid-stack>.grid-stack-item[gs-x="0"]{left:0}

@ -0,0 +1,197 @@
/* Global styles */
body,
html {
margin: 0;
padding: 0;
height: 100%;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f2f5;
color: #333;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#app {
display: flex;
height: 100vh;
}
/* Components panel */
#components-panel {
flex: 1;
padding: 10px;
background-color: #fff;
overflow-y: auto;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
gap: 10px;
border-right: 1px solid #e0e0e0;
}
.component-item {
width: calc(50% - 5px);
display: flex;
height: 80px;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
text-align: left;
cursor: pointer;
transition: all 0.3s ease;
background-color: #fafafa;
}
.component-item:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: #007bff;
}
.component-item i {
font-size: 18px;
margin-bottom: 5px;
color: #007bff;
}
.component-item span {
font-size: 14px;
}
/* Canvas panel */
#canvas-panel {
flex: 4;
height: 100%;
background-color: #f9f9f9;
margin: 0;
position: relative;
overflow-y: scroll;
}
.grid-stack-item-content {
cursor: pointer;
background: #fff;
}
.global-save-button-container {
position: absolute;
top: 20px;
right: 20px;
}
.global-save-button {
padding: 10px 20px;
background-color: #28a745;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.global-save-button:hover {
background-color: #218838;
}
/* Props panel */
#props-panel {
flex: 1;
padding: 20px;
background-color: #fff;
overflow-y: auto;
border-left: 1px solid #e0e0e0;
}
.form-item {
margin-bottom: 20px;
}
.form-item label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #555;
}
.form-item input,
.form-item textarea {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
transition: border-color 0.3s;
}
.form-item input:focus,
.form-item textarea:focus {
outline: none;
border-color: #007bff;
}
.form-actions {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.form-actions button {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
flex: 1;
margin: 0 5px;
}
.save-button {
background-color: #007bff;
color: #fff;
}
.save-button:hover {
background-color: #0056b3;
}
.delete-button {
background-color: #dc3545;
color: #fff;
}
.delete-button:hover {
background-color: #c82333;
}
/* Scrollbar styles */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #aaa;
}
/* Responsive layout */
@media (max-width: 1200px) {
#components-panel {
grid-template-columns: 1fr;
}
}

@ -0,0 +1,90 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Page Builder</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
/>
<link
rel="stylesheet"
href="css/index.css"
/>
<link
rel="stylesheet"
href="./css/gridstack.min.css"
/>
<script src="./js/gridstack-all.js"></script>
<script src="./js/jquery.min.js"></script>
</head>
<body>
<div id="app">
<!-- 组件列表 -->
<div id="components-panel">
<div class="component-item grid-stack-item"><i class="fas fa-images"></i><span>轮播图</span></div>
<div class="component-item grid-stack-item"><i class="fas fa-search"></i><span>搜索</span></div>
<div class="component-item grid-stack-item"><i class="fas fa-tv"></i><span>热门频道</span></div>
<div class="component-item grid-stack-item"><i class="fas fa-video"></i><span>直播</span></div>
<div class="component-item grid-stack-item"><i class="fas fa-thumbs-up"></i><span>推荐内容</span></div>
<div class="component-item grid-stack-item"><i class="fas fa-link"></i><span>快捷入口</span></div>
</div>
<!-- 画布容器 -->
<div id="canvas-panel">
<div class="global-save-button-container">
<button class="global-save-button">保存</button>
</div>
<div class="grid-stack"></div>
</div>
<!-- 当前选中组件属性 -->
<div id="props-panel">
<form>
<div class="form-item">
<label for="name">名称</label>
<input
type="text"
id="name"
/>
</div>
<div class="form-item">
<label for="image">图片</label>
<input
type="text"
id="image"
/>
</div>
<div class="form-item">
<label for="description">描述</label>
<textarea id="description"></textarea>
</div>
<div class="form-item">
<label for="link">跳转链接</label>
<input
type="text"
id="link"
/>
</div>
<div class="form-actions">
<button
type="button"
class="save-button"
>
保存
</button>
<button
type="button"
class="delete-button"
>
删除
</button>
</div>
</form>
</div>
</div>
</body>
</html>
<script src="./js/index.js"></script>

File diff suppressed because one or more lines are too long

@ -0,0 +1,70 @@
(function () {
var grid = null;
/** 初始化 */
var init = function () {
grid = GridStack.init({
// 一行高度
cellHeight: 80,
// 间距
margin: 5,
minRow: 5,
acceptWidgets: true,
});
GridStack.setupDragIn('#components-panel > .component-item', undefined, [
{ w: 12, h: 2, content: '轮播图' },
{ w: 2, h: 1, content: '搜索' },
]);
console.log(grid, 'grid实例');
var initData = [
// {
// x: 0,
// y: 0,
// w: 2,
// h: 2,
// content: 'item 1',
// data: {
// name: 'item 1',
// },
// },
// {
// x: 2,
// y: 3,
// w: 3,
// content: 'item 2',
// data: {
// name: 'item 2',
// },
// },
];
// grid.load(initData);
grid.engine.nodes.forEach((item) => {
item.el.firstChild.addEventListener('click', () => {
console.log(item);
});
});
grid.on('added', function (_event, itemArray) {
console.log(itemArray, '这里触发了添加了added事件');
itemArray.forEach((item) => {
item.el.firstChild.addEventListener('click', () => {
console.log(item);
});
});
});
};
$('.save').click(function () {
grid.addWidget({ w: 2, content: 'item 1' });
grid.engine.nodes[0].data.name += '_test';
console.log(grid.save());
console.log(grid);
});
/** 执行方法 */
$(function () {
init();
});
})();

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save