Commit 40754181 authored by 郝聪敏's avatar 郝聪敏

feature: 低代码demo1.0

parent 6372a614
import { Controller, Context } from 'egg';
import { deserialize } from '@hubcarl/json-typescript-mapper';
export default class ActivityController extends Controller {
public async home(ctx: Context) {
await ctx.render('activity.js', { url: ctx.url });
}
}
\ No newline at end of file
import { Controller, Context } from 'egg'; import { Controller, Context } from 'egg';
import { deserialize } from '@hubcarl/json-typescript-mapper'; import { deserialize } from '@hubcarl/json-typescript-mapper';
import Article from '../model/article'; // import Article from '../model/article';
import Condition from '../lib/condition'; import Condition from '../lib/condition';
export default class AdminController extends Controller { export default class AdminController extends Controller {
...@@ -15,21 +15,21 @@ export default class AdminController extends Controller { ...@@ -15,21 +15,21 @@ export default class AdminController extends Controller {
public async list(ctx: Context) { public async list(ctx: Context) {
const condition = deserialize(Condition, ctx.request.body); const condition = deserialize(Condition, ctx.request.body);
ctx.body = await ctx.service.article.getArtilceList(condition); // ctx.body = await ctx.service.article.getArtilceList(condition);
} }
public async add(ctx: Context) { public async add(ctx: Context) {
const article = deserialize(Article, ctx.request.body); // const article = deserialize(Article, ctx.request.body);
ctx.body = await ctx.service.article.saveArticle(article); // ctx.body = await ctx.service.article.saveArticle(article);
} }
public async del(ctx: Context) { public async del(ctx: Context) {
const { id } = ctx.request.body; const { id } = ctx.request.body;
ctx.body = await ctx.service.article.deleteArticle(id); // ctx.body = await ctx.service.article.deleteArticle(id);
} }
public async detail(ctx: Context) { public async detail(ctx: Context) {
const { id } = ctx.params; const { id } = ctx.params;
ctx.body = await ctx.service.article.query({ id: Number(id) }); // ctx.body = await ctx.service.article.query({ id: Number(id) });
} }
} }
\ No newline at end of file
import { Controller, Context } from 'egg'; import { Controller, Context } from 'egg';
import { deserialize } from '@hubcarl/json-typescript-mapper'; import { deserialize } from '@hubcarl/json-typescript-mapper';
export default class AdminController extends Controller { export default class EditorController extends Controller {
public async home(ctx: Context) { public async home(ctx: Context) {
await ctx.renderClient('editor.js', {}); await ctx.renderClient('editor.js', {});
} }
public async save(ctx: Context) {
const pageData = ctx.request.body;
ctx.body = await ctx.model.PageInfo.create({page: JSON.stringify(pageData)});
}
public async update(ctx: Context) {
const pageInfo = ctx.request.body;
ctx.body = await ctx.model.PageInfo.update(pageInfo, {where: { id: +pageInfo.pageId }});
}
public async get(ctx: Context) {
console.log('pageId', ctx.params.pageId);
ctx.body = await ctx.model.PageInfo.findOne({where: { id: +ctx.params.pageId }});
}
} }
\ No newline at end of file
'use strict';
import { JsonProperty } from '@hubcarl/json-typescript-mapper';
export default class Article {
@JsonProperty('id')
public id?: string;
@JsonProperty('title')
public title?: string;
@JsonProperty('summary')
public summary?: string;
@JsonProperty('categoryId')
public categoryId?: number;
@JsonProperty('tag')
public tag?: string ;
@JsonProperty('categoryId')
public authorId?: number;
@JsonProperty('createTime')
public createTime?: number;
@JsonProperty('hits')
public hits?: number;
@JsonProperty('url')
public url?: string;
@JsonProperty('status')
public status?: number;
@JsonProperty('content')
public content?: string;
// constructor must be init everyone JsonProperty
constructor() {
this.id = '';
this.title = '';
this.summary = '';
this.categoryId = -1;
this.tag = '';
this.authorId = -1;
this.url = '';
this.status = 0;
this.hits = 0;
this.content = '';
this.createTime = Date.now();
}
}
\ No newline at end of file
module.exports = app => {
const { INTEGER, TEXT } = app.Sequelize;
console.log('model pageInfo');
const pageInfo = app.model.define("pageInfo", {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
page: {
field: "page_data",
type: TEXT,
},
}, {
createdAt: "created_at",
tableName: "page_info",
updatedAt: "updated_at",
});
return pageInfo;
};
...@@ -7,8 +7,12 @@ export default (application: Application) => { ...@@ -7,8 +7,12 @@ export default (application: Application) => {
router.post('/admin/api/article/add', controller.admin.add); router.post('/admin/api/article/add', controller.admin.add);
router.post('/admin/api/article/del', controller.admin.del); router.post('/admin/api/article/del', controller.admin.del);
router.get('/admin/api/article/:id', controller.admin.detail); router.get('/admin/api/article/:id', controller.admin.detail);
router.post('/editor/save', controller.editor.save);
router.post('/editor/update', controller.editor.update);
router.get('/editor/get/:pageId', controller.editor.get);
router.get('/', controller.admin.login); router.get('/', controller.admin.login);
router.get('/admin', controller.admin.home); router.get('/admin', controller.admin.home);
router.get('/admin/*', controller.admin.home); router.get('/admin/*', controller.admin.home);
router.get('/editor', controller.editor.home); router.get('/editor', controller.editor.home);
router.get('/activity/:id', controller.activity.home);
}; };
\ No newline at end of file
import { Context, Service } from 'egg'; // import { Context, Service } from 'egg';
import { deserialize } from '@hubcarl/json-typescript-mapper'; // import { deserialize } from '@hubcarl/json-typescript-mapper';
import Colllection from '../lib/db/collection'; // import Colllection from '../lib/db/collection';
import Article from '../model/article'; // import Article from '../model/article';
import Condition from '../lib/condition'; // import Condition from '../lib/condition';
export default class ArticeService extends Service { // export default class ArticeService extends Service {
private context: Context; // private context: Context;
private colllection: Colllection; // private colllection: Colllection;
constructor(ctx: Context) { // constructor(ctx: Context) {
super(ctx); // super(ctx);
this.context = ctx; // this.context = ctx;
this.colllection = new Colllection(ctx.db, 'article'); // this.colllection = new Colllection(ctx.db, 'article');
} // }
public async getArtilceList(condition: Condition) { // public async getArtilceList(condition: Condition) {
if (condition.categoryId) { // if (condition.categoryId) {
condition.where.categoryId = condition.categoryId; // condition.where.categoryId = condition.categoryId;
} // }
if (condition.status) { // if (condition.status) {
condition.where.status = condition.status; // condition.where.status = condition.status;
} // }
if (condition.title) { // if (condition.title) {
condition.like.title = condition.title; // condition.like.title = condition.title;
} // }
return this.colllection.getPager(condition); // return this.colllection.getPager(condition);
} // }
public async saveArticle(data: object) { // public async saveArticle(data: object) {
const article: Article = deserialize(Article, data); // const article: Article = deserialize(Article, data);
if (article.id) { // if (article.id) {
return this.colllection.update({ id: article.id }, article); // return this.colllection.update({ id: article.id }, article);
} // }
article.id = this.context.db.getUniqueId(); // article.id = this.context.db.getUniqueId();
this.colllection.add(article); // this.colllection.add(article);
return article; // return article;
} // }
public async query(json: object) { // public async query(json: object) {
return this.colllection.query(json); // return this.colllection.query(json);
} // }
public async deleteArticle(id: string) { // public async deleteArticle(id: string) {
return this.colllection.delete({ id }); // return this.colllection.delete({ id });
} // }
} // }
import { Vue, Component, Prop } from 'vue-property-decorator';
import cherryUi from '../../../../../node_modules/@qg/cherry-ui/src/index';
// import cherryUi from '@qg/cherry-ui';
// import '@qg/cherry-ui/dist/cherry.css';
Vue.use(cherryUi);
@Component({
name: 'Layout'
})
export default class Layout extends Vue {
@Prop({ type: String, default: 'egg' }) title?: string;
@Prop({ type: String, default: 'Vue TypeScript Framework, Server Side Render' }) description?: string;
@Prop({ type: String, default: 'Vue,TypeScript,Isomorphic' }) keywords?: string;
isNode: boolean = EASY_ENV_IS_NODE;
created() {
console.log('>>EASY_ENV_IS_NODE create', EASY_ENV_IS_NODE);
}
}
\ No newline at end of file
<template>
<html v-if="isNode" style="font-size: 10vw;">
<head>
<title>{{title}}</title>
<meta name="keywords" :content="keywords">
<meta name="description" :content="description">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="/public/asset/css/bootstrap.min.css">
</head>
<body>
<div id="app"><slot></slot></div>
</body>
</html>
<div v-else-if="!isNode" id="app"><slot></slot></div>
</template>
<style>
</style>
<script lang="ts" src="./index.ts"></script>
\ No newline at end of file
...@@ -2,6 +2,7 @@ import { Vue, Component, Prop } from 'vue-property-decorator'; ...@@ -2,6 +2,7 @@ import { Vue, Component, Prop } from 'vue-property-decorator';
import iView from 'iview'; import iView from 'iview';
import cherryUi from '../../../../../node_modules/@qg/cherry-ui/src/index'; import cherryUi from '../../../../../node_modules/@qg/cherry-ui/src/index';
import 'iview/dist/styles/iview.css'; import 'iview/dist/styles/iview.css';
import '@qg/cherry-ui/dist/cherry.css';
Vue.use(iView); Vue.use(iView);
Vue.use(cherryUi); Vue.use(cherryUi);
......
...@@ -8,6 +8,7 @@ export default class App { ...@@ -8,6 +8,7 @@ export default class App {
} }
bootstrap() { bootstrap() {
console.log('EASY_ENV_IS_NODE', EASY_ENV_IS_NODE);
if (EASY_ENV_IS_NODE) { if (EASY_ENV_IS_NODE) {
return this.server(); return this.server();
} }
......
<template>
<span class="text">
{{text}}
<button>xxx</button>
<span class="vue-draggable-handle"></span>
</span>
</template>
<style>
.vue-draggable-handle {
position: absolute;
width: 20px;
height: 20px;
top: 0;
left: 0;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
background-position: bottom right;
padding: 0 8px 8px 0;
background-repeat: no-repeat;
background-origin: content-box;
box-sizing: border-box;
cursor: pointer;
}
</style>
<script>
export default {
name: "CustomDragElement",
props: {
text : {
type: String,
default: "x",
},
},
data: function() {
return {
}
},
mounted: function() {
console.log("### " + this.text + " ready!");
},
}
</script>
\ No newline at end of file
<template>
<div ref="item"
class="vue-grid-item"
:class="classObj"
:style="style"
>
<slot></slot>
<span v-if="resizableAndNotStatic" ref="handle" :class="resizableHandleClass"></span>
<!--<span v-if="draggable" ref="dragHandle" class="vue-draggable-handle"></span>-->
</div>
</template>
<style>
.vue-grid-item {
transition: all 200ms ease;
transition-property: left, top, right;
/* add right for rtl */
}
.vue-grid-item.no-touch {
-ms-touch-action: none;
touch-action: none;
}
.vue-grid-item.cssTransforms {
transition-property: transform;
left: 0;
right: auto;
}
.vue-grid-item.cssTransforms.render-rtl {
left: auto;
right: 0;
}
.vue-grid-item.resizing {
opacity: 0.6;
z-index: 3;
}
.vue-grid-item.vue-draggable-dragging {
transition:none;
z-index: 3;
}
.vue-grid-item.vue-grid-placeholder {
background: red;
opacity: 0.2;
transition-duration: 100ms;
z-index: 2;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
.vue-grid-item > .vue-resizable-handle {
position: absolute;
width: 20px;
height: 20px;
bottom: 0;
right: 0;
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
background-position: bottom right;
padding: 0 3px 3px 0;
background-repeat: no-repeat;
background-origin: content-box;
box-sizing: border-box;
cursor: se-resize;
}
.vue-grid-item > .vue-rtl-resizable-handle {
bottom: 0;
left: 0;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAuMDAwMDAwMDAwMDAwMDAyIiBoZWlnaHQ9IjEwLjAwMDAwMDAwMDAwMDAwMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KIDwhLS0gQ3JlYXRlZCB3aXRoIE1ldGhvZCBEcmF3IC0gaHR0cDovL2dpdGh1Yi5jb20vZHVvcGl4ZWwvTWV0aG9kLURyYXcvIC0tPgogPGc+CiAgPHRpdGxlPmJhY2tncm91bmQ8L3RpdGxlPgogIDxyZWN0IGZpbGw9Im5vbmUiIGlkPSJjYW52YXNfYmFja2dyb3VuZCIgaGVpZ2h0PSIxMiIgd2lkdGg9IjEyIiB5PSItMSIgeD0iLTEiLz4KICA8ZyBkaXNwbGF5PSJub25lIiBvdmVyZmxvdz0idmlzaWJsZSIgeT0iMCIgeD0iMCIgaGVpZ2h0PSIxMDAlIiB3aWR0aD0iMTAwJSIgaWQ9ImNhbnZhc0dyaWQiPgogICA8cmVjdCBmaWxsPSJ1cmwoI2dyaWRwYXR0ZXJuKSIgc3Ryb2tlLXdpZHRoPSIwIiB5PSIwIiB4PSIwIiBoZWlnaHQ9IjEwMCUiIHdpZHRoPSIxMDAlIi8+CiAgPC9nPgogPC9nPgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxsaW5lIGNhbnZhcz0iI2ZmZmZmZiIgY2FudmFzLW9wYWNpdHk9IjEiIHN0cm9rZS1saW5lY2FwPSJ1bmRlZmluZWQiIHN0cm9rZS1saW5lam9pbj0idW5kZWZpbmVkIiBpZD0ic3ZnXzEiIHkyPSItNzAuMTc4NDA3IiB4Mj0iMTI0LjQ2NDE3NSIgeTE9Ii0zOC4zOTI3MzciIHgxPSIxNDQuODIxMjg5IiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSIjMDAwIiBmaWxsPSJub25lIi8+CiAgPGxpbmUgc3Ryb2tlPSIjNjY2NjY2IiBzdHJva2UtbGluZWNhcD0idW5kZWZpbmVkIiBzdHJva2UtbGluZWpvaW49InVuZGVmaW5lZCIgaWQ9InN2Z181IiB5Mj0iOS4xMDY5NTciIHgyPSIwLjk0NzI0NyIgeTE9Ii0wLjAxODEyOCIgeDE9IjAuOTQ3MjQ3IiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz4KICA8bGluZSBzdHJva2UtbGluZWNhcD0idW5kZWZpbmVkIiBzdHJva2UtbGluZWpvaW49InVuZGVmaW5lZCIgaWQ9InN2Z183IiB5Mj0iOSIgeDI9IjEwLjA3MzUyOSIgeTE9IjkiIHgxPSItMC42NTU2NCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiM2NjY2NjYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+);
background-position: bottom left;
padding-left: 3px;
background-repeat: no-repeat;
background-origin: content-box;
cursor: sw-resize;
right: auto;
}
.vue-grid-item.disable-userselect {
user-select: none;
}
</style>
<script>
import {setTopLeft, setTopRight, setTransformRtl, setTransform} from '../helpers/utils';
import {getControlPosition, createCoreData} from '../helpers/draggableUtils';
import {getColsFromBreakpoint} from '../helpers/responsiveUtils';
import {getDocumentDir} from "../helpers/DOM";
// var eventBus = require('./eventBus');
import '@interactjs/auto-start'
import '@interactjs/actions/drag'
import '@interactjs/actions/resize'
import '@interactjs/modifiers'
import '@interactjs/dev-tools'
import interact from '@interactjs/interact'
export default {
name: "GridItem",
props: {
/*cols: {
type: Number,
required: true
},*/
/*containerWidth: {
type: Number,
required: true
},
rowHeight: {
type: Number,
required: true
},
margin: {
type: Array,
required: true
},
maxRows: {
type: Number,
required: true
},*/
isDraggable: {
type: Boolean,
required: false,
default: null
},
isResizable: {
type: Boolean,
required: false,
default: null
},
/*useCssTransforms: {
type: Boolean,
required: true
},
*/
static: {
type: Boolean,
required: false,
default: false
},
minH: {
type: Number,
required: false,
default: 1
},
minW: {
type: Number,
required: false,
default: 1
},
maxH: {
type: Number,
required: false,
default: Infinity
},
maxW: {
type: Number,
required: false,
default: Infinity
},
x: {
type: Number,
required: true
},
y: {
type: Number,
required: true
},
w: {
type: Number,
required: true
},
h: {
type: Number,
required: true
},
i: {
required: true
},
dragIgnoreFrom: {
type: String,
required: false,
default: 'a, button'
},
dragAllowFrom: {
type: String,
required: false,
default: null
},
resizeIgnoreFrom: {
type: String,
required: false,
default: 'a, button'
},
},
inject: ["eventBus", "layout"],
data: function () {
return {
cols: 1,
containerWidth: 100,
rowHeight: 30,
margin: [10, 10],
maxRows: Infinity,
draggable: null,
resizable: null,
useCssTransforms: true,
useStyleCursor: true,
isDragging: false,
dragging: null,
isResizing: false,
resizing: null,
lastX: NaN,
lastY: NaN,
lastW: NaN,
lastH: NaN,
style: {},
rtl: false,
dragEventSet: false,
resizeEventSet: false,
previousW: null,
previousH: null,
previousX: null,
previousY: null,
innerX: this.x,
innerY: this.y,
innerW: this.w,
innerH: this.h
}
},
created () {
let self = this;
// Accessible refernces of functions for removing in beforeDestroy
self.updateWidthHandler = function (width) {
self.updateWidth(width);
};
self.compactHandler = function (layout) {
self.compact(layout);
};
self.setDraggableHandler = function (isDraggable) {
if (self.isDraggable === null) {
self.draggable = isDraggable;
}
};
self.setResizableHandler = function (isResizable) {
if (self.isResizable === null) {
self.resizable = isResizable;
}
};
self.setRowHeightHandler = function (rowHeight) {
self.rowHeight = rowHeight;
};
self.setMaxRowsHandler = function (maxRows) {
self.maxRows = maxRows;
};
self.directionchangeHandler = () => {
this.rtl = getDocumentDir() === 'rtl';
this.compact();
};
self.setColNum = (colNum) => {
self.cols = parseInt(colNum);
}
this.eventBus.$on('updateWidth', self.updateWidthHandler);
this.eventBus.$on('compact', self.compactHandler);
this.eventBus.$on('setDraggable', self.setDraggableHandler);
this.eventBus.$on('setResizable', self.setResizableHandler);
this.eventBus.$on('setRowHeight', self.setRowHeightHandler);
this.eventBus.$on('setMaxRows', self.setMaxRowsHandler);
this.eventBus.$on('directionchange', self.directionchangeHandler);
this.eventBus.$on('setColNum', self.setColNum)
this.rtl = getDocumentDir() === 'rtl';
},
beforeDestroy: function(){
let self = this;
//Remove listeners
this.eventBus.$off('updateWidth', self.updateWidthHandler);
this.eventBus.$off('compact', self.compactHandler);
this.eventBus.$off('setDraggable', self.setDraggableHandler);
this.eventBus.$off('setResizable', self.setResizableHandler);
this.eventBus.$off('setRowHeight', self.setRowHeightHandler);
this.eventBus.$off('setMaxRows', self.setMaxRowsHandler);
this.eventBus.$off('directionchange', self.directionchangeHandler);
this.eventBus.$off('setColNum', self.setColNum);
if (this.interactObj) {
this.interactObj.unset() // destroy interact intance
}
},
mounted: function () {
if (this.layout.responsive && this.layout.lastBreakpoint) {
this.cols = getColsFromBreakpoint(this.layout.lastBreakpoint, this.layout.cols);
} else {
this.cols = this.layout.colNum;
}
this.rowHeight = this.layout.rowHeight;
this.containerWidth = this.layout.width !== null ? this.layout.width : 100;
this.margin = this.layout.margin !== undefined ? this.layout.margin : [10, 10];
this.maxRows = this.layout.maxRows;
if (this.isDraggable === null) {
this.draggable = this.layout.isDraggable;
} else {
this.draggable = this.isDraggable;
}
if (this.isResizable === null) {
this.resizable = this.layout.isResizable;
} else {
this.resizable = this.isResizable;
}
this.useCssTransforms = this.layout.useCssTransforms;
this.useStyleCursor = this.layout.useStyleCursor;
this.createStyle();
},
watch: {
isDraggable: function () {
this.draggable = this.isDraggable;
},
static: function () {
this.tryMakeDraggable();
this.tryMakeResizable();
},
draggable: function () {
this.tryMakeDraggable();
},
isResizable: function () {
this.resizable = this.isResizable;
},
resizable: function () {
this.tryMakeResizable();
},
rowHeight: function () {
this.createStyle();
this.emitContainerResized();
},
cols: function () {
this.tryMakeResizable();
this.createStyle();
this.emitContainerResized();
},
containerWidth: function () {
this.tryMakeResizable();
this.createStyle();
this.emitContainerResized();
},
x: function (newVal) {
this.innerX = newVal;
this.createStyle();
},
y: function (newVal) {
this.innerY = newVal;
this.createStyle();
},
h: function (newVal) {
this.innerH = newVal
this.createStyle();
// this.emitContainerResized();
},
w: function (newVal) {
this.innerW = newVal;
this.createStyle();
// this.emitContainerResized();
},
renderRtl: function () {
// console.log("### renderRtl");
this.tryMakeResizable();
this.createStyle();
},
minH: function () {
this.tryMakeResizable();
},
maxH: function () {
this.tryMakeResizable();
},
minW: function () {
this.tryMakeResizable();
},
maxW: function () {
this.tryMakeResizable();
},
"$parent.margin": function (margin) {
if (!margin || (margin[0] == this.margin[0] && margin[1] == this.margin[1])) {
return;
}
this.margin = margin.map(m => Number(m));
this.createStyle();
this.emitContainerResized();
},
},
computed: {
classObj() {
return {
'vue-resizable' : this.resizableAndNotStatic,
'static': this.static,
'resizing' : this.isResizing,
'vue-draggable-dragging' : this.isDragging,
'cssTransforms' : this.useCssTransforms,
'render-rtl' : this.renderRtl,
'disable-userselect': this.isDragging,
'no-touch': this.isAndroid && this.draggableOrResizableAndNotStatic
}
},
resizableAndNotStatic(){
return this.resizable && !this.static;
},
draggableOrResizableAndNotStatic(){
return (this.draggable || this.resizable) && !this.static;
},
isAndroid() {
return true;
// return navigator.userAgent.toLowerCase().indexOf("android") !== -1;
},
renderRtl() {
return (this.layout.isMirrored) ? !this.rtl : this.rtl;
},
resizableHandleClass() {
if (this.renderRtl) {
return 'vue-resizable-handle vue-rtl-resizable-handle';
} else {
return 'vue-resizable-handle';
}
}
},
methods: {
createStyle: function () {
if (this.x + this.w > this.cols) {
this.innerX = 0;
this.innerW = (this.w > this.cols) ? this.cols : this.w
} else {
this.innerX = this.x;
this.innerW = this.w;
}
let pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH);
if (this.isDragging) {
pos.top = this.dragging.top;
// Add rtl support
if (this.renderRtl) {
pos.right = this.dragging.left;
} else {
pos.left = this.dragging.left;
}
}
if (this.isResizing) {
pos.width = this.resizing.width;
pos.height = this.resizing.height;
}
let style;
// CSS Transforms support (default)
if (this.useCssTransforms) {
// Add rtl support
if (this.renderRtl) {
style = setTransformRtl(pos.top, pos.right, pos.width, pos.height);
} else {
style = setTransform(pos.top, pos.left, pos.width, pos.height);
}
} else { // top,left (slow)
// Add rtl support
if (this.renderRtl) {
style = setTopRight(pos.top, pos.right, pos.width, pos.height);
} else {
style = setTopLeft(pos.top, pos.left, pos.width, pos.height);
}
}
this.style = style;
},
emitContainerResized() {
// this.style has width and height with trailing 'px'. The
// resized event is without them
let styleProps = {};
for (let prop of ['width', 'height']) {
let val = this.style[prop];
let matches = val.match(/^(\d+)px$/);
if (! matches)
return;
styleProps[prop] = matches[1];
}
this.$emit("container-resized", this.i, this.h, this.w, styleProps.height, styleProps.width);
},
handleResize: function (event) {
if (this.static) return;
const position = getControlPosition(event);
// Get the current drag point from the event. This is used as the offset.
if (position == null) return; // not possible but satisfies flow
const {x, y} = position;
const newSize = {width: 0, height: 0};
let pos;
switch (event.type) {
case "resizestart": {
this.previousW = this.innerW;
this.previousH = this.innerH;
pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH);
newSize.width = pos.width;
newSize.height = pos.height;
this.resizing = newSize;
this.isResizing = true;
break;
}
case "resizemove": {
// console.log("### resize => " + event.type + ", lastW=" + this.lastW + ", lastH=" + this.lastH);
const coreEvent = createCoreData(this.lastW, this.lastH, x, y);
if (this.renderRtl) {
newSize.width = this.resizing.width - coreEvent.deltaX;
} else {
newSize.width = this.resizing.width + coreEvent.deltaX;
}
newSize.height = this.resizing.height + coreEvent.deltaY;
///console.log("### resize => " + event.type + ", deltaX=" + coreEvent.deltaX + ", deltaY=" + coreEvent.deltaY);
this.resizing = newSize;
break;
}
case "resizeend": {
//console.log("### resize end => x=" +this.innerX + " y=" + this.innerY + " w=" + this.innerW + " h=" + this.innerH);
pos = this.calcPosition(this.innerX, this.innerY, this.innerW, this.innerH);
newSize.width = pos.width;
newSize.height = pos.height;
// console.log("### resize end => " + JSON.stringify(newSize));
this.resizing = null;
this.isResizing = false;
break;
}
}
// Get new WH
pos = this.calcWH(newSize.height, newSize.width);
if (pos.w < this.minW) {
pos.w = this.minW;
}
if (pos.w > this.maxW) {
pos.w = this.maxW;
}
if (pos.h < this.minH) {
pos.h = this.minH;
}
if (pos.h > this.maxH) {
pos.h = this.maxH;
}
if (pos.h < 1) {
pos.h = 1;
}
if (pos.w < 1) {
pos.w = 1;
}
this.lastW = x;
this.lastH = y;
if (this.innerW !== pos.w || this.innerH !== pos.h) {
this.$emit("resize", this.i, pos.h, pos.w, newSize.height, newSize.width);
}
if (event.type === "resizeend" && (this.previousW !== this.innerW || this.previousH !== this.innerH)) {
this.$emit("resized", this.i, pos.h, pos.w, newSize.height, newSize.width);
}
this.eventBus.$emit("resizeEvent", event.type, this.i, this.innerX, this.innerY, pos.h, pos.w);
},
handleDrag(event) {
if (this.static) return;
if (this.isResizing) return;
const position = getControlPosition(event);
// Get the current drag point from the event. This is used as the offset.
if (position === null) return; // not possible but satisfies flow
const {x, y} = position;
// let shouldUpdate = false;
let newPosition = {top: 0, left: 0};
switch (event.type) {
case "dragstart": {
this.previousX = this.innerX;
this.previousY = this.innerY;
let parentRect = event.target.offsetParent.getBoundingClientRect();
let clientRect = event.target.getBoundingClientRect();
if (this.renderRtl) {
newPosition.left = (clientRect.right - parentRect.right) * -1;
} else {
newPosition.left = clientRect.left - parentRect.left;
}
newPosition.top = clientRect.top - parentRect.top;
this.dragging = newPosition;
this.isDragging = true;
break;
}
case "dragend": {
if (!this.isDragging) return;
let parentRect = event.target.offsetParent.getBoundingClientRect();
let clientRect = event.target.getBoundingClientRect();
// Add rtl support
if (this.renderRtl) {
newPosition.left = (clientRect.right - parentRect.right) * -1;
} else {
newPosition.left = clientRect.left - parentRect.left;
}
newPosition.top = clientRect.top - parentRect.top;
// console.log("### drag end => " + JSON.stringify(newPosition));
// console.log("### DROP: " + JSON.stringify(newPosition));
this.dragging = null;
this.isDragging = false;
// shouldUpdate = true;
break;
}
case "dragmove": {
const coreEvent = createCoreData(this.lastX, this.lastY, x, y);
// Add rtl support
if (this.renderRtl) {
newPosition.left = this.dragging.left - coreEvent.deltaX;
} else {
newPosition.left = this.dragging.left + coreEvent.deltaX;
}
newPosition.top = this.dragging.top + coreEvent.deltaY;
// console.log("### drag => " + event.type + ", x=" + x + ", y=" + y);
// console.log("### drag => " + event.type + ", deltaX=" + coreEvent.deltaX + ", deltaY=" + coreEvent.deltaY);
// console.log("### drag end => " + JSON.stringify(newPosition));
this.dragging = newPosition;
break;
}
}
// Get new XY
let pos;
if (this.renderRtl) {
pos = this.calcXY(newPosition.top, newPosition.left);
} else {
pos = this.calcXY(newPosition.top, newPosition.left);
}
this.lastX = x;
this.lastY = y;
if (this.innerX !== pos.x || this.innerY !== pos.y) {
this.$emit("move", this.i, pos.x, pos.y);
}
if (event.type === "dragend" && (this.previousX !== this.innerX || this.previousY !== this.innerY)) {
this.$emit("moved", this.i, pos.x, pos.y);
}
this.eventBus.$emit("dragEvent", event.type, this.i, pos.x, pos.y, this.innerH, this.innerW);
},
calcPosition: function (x, y, w, h) {
const colWidth = this.calcColWidth();
// add rtl support
let out;
if (this.renderRtl) {
out = {
right: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
};
} else {
out = {
left: Math.round(colWidth * x + (x + 1) * this.margin[0]),
top: Math.round(this.rowHeight * y + (y + 1) * this.margin[1]),
// 0 * Infinity === NaN, which causes problems with resize constriants;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * this.margin[0]),
height: h === Infinity ? h : Math.round(this.rowHeight * h + Math.max(0, h - 1) * this.margin[1])
};
}
return out;
},
/**
* Translate x and y coordinates from pixels to grid units.
* @param {Number} top Top position (relative to parent) in pixels.
* @param {Number} left Left position (relative to parent) in pixels.
* @return {Object} x and y in grid units.
*/
// TODO check if this function needs change in order to support rtl.
calcXY(top, left) {
const colWidth = this.calcColWidth();
// left = colWidth * x + margin * (x + 1)
// l = cx + m(x+1)
// l = cx + mx + m
// l - m = cx + mx
// l - m = x(c + m)
// (l - m) / (c + m) = x
// x = (left - margin) / (coldWidth + margin)
let x = Math.round((left - this.margin[0]) / (colWidth + this.margin[0]));
let y = Math.round((top - this.margin[1]) / (this.rowHeight + this.margin[1]));
// Capping
x = Math.max(Math.min(x, this.cols - this.innerW), 0);
y = Math.max(Math.min(y, this.maxRows - this.innerH), 0);
return {x, y};
},
// Helper for generating column width
calcColWidth() {
const colWidth = (this.containerWidth - (this.margin[0] * (this.cols + 1))) / this.cols;
// console.log("### COLS=" + this.cols + " COL WIDTH=" + colWidth + " MARGIN " + this.margin[0]);
return colWidth;
},
/**
* Given a height and width in pixel values, calculate grid units.
* @param {Number} height Height in pixels.
* @param {Number} width Width in pixels.
* @return {Object} w, h as grid units.
*/
calcWH(height, width) {
const colWidth = this.calcColWidth();
// width = colWidth * w - (margin * (w - 1))
// ...
// w = (width + margin) / (colWidth + margin)
let w = Math.round((width + this.margin[0]) / (colWidth + this.margin[0]));
let h = Math.round((height + this.margin[1]) / (this.rowHeight + this.margin[1]));
// Capping
w = Math.max(Math.min(w, this.cols - this.innerX), 0);
h = Math.max(Math.min(h, this.maxRows - this.innerY), 0);
return {w, h};
},
updateWidth: function (width, colNum) {
this.containerWidth = width;
if (colNum !== undefined && colNum !== null) {
this.cols = colNum;
}
},
compact: function () {
this.createStyle();
},
tryMakeDraggable: function(){
const self = this;
if (this.interactObj === null || this.interactObj === undefined) {
this.interactObj = interact(this.$refs.item);
if (!this.useStyleCursor) {
this.interactObj.styleCursor(false);
}
}
if (this.draggable && !this.static) {
const opts = {
ignoreFrom: this.dragIgnoreFrom,
allowFrom: this.dragAllowFrom
};
this.interactObj.draggable(opts);
/*this.interactObj.draggable({allowFrom: '.vue-draggable-handle'});*/
if (!this.dragEventSet) {
this.dragEventSet = true;
this.interactObj.on('dragstart dragmove dragend', function (event) {
self.handleDrag(event);
});
}
} else {
this.interactObj.draggable({
enabled: false
});
}
},
tryMakeResizable: function(){
const self = this;
if (this.interactObj === null || this.interactObj === undefined) {
this.interactObj = interact(this.$refs.item);
if (!this.useStyleCursor) {
this.interactObj.styleCursor(false);
}
}
if (this.resizable && !this.static) {
let maximum = this.calcPosition(0,0,this.maxW, this.maxH);
let minimum = this.calcPosition(0,0, this.minW, this.minH);
// console.log("### MAX " + JSON.stringify(maximum));
// console.log("### MIN " + JSON.stringify(minimum));
const opts = {
preserveAspectRatio: true,
// allowFrom: "." + this.resizableHandleClass.trim().replace(" ", "."),
edges: {
left: false,
right: "." + this.resizableHandleClass.trim().replace(" ", "."),
bottom: "." + this.resizableHandleClass.trim().replace(" ", "."),
top: false
},
ignoreFrom: this.resizeIgnoreFrom,
restrictSize: {
min: {
height: minimum.height,
width: minimum.width
},
max: {
height: maximum.height,
width: maximum.width
}
}
};
this.interactObj.resizable(opts);
if (!this.resizeEventSet) {
this.resizeEventSet = true;
this.interactObj
.on('resizestart resizemove resizeend', function (event) {
self.handleResize(event);
});
}
} else {
this.interactObj.resizable({
enabled: false
});
}
},
autoSize: function() {
// ok here we want to calculate if a resize is needed
this.previousW = this.innerW;
this.previousH = this.innerH;
let newSize=this.$slots.default[0].elm.getBoundingClientRect();
let pos = this.calcWH(newSize.height, newSize.width);
if (pos.w < this.minW) {
pos.w = this.minW;
}
if (pos.w > this.maxW) {
pos.w = this.maxW;
}
if (pos.h < this.minH) {
pos.h = this.minH;
}
if (pos.h > this.maxH) {
pos.h = this.maxH;
}
if (pos.h < 1) {
pos.h = 1;
}
if (pos.w < 1) {
pos.w = 1;
}
// this.lastW = x; // basically, this is copied from resizehandler, but shouldn't be needed
// this.lastH = y;
if (this.innerW !== pos.w || this.innerH !== pos.h) {
this.$emit("resize", this.i, pos.h, pos.w, newSize.height, newSize.width);
}
if (this.previousW !== pos.w || this.previousH !== pos.h) {
this.$emit("resized", this.i, pos.h, pos.w, newSize.height, newSize.width);
this.eventBus.$emit("resizeEvent", "resizeend", this.i, this.innerX, this.innerY, pos.h, pos.w);
}
}
},
}
</script>
<template>
<div ref="item" class="vue-grid-layout" :style="mergedStyle">
<slot></slot>
<grid-item class="vue-grid-placeholder"
v-show="isDragging"
:x="placeholder.x"
:y="placeholder.y"
:w="placeholder.w"
:h="placeholder.h"
:i="placeholder.i"></grid-item>
</div>
</template>
<style>
.vue-grid-layout {
position: relative;
transition: height 200ms ease;
}
</style>
<script>
import Vue from 'vue';
const elementResizeDetectorMaker = require("element-resize-detector");
import {bottom, compact, getLayoutItem, moveElement, validateLayout, cloneLayout, getAllCollisions} from '../helpers/utils';
import {getBreakpointFromWidth, getColsFromBreakpoint, findOrGenerateResponsiveLayout} from "../helpers/responsiveUtils";
//var eventBus = require('./eventBus');
import GridItem from './GridItem.vue'
import {addWindowEventListener, removeWindowEventListener} from "../helpers/DOM";
export default {
name: "GridLayout",
provide() {
return {
eventBus: null,
layout: this
}
},
components: {
GridItem,
},
props: {
// If true, the container height swells and contracts to fit contents
autoSize: {
type: Boolean,
default: true
},
colNum: {
type: Number,
default: 12
},
rowHeight: {
type: Number,
default: 150
},
maxRows: {
type: Number,
default: Infinity
},
margin: {
type: Array,
default: function () {
return [10, 10];
}
},
isDraggable: {
type: Boolean,
default: true
},
isResizable: {
type: Boolean,
default: true
},
isMirrored: {
type: Boolean,
default: false
},
useCssTransforms: {
type: Boolean,
default: true
},
verticalCompact: {
type: Boolean,
default: true
},
layout: {
type: Array,
required: true,
},
responsive: {
type: Boolean,
default: false
},
responsiveLayouts: {
type: Object,
default: function() {
return {};
}
},
breakpoints:{
type: Object,
default: function(){return{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
},
cols:{
type: Object,
default: function(){return{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }},
},
preventCollision: {
type: Boolean,
default: false
},
useStyleCursor: {
type: Boolean,
default: true
}
},
data: function () {
return {
width: null,
mergedStyle: {},
lastLayoutLength: 0,
isDragging: false,
placeholder: {
x: 0,
y: 0,
w: 0,
h: 0,
i: -1
},
layouts: {}, // array to store all layouts from different breakpoints
lastBreakpoint: null, // store last active breakpoint
originalLayout: null, // store original Layout
};
},
created () {
const self = this;
// Accessible refernces of functions for removing in beforeDestroy
self.resizeEventHandler = function(eventType, i, x, y, h, w) {
self.resizeEvent(eventType, i, x, y, h, w);
};
self.dragEventHandler = function(eventType, i, x, y, h, w) {
self.dragEvent(eventType, i, x, y, h, w);
};
self._provided.eventBus = new Vue();
self.eventBus = self._provided.eventBus;
self.eventBus.$on('resizeEvent', self.resizeEventHandler);
self.eventBus.$on('dragEvent', self.dragEventHandler);
self.$emit('layout-created', self.layout);
},
beforeDestroy: function(){
//Remove listeners
this.eventBus.$off('resizeEvent', this.resizeEventHandler);
this.eventBus.$off('dragEvent', this.dragEventHandler);
this.eventBus.$destroy();
removeWindowEventListener("resize", this.onWindowResize);
this.erd.uninstall(this.$refs.item);
},
beforeMount: function() {
this.$emit('layout-before-mount', this.layout);
},
mounted: function() {
this.$emit('layout-mounted', this.layout);
this.$nextTick(function () {
validateLayout(this.layout);
this.originalLayout = this.layout;
const self = this;
this.$nextTick(function() {
self.onWindowResize();
self.initResponsiveFeatures();
//self.width = self.$el.offsetWidth;
addWindowEventListener('resize', self.onWindowResize);
compact(self.layout, self.verticalCompact);
self.$emit('layout-updated',self.layout)
self.updateHeight();
self.$nextTick(function () {
this.erd = elementResizeDetectorMaker({
strategy: "scroll", //<- For ultra performance.
// See https://github.com/wnr/element-resize-detector/issues/110 about callOnAdd.
callOnAdd: false,
});
this.erd.listenTo(self.$refs.item, function () {
self.onWindowResize();
});
});
});
});
},
watch: {
width: function (newval, oldval) {
const self = this;
this.$nextTick(function () {
//this.$broadcast("updateWidth", this.width);
this.eventBus.$emit("updateWidth", this.width);
if (oldval === null) {
/*
If oldval == null is when the width has never been
set before. That only occurs when mouting is
finished, and onWindowResize has been called and
this.width has been changed the first time after it
got set to null in the constructor. It is now time
to issue layout-ready events as the GridItems have
their sizes configured properly.
The reason for emitting the layout-ready events on
the next tick is to allow for the newly-emitted
updateWidth event (above) to have reached the
children GridItem-s and had their effect, so we're
sure that they have the final size before we emit
layout-ready (for this GridLayout) and
item-layout-ready (for the GridItem-s).
This way any client event handlers can reliably
invistigate stable sizes of GridItem-s.
*/
this.$nextTick(() => {
this.$emit('layout-ready', self.layout);
});
}
this.updateHeight();
});
},
layout: function () {
this.layoutUpdate();
},
colNum: function (val) {
this.eventBus.$emit("setColNum", val);
},
rowHeight: function() {
this.eventBus.$emit("setRowHeight", this.rowHeight);
},
isDraggable: function() {
this.eventBus.$emit("setDraggable", this.isDraggable);
},
isResizable: function() {
this.eventBus.$emit("setResizable", this.isResizable);
},
responsive() {
if (!this.responsive) {
this.$emit('update:layout', this.originalLayout);
this.eventBus.$emit("setColNum", this.colNum);
}
this.onWindowResize();
},
maxRows: function() {
this.eventBus.$emit("setMaxRows", this.maxRows);
},
margin() {
this.updateHeight();
}
},
methods: {
layoutUpdate() {
if (this.layout !== undefined && this.originalLayout !== null) {
if (this.layout.length !== this.originalLayout.length) {
// console.log("### LAYOUT UPDATE!", this.layout.length, this.originalLayout.length);
let diff = this.findDifference(this.layout, this.originalLayout);
if (diff.length > 0){
// console.log(diff);
if (this.layout.length > this.originalLayout.length) {
this.originalLayout = this.originalLayout.concat(diff);
} else {
this.originalLayout = this.originalLayout.filter(obj => {
return !diff.some(obj2 => {
return obj.i === obj2.i;
});
});
}
}
this.lastLayoutLength = this.layout.length;
this.initResponsiveFeatures();
}
compact(this.layout, this.verticalCompact);
this.eventBus.$emit("updateWidth", this.width);
this.updateHeight();
this.$emit('layout-updated',this.layout)
}
},
updateHeight: function () {
this.mergedStyle = {
height: this.containerHeight()
};
},
onWindowResize: function () {
if (this.$refs !== null && this.$refs.item !== null && this.$refs.item !== undefined) {
this.width = this.$refs.item.offsetWidth;
}
this.eventBus.$emit("resizeEvent");
},
containerHeight: function () {
if (!this.autoSize) return;
// console.log("bottom: " + bottom(this.layout))
// console.log("rowHeight + margins: " + (this.rowHeight + this.margin[1]) + this.margin[1])
const containerHeight = bottom(this.layout) * (this.rowHeight + this.margin[1]) + this.margin[1] + 'px';
return containerHeight;
},
dragEvent: function (eventName, id, x, y, h, w) {
//console.log(eventName + " id=" + id + ", x=" + x + ", y=" + y);
let l = getLayoutItem(this.layout, id);
//GetLayoutItem sometimes returns null object
if (l === undefined || l === null){
l = {x:0, y:0}
}
if (eventName === "dragmove" || eventName === "dragstart") {
this.placeholder.i = id;
this.placeholder.x = l.x;
this.placeholder.y = l.y;
this.placeholder.w = w;
this.placeholder.h = h;
this.$nextTick(function() {
this.isDragging = true;
});
//this.$broadcast("updateWidth", this.width);
this.eventBus.$emit("updateWidth", this.width);
} else {
this.$nextTick(function() {
this.isDragging = false;
});
}
// Move the element to the dragged location.
this.layout = moveElement(this.layout, l, x, y, true, this.preventCollision);
compact(this.layout, this.verticalCompact);
// needed because vue can't detect changes on array element properties
this.eventBus.$emit("compact");
this.updateHeight();
if (eventName === 'dragend') this.$emit('layout-updated', this.layout);
},
resizeEvent: function (eventName, id, x, y, h, w) {
let l = getLayoutItem(this.layout, id);
//GetLayoutItem sometimes return null object
if (l === undefined || l === null){
l = {h:0, w:0}
}
let hasCollisions;
if (this.preventCollision) {
const collisions = getAllCollisions(this.layout, { ...l, w, h }).filter(
layoutItem => layoutItem.i !== l.i
);
hasCollisions = collisions.length > 0;
// If we're colliding, we need adjust the placeholder.
if (hasCollisions) {
// adjust w && h to maximum allowed space
let leastX = Infinity,
leastY = Infinity;
collisions.forEach(layoutItem => {
if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x);
if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y);
});
if (Number.isFinite(leastX)) l.w = leastX - l.x;
if (Number.isFinite(leastY)) l.h = leastY - l.y;
}
}
if (!hasCollisions) {
// Set new width and height.
l.w = w;
l.h = h;
}
if (eventName === "resizestart" || eventName === "resizemove") {
this.placeholder.i = id;
this.placeholder.x = x;
this.placeholder.y = y;
this.placeholder.w = l.w;
this.placeholder.h = l.h;
this.$nextTick(function() {
this.isDragging = true;
});
//this.$broadcast("updateWidth", this.width);
this.eventBus.$emit("updateWidth", this.width);
} else {
this.$nextTick(function() {
this.isDragging = false;
});
}
if (this.responsive) this.responsiveGridLayout();
compact(this.layout, this.verticalCompact);
this.eventBus.$emit("compact");
this.updateHeight();
if (eventName === 'resizeend') this.$emit('layout-updated', this.layout);
},
// finds or generates new layouts for set breakpoints
responsiveGridLayout(){
let newBreakpoint = getBreakpointFromWidth(this.breakpoints, this.width);
let newCols = getColsFromBreakpoint(newBreakpoint, this.cols);
// save actual layout in layouts
if(this.lastBreakpoint != null && !this.layouts[this.lastBreakpoint])
this.layouts[this.lastBreakpoint] = cloneLayout(this.layout);
// Find or generate a new layout.
let layout = findOrGenerateResponsiveLayout(
this.originalLayout,
this.layouts,
this.breakpoints,
newBreakpoint,
this.lastBreakpoint,
newCols,
this.verticalCompact
);
// Store the new layout.
this.layouts[newBreakpoint] = layout;
if (this.lastBreakpoint !== newBreakpoint) {
this.$emit('breakpoint-changed', newBreakpoint, layout);
}
// new prop sync
this.$emit('update:layout', layout);
this.lastBreakpoint = newBreakpoint;
this.eventBus.$emit("setColNum", getColsFromBreakpoint(newBreakpoint, this.cols));
},
// clear all responsive layouts
initResponsiveFeatures(){
// clear layouts
this.layouts = Object.assign({}, this.responsiveLayouts);
},
// find difference in layouts
findDifference(layout, originalLayout){
//Find values that are in result1 but not in result2
let uniqueResultOne = layout.filter(function(obj) {
return !originalLayout.some(function(obj2) {
return obj.i === obj2.i;
});
});
//Find values that are in result2 but not in result1
let uniqueResultTwo = originalLayout.filter(function(obj) {
return !layout.some(function(obj2) {
return obj.i === obj2.i;
});
});
//Combine the two arrays of unique entries#
return uniqueResultOne.concat(uniqueResultTwo);
}
},
}
</script>
<template>
<span class="text">
{{text}}
</span>
</template>
<style>
</style>
<script>
export default {
name: "TestElement",
props: {
text : {
type: String,
default: "x",
},
},
data: function() {
return {
}
},
mounted: function() {
console.log("### " + this.text + " ready!");
},
}
</script>
\ No newline at end of file
'use strict';
import App from '../../framework/app';
import createStore from '../store/index';
import createRouter from './router/index';
import entry from './view/home/index.vue';
export default new App({ entry, createStore, createRouter }).bootstrap();
\ No newline at end of file
import Vue from 'vue';
import VueRouter from 'vue-router';
import Activity from '../view/activity/index.vue';
Vue.use(VueRouter);
export default function createRouter() {
return new VueRouter({
mode: 'history',
routes: [
{
path: '/activity/:pageId',
component: Activity
}
]
});
}
import { Vue, Component } from 'vue-property-decorator';
import eleConfig from '../../../editor/utils/config';
import FreedomContainer from '../../../editor/component/FreedomContainer/index.vue';
import schameConfig from '@qg/cherry-ui/src/button/schame.js';
import components from '@qg/cherry-ui/src/index.js';
import { kebabCase } from 'lodash';
// import {GridLayout, GridItem} from 'vue-grid-layout';
import GridLayout from '../../component/components/GridLayout.vue';
import GridItem from '../../component/components/GridItem.vue';
import {
Getter,
Action,
State,
Getter
} from 'vuex-class';
@Component({ components: { FreedomContainer, GridLayout, GridItem }, name: 'Activity'})
export default class Activity extends Vue {
@Getter('pageData') pageData;
// pageData = {elements: [{name: 'cr-field', point: {x: 0, y: 0, w: 12, h: 1, i: '1', moved: false}}, {name: 'cr-button', point: {x: 0, y: 1, w: 12, h: 1, i: '0', moved: false}, schame: [{key: 'color', name: '按钮颜色', type: 'ColorPicker'}, {key: 'text', name: '按钮文案', type: 'Input'}], props: {tag: 'button', type: 'default', size: 'normal', color: '#07c160', text: '按钮1', shape: 'square', nativeType: 'button', loadingSize: '20px'}, commonStyle: {position: 'absolute', top: 5, left: 5}}, {name: 'freedom-container', point: {x: 0, y: 2, w: 12, h: 5, i: '2', moved: false}, child: []}]};
isLayoutComReady = false;
get layout() {
console.log('this.pageData', this.pageData);
return this.pageData && this.pageData.elements.map(v => v.point) || [];
}
fetchApi(options) {
const { store, route } = options;
const { pageId } = route.params;
return store.dispatch('getPageDate', { pageId });
}
createStyle({h}) {
return EASY_ENV_IS_NODE ? {
height: `${h * 50 + Math.max(0, h - 1) * 10}px`,
marginTop: '10px'
} : {};
}
}
\ No newline at end of file
<template>
<div class="activity">
<grid-layout
:layout.sync="layout"
:isDraggable="false"
:isResizable="false"
:col-num="12"
:row-height="50"
:margin="[0, 10]"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item :style="createStyle(item.point)" v-for="(item, index) in pageData.elements"
:x="item.point.x"
:y="item.point.y"
:w="item.point.w"
:h="item.point.h"
:i="item.point.i"
:key="item.point.i">
<component class="Dcmc-panel-com" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
</grid-item>
</grid-layout>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less">
html,
body,
#app {
height: 100%;
}
.activity {
width: 100%;
height: 100%;
min-height: 100%;
overflow-y: scroll;
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout {
transform: translateY(-10px);
transition-property: none;
.vue-grid-item {
transition-property: none;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
}
}
</style>
import { Vue, Component, Emit } from 'vue-property-decorator';
import Layout from 'component/layout/activity/index.vue';
@Component({
components: {
Layout
}
})
export default class Home extends Vue {}
\ No newline at end of file
<template>
<Layout>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</Layout>
</template>
<script lang="ts" src="./index.ts"></script>
import { Vue, Component } from 'vue-property-decorator'; import { Vue, Component } from 'vue-property-decorator';
import Article from '../../../../../../model/article'; // import Article from '../../../../../../model/article';
import { import {
Getter, Getter,
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
@Component @Component
export default class Detail extends Vue { export default class Detail extends Vue {
@Getter('article') article?: Article; @Getter('article') article;
fetchApi(options) { fetchApi(options) {
const { store, route } = options; const { store, route } = options;
const { id } = route.params; const { id } = route.params;
......
import Vue from 'vue'; import Vue from 'vue';
import { Getter, Action } from 'vuex-class'; import { Getter, Action } from 'vuex-class';
import { Component, Prop, Watch } from 'vue-property-decorator'; import { Component, Prop, Watch } from 'vue-property-decorator';
import Article from '../../../../../../model/article'; // import Article from '../../../../../../model/article';
@Component({ @Component({
components: { components: {
...@@ -9,7 +9,7 @@ import Article from '../../../../../../model/article'; ...@@ -9,7 +9,7 @@ import Article from '../../../../../../model/article';
} }
}) })
export default class Write extends Vue { export default class Write extends Vue {
@Getter('article') article?: Article; @Getter('article') article;
@Action('saveArticle') saveArticle; @Action('saveArticle') saveArticle;
@Action('getArticle') getArticle; @Action('getArticle') getArticle;
text: string = ''; text: string = '';
......
...@@ -12,4 +12,15 @@ export default class Login extends Vue { ...@@ -12,4 +12,15 @@ export default class Login extends Vue {
username: string = 'admin'; username: string = 'admin';
password: string = 'admin'; password: string = 'admin';
remenber: boolean = false; remenber: boolean = false;
beforeMount() {
setInterval(() => console.log('123'), 100);
console.log(123);
}
mounted() {
this.username = '123';
setInterval(() => console.log('123'), 100);
console.log(123);
}
} }
\ No newline at end of file
import {Component, Vue} from 'vue-property-decorator'; import {Component, Vue} from 'vue-property-decorator';
// import schameConfig from '@qg/cherry-ui/src/button/schame.js';
import components from '@qg/cherry-ui/src/index.js';
import { kebabCase } from 'lodash';
@Component({ name: 'DynamicConponent' }) @Component({ name: 'DynamicConponent' })
export default class DynamicConponent extends Vue { export default class DynamicConponent extends Vue {
eleConfig = [
{
eleName: 'cr-button',
config: [
{
key: 'color',
name: '按钮颜色',
type: 'ColorPicker'
},
{
key: 'text',
name: '按钮文案',
type: 'Input'
}
],
value: {
color: '#07c160',
text: '按钮1'
}
},
{
eleName: 'cr-field',
config: [
{
key: 'placeholder',
name: '提示信息',
type: 'Input'
},
{
key: 'label',
name: 'label文案',
type: 'Input'
}
],
value: {
placeholder: '请输入',
label: 'label'
}
}
];
dragstart(event) { dragstart(event, eleName) {
console.log('dragstart');
// dataTransfer.setData()方法设置数据类型和拖动的数据 this.$emit('dragstart');
event.dataTransfer.setData('text', JSON.stringify({ if (eleName === 'freedom-container') {
name: 'cr-button', event.dataTransfer.setData('text', JSON.stringify({
point: {x: 0, y: 0, w: 12, h: 1, i: '0'}, name: eleName,
point: {x: 0, y: 2, w: 12, h: 5, i: '2'},
child: [],
})); }));
} else {
const props = this.getProps(eleName);
const eleConfig = this.eleConfig.find(config => config.eleName === eleName);
event.dataTransfer.setData('text', JSON.stringify({
name: eleName,
point: {x: 0, y: 0, w: 12, h: 1, i: '0'},
schame: eleConfig.config,
props: {...props, ...eleConfig.value},
commonStyle: {
position: 'absolute',
top: 5,
left: 5
}
}));
}
console.log('getData', event.dataTransfer.getData('text')); console.log('getData', event.dataTransfer.getData('text'));
// 指定拖放操作所允许的一个效果
event.dataTransfer.effectAllowed = 'copyMove'; event.dataTransfer.effectAllowed = 'copyMove';
} }
dragend() { dragend() {
this.$emit('dragend');
console.log('dragend'); console.log('dragend');
} }
getProps(eleName) {
const props = {};
for (const key of Object.keys(components)) {
const component = components[key];
if (kebabCase(component.name) === eleName && component.props) {
for (const prop of Object.keys(component.props)) {
props[prop] = ['Object', 'Array'].includes(component.props[prop].type.name) ? component.props[prop].default && component.props[prop].default() : component.props[prop].default;
}
}
}
console.log(props);
return props;
}
} }
\ No newline at end of file
<template> <template>
<div class="dynamic"> <div class="dynamic">
<Row class="dynamic-row" type="flex" justify="space-between"> <Row class="dynamic-row" type="flex" justify="space-between" v-cloak>
<Col span="10"> <Col span="10">
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart"> <Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'cr-button')">
<p slot="title">Button</p> <p slot="title">按钮</p>
<div> <div class="dynamic-row-card">
<img <img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg" src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/> />
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
</Card> </Card>
</Col> </Col>
<Col span="10"> <Col span="10">
<Card> <Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'freedom-container')">
<p slot="title">Icon</p> <p slot="title">自由容器</p>
<div > <div class="dynamic-row-card">
<img <img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg" src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/> />
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
</Row> </Row>
<Row class="dynamic-row" type="flex" justify="space-between"> <Row class="dynamic-row" type="flex" justify="space-between">
<Col span="10"> <Col span="10">
<Card> <Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'cr-field')">
<p slot="title">Field</p> <p slot="title">输入框</p>
<div > <div class="dynamic-row-card">
<img <img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg" src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/> />
...@@ -37,9 +37,9 @@ ...@@ -37,9 +37,9 @@
</Card> </Card>
</Col> </Col>
<Col span="10"> <Col span="10">
<Card> <Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart">
<p slot="title">Swipe</p> <p slot="title">轮播图</p>
<div > <div class="dynamic-row-card">
<img <img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg" src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/> />
...@@ -59,11 +59,15 @@ ...@@ -59,11 +59,15 @@
p { p {
text-align: center; text-align: center;
} }
img {
width: 100%; &-card {
height: 100%; display: flex;
object-fit: fill; justify-content: center;
-webkit-user-drag: none; img {
width: 64px;
object-fit: fill;
-webkit-user-drag: none;
}
} }
} }
} }
......
import {Component, Vue, Prop, Watch} from 'vue-property-decorator';
import { reduce } from 'lodash';
@Component({ name: 'DynamicForm' })
export default class DynamicForm extends Vue {
@Prop({type: Object, default: () => ({ schame: { config: [], value: [] } })}) curElement;
form: object = {};
@Watch('curElement', { immediate: true, deep: true })
onElementChange(newVal) {
this.form = reduce(newVal.schame, (obj, param) => {
obj[param.key] = newVal.props[param.key];
return obj;
}, {});
}
change(val) {
this.$emit('modProps', this.form);
}
}
\ No newline at end of file
<template>
<div class="dynamic-form">
<h4>组件属性</h4>
<Form ref="formCustom" :label-width="80" :model="form">
<FormItem :label="item.name" :key="index" v-for="(item, index) in curElement.schame">
<component :is="item.type" v-model="form[item.key]" @on-change="change" />
</FormItem>
</Form>
<!-- <h4>基础样式</h4>
<Form ref="formCustom" :label-width="80">
<FormItem label="文本内容">
<Input type="text" text></Input>
</FormItem>
</Form> -->
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less">
.dynamic-form {
padding: 0 15px;
h4 {
padding: 10px 0;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
/deep/ .ivu-form-item-label {
font-size: 14px;
}
}
</style>
\ No newline at end of file
import {Component, Vue, Prop} from 'vue-property-decorator';
import _ from 'lodash';
@Component({ name: 'FreedomContainer' })
export default class FreedomContainer extends Vue {
@Prop({type: Object, default: () => ({ child: [] })}) childItem;
@Prop({type: Number, default: 0}) containerIndex;
mousedown(childIndex, event) {
console.log(childIndex, event);
const childItem = _.cloneDeep(this.childItem);
const { top: startTop, left: startLeft } = childItem.child[childIndex].commonStyle;
const { clientY, clientX } = event;
const move = moveEvent => {
moveEvent.stopPropagation();
moveEvent.preventDefault();
const top = moveEvent.clientY - clientY + startTop;
const left = moveEvent.clientX - clientX + startLeft;
this.$emit('resize', this.containerIndex, childIndex, {...childItem.child[childIndex].commonStyle, top, left});
};
const up = () => {
document.removeEventListener('mousemove', move, true);
document.removeEventListener('mouseup', up, true);
};
document.addEventListener('mousemove', move, true);
document.addEventListener('mouseup', up, true);
}
transformStyle(styleObj) {
const style = {};
for (const key of Object.keys(styleObj)) {
style[key] = typeof styleObj[key] !== 'string' ? `${styleObj[key]}px` : styleObj[key];
if (key === 'backgroundImage') {
style.backgroundImage = `url(${style.backgroundImage})`;
}
}
return style;
}
handleElementClick(curEleIndex, curChildIndex) {
this.$emit('handleElementClick', curEleIndex, curChildIndex);
}
}
\ No newline at end of file
<template>
<div class="freedom">
<header>标题栏</header>
<div class="freedom-body">
<component v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle)" :is="item.name" :key="index" @click.stop="handleElementClick(containerIndex, index)" @mousedown.native.stop.prevent="mousedown(index, $event)" v-bind="item.props"></component>
</div>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less">
.freedom {
width: 100%;
header {
width: 100%;
height: 48px;
line-height: 48px;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
.freedom-body {
position: relative;
width: 100%;
height: 234px;
}
}
</style>
\ No newline at end of file
import { Vue, Component } from 'vue-property-decorator'; import { Vue, Component, Watch } from 'vue-property-decorator';
import DynamicConponent from '@editor/component/DynamicComponent/index.vue'; import DynamicConponent from '@editor/component/DynamicComponent/index.vue';
import VueGridLayout from 'vue-grid-layout'; import VueGridLayout from 'vue-grid-layout';
import eleConfig from '@editor/utils/config'; import eleConfig from '@editor/utils/config';
import FreedomContainer from '../../component/FreedomContainer/index.vue';
import DynamicForm from '../../component/DynamicForm/index.vue';
import schameConfig from '@qg/cherry-ui/src/button/schame.js';
import components from '@qg/cherry-ui/src/index.js';
import { kebabCase } from 'lodash';
import { import {
Getter, Getter,
Action Action,
State
} from 'vuex-class'; } from 'vuex-class';
@Component({components: { DynamicConponent, GridLayout: VueGridLayout.GridLayout, @Component({components: { DynamicConponent, FreedomContainer, DynamicForm, GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem }, name: 'DashBoard') GridItem: VueGridLayout.GridItem }, name: 'DashBoard'})
export default class DashBoard extends Vue { export default class DashBoard extends Vue {
@Action('addPageDate') addPageDate;
@Action('updatePageDate') updatePageDate;
@Getter('pageId') pageId;
activeName: string = '1'; activeName: string = '1';
isCollapsed: boolean = false; isCollapsed: boolean = false;
isDrag: boolean = false; isDragIn: boolean = false;
isDraging: boolean = false;
curEleIndex: number | null = null;
curChildIndex: number | null = null;
// todo:
// 1. grid拖拽进去后的顺序
// 2. init height
pageData = { pageData = {
elements: [ elements: [
{ // {
name: 'cr-button', // name: 'cr-button',
point: {x: 0, y: 0, w: 12, h: 1, i: '0'}, // point: {x: 0, y: 0, w: 12, h: 1, i: '0'},
}, // schame: schameConfig.config,
{ // props: {...this.getProps('cr-button'), ...schameConfig.value},
name: 'cr-field', // commonStyle: {
point: {x: 0, y: 1, w: 12, h: 1, i: '1'}, // position: 'absolute',
}, // top: 5,
// left: 5
// }
// },
// {
// name: 'cr-field',
// point: {x: 0, y: 1, w: 12, h: 1, i: '1'},
// },
// {
// name: 'freedom-container',
// point: {x: 0, y: 2, w: 12, h: 5, i: '2'},
// child: [],
// },
] ]
}; };
...@@ -31,32 +60,113 @@ export default class DashBoard extends Vue { ...@@ -31,32 +60,113 @@ export default class DashBoard extends Vue {
return this.pageData.elements.map(v => v.point); return this.pageData.elements.map(v => v.point);
} }
get curElement() {
let element = {};
if (this.curEleIndex !== null) {
if (this.curChildIndex !== null) {
element = this.pageData.elements[this.curEleIndex].child[this.curChildIndex];
} else {
element = this.pageData.elements[this.curEleIndex];
}
}
return element;
}
// 选择组件库 // 选择组件库
selectComponent(val: string) { selectMaterial(val: string) {
this.activeName = val; this.activeName = val;
} }
async preview() {
this.pageData.elements.sort((a, b) => a.point.y - b.point.y);
if (this.pageId) {
await this.updatePageDate({id: this.pageId, pageData: this.pageData});
} else {
await this.addPageDate(this.pageData);
}
window.open(`http://localhost:7001/activity/${this.pageId}`);
}
handleElementClick(curEleIndex, curChildIndex) {
console.log(curEleIndex, curChildIndex);
this.toggle(false);
this.curEleIndex = curEleIndex;
this.curChildIndex = curChildIndex;
}
toggle(val) { toggle(val) {
console.log(val);
this.isCollapsed = val; this.isCollapsed = val;
} }
dragstart() {
this.isDraging = true;
}
dragend() {
this.isDraging = false;
}
dragenter() { dragenter() {
console.log('dragenter'); console.log('dragenter');
this.isDrag = true; this.isDragIn = true;
} }
dragleave() { dragleave() {
console.log('dragleave'); console.log('dragleave');
this.isDrag = false; this.isDragIn = false;
}
dragover(event) {
if (event.target.classList.contains('freedom')) {
event.dataTransfer.dropEffect = 'move';
} else {
event.dataTransfer.dropEffect = 'copy';
}
}
resize(containerIndex, childIndex, style) {
this.pageData.elements[containerIndex].child[childIndex].commonStyle = style;
}
modProps(props) {
console.log('modProps');
if (this.curEleIndex !== null) {
if (this.curChildIndex !== null) {
this.pageData.elements[this.curEleIndex].child[this.curChildIndex].props = { ...this.pageData.elements[this.curEleIndex].child[this.curChildIndex].props, ...props};
console.log(this.pageData.elements[this.curEleIndex].child[this.curChildIndex].props);
} else {
this.pageData.elements[this.curEleIndex].props = { ...this.pageData.elements[this.curEleIndex].props, ...props};
}
}
} }
drops(event) { drops(event) {
this.isDrag = false; console.log(event.target);
this.isDragIn = false;
this.isCollapsed = false; this.isCollapsed = false;
const data = JSON.parse(event.dataTransfer.getData('text')); const data = JSON.parse(event.dataTransfer.getData('text'));
event.dataTransfer.clearData(); event.dataTransfer.clearData();
console.log({...data, i: this.pageData.elements.length}); console.log({...data, i: this.pageData.elements.length}, this.pageData.elements[event.target.dataset.index]);
this.pageData.elements.push({...data, point: { ...data.point, i: this.pageData.elements.length}}); if (event.target.classList.contains('freedom')) {
this.pageData.elements[event.target.dataset.index].child.push(data);
} else {
this.pageData.elements.push({...data, point: { ...data.point, i: this.pageData.elements.length}});
}
}
getProps(eleName) {
const props = {};
for (const key of Object.keys(components)) {
const component = components[key];
if (kebabCase(component.name) === eleName && component.props) {
console.log(key, component.props);
for (const prop of Object.keys(component.props)) {
props[prop] = [Object, Array].includes(component.props[prop].type) ? component.props[prop].default() : component.props[prop].default;
}
}
}
console.log(props);
return props;
} }
} }
\ No newline at end of file
<template> <template>
<Row class="dashboard"> <Row class="dashboard" v-cloak>
<Row class="dashboard-header" type="flex" align="middle"> <Row class="dashboard-header" type="flex" align="middle">
<Col span="8"> <Col span="8">
<h3>低代码开发平台</h3> <h3>低代码开发平台</h3>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<Icon type="ionic"></Icon> <Icon type="ionic"></Icon>
<span>保存</span> <span>保存</span>
</Col> </Col>
<Col span="7"> <Col span="7" @click.native="preview">
<Icon type="monitor"></Icon> <Icon type="monitor"></Icon>
<span>预览</span> <span>预览</span>
</Col> </Col>
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
</Row> </Row>
<Row class="dashboard-container"> <Row class="dashboard-container">
<Col class="dashboard-container-left" span="6"> <Col class="dashboard-container-left" span="6">
<Menu class="Dc-left-menu" active-name="1" @on-select="selectComponent"> <Menu class="Dc-left-menu" active-name="1" @on-select="selectMaterial">
<MenuItem name="1"> <MenuItem name="1">
<Icon type="ios-paper"></Icon> <Icon type="ios-paper"></Icon>
</MenuItem> </MenuItem>
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<div class="Dc-left-content"> <div class="Dc-left-content">
<div v-show="activeName === '1'"> <div v-show="activeName === '1'">
<h4>基础库</h4> <h4>基础库</h4>
<dynamic-conponent></dynamic-conponent> <dynamic-conponent @dragstart="dragstart" @dragend="dragend"></dynamic-conponent>
</div> </div>
<div v-show="activeName === '2'"> <div v-show="activeName === '2'">
<h4>业务库</h4> <h4>业务库</h4>
...@@ -46,36 +46,39 @@ ...@@ -46,36 +46,39 @@
<Col class="dashboard-container-middle" span="18"> <Col class="dashboard-container-middle" span="18">
<Row class="Dc-middle-row"> <Row class="Dc-middle-row">
<Col :span="isCollapsed ? 24 : 16" class="Dc-middle-container" @click.native="toggle(true)"> <Col :span="isCollapsed ? 24 : 16" class="Dc-middle-container" @click.native="toggle(true)">
<div :class="[{'Dcm-container-panel_in': isDrag}, 'Dcm-container-panel']" @dragover.prevent @dragenter="dragenter" <div :class="[{'Dcm-container-panel_in': isDragIn, 'Dcm-container-panel_draging': isDraging}, 'Dcm-container-panel']" @dragover.prevent @dragenter="dragenter" @dragover="dragover"
@dragleave="dragleave" @drop="drops"> @dragleave="dragleave" @drop="drops">
<grid-layout <grid-layout
:layout.sync="layout" :layout.sync="layout"
:col-num="12" :col-num="12"
:row-height="50" :row-height="50"
:margin="[0, 10]"
:is-draggable="true" :is-draggable="true"
:is-resizable="true" :is-resizable="true"
:is-mirrored="false" :is-mirrored="false"
:vertical-compact="true" :vertical-compact="true"
:use-css-transforms="true" :use-css-transforms="true"
> >
<grid-item @click.native.stop="toggle(false)" v-for="(item, index) in pageData.elements" <grid-item @click.native.stop="handleElementClick(index, null)" v-for="(item, index) in pageData.elements"
:x="item.point.x" :x="item.point.x"
:y="item.point.y" :y="item.point.y"
:w="item.point.w" :w="item.point.w"
:h="item.point.h" :h="item.point.h"
:i="item.point.i" :i="item.point.i"
:key="item.point.i"> :key="item.point.i">
<component class="Dcmc-panel-com" :is="item.name" :key="index"></component> <component class="Dcmc-panel-com" @handleElementClick="handleElementClick" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" @resize="resize" v-bind="item.props"></component>
</grid-item> </grid-item>
</grid-layout> </grid-layout>
</div> </div>
</Col> </Col>
<Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']"> <Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']">
<Tabs class="Dc-middle-editing"> <Tabs class="Dc-middle-editing">
<TabPane label="属性">属性</TabPane> <TabPane label="属性">
<TabPane label="事件">事件</TabPane> <dynamic-form :curElement="curElement" @modProps="modProps"></dynamic-form>
<TabPane label="页面设置">页面设置</TabPane> </TabPane>
</Tabs> <TabPane label="事件">事件</TabPane>
<TabPane label="页面设置">页面设置</TabPane>
</Tabs>
</Col> </Col>
</Row> </Row>
</Col> </Col>
...@@ -84,12 +87,16 @@ ...@@ -84,12 +87,16 @@
</template> </template>
<style lang="less"> <style lang="less">
.tabs-position() { .tabs-position() {
/deep/ .ivu-tabs-nav-scroll { /deep/ .ivu-tabs-nav-scroll {
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
}
[v-cloak] {
display: none;
} }
.dashboard { .dashboard {
min-width: 1280px;
height: 100% !important; height: 100% !important;
&-header { &-header {
position: relative; position: relative;
...@@ -124,26 +131,39 @@ ...@@ -124,26 +131,39 @@
.Dc-middle-row { .Dc-middle-row {
height: 100%; height: 100%;
.Dc-middle-container { .Dc-middle-container {
display: flex; overflow: scroll;
justify-content: center;
align-items: center;
height: 100%; height: 100%;
opacity: 1; opacity: 1;
transition: width ease-in-out 0.5s; transition: width ease-in-out 0.5s;
.Dcm-container-panel { .Dcm-container-panel {
margin: 30px auto;
width: 375px; width: 375px;
min-height: 664px; height: 667px;
background-color: #fff; min-height: 667px;
overflow-y: scroll;
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2); box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-item { /deep/ .vue-grid-layout {
display: flex; transform: translateY(-10px);
justify-content: center; .vue-grid-item {
align-items: center; display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
} }
.Dcmc-panel-com { .Dcmc-panel-com {
height: 50px; // height: 38px;
} }
&_in { // &_in {
// opacity: 0.7;
// }
&_draging {
.Dcmc-panel-com {
* {
pointer-events: none;
}
}
opacity: 0.7; opacity: 0.7;
} }
} }
...@@ -154,6 +174,7 @@ ...@@ -154,6 +174,7 @@
transition: width ease-in-out 0.5s; transition: width ease-in-out 0.5s;
.Dc-middle-editing { .Dc-middle-editing {
height: 100%; height: 100%;
min-width: 320px;
.tabs-position(); .tabs-position();
} }
} }
......
...@@ -3,16 +3,18 @@ import Vue from 'vue'; ...@@ -3,16 +3,18 @@ import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import RootState from './state'; import RootState from './state';
import Admin from './modules/admin'; import Admin from './modules/admin';
import Editor from './modules/editor';
Vue.use(Vuex); Vue.use(Vuex);
export default function createStore(initState: any = {}) { export default function createStore(initState: any = {}) {
const { title, url, origin, locale, csrf, admin } = initState; const { title, url, origin, locale, csrf, admin, editor } = initState;
const state = { title, url, origin, locale, csrf }; const state = { title, url, origin, locale, csrf };
return new Vuex.Store<RootState>({ return new Vuex.Store<RootState>({
state, state,
modules: { modules: {
admin: new Admin(admin) admin: new Admin(admin),
editor: new Editor(editor)
} }
}); });
} }
\ No newline at end of file
import axios from 'axios';
import { Module, GetterTree, ActionTree, MutationTree } from 'vuex';
import {
SET_PAGE_INFO
} from './type';
import RootState from '../../state';
import EditorState from './state';
axios.defaults.baseURL = 'http://localhost:7001';
axios.defaults.timeout = 15000;
axios.defaults.xsrfHeaderName = 'x-csrf-token';
axios.defaults.xsrfCookieName = 'csrfToken';
export default class EditorModule implements Module<EditorState, RootState> {
state: EditorState;
getters: GetterTree<EditorState, RootState> = {
pageData(state) {
return state.pageInfo.pageData;
},
pageId(state) {
return state.pageInfo.id;
}
};
actions: ActionTree<EditorState, RootState> = {
async addPageDate({ commit, dispatch, state, rootState }, condition) {
const res = await axios.post(`/editor/save`, condition);
const { id, pageData } = res.data;
commit(SET_PAGE_INFO, { id, pageData });
},
async updatePageDate({ commit, dispatch, state, rootState }, condition) {
await axios.post(`/editor/update`, condition);
commit(SET_PAGE_INFO, condition);
},
async getPageDate({ commit, dispatch, state, rootState }, condition) {
const res = await axios.get(`/editor/get/${condition.pageId}`);
const { id, page: pageData } = res.data;
commit(SET_PAGE_INFO, { id, pageData: JSON.parse(pageData) });
},
};
mutations: MutationTree<EditorState> = {
[SET_PAGE_INFO](state, data) {
state.pageInfo = data;
},
};
constructor(initState: EditorState) {
this.state = {
pageInfo: {
id: 0,
pageData: {
elements: []
},
},
...initState
};
}
}
\ No newline at end of file
import Article from '../../../../../model/article';
export default interface AdminState {
pageData: object;
}
\ No newline at end of file
'use strict';
export const SET_PAGE_INFO = 'SET_PAGE_INFO';
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" style="font-size: 37.5px;">
<head> <head>
<title>Egg + Vue + TypeScript</title> <title>Egg + Vue + TypeScript</title>
<meta name="keywords"> <meta name="keywords">
......
...@@ -37,5 +37,14 @@ export default (appInfo: EggAppConfig) => { ...@@ -37,5 +37,14 @@ export default (appInfo: EggAppConfig) => {
'global' 'global'
]; ];
config.sequelize = {
dialect: 'mysql',
username: 'qa',
password: 'qatest',
host: '172.17.5.9',
port: 31024,
database: 'low_code',
};
return config; return config;
}; };
...@@ -27,5 +27,14 @@ export default (appInfo: EggAppConfig) => { ...@@ -27,5 +27,14 @@ export default (appInfo: EggAppConfig) => {
webpackConfigList: getWebpackConfig() webpackConfigList: getWebpackConfig()
}; };
exports.sequelize = {
dialect: 'mysql',
username: 'qa',
password: 'qatest',
host: '172.17.5.9',
port: 31024,
database: 'low_code',
};
return exports; return exports;
}; };
export default { export default {
vuessr: { vuessr: {
package: 'egg-view-vue-ssr' package: 'egg-view-vue-ssr'
},
sequelize: {
enable: true,
package: 'egg-sequelize',
} }
}; };
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"backend": "nohup egg-scripts start --port 7001 --workers 4 &", "backend": "nohup egg-scripts start --port 7001 --workers 4 &",
"dev": "egg-bin dev -r egg-ts-helper/register", "dev": "egg-bin dev -r egg-ts-helper/register",
"debug": "egg-bin debug -r egg-ts-helper/register", "debug": "egg-bin debug -r egg-ts-helper/register",
"build": "npm run tsc && easy build", "build": "npm run tsc && easy build -s",
"tsc": "ets && tsc -p tsconfig.json", "tsc": "ets && tsc -p tsconfig.json",
"clean": "ets clean", "clean": "ets clean",
"kill": "easy kill", "kill": "easy kill",
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
"dependencies": { "dependencies": {
"@better-scroll/core": "^2.0.5", "@better-scroll/core": "^2.0.5",
"@hubcarl/json-typescript-mapper": "^2.0.0", "@hubcarl/json-typescript-mapper": "^2.0.0",
"@qg/cherry-ui": "^2.8.35", "@qg/cherry-ui": "^2.10.40",
"axios": "^0.18.1", "axios": "^0.18.1",
"egg": "^2.3.0", "egg": "^2.3.0",
"egg-cors": "^2.1.1", "egg-cors": "^2.1.1",
"egg-scripts": "^2.10.0", "egg-scripts": "^2.10.0",
"egg-sequelize": "^6.0.0",
"egg-view-vue-ssr": "^3.0.5", "egg-view-vue-ssr": "^3.0.5",
"egg-webpack": "^4.4.7", "egg-webpack": "^4.4.7",
"egg-webpack-vue": "^2.0.0", "egg-webpack-vue": "^2.0.0",
...@@ -36,6 +37,8 @@ ...@@ -36,6 +37,8 @@
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"mockjs": "^1.0.1-beta3", "mockjs": "^1.0.1-beta3",
"moment": "^2.17.1", "moment": "^2.17.1",
"mysql2": "^2.2.5",
"postcss-px2rem": "^0.3.0",
"shortid": "^2.2.8", "shortid": "^2.2.8",
"showdown": "^1.8.6", "showdown": "^1.8.6",
"simplemde": "^1.11.2", "simplemde": "^1.11.2",
......
module.exports = {
plugins: {
"postcss-px2rem": {
remUnit: 37.5
}
}
};
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
// Do not modify this file!!!!!!!!! // Do not modify this file!!!!!!!!!
import 'egg'; import 'egg';
import ExportActivity from '../../../app/controller/activity';
import ExportAdmin from '../../../app/controller/admin'; import ExportAdmin from '../../../app/controller/admin';
import ExportEditor from '../../../app/controller/editor'; import ExportEditor from '../../../app/controller/editor';
declare module 'egg' { declare module 'egg' {
interface IController { interface IController {
activity: ExportActivity;
admin: ExportAdmin; admin: ExportAdmin;
editor: ExportEditor; editor: ExportEditor;
} }
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// Do not modify this file!!!!!!!!! // Do not modify this file!!!!!!!!!
import 'egg'; import 'egg';
import ExportArticle from '../../../app/model/article'; import ExportPageInfo from '../../../app/model/pageInfo';
declare module 'egg' { declare module 'egg' {
interface IModel { interface IModel {
Article: ReturnType<typeof ExportArticle>; PageInfo: ReturnType<typeof ExportPageInfo>;
} }
} }
...@@ -15,6 +15,7 @@ import 'egg-static'; ...@@ -15,6 +15,7 @@ import 'egg-static';
import 'egg-jsonp'; import 'egg-jsonp';
import 'egg-view'; import 'egg-view';
import 'egg-view-vue-ssr'; import 'egg-view-vue-ssr';
import 'egg-sequelize';
import 'egg-cors'; import 'egg-cors';
import 'egg-webpack'; import 'egg-webpack';
import 'egg-webpack-vue'; import 'egg-webpack-vue';
...@@ -34,6 +35,7 @@ declare module 'egg' { ...@@ -34,6 +35,7 @@ declare module 'egg' {
jsonp?: EggPluginItem; jsonp?: EggPluginItem;
view?: EggPluginItem; view?: EggPluginItem;
vuessr?: EggPluginItem; vuessr?: EggPluginItem;
sequelize?: EggPluginItem;
cors?: EggPluginItem; cors?: EggPluginItem;
webpack?: EggPluginItem; webpack?: EggPluginItem;
webpackvue?: EggPluginItem; webpackvue?: EggPluginItem;
......
...@@ -6,6 +6,7 @@ module.exports = { ...@@ -6,6 +6,7 @@ module.exports = {
'admin/login': 'app/web/page/admin/login/login.vue', 'admin/login': 'app/web/page/admin/login/login.vue',
'admin/home': 'app/web/page/admin/home/index.ts', 'admin/home': 'app/web/page/admin/home/index.ts',
'editor': 'app/web/page/editor/index.ts', 'editor': 'app/web/page/editor/index.ts',
'activity': 'app/web/page/activity/index.ts',
}, },
resolve: { resolve: {
alias:{ alias:{
...@@ -20,16 +21,16 @@ module.exports = { ...@@ -20,16 +21,16 @@ module.exports = {
}, },
nodeExternals: { nodeExternals: {
whitelist: [ moduleName => { whitelist: [ moduleName => {
if (moduleName.includes('cherry-ui')) { if (moduleName.includes('cherry-ui') || moduleName.includes('@interactjs')) {
console.log(moduleName); console.log(moduleName);
} }
return /cherry-ui/.test(moduleName); return /cherry-ui/.test(moduleName) || /@interactjs/.test(moduleName);
}] }]
}, },
module:{ module:{
rules:[ rules:[
{ babel: { { babel: {
include: [resolve('app/web'), resolve('node_modules/@qg/cherry-ui')], include: [resolve('app/web'), resolve('node_modules/@qg/cherry-ui'), resolve('node_modules/@interactjs')],
exclude: [] exclude: []
} }
}, },
...@@ -44,7 +45,7 @@ module.exports = { ...@@ -44,7 +45,7 @@ module.exports = {
} }
}, },
{ less: true }, { less: true },
{ tslint: { options: { fix: true } } } { tslint: { options: { fix: true } } },
] ]
}, },
plugins: [ plugins: [
...@@ -55,7 +56,8 @@ module.exports = { ...@@ -55,7 +56,8 @@ module.exports = {
}] }]
} }
], ],
customize(webpackConfig){ devtool:'source-map',
customize(webpackConfig){
// 此外 webpackConfig 为原生生成的 webpack config,可以进行自定义处理 // 此外 webpackConfig 为原生生成的 webpack config,可以进行自定义处理
return webpackConfig; return webpackConfig;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment