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 { deserialize } from '@hubcarl/json-typescript-mapper';
import Article from '../model/article';
// import Article from '../model/article';
import Condition from '../lib/condition';
export default class AdminController extends Controller {
......@@ -15,21 +15,21 @@ export default class AdminController extends Controller {
public async list(ctx: Context) {
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) {
const article = deserialize(Article, ctx.request.body);
ctx.body = await ctx.service.article.saveArticle(article);
// const article = deserialize(Article, ctx.request.body);
// ctx.body = await ctx.service.article.saveArticle(article);
}
public async del(ctx: Context) {
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) {
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 { deserialize } from '@hubcarl/json-typescript-mapper';
export default class AdminController extends Controller {
export default class EditorController extends Controller {
public async home(ctx: Context) {
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) => {
router.post('/admin/api/article/add', controller.admin.add);
router.post('/admin/api/article/del', controller.admin.del);
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('/admin', controller.admin.home);
router.get('/admin/*', controller.admin.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 { deserialize } from '@hubcarl/json-typescript-mapper';
import Colllection from '../lib/db/collection';
import Article from '../model/article';
import Condition from '../lib/condition';
// import { Context, Service } from 'egg';
// import { deserialize } from '@hubcarl/json-typescript-mapper';
// import Colllection from '../lib/db/collection';
// import Article from '../model/article';
// import Condition from '../lib/condition';
export default class ArticeService extends Service {
private context: Context;
private colllection: Colllection;
constructor(ctx: Context) {
super(ctx);
this.context = ctx;
this.colllection = new Colllection(ctx.db, 'article');
}
// export default class ArticeService extends Service {
// private context: Context;
// private colllection: Colllection;
// constructor(ctx: Context) {
// super(ctx);
// this.context = ctx;
// this.colllection = new Colllection(ctx.db, 'article');
// }
public async getArtilceList(condition: Condition) {
if (condition.categoryId) {
condition.where.categoryId = condition.categoryId;
}
if (condition.status) {
condition.where.status = condition.status;
}
if (condition.title) {
condition.like.title = condition.title;
}
return this.colllection.getPager(condition);
}
// public async getArtilceList(condition: Condition) {
// if (condition.categoryId) {
// condition.where.categoryId = condition.categoryId;
// }
// if (condition.status) {
// condition.where.status = condition.status;
// }
// if (condition.title) {
// condition.like.title = condition.title;
// }
// return this.colllection.getPager(condition);
// }
public async saveArticle(data: object) {
const article: Article = deserialize(Article, data);
if (article.id) {
return this.colllection.update({ id: article.id }, article);
}
article.id = this.context.db.getUniqueId();
this.colllection.add(article);
return article;
}
// public async saveArticle(data: object) {
// const article: Article = deserialize(Article, data);
// if (article.id) {
// return this.colllection.update({ id: article.id }, article);
// }
// article.id = this.context.db.getUniqueId();
// this.colllection.add(article);
// return article;
// }
public async query(json: object) {
return this.colllection.query(json);
}
// public async query(json: object) {
// return this.colllection.query(json);
// }
public async deleteArticle(id: string) {
return this.colllection.delete({ id });
}
}
// public async deleteArticle(id: string) {
// 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';
import iView from 'iview';
import cherryUi from '../../../../../node_modules/@qg/cherry-ui/src/index';
import 'iview/dist/styles/iview.css';
import '@qg/cherry-ui/dist/cherry.css';
Vue.use(iView);
Vue.use(cherryUi);
......
......@@ -8,6 +8,7 @@ export default class App {
}
bootstrap() {
console.log('EASY_ENV_IS_NODE', EASY_ENV_IS_NODE);
if (EASY_ENV_IS_NODE) {
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
This diff is collapsed.
This diff is collapsed.
<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 Article from '../../../../../../model/article';
// import Article from '../../../../../../model/article';
import {
Getter,
......@@ -8,7 +8,7 @@ import {
@Component
export default class Detail extends Vue {
@Getter('article') article?: Article;
@Getter('article') article;
fetchApi(options) {
const { store, route } = options;
const { id } = route.params;
......
import Vue from 'vue';
import { Getter, Action } from 'vuex-class';
import { Component, Prop, Watch } from 'vue-property-decorator';
import Article from '../../../../../../model/article';
// import Article from '../../../../../../model/article';
@Component({
components: {
......@@ -9,7 +9,7 @@ import Article from '../../../../../../model/article';
}
})
export default class Write extends Vue {
@Getter('article') article?: Article;
@Getter('article') article;
@Action('saveArticle') saveArticle;
@Action('getArticle') getArticle;
text: string = '';
......
......@@ -12,4 +12,15 @@ export default class Login extends Vue {
username: string = 'admin';
password: string = 'admin';
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 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' })
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) {
console.log('dragstart');
// dataTransfer.setData()方法设置数据类型和拖动的数据
event.dataTransfer.setData('text', JSON.stringify({
name: 'cr-button',
point: {x: 0, y: 0, w: 12, h: 1, i: '0'},
dragstart(event, eleName) {
this.$emit('dragstart');
if (eleName === 'freedom-container') {
event.dataTransfer.setData('text', JSON.stringify({
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'));
// 指定拖放操作所允许的一个效果
event.dataTransfer.effectAllowed = 'copyMove';
}
dragend() {
this.$emit('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>
<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">
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart">
<p slot="title">Button</p>
<div>
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'cr-button')">
<p slot="title">按钮</p>
<div class="dynamic-row-card">
<img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/>
......@@ -13,9 +13,9 @@
</Card>
</Col>
<Col span="10">
<Card>
<p slot="title">Icon</p>
<div >
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'freedom-container')">
<p slot="title">自由容器</p>
<div class="dynamic-row-card">
<img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/>
......@@ -26,9 +26,9 @@
</Row>
<Row class="dynamic-row" type="flex" justify="space-between">
<Col span="10">
<Card>
<p slot="title">Field</p>
<div >
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart($event,'cr-field')">
<p slot="title">输入框</p>
<div class="dynamic-row-card">
<img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/>
......@@ -37,9 +37,9 @@
</Card>
</Col>
<Col span="10">
<Card>
<p slot="title">Swipe</p>
<div >
<Card draggable="true" @dragend.native="dragend" @dragstart.native="dragstart">
<p slot="title">轮播图</p>
<div class="dynamic-row-card">
<img
src="http://desk.fd.zol-img.com.cn/g5/M00/00/07/ChMkJ1ZqMb2IWITEAAbRDaofaNIAAGBHwO3hh0ABtEl380.jpg"
/>
......@@ -59,11 +59,15 @@
p {
text-align: center;
}
img {
width: 100%;
height: 100%;
object-fit: fill;
-webkit-user-drag: none;
&-card {
display: flex;
justify-content: center;
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 VueGridLayout from 'vue-grid-layout';
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 {
Getter,
Action
Action,
State
} from 'vuex-class';
@Component({components: { DynamicConponent, GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem }, name: 'DashBoard')
@Component({components: { DynamicConponent, FreedomContainer, DynamicForm, GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem }, name: 'DashBoard'})
export default class DashBoard extends Vue {
@Action('addPageDate') addPageDate;
@Action('updatePageDate') updatePageDate;
@Getter('pageId') pageId;
activeName: string = '1';
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 = {
elements: [
{
name: 'cr-button',
point: {x: 0, y: 0, w: 12, h: 1, i: '0'},
},
{
name: 'cr-field',
point: {x: 0, y: 1, w: 12, h: 1, i: '1'},
},
// {
// name: 'cr-button',
// point: {x: 0, y: 0, w: 12, h: 1, i: '0'},
// schame: schameConfig.config,
// props: {...this.getProps('cr-button'), ...schameConfig.value},
// commonStyle: {
// 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 {
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;
}
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) {
console.log(val);
this.isCollapsed = val;
}
dragstart() {
this.isDraging = true;
}
dragend() {
this.isDraging = false;
}
dragenter() {
console.log('dragenter');
this.isDrag = true;
this.isDragIn = true;
}
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) {
this.isDrag = false;
console.log(event.target);
this.isDragIn = false;
this.isCollapsed = false;
const data = JSON.parse(event.dataTransfer.getData('text'));
event.dataTransfer.clearData();
console.log({...data, i: this.pageData.elements.length});
this.pageData.elements.push({...data, point: { ...data.point, i: this.pageData.elements.length}});
console.log({...data, i: this.pageData.elements.length}, this.pageData.elements[event.target.dataset.index]);
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>
<Row class="dashboard">
<Row class="dashboard" v-cloak>
<Row class="dashboard-header" type="flex" align="middle">
<Col span="8">
<h3>低代码开发平台</h3>
......@@ -10,7 +10,7 @@
<Icon type="ionic"></Icon>
<span>保存</span>
</Col>
<Col span="7">
<Col span="7" @click.native="preview">
<Icon type="monitor"></Icon>
<span>预览</span>
</Col>
......@@ -19,7 +19,7 @@
</Row>
<Row class="dashboard-container">
<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">
<Icon type="ios-paper"></Icon>
</MenuItem>
......@@ -33,7 +33,7 @@
<div class="Dc-left-content">
<div v-show="activeName === '1'">
<h4>基础库</h4>
<dynamic-conponent></dynamic-conponent>
<dynamic-conponent @dragstart="dragstart" @dragend="dragend"></dynamic-conponent>
</div>
<div v-show="activeName === '2'">
<h4>业务库</h4>
......@@ -46,36 +46,39 @@
<Col class="dashboard-container-middle" span="18">
<Row class="Dc-middle-row">
<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">
<grid-layout
:layout.sync="layout"
: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 @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"
: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" :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-layout>
</div>
</Col>
<Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']">
<Tabs class="Dc-middle-editing">
<TabPane label="属性">属性</TabPane>
<TabPane label="事件">事件</TabPane>
<TabPane label="页面设置">页面设置</TabPane>
</Tabs>
<TabPane label="属性">
<dynamic-form :curElement="curElement" @modProps="modProps"></dynamic-form>
</TabPane>
<TabPane label="事件">事件</TabPane>
<TabPane label="页面设置">页面设置</TabPane>
</Tabs>
</Col>
</Row>
</Col>
......@@ -84,12 +87,16 @@
</template>
<style lang="less">
.tabs-position() {
/deep/ .ivu-tabs-nav-scroll {
display: flex;
justify-content: center;
}
/deep/ .ivu-tabs-nav-scroll {
display: flex;
justify-content: center;
}
}
[v-cloak] {
display: none;
}
.dashboard {
min-width: 1280px;
height: 100% !important;
&-header {
position: relative;
......@@ -124,26 +131,39 @@
.Dc-middle-row {
height: 100%;
.Dc-middle-container {
display: flex;
justify-content: center;
align-items: center;
overflow: scroll;
height: 100%;
opacity: 1;
transition: width ease-in-out 0.5s;
.Dcm-container-panel {
margin: 30px auto;
width: 375px;
min-height: 664px;
background-color: #fff;
height: 667px;
min-height: 667px;
overflow-y: scroll;
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-item {
display: flex;
justify-content: center;
align-items: center;
/deep/ .vue-grid-layout {
transform: translateY(-10px);
.vue-grid-item {
display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
}
.Dcmc-panel-com {
height: 50px;
// height: 38px;
}
&_in {
// &_in {
// opacity: 0.7;
// }
&_draging {
.Dcmc-panel-com {
* {
pointer-events: none;
}
}
opacity: 0.7;
}
}
......@@ -154,6 +174,7 @@
transition: width ease-in-out 0.5s;
.Dc-middle-editing {
height: 100%;
min-width: 320px;
.tabs-position();
}
}
......
......@@ -3,16 +3,18 @@ import Vue from 'vue';
import Vuex from 'vuex';
import RootState from './state';
import Admin from './modules/admin';
import Editor from './modules/editor';
Vue.use(Vuex);
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 };
return new Vuex.Store<RootState>({
state,
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>
<html lang="en">
<html lang="en" style="font-size: 37.5px;">
<head>
<title>Egg + Vue + TypeScript</title>
<meta name="keywords">
......
......@@ -37,5 +37,14 @@ export default (appInfo: EggAppConfig) => {
'global'
];
config.sequelize = {
dialect: 'mysql',
username: 'qa',
password: 'qatest',
host: '172.17.5.9',
port: 31024,
database: 'low_code',
};
return config;
};
......@@ -27,5 +27,14 @@ export default (appInfo: EggAppConfig) => {
webpackConfigList: getWebpackConfig()
};
exports.sequelize = {
dialect: 'mysql',
username: 'qa',
password: 'qatest',
host: '172.17.5.9',
port: 31024,
database: 'low_code',
};
return exports;
};
export default {
vuessr: {
package: 'egg-view-vue-ssr'
},
sequelize: {
enable: true,
package: 'egg-sequelize',
}
};
\ No newline at end of file
......@@ -7,7 +7,7 @@
"backend": "nohup egg-scripts start --port 7001 --workers 4 &",
"dev": "egg-bin dev -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",
"clean": "ets clean",
"kill": "easy kill",
......@@ -19,11 +19,12 @@
"dependencies": {
"@better-scroll/core": "^2.0.5",
"@hubcarl/json-typescript-mapper": "^2.0.0",
"@qg/cherry-ui": "^2.8.35",
"@qg/cherry-ui": "^2.10.40",
"axios": "^0.18.1",
"egg": "^2.3.0",
"egg-cors": "^2.1.1",
"egg-scripts": "^2.10.0",
"egg-sequelize": "^6.0.0",
"egg-view-vue-ssr": "^3.0.5",
"egg-webpack": "^4.4.7",
"egg-webpack-vue": "^2.0.0",
......@@ -36,6 +37,8 @@
"lowdb": "^1.0.0",
"mockjs": "^1.0.1-beta3",
"moment": "^2.17.1",
"mysql2": "^2.2.5",
"postcss-px2rem": "^0.3.0",
"shortid": "^2.2.8",
"showdown": "^1.8.6",
"simplemde": "^1.11.2",
......
module.exports = {
plugins: {
"postcss-px2rem": {
remUnit: 37.5
}
}
};
......@@ -2,11 +2,13 @@
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportActivity from '../../../app/controller/activity';
import ExportAdmin from '../../../app/controller/admin';
import ExportEditor from '../../../app/controller/editor';
declare module 'egg' {
interface IController {
activity: ExportActivity;
admin: ExportAdmin;
editor: ExportEditor;
}
......
......@@ -2,10 +2,10 @@
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportArticle from '../../../app/model/article';
import ExportPageInfo from '../../../app/model/pageInfo';
declare module 'egg' {
interface IModel {
Article: ReturnType<typeof ExportArticle>;
PageInfo: ReturnType<typeof ExportPageInfo>;
}
}
......@@ -15,6 +15,7 @@ import 'egg-static';
import 'egg-jsonp';
import 'egg-view';
import 'egg-view-vue-ssr';
import 'egg-sequelize';
import 'egg-cors';
import 'egg-webpack';
import 'egg-webpack-vue';
......@@ -34,6 +35,7 @@ declare module 'egg' {
jsonp?: EggPluginItem;
view?: EggPluginItem;
vuessr?: EggPluginItem;
sequelize?: EggPluginItem;
cors?: EggPluginItem;
webpack?: EggPluginItem;
webpackvue?: EggPluginItem;
......
......@@ -6,6 +6,7 @@ module.exports = {
'admin/login': 'app/web/page/admin/login/login.vue',
'admin/home': 'app/web/page/admin/home/index.ts',
'editor': 'app/web/page/editor/index.ts',
'activity': 'app/web/page/activity/index.ts',
},
resolve: {
alias:{
......@@ -20,16 +21,16 @@ module.exports = {
},
nodeExternals: {
whitelist: [ moduleName => {
if (moduleName.includes('cherry-ui')) {
if (moduleName.includes('cherry-ui') || moduleName.includes('@interactjs')) {
console.log(moduleName);
}
return /cherry-ui/.test(moduleName);
return /cherry-ui/.test(moduleName) || /@interactjs/.test(moduleName);
}]
},
module:{
rules:[
{ 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: []
}
},
......@@ -44,7 +45,7 @@ module.exports = {
}
},
{ less: true },
{ tslint: { options: { fix: true } } }
{ tslint: { options: { fix: true } } },
]
},
plugins: [
......@@ -55,7 +56,8 @@ module.exports = {
}]
}
],
customize(webpackConfig){
devtool:'source-map',
customize(webpackConfig){
// 此外 webpackConfig 为原生生成的 webpack config,可以进行自定义处理
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