Commit 92c63c3c authored by 郝聪敏's avatar 郝聪敏

feature: 基础组件库迁入;自由容器内组件支持拖拽;组件添加基础样式

parent 86be0fcf
......@@ -70,7 +70,6 @@ export class PageInfo extends Model<PageInfo> {
get() {
const moment = require('moment');
const updatedAt = this.getDataValue('updatedAt');
console.log('updatedAt', moment(updatedAt).format('YYYY-MM-DD HH:mm:ss'));
return moment(updatedAt).format('YYYY-MM-DD HH:mm:ss');
},
})
......
export const basicComponents = [
{
eleName: 'freedom-container',
title: '自由容器',
h: 300,
config: [
{
key: 'backgroundImage',
name: '背景图片',
type: 'Upload'
},
],
value: {
backgroundImage: 'http://activitystatic.q-gp.com/landing-bg.png';
},
commonStyle: {}
},
{
eleName: 'cr-button',
title: '按钮',
h: 42,
config: [
{
key: 'color',
name: '按钮颜色',
type: 'ColorPicker'
},
{
key: 'text',
name: '按钮文案',
type: 'Input'
}
],
value: {
color: '#07c160',
text: '按钮1'
},
commonStyle: {}
},
{
eleName: 'cr-field',
title: '输入框',
h: 48,
config: [
{
key: 'placeholder',
name: '提示信息',
type: 'Input'
},
{
key: 'label',
name: 'label',
type: 'Input'
}
],
value: {
placeholder: '请输入',
label: 'label'
},
commonStyle: {}
},
{
eleName: 'cr-image',
title: '图片',
h: 140,
config: [
{
key: 'width',
name: '宽度',
type: 'Input'
},
{
key: 'height',
name: '高度',
type: 'Input'
},
{
key: 'src',
name: '图片',
type: 'Upload'
},
],
value: {
width: '5.067rem',
height: '3.733rem',
src: 'https://appsync.lkbang.net/Fs0qmUsMry39AjHDf_W-qgn8XEy6',
fit: 'contain',
},
commonStyle: {}
}
];
const mdData = require('@qg/cherry-ui/md/index.json');
// mdData.forEach(com => com.value = com.config.reduce((pre, cur) => {
// pre[cur.key] = cur.val;
// return pre;
// }, {}));
// console.log('basicComponents', mdData);
export const basicComponents = mdData;
// export const basicComponents = [
// {
// eleName: 'freedom-container',
// title: '自由容器',
// h: 300,
// config: [
// {
// key: 'backgroundImage',
// name: '背景图片',
// type: 'Upload'
// },
// ],
// value: {
// backgroundImage: 'http://activitystatic.q-gp.com/landing-bg.png';
// },
// commonStyle: {}
// },
// {
// h: 44,
// eleName: 'cr-button',
// title: 'Button 按钮',
// config: [
// {
// key: 'type',
// name: '类型',
// desc: '类型,可选值为',
// options: [
// 'default',
// 'primary',
// 'info',
// 'danger',
// 'waring',
// 'success'
// ],
// type: 'select'
// },
// {
// key: 'size',
// name: '尺寸',
// desc: '尺寸,可选值为',
// options: [
// 'normal',
// 'large',
// 'small',
// 'mini'
// ],
// type: 'select'
// },
// {
// key: 'color',
// name: '颜色',
// desc: '按钮颜色,支持传入',
// type: 'ColorSelector'
// },
// {
// key: 'icon',
// name: '图标',
// desc: '左侧',
// type: 'text'
// },
// {
// key: 'block',
// name: '块级元素',
// desc: '是否为块级元素',
// type: 'checkbox'
// },
// {
// key: 'shape',
// name: '形状',
// desc: '按钮形状,可选值为',
// options: [
// 'square',
// 'circle'
// ],
// type: 'select'
// },
// {
// key: 'disabled',
// name: '禁用',
// desc: '是否禁用按钮',
// type: 'checkbox'
// },
// {
// key: 'plain',
// name: '朴素按钮',
// desc: '朴素按钮',
// type: 'checkbox'
// },
// {
// key: 'hairline',
// name: '细边框',
// desc: '细边框',
// type: 'checkbox'
// },
// {
// key: 'loading',
// name: '加载中',
// desc: '是否显示为加载状态',
// type: 'checkbox'
// },
// {
// key: 'loadingText',
// name: '加载文字',
// desc: '加载状态提示文字',
// type: 'text'
// },
// {
// key: 'loadingType',
// name: '加载图标类型',
// desc: '',
// options: [
// 'circular',
// 'spinner'
// ],
// type: 'select'
// },
// {
// key: 'loadingSize',
// name: '加载图标大小',
// desc: '加载图标大小',
// type: 'text'
// }
// ],
// value: {
// type: 'default',
// size: 'normal',
// color: '',
// icon: '',
// block: false,
// shape: 'square',
// disabled: false,
// plain: false,
// hairline: false,
// loading: false,
// loadingText: '',
// loadingType: 'circular',
// loadingSize: '20px'
// },
// commonStyle: {}
// },
// {
// eleName: 'cr-image',
// title: '图片',
// h: 140,
// config: [
// {
// key: 'width',
// name: '宽度',
// type: 'text'
// },
// {
// key: 'height',
// name: '高度',
// type: 'text'
// },
// {
// key: 'src',
// name: '图片',
// type: 'Upload'
// },
// ],
// value: {
// width: '5.067rem',
// height: '3.733rem',
// src: 'https://appsync.lkbang.net/Fs0qmUsMry39AjHDf_W-qgn8XEy6',
// fit: 'contain',
// },
// commonStyle: {}
// },
// {
// eleName: 'cr-nav-bar',
// title: 'NavBar 导航栏',
// h: 48,
// config: [
// {
// key: 'title',
// name: '标题',
// desc: '标题',
// propType: 'string',
// options: [],
// type: 'text'
// },
// {
// key: 'leftText',
// name: '左侧文案',
// desc: '左侧文案',
// propType: 'string',
// options: [],
// type: 'text'
// },
// {
// key: 'rightText',
// name: '右侧文案',
// desc: '右侧文案',
// default: '',
// options: [
// 'info',
// 'danger',
// 'primary',
// 'warning'
// ],
// type: 'text'
// },
// {
// key: 'leftArrow',
// name: '左侧箭头',
// desc: '是否显示左侧箭头',
// propType: 'boolean',
// options: [],
// type: 'checkbox'
// },
// {
// key: 'border',
// name: '边框',
// desc: '是否显示下边框',
// propType: 'boolean',
// options: [],
// type: 'checkbox'
// },
// {
// key: 'fixed',
// name: '固定导航栏',
// desc: '是否固定导航栏',
// propType: 'boolean',
// options: [],
// type: 'checkbox'
// },
// {
// key: 'zIndex',
// name: 'z-index',
// desc: '元素z-index',
// propType: [
// 'number',
// 'string'
// ],
// options: [],
// type: 'text'
// }
// ],
// value: {
// title: '',
// leftText: '返回',
// rightText: '',
// leftArrow: true,
// border: true,
// fixed: true,
// zIndex: '9999'
// },
// commonStyle: {}
// }
// ];
export const businessComponents = [
{
......@@ -99,7 +266,7 @@ export const businessComponents = [
{
key: 'btnTxt',
name: '按钮文案',
type: 'Input'
type: 'text'
},
{
key: 'btnColor',
......@@ -113,13 +280,13 @@ export const businessComponents = [
},
{
key: 'vcBgColor',
name: '验证码背景色',
name: '验证码背景色(可点击态)',
type: 'ColorSelector'
},
{
key: 'registerFrom',
name: '渠道号',
type: 'Input'
type: 'text'
},
],
value: {
......@@ -139,7 +306,7 @@ export const businessComponents = [
{
key: 'href',
name: '跳转链接',
type: 'Input'
type: 'text'
},
{
key: 'leftImg',
......@@ -148,7 +315,7 @@ export const businessComponents = [
}
],
value: {
href: 'https://s.xyqb.com/m',
href: 'https://s.xyqb.com/4',
leftImg: 'http://activitystatic.q-gp.com/xyqb%402x.png'
},
commonStyle: {}
......
......@@ -21,7 +21,7 @@ export default class Activity extends Vue {
return this.pageData && this.pageData.elements.map(v => v.point) || [];
}
@Watch('pageName')
@Watch('pageName', { immediate: true })
onPageNameChange(newVal) {
if (newVal) {
document.title = newVal;
......
import { Component, Vue, Prop, Watch } from 'vue-property-decorator';
import components from '@qg/cherry-ui/src/index.js';
import FreedomContainer from '../../component/FreedomContainer/index.vue';
import { kebabCase } from 'lodash';
import { chunk, flatten } from 'lodash';
import { kebabCase, chunk, flatten } from 'lodash';
import { State } from 'vuex-class';
@Component({ name: 'DynamicComponent' })
export default class DynamicComponent extends Vue {
@State(state => state.editor.gridLayout.colNum) colNum;
@Prop({ default: () => ([]), type: Array }) data;
eleConfig: array = [];
......@@ -18,7 +19,7 @@ export default class DynamicComponent extends Vue {
dragstart(event, eleName) {
this.$emit('dragstart');
const eleConfig = flatten(this.eleConfig).find(config => config.eleName === eleName);
const props = this.getProps(eleName);
// const props = this.getProps(eleName);
if (eleName.includes('template')) {
event.dataTransfer.setData('text', JSON.stringify({
template: eleConfig.page
......@@ -26,18 +27,20 @@ export default class DynamicComponent extends Vue {
} else if (eleName === 'freedom-container') {
event.dataTransfer.setData('text', JSON.stringify({
name: eleName,
point: {x: 0, y: 2, w: 12, h: eleConfig.h || 1, i: '0'},
title: eleConfig.title,
point: {x: 0, y: 2, w: this.colNum, h: eleConfig.h || 1, i: '0'},
child: [],
schame: eleConfig.config,
props: {...props, ...eleConfig.value},
props: {...eleConfig.value},
commonStyle: eleConfig.commonStyle
}));
} else {
event.dataTransfer.setData('text', JSON.stringify({
name: eleName,
point: {x: 0, y: 0, w: 12, h: eleConfig.h || 1, i: '0'},
title: eleConfig.title,
point: {x: 0, y: 0, w: this.colNum, h: eleConfig.h || 1, i: '0'},
schame: eleConfig.config,
props: {...props, ...eleConfig.value},
props: {...eleConfig.value},
commonStyle: eleConfig.commonStyle
}));
}
......
<template>
<div class="select">
<Select v-model="selected">
<Option v-for="item in options" :value="item" :key="item">{{ item }}</Option>
</Select>
</div>
</template>
<script>
export default {
props: {
value: String,
options: Array,
},
data() {
return {
selected: this.value,
}
},
watch: {
selected(val) {
this.$emit('input', val);
}
}
}
</script>
<style lang="less">
.color-selector {
display: flex;
align-items: center;
justify-content: space-between;
&-input {
flex-basis: 150px;
}
}
</style>
\ No newline at end of file
<template>
<Input v-model="text" :number="true"></Input>
</template>
<script>
export default {
props: {
value: Number,
},
data() {
return {
text: this.value,
}
},
watch: {
text(val) {
this.$emit('input', val);
}
}
}
</script>
\ No newline at end of file
<template>
<Input v-model="text" type="textarea"></Input>
</template>
<script>
export default {
props: {
value: Object | Array,
},
data() {
return {
text: JSON.stringify(this.value),
}
},
watch: {
text(val) {
try {
const jsonData = JSON.parse(val);
this.$emit('input', jsonData);
} catch(e) {
console.log(e);
}
}
}
}
</script>
\ No newline at end of file
......@@ -4,18 +4,39 @@ import { reduce, ceil, subtract, divide } from 'lodash';
import { ContextMenu } from '@editor/mixins/contextMenu.mixin';
import Upload from './component/Upload/index.vue';
import ColorSelector from './component/ColorSelector/index.vue';
import { resizeDiv } from '@/service/utils.service';
import BaseSelect from './component/BaseSelect/index.vue';
import Textarea from './component/Textarea/index.vue';
import Number from './component/Number/index.vue';
import { resizeDiv, getStyle } from '@/service/utils.service';
@Component({ components: { Upload, ColorSelector }, name: 'DynamicForm' })
@Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number }, name: 'DynamicForm' })
export default class DynamicForm extends Mixins(ContextMenu) {
@State(state => state.editor.curEleIndex) curEleIndex;
@State(state => state.editor.curChildIndex) curChildIndex;
@Getter('pageData') pageData;
@Prop({type: Object, default: () => ({ schame: [], props: {} })}) curElement;
form: object = {};
get curElement() {
let element = {};
if (this.curEleIndex !== null) {
if (this.curChildIndex !== null && this.pageData.elements[this.curEleIndex]) {
element = this.pageData.elements[this.curEleIndex].child[this.curChildIndex];
} else {
element = this.pageData.elements[this.curEleIndex];
}
}
return element;
}
get point() {
return this.curEleIndex || this.curEleIndex === 0 ? this.pageData.elements[this.curEleIndex]?.point : { h: 0, w: 0 };
}
get commonStyle() {
return (this.curEleIndex || this.curEleIndex === 0) && (this.curChildIndex || this.curChildIndex === 0) ? this.pageData.elements[this.curEleIndex].child[this.curChildIndex].commonStyle : { h: 0, w: 0 };
}
@Watch('curElement', { immediate: true, deep: true })
onElementChange(newVal) {
newVal?.schame?.forEach(schame => {
......@@ -47,16 +68,16 @@ export default class DynamicForm extends Mixins(ContextMenu) {
}
}
getStyle(oElement, sName) {
const result = oElement.currentStyle ? oElement.currentStyle[sName] : getComputedStyle(oElement, null)[sName];
return result.includes('px') ? result.slice(0, -2) : result;
resizedChildEvent(type) {
this.$emit('resizedChildEvent', type);
// const containerEle = this.$refs.freedomContainer[this.curEleIndex];
}
changeAlignType(type) {
const freedomBody = document.querySelector('.freedom-body');
const curElement = (freedomBody as Element).children[this.curChildIndex];
const [ containerW, containerH ] = [this.getStyle(freedomBody, 'width'), this.getStyle(freedomBody, 'height')];
const [ eleW, eleH ] = [this.getStyle(curElement, 'width'), this.getStyle(curElement, 'height')];
const [ containerW, containerH ] = [getStyle(freedomBody, 'width'), getStyle(freedomBody, 'height')];
const [ eleW, eleH ] = [getStyle(curElement, 'width'), getStyle(curElement, 'height')];
const elements = this.pageData.elements[this.curEleIndex].child[this.curChildIndex];
let { left, top } = elements.commonStyle;
switch (type) {
......@@ -76,4 +97,29 @@ export default class DynamicForm extends Mixins(ContextMenu) {
}
this.updatePageInfo({ containerIndex: this.curEleIndex, childIndex: this.curChildIndex, data: { ...elements, commonStyle: { ...elements.commonStyle, left, top } } });
}
getComponent(type) {
let result = 'Input';
switch (type) {
case 'text':
result = 'Input';
break;
case 'select':
result = 'BaseSelect';
break;
case 'checkbox' :
result = 'Checkbox';
break;
case 'ColorSelector' :
result = 'ColorSelector';
break;
case 'textarea' :
result = 'Textarea';
break;
case 'number' :
result = 'Number';
break;
}
return result;
}
}
\ No newline at end of file
<template>
<div class="dynamic-form">
<h2>{{curElement.title}}</h2>
<template>
<h4>组件属性</h4>
<Form ref="formCustom" :label-width="70" :model="form">
<Form class="dynamic-form-component" :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]" />
<component :is="getComponent(item.type)" :options="item.options" v-model="form[item.key]" />
</FormItem>
</Form>
</template>
<template v-if="eleName === 'freedom-container' || curChildIndex || curChildIndex === 0">
<template>
<h4>基础样式</h4>
<Form ref="formCustom" :label-width="70">
<FormItem label="尺寸" v-if="eleName === 'freedom-container'">
<Tooltip placement="top" content="全屏">
<Button type="ghost" icon="arrow-resize" @click="resizedEvent(667, 12)"></Button>
</Tooltip>
<Tooltip placement="top" content="根据背景图片自动调整宽高">
<Button type="ghost" icon="image" @click="resizedEvent(667, 12, true)" ></Button>
</Tooltip>
<Tooltip placement="top" content="宽100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(null, 12)"></Button>
</Tooltip>
<Tooltip placement="top" content="高100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(667, null)" ></Button>
</Tooltip>
</FormItem>
<FormItem label="定位" v-if="curChildIndex || curChildIndex === 0">
<Form class="dynamic-form-basic" :label-width="80">
<template v-if="curChildIndex || curChildIndex === 0">
<FormItem label="定位">
<Tooltip placement="top" content="上对齐">
<Button type="ghost" icon="arrow-up-c" @click="changeAlignType('top')"></Button>
<!-- <Button @click="changeAlignType('top')">上对齐</Button> -->
</Tooltip>
<Tooltip placement="top" content="右对齐">
<Button type="ghost" icon="arrow-right-c" @click="changeAlignType('right')"></Button>
<!-- <Button @click="changeAlignType('right')">右对齐</Button> -->
</Tooltip>
<Tooltip placement="top" content="下对齐">
<Button type="ghost" icon="arrow-down-c" @click="changeAlignType('bottom')"></Button>
<!-- <Button @click="changeAlignType('bottom')">下对齐</Button> -->
</Tooltip>
<Tooltip placement="top" content="左对齐">
<Button type="ghost" icon="arrow-left-c" @click="changeAlignType('left')"></Button>
<!-- <Button @click="changeAlignType('left')">左对齐</Button> -->
</Tooltip>
<Tooltip placement="top" content="垂直居中">
<Button type="ghost" icon="android-film" @click="changeAlignType('vertical')"></Button>
<!-- <Button @click="changeAlignType('vertical')">垂直居中</Button> -->
</Tooltip>
<Tooltip placement="top" content="水平居中">
<Button type="ghost" icon="android-film" @click="changeAlignType('horizontal')"></Button>
<!-- <Button @click="changeAlignType('horizontal')">水平居中</Button> -->
</Tooltip>
</FormItem>
</FormItem>
<FormItem label="位置">
<InputNumber class="Df-basic-inputnumber" v-model="commonStyle.left"></InputNumber>
<InputNumber v-model="commonStyle.top"></InputNumber>
</FormItem>
<FormItem label="尺寸">
<Tooltip placement="top" content="全屏">
<Button type="ghost" icon="arrow-resize" @click="resizedChildEvent('full')"></Button>
</Tooltip>
<Tooltip placement="top" content="宽100%">
<Button type="ghost" icon="arrow-swap" @click="resizedChildEvent('width')"></Button>
</Tooltip>
<Tooltip placement="top" content="高100%">
<Button type="ghost" icon="arrow-swap" @click="resizedChildEvent('height')" ></Button>
</Tooltip>
</FormItem>
<FormItem label="宽高">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="commonStyle.width"></InputNumber>
<InputNumber :max="667" :min="0" v-model="commonStyle.height"></InputNumber>
</FormItem>
</template>
<template v-if="(curEleIndex || curEleIndex === 0) && !curChildIndex && curChildIndex !== 0">
<FormItem label="尺寸">
<Tooltip placement="top" content="全屏">
<Button type="ghost" icon="arrow-resize" @click="resizedEvent(667, 375)"></Button>
</Tooltip>
<Tooltip placement="top" content="根据背景图片自动调整宽高">
<Button type="ghost" icon="image" @click="resizedEvent(667, 375, true)" ></Button>
</Tooltip>
<Tooltip placement="top" content="宽100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(null, 375)"></Button>
</Tooltip>
<Tooltip placement="top" content="高100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(667, null)" ></Button>
</Tooltip>
</FormItem>
<FormItem label="宽高">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="point.w"></InputNumber>
<InputNumber :max="667" :min="0" v-model="point.h"></InputNumber>
</FormItem>
</template>
</Form>
</template>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less">
<style lang="less" scoped>
.dynamic-form {
padding: 0 15px;
padding: 0 15px 16px;
h2 {
text-align: center;
}
h4 {
padding: 10px 0;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
&-component {
padding: 0 20px;
}
&-basic {
padding: 0 20px;
.Df-basic-inputnumber {
margin-right: 10px;
}
}
/deep/ .ivu-form-item-label {
font-size: 14px;
}
/deep/ .ivu-input-number {
width: 60px;
}
/deep/ .ivu-form-item-content {
.ivu-tooltip {
margin-right: 6px;
......
import { Component, Prop, Mixins } from 'vue-property-decorator';
import { Component, Prop, Mixins, Watch } from 'vue-property-decorator';
import LoginForm from '@/lib/Form/index.vue';
import DownloadGuide from '@/lib/DownloadGuide/index.vue';
import { ContextMenu } from '@editor/mixins/contextMenu.mixin';
import { cloneDeep } from 'lodash';
import { cloneDeep, pick, omit, throttle } from 'lodash';
import { Action, Mutation, State } from 'vuex-class';
import { convertPointStyle, getStyle } from '@/service/utils.service';
@Component({ components: { LoginForm }, name: 'FreedomContainer' })
@Component({ components: { LoginForm, DownloadGuide }, name: 'FreedomContainer' })
export default class FreedomContainer extends Mixins(ContextMenu) {
@Action('setDragable') setDragable;
@State(state => state.editor.curChildIndex) curChildIndex;
......@@ -20,7 +22,7 @@ export default class FreedomContainer extends Mixins(ContextMenu) {
const { top: startTop, left: startLeft } = childItem.child[childIndex].commonStyle;
const { clientY, clientX } = event;
const move = moveEvent => {
const move = throttle(moveEvent => {
moveEvent.stopPropagation();
moveEvent.preventDefault();
......@@ -28,7 +30,7 @@ export default class FreedomContainer extends Mixins(ContextMenu) {
const left = moveEvent.clientX - clientX + startLeft;
this.updatePageInfo({ containerIndex: this.containerIndex, childIndex, data: { ...childItem.child[childIndex], commonStyle: {...childItem.child[childIndex].commonStyle, top, left} } });
};
}, 50);
const up = () => {
this.setDragable(true);
document.removeEventListener('mousemove', move, true);
......@@ -38,23 +40,123 @@ export default class FreedomContainer extends Mixins(ContextMenu) {
document.addEventListener('mouseup', up, true);
}
transformStyle(styleObj) {
const style = {};
transformStyle(styleObj, element) {
let style = {};
for (const key of Object.keys(styleObj)) {
if ( typeof styleObj[key] === 'number') {
style[key] = `${(styleObj[key] / 37.5).toFixed(2)}rem`;
} else {
style[key] = styleObj[key].includes('px') ? `${(+(styleObj[key].slice(0, -2)) / 37.5).toFixed(2)}rem` : styleObj[key];
style[key] = styleObj[key]?.includes('px') ? `${(+(styleObj[key].slice(0, -2)) / 37.5).toFixed(2)}rem` : styleObj[key];
}
if (key === 'backgroundImage') {
style.backgroundImage = `url(${style.backgroundImage})`;
}
}
const transformFun = element === 'container' ? pick : omit;
style = transformFun(style, ['position', 'top', 'left']);
return style;
}
handleElementClick(curEleIndex, curChildIndex) {
console.log('handleElementClick', curEleIndex, curChildIndex, this.childItem);
this.$emit('handleElementClick', curEleIndex, curChildIndex);
}
@Watch('curChildIndex')
onIndexChange(newVal) {
this.childItem.child.forEach(item => delete item.dots);
if (newVal || newVal === 0) {
this.setPointStyle();
}
}
// 获取point计算后样式
setPointStyle() {
this.$nextTick(() => {
const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b'];
const [height, width] = this.getHW(this.curChildIndex);
const dots = points.reduce((pre, cur) => {
pre[cur] = convertPointStyle(cur, {height, width});
return pre;
}, {});
const childEle = this.childItem.child[this.curChildIndex];
// this.updatePageInfo({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: { ...childEle, commonStyle: { ...childEle.commonStyle, height: +height, width: +width } } });
this.updateCommonStyle({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: { ...childEle.commonStyle, height: +height, width: +width }});
this.$set(this.childItem.child[this.curChildIndex], 'dots', dots);
});
}
getHW(index) {
const childComponent = this.$refs.childComponent[index];
return [getStyle(childComponent.$el, 'height'), getStyle(childComponent.$el, 'width')];
}
setChildSize(type) {
const childEle = this.childItem.child[this.curChildIndex];
const [height, width] = [getStyle(this.$el, 'height'), getStyle(this.$el, 'width')];
console.log('setChildSize', this, height, width);
switch (type) {
case 'width':
this.updateCommonStyle({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: { ...childEle.commonStyle, width: +width }});
break;
case 'height':
this.updateCommonStyle({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: { ...childEle.commonStyle, height: +height }});
break;
case 'full':
this.updateCommonStyle({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: { ...childEle.commonStyle, height: +height, width: +width }});
break;
}
}
handleMouseDownOnPoint(point, event) {
const downEvent = event;
// 抛出事件让父组件设置当前元素选中状态
downEvent.stopPropagation();
downEvent.preventDefault();
const [height, width] = this.getHW(this.curChildIndex);
const pos = {...this.childItem.child[this.curChildIndex].commonStyle, height, width };
const top = pos.top;
const left = pos.left;
const startX = downEvent.clientX;
const startY = downEvent.clientY;
// 当前模块的最小宽度值
let minWidth = 0;
if (pos.minWidth) {
minWidth = pos.minWidth;
}
// 当前模块的最小高度值
let minHeight = 0;
if (pos.minHeight) {
minHeight = pos.minHeight;
}
const move = throttle(moveEvent => {
this.setDragable(false);
moveEvent.stopPropagation();
moveEvent.preventDefault();
const currX = moveEvent.clientX;
const currY = moveEvent.clientY;
const disY = currY - startY;
const disX = currX - startX;
const hasT = /t/.test(point);
const hasB = /b/.test(point);
const hasL = /l/.test(point);
const hasR = /r/.test(point);
const newHeight = +height + (hasT ? -disY : hasB ? disY : 0);
const newWidth = +width + (hasL ? -disX : hasR ? disX : 0);
pos.width = newWidth > 0 ? newWidth : 0;
pos.height = newHeight > 0 ? newHeight : 0;
pos.left = +left + (hasL ? disX : 0);
pos.top = +top + (hasT ? disY : 0);
this.updateCommonStyle({ containerIndex: this.containerIndex, childIndex: this.curChildIndex, data: pos });
this.setPointStyle();
}, 100);
const up = () => {
this.setDragable(true);
this.$emit('resize');
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
}
}
\ No newline at end of file
<template>
<div class="freedom" @click.stop="handleElementClick(containerIndex)">
<div class="freedom" :ref="`freedomContainer${containerIndex}`" @click.stop="handleElementClick(containerIndex)">
<div class="freedom-body" :style="{background: `url(${backgroundImage}) no-repeat 0 0 / cover`}">
<component :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle)" :is="item.name" :key="index" @click.stop.native="handleElementClick(containerIndex, index)" @mousedown.native.stop="mousedown(index, $event)" v-bind="item.props" @contextmenu.native.prevent.stop="show($event, containerIndex, index)"></component>
<div v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle, 'container')" :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" :key="index" @click.stop="handleElementClick(containerIndex, index)" @mousedown.stop="mousedown(index, $event)" @contextmenu.prevent.stop="show($event, containerIndex, index)">
<component ref="childComponent" :style="transformStyle(item.commonStyle, 'component')" :is="item.name" v-bind="item.props"></component>
<div class="freedom-body-dot"
v-for="(style, key) in item.dots"
:key="key"
:style="style"
@mousedown.stop.prevent="handleMouseDownOnPoint(key, $event)"/>
</div>
</div>
</div>
</div>
</template>
......@@ -22,13 +30,23 @@
width: 100%;
height: 100%;
overflow: hidden;
.freedom-body-item {
&-item {
font-size: 0;
&:hover {
border: 2px dashed #0c0c0c !important;
border: 1px dashed #0c0c0c !important;
}
}
&-dot {
width: 8px;
height: 8px;
background-color: #fff;
border: 1px solid #59c7f9;
border-radius: 8px;
position: absolute;
z-index: 1001;
}
.Fb-item_selected {
border: 2px dashed #0c0c0c !important;
border: 1px dashed #0c0c0c !important;
}
}
.activity {
......
......@@ -5,6 +5,7 @@ import { Mutation } from 'vuex-class';
export class ContextMenu extends Vue {
@Mutation('COPY_OR_DELETE_PAGE_INFO') updatePageData;
@Mutation('UPDATE_PAGE_INFO') updatePageInfo;
@Mutation('UPDATE_COMMON_STYLE') updateCommonStyle;
show(event, containerIndex, childIndex) {
console.log('删除', event, containerIndex, childIndex);
......
......@@ -31,6 +31,7 @@ export default class DashBoard extends Mixins(ContextMenu) {
@Getter('pageData') pageData;
@State(state => state.editor.gridLayout.draggable) draggable;
@State(state => state.editor.gridLayout.rowHeight) rowHeight;
@State(state => state.editor.gridLayout.colNum) colNum;
@State(state => state.editor.curEleIndex) curEleIndex;
@State(state => state.editor.curChildIndex) curChildIndex;
@State(state => state.editor.templateList) templateList;
......@@ -59,18 +60,6 @@ export default class DashBoard extends Mixins(ContextMenu) {
return this.pageData.elements.map(v => v.point);
}
get curElement() {
let element = {};
if (this.curEleIndex !== null) {
if (this.curChildIndex !== null && this.pageData.elements[this.curEleIndex]) {
element = this.pageData.elements[this.curEleIndex].child[this.curChildIndex];
} else {
element = this.pageData.elements[this.curEleIndex];
}
}
return element;
}
// 选择组件库
selectMaterial(val: string) {
this.activeName = val;
......@@ -98,6 +87,7 @@ export default class DashBoard extends Mixins(ContextMenu) {
}
handleElementClick(curEleIndex = null, curChildIndex = null) {
console.log('handleElementClick - DashBoard', curEleIndex, curChildIndex);
this.toggle(false);
this.setCurEleIndex(curEleIndex);
this.setCurChildIndex(curChildIndex);
......@@ -185,4 +175,13 @@ export default class DashBoard extends Mixins(ContextMenu) {
const index = this.pageData.elements.findIndex(ele => ele.point.i === i);
this.updatePageInfo({ containerIndex: index, data: { ...this.pageData.elements[index], point: { ...this.pageData.elements[index].point, w, h } } });
}
/**
* 调整自由容器子元素宽高及边框原点位置
* @param {[type]} type 尺寸类型
*/
resizedChildEvent(type) {
const containerEle = this.$refs.container[this.curEleIndex];
containerEle.setChildSize(type);
containerEle.setPointStyle();
}
}
\ No newline at end of file
......@@ -52,7 +52,7 @@
@dragleave="dragleave" @drop="drops">
<grid-layout
:layout.sync="layout"
:col-num="12"
:col-num="colNum"
:row-height="rowHeight"
:margin="[0, 0]"
:is-draggable="draggable"
......@@ -71,7 +71,7 @@
@contextmenu.native.prevent="show($event, index)"
@resized="resizedEvent"
:class="{'Dcmcp-item_selected': curEleIndex === index && curChildIndex === null}">
<component class="Dcmcp-item-com" @handleElementClick="handleElementClick" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
<component ref="container" class="Dcmcp-item-com" @handleElementClick="handleElementClick" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
</grid-item>
</grid-layout>
</div>
......@@ -79,7 +79,7 @@
<Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']">
<Tabs class="Dc-middle-editing" type="card">
<TabPane label="属性">
<dynamic-form :curElement="curElement" @modProps="modProps"></dynamic-form>
<dynamic-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-form>
</TabPane>
<TabPane label="事件">事件</TabPane>
<TabPane label="页面设置">页面设置</TabPane>
......@@ -129,6 +129,7 @@
flex: 1;
padding: 10px;
background: #fff;
overflow-y: scroll;
}
.tabs-position();
}
......@@ -152,9 +153,7 @@
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout {
.vue-grid-item {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background: #fff;
overflow: hidden;
&:hover {
......@@ -163,6 +162,9 @@
&>*:first-child {
height: 100%;
}
.vue-resizable-handle {
z-index: 10000;
}
}
.Dcmcp-item_selected {
border: 1px dashed #0c0c0c !important;
......@@ -187,6 +189,11 @@
height: 100%;
min-width: 320px;
.tabs-position();
/deep/ .ivu-tabs-content {
height: calc(100% - 48px);
overflow-y: scroll;
overflow-x: hidden;
}
}
}
.Dcm-sider_none {
......
......@@ -13,6 +13,7 @@ import {
RESET_PAGE_DATA,
SET_TEMPLATE_LIST,
SET_PAGE_DATA,
UPDATE_COMMON_STYLE,
} from './type';
import RootState from '../../state';
......@@ -99,6 +100,8 @@ export default class EditorModule implements Module<EditorState, RootState> {
} else {
page.splice(containerIndex, 1);
}
state.curEleIndex = null;
state.curChildIndex = null;
} else if (type === 'copy') {
let eleCopyed = {} as PageElement;
if (childIndex || childIndex === 0) {
......@@ -119,6 +122,14 @@ export default class EditorModule implements Module<EditorState, RootState> {
page.splice(containerIndex, 1, data);
}
},
[UPDATE_COMMON_STYLE](state, {containerIndex, childIndex, data}) {
const page = (state.pageInfo.page as Page).elements;
if (childIndex || childIndex === 0) {
page[containerIndex].child[childIndex].commonStyle = data;
} else {
page[containerIndex].commonStyle = data;
}
},
[ADD_ELEMENTS](state, { containerIndex, data }) {
const page = (state.pageInfo.page as Page).elements;
if (containerIndex || containerIndex === 0) {
......
......@@ -25,6 +25,7 @@ interface GridLayout {
export interface PageElement {
name: string;
title: string;
schame: Schame[];
props: object;
point: Point;
......@@ -62,7 +63,8 @@ export const defaultState = {
templateList: [],
gridLayout: {
draggable: true,
rowHeight: 1
rowHeight: 1,
colNum: 375
},
};
......
......@@ -9,4 +9,5 @@ export const SET_CUR_ELE_INDEX = 'SET_CUR_ELE_INDEX';
export const SET_CUR_CHILD_INDEX = 'SET_CUR_CHILD_INDEX';
export const RESET_PAGE_DATA = 'RESET_PAGE_DATA';
export const SET_TEMPLATE_LIST = 'SET_TEMPLATE_LIST';
export const SET_PAGE_DATA = 'SET_PAGE_DATA';
\ No newline at end of file
export const SET_PAGE_DATA = 'SET_PAGE_DATA';
export const UPDATE_COMMON_STYLE = 'UPDATE_COMMON_STYLE';
\ No newline at end of file
......@@ -52,3 +52,44 @@ export function resizeDiv(imgUrl, clientHeight = 0, clientWidth = 0, callback) {
};
img.src = imgUrl;
}
const DK = {t: 'n', b: 's', l: 'w', r: 'e'}; // 上下左右 对应的 东南西北
export const convertPointStyle = (point, defaultStyle, directionKey = DK) => {
const pos = defaultStyle;
const height = pos.height;
const width = pos.width;
const hasT = /t/.test(point);
const hasB = /b/.test(point);
const hasL = /l/.test(point);
const hasR = /r/.test(point);
let newLeft = 0;
let newTop = 0;
if (point.length === 2) {
newLeft = hasL ? 0 : width;
newTop = hasT ? 0 : height;
} else {
// !#zh 上下点,宽度固定在中间
if (hasT || hasB) {
newLeft = width / 2 - 4;
newTop = hasT ? 0 : height;
}
// !#zh 左右点,高度固定在中间
if (hasL || hasR) {
newLeft = hasL ? 0 : width;
newTop = height / 2 - 4;
}
}
const style = {
marginLeft: (hasL || hasR) ? '-4px' : 0,
marginTop: (hasT || hasB) ? '-4px' : 0,
left: `${newLeft}px`,
top: `${newTop}px`,
cursor: point.split('').reverse().map(m => directionKey[m]).join('') + '-resize'
};
return style;
};
export const getStyle = function(oElement, sName) {
const result = oElement.currentStyle ? oElement.currentStyle[sName] : getComputedStyle(oElement, null)[sName];
return result.includes('px') ? result.slice(0, -2) : result;
};
<!DOCTYPE html>
<html lang="en" style="font-size: 37.5px;">
<head>
<title>低代码平台</title>
<meta name="keywords">
<meta name="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/reset.css">
</head>
<body>
<div id="app"><!--vue-ssr-outlet--></div>
<script src="https://cdn.staticfile.org/plupload/2.1.2/plupload.full.min.js"></script>
</body>
<head>
<title>低代码平台1</title>
<meta name="keywords">
<meta name="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/reset.css">
<style>
html, body {
line-height: 1.15 !important;
}
</style>
</head>
<body>
<div id="app"><!--vue-ssr-outlet--></div>
<script src="https://cdn.staticfile.org/plupload/2.1.2/plupload.full.min.js"></script>
</body>
</html>
\ No newline at end of file
......@@ -17,7 +17,8 @@
"max-line-length": false,
"only-arrow-functions": false,
"interface-over-type-literal": false,
"ter-indent": [ true, 2]
"ter-indent": [ true, 2],
"no-var-requires": false
},
"rulesDirectory": ["app"]
}
\ No newline at end of file
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