Commit 6a239e91 authored by 郭志伟's avatar 郭志伟

Merge branch 'feat/v1.3' into 'master'

Feat/v1.3

See merge request !37
parents 81bf55b8 28d38401
...@@ -27,4 +27,6 @@ app/view/* ...@@ -27,4 +27,6 @@ app/view/*
!app/view/.gitkeep !app/view/.gitkeep
yarn.lock yarn.lock
*.log *.log
coverage coverage
\ No newline at end of file .yalc/
yalc.lock
\ No newline at end of file
...@@ -71,6 +71,25 @@ export default class EditorController extends Controller { ...@@ -71,6 +71,25 @@ export default class EditorController extends Controller {
ctx.body = ctx.helper.ok(list); ctx.body = ctx.helper.ok(list);
} }
public async clearCache(ctx: Context) {
const where = {
updated_at: {
[ctx.model.Sequelize.Op.lt]: Date.now(),
[ctx.model.Sequelize.Op.gt]: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)
},
enable: 1,
isPublish: 1
};
const pageInfo = await ctx.model.PageInfo.findAll({
attributes: ['uuid'],
where
});
pageInfo.forEach(async item => {
await ctx.service.redis.del(`page:${item.uuid}`);
});
ctx.body = ctx.helper.ok();
}
public async delete(ctx: Context) { public async delete(ctx: Context) {
const pageInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.pageId }}); const pageInfo = await ctx.model.PageInfo.update({ enable: 0 }, {where: { id: +ctx.params.pageId }});
ctx.body = ctx.helper.ok(pageInfo); ctx.body = ctx.helper.ok(pageInfo);
......
...@@ -11,6 +11,7 @@ export default (application: Application) => { ...@@ -11,6 +11,7 @@ export default (application: Application) => {
router.post('/editor/save', controller.editor.save); router.post('/editor/save', controller.editor.save);
router.post('/editor/update', controller.editor.update); router.post('/editor/update', controller.editor.update);
router.post('/editor/clearcache', controller.editor.clearCache);
router.get('/editor/get/list', controller.editor.getList); router.get('/editor/get/list', controller.editor.getList);
router.get('/editor/get/template', controller.editor.getTemplateList); router.get('/editor/get/template', controller.editor.getTemplateList);
router.get('/editor/get/:uuid', controller.editor.get); router.get('/editor/get/:uuid', controller.editor.get);
......
...@@ -6,6 +6,9 @@ export default { ...@@ -6,6 +6,9 @@ export default {
getPageList(params) { getPageList(params) {
return http.get('editor/get/list', { params }); return http.get('editor/get/list', { params });
}, },
refreshCache() {
return http.post('editor/clearcache');
},
getPageById(params) { getPageById(params) {
return http.get(`editor/get/${params.pageId}`); return http.get(`editor/get/${params.pageId}`);
}, },
......
...@@ -8,4 +8,8 @@ interface Window { ...@@ -8,4 +8,8 @@ interface Window {
__POWERED_BY_QIANKUN__: string; __POWERED_BY_QIANKUN__: string;
__INJECTED_PUBLIC_PATH_BY_QIANKUN__: string; __INJECTED_PUBLIC_PATH_BY_QIANKUN__: string;
} }
declare var apollo: any; declare var apollo: any;
\ No newline at end of file
interface HTMLDivElement {
scrollTop?: number
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ export default class DynamicForm extends Vue { ...@@ -28,7 +28,7 @@ export default class DynamicForm extends Vue {
}; };
get enableShare() { get enableShare() {
return this.pageData.props?.showShare; return this.pageData.props?.btAttachVal.some(item => item.persets === '分享');
} }
@Watch('pageData', { immediate: true }) @Watch('pageData', { immediate: true })
......
import { Component, Vue } from 'vue-property-decorator';
import { Getter, Mutation, State } from 'vuex-class';
interface Point {
h: number;
i: string;
moved: boolean;
w: number;
x: number;
y: number;
}
@Component({ name: 'DynamicComponentSort' })
export default class DynamicComponentSort extends Vue {
@Mutation('SET_CUR_ELE_INDEX') setCurEleIndex;
@State(state => state.editor.curEleIndex) curEleIndex!: number | null;
@Getter('pageData') pageData;
get layout() {
return this.pageData.elements.map((v, i) => {
const { point, title, id } = v;
return {
point,
id,
index: i,
title: `${title}-${id}`
};
}).sort((cur, next) => {
return cur.point.y > next.point.y ? 1 : -1;
});
}
handleComponentClick(point: Point, index: number) {
this.$nextTick(() => {
this.setCurEleIndex(index);
(document.getElementById('DcmContainerPanel') as HTMLDivElement).scrollTop = point?.y;
});
}
}
\ No newline at end of file
<template>
<div class="dynamic-sort">
<div :class="['dynamic-sort__item', { 'active': curEleIndex === item.index }]" v-for="(item, index) in layout" :key="index" @click="handleComponentClick(item.point, item.index)">
{{ item.title }}
</div>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less" scoped>
.dynamic-sort {
padding: 12px;
&__item {
background-color: #fff;
border-radius: 4px;
border: 1px solid #f7f7f7;
color: #333;
font-size: 13px;
padding: 8px 12px;
margin-bottom: 8px;
&:hover {
cursor: pointer;
border-color: #2d8cf0;
}
&.active {
border-color: #2d8cf0;
}
}
}
</style>
<template>
<div class="form-list">
<Card class="form-list-card" :key="index" v-for="(item, index) in list">
<div slot='title' class="Fl-card-title">
<h4>项目{{index + 1}}</h4>
<a @click="del(index)">删除</a>
</div>
<Form @submit.native.prevent :model="item" :label-width="50">
<FormItem :prop="`${keywords.key}`" :label="keywords.name" :key="idx" v-for="(keywords, idx) in formControl">
<component :is="getComponent(keywords.type)" v-bind="keywords.props" :options="keywords.options" :disabled="shareDisableUrl(item, keywords)" v-model="item[keywords.key]" @input="handleModelChange($event, keywords.key, index)" />
</FormItem>
</Form>
</Card>
<div class="form-list-button">
<Button type="dashed" icon="plus-round" @click="add">添加项目</Button>
</div>
</div>
</template>
<script>
import FormList from '../mixins/formList.mixin.ts';
import { SHOP_CART_CONFIG, SHARE_CONFIG, DEFAULT_CONFIG } from '@service/staticData.service';
import { v4 as uuid } from 'uuid';
const CONFIG_MAP = {
['购物车']: SHOP_CART_CONFIG,
['分享']: SHARE_CONFIG,
['自定义']: DEFAULT_CONFIG
};
export default {
mixins: [FormList],
data() {
return {
oldPersets: []
};
},
methods: {
shareDisableUrl(item, keywords) {
return item.persets === '分享' && keywords.key === 'url';
},
handleModelChange(e, key, index) {
if (key !== 'persets') return;
if (e !== this.oldPersets[index]) {
this.$set(this.list, index, JSON.parse(JSON.stringify(CONFIG_MAP[e])));
this.oldPersets[index] = e;
}
},
add() {
const object = this.formControl.reduce((pre, cur) => {
pre[cur.key] = this.formDefault[cur.key] || '';
return pre;
}, {});
if (object.persets === '自定义') {
object.name = '自定义-' + uuid().slice(19);
}
this.list.push(object);
},
del(index) {
const list = JSON.parse(JSON.stringify(this.list));
list.splice(index, 1);
this.list = [];
this.$nextTick(() => {
this.list = list;
});
}
}
}
</script>
<style lang="less" scoped>
.form-list {
/deep/ .ivu-card {
margin-bottom: 16px;
.ivu-form {
.ivu-form-item {
display: flex;
margin-bottom: 12px;
}
.ivu-form-item-label {
text-align: left;
width: 60px !important;
font-size: 12px;
}
.ivu-form-item-content {
flex: 1 !important;
}
}
&:hover {
box-shadow: 0 0 14px 0 rgba(46, 140, 240, 0.2);
border: 1px solid #2e8cf0 !important;
}
}
h3 {
padding-bottom: 10px;
text-align: center;
}
&-card {
/deep/ .ivu-card-head {
background: #f0f2f5;
}
/deep/ .ivu-card-body {
padding: 10px !important;
}
.Fl-card-title {
display: flex;
justify-content: space-between;
}
}
&-button {
// margin-top: 20px;
// text-align: center;
}
}
</style>
\ No newline at end of file
...@@ -19,6 +19,12 @@ ...@@ -19,6 +19,12 @@
watch: { watch: {
selected(val) { selected(val) {
this.$emit('input', val); this.$emit('input', val);
},
value: {
immediate: true,
handler(val) {
this.selected = val;
}
} }
} }
} }
...@@ -28,6 +34,7 @@ ...@@ -28,6 +34,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
&-input { &-input {
flex-basis: 150px; flex-basis: 150px;
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<Icon type="ios-arrow-forward" /> <Icon type="ios-arrow-forward" />
</div> </div>
<div class="color-selector" v-if="showToColor"> <div class="color-selector" v-if="showToColor">
<Input class="color-selector-input" v-model="toColor" placeholder="请输入" @input="change"></Input> <Input class="color-selector-input" v-model="toColor" placeholder="请输入" @input="change($event, 'to')"></Input>
<ColorPicker placement="bottom-end" v-model="toColor" :alpha="alpha" @on-active-change="change($event, 'to')" @on-change="change($event, 'to')" /> <ColorPicker placement="bottom-end" v-model="toColor" :alpha="alpha" @on-active-change="change($event, 'to')" @on-change="change($event, 'to')" />
</div> </div>
</div> </div>
...@@ -53,13 +53,14 @@ ...@@ -53,13 +53,14 @@
if (val && this.toColor) { if (val && this.toColor) {
this.$emit('input', [this.color, this.toColor]); this.$emit('input', [this.color, this.toColor]);
} else { } else {
this.toColor = '';
this.$emit('input', this.color); this.$emit('input', this.color);
} }
} }
}, },
methods: { methods: {
change(val, type) { change(val, type) {
if (type === 'to') { if (type === 'to' || this.toColor) {
this.$emit('input', [this.color, this.toColor]); this.$emit('input', [this.color, this.toColor]);
} else { } else {
this.$emit('input', val); this.$emit('input', val);
......
...@@ -38,10 +38,14 @@ export default class DynamicForm extends Vue { ...@@ -38,10 +38,14 @@ export default class DynamicForm extends Vue {
} else { } else {
list = this.pageData?.elements?.filter(v => pointY < v?.point?.y); list = this.pageData?.elements?.filter(v => pointY < v?.point?.y);
} }
list = list.sort((cur, next) => {
return cur.point.y > next.point.y ? 1 : -1;
});
this.list = list.map((element, index) => ({ id: element.id, label: element.title + '-' + element.id})); this.list = list.map((element, index) => ({ id: element.id, label: element.title + '-' + element.id}));
if (!this.list.some(item => item.id === this.selected)) {
console.log('curEleIndex', pointY, this.pageData?.elements?.filter(v => pointY < v?.point?.y), this.list); this.selected = '';
}
// console.log('curEleIndex', pointY, this.pageData?.elements?.filter(v => pointY < v?.point?.y), this.list);
} }
} }
}
}
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
</div> </div>
<Form @submit.native.prevent :model="item" :label-width="50"> <Form @submit.native.prevent :model="item" :label-width="50">
<FormItem :prop="`${keywords.key}`" :label="keywords.name" :key="idx" v-for="(keywords, idx) in formControl"> <FormItem :prop="`${keywords.key}`" :label="keywords.name" :key="idx" v-for="(keywords, idx) in formControl">
<component :is="getComponent(keywords.type)" v-bind="keywords.props" :limit-name="limitName" :options="keywords.options" v-model="item[keywords.key]" /> <component :is="getComponent(keywords.type)" v-bind="keywords.props" :limit-name="limitName" :options="keywords.options" v-model="item[keywords.key]" />
</FormItem> </FormItem>
</Form> </Form>
</Card> </Card>
...@@ -17,97 +17,40 @@ ...@@ -17,97 +17,40 @@
</div> </div>
</template> </template>
<script> <script>
import ComponentSelect from '../ComponentSelect/index.vue'; import FormList from '../mixins/formList.mixin.ts';
import Upload from '../Upload/index.vue';
import ColorSelector from '../ColorSelector/index.vue';
export default { export default {
components: { mixins: [FormList],
ComponentSelect,
Upload,
ColorSelector
},
props: { props: {
limitName: String, limitName: String
value: {
type: Array,
default: () => []
},
formControl: {
type: Array,
default: () => []
},
name: String
},
data() {
return {
list: this.value,
}
},
watch: {
list(val) {
this.$emit('input', val);
},
value(val) {
this.list = val;
}
}, },
methods: {
getComponent(type) {
let result = type;
switch (type) {
case 'text':
result = 'Input';
break;
case 'select':
result = 'BaseSelect';
break;
case 'checkbox' :
result = 'Checkbox';
break;
case 'textarea' :
result = 'Textarea';
break;
case 'number' :
result = 'Number';
break;
}
return result;
},
add() {
const object = this.formControl.reduce((pre, cur) => {
pre[cur.key] = '';
return pre;
}, {})
this.list.push(object);
},
del(index) {
this.list.splice(index, 1);
}
}
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.form-list { .form-list {
/deep/ .ivu-card { /deep/ .ivu-card {
margin-bottom: 16px; margin-bottom: 16px;
.ivu-form { .ivu-form {
.ivu-form-item { .ivu-form-item {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 12px;
} }
.ivu-form-item-label { .ivu-form-item-label {
text-align: left; text-align: left;
width: 60px !important; width: 60px !important;
font-size: 12px; font-size: 12px;
} }
.ivu-form-item-content { .ivu-form-item-content {
flex: 1 !important; flex: 1 !important;
} }
} }
&:hover { &:hover {
box-shadow: 0 0 14px 0 rgba(46,140,240,0.20); box-shadow: 0 0 14px 0 rgba(46, 140, 240, 0.2);
border: 1px solid #2E8CF0 !important; border: 1px solid #2e8cf0 !important;
} }
} }
...@@ -117,21 +60,18 @@ ...@@ -117,21 +60,18 @@
} }
&-card { &-card {
/deep/ .ivu-card-head{ /deep/ .ivu-card-head {
background: #f0f2f5; background: #f0f2f5;
} }
/deep/ .ivu-card-body { /deep/ .ivu-card-body {
padding: 10px !important; padding: 10px !important;
} }
.Fl-card-title { .Fl-card-title {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
} }
&-button {
// margin-top: 20px;
// text-align: center;
}
} }
</style> </style>
\ No newline at end of file
<template>
<iSwitch v-model="switchVal" size="large" class="switch-btn">
<span slot="open">开启</span>
<span slot="close">关闭</span>
</iSwitch>
</template>
<script>
export default {
props: {
value: Boolean,
},
data() {
return {
switchVal: this.value,
}
},
watch: {
switchVal(val) {
this.$emit('input', val);
}
}
}
</script>
<style scoped>
.switch-btn {
transform: scale(0.8);
margin-left: -7px;
}
</style>
\ No newline at end of file
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<Modal <Modal
:width="1150" :width="1150"
v-model="modal" v-model="modal"
:mask-closable="false"
@on-ok="ok" @on-ok="ok"
@on-cancel="cancel" @on-cancel="cancel"
class="table-modal-popup" class="table-modal-popup"
...@@ -29,11 +30,13 @@ ...@@ -29,11 +30,13 @@
<template v-for="(item, index) in table"> <template v-for="(item, index) in table">
<div v-show="activeName === index"> <div v-show="activeName === index">
<QGTable <QGTable
:show-selected="true"
:ref="`qgTable${index}`" :ref="`qgTable${index}`"
:columns="item.columns" :columns="item.columns"
:request="item.query" :request="item.query"
:hideAdd="true" :hideAdd="true"
:height="500" :height="500"
:title="title"
@on-selection-change="selectionChange" @on-selection-change="selectionChange"
@on-page-change="changePageNo" @on-page-change="changePageNo"
> >
......
...@@ -63,8 +63,9 @@ ...@@ -63,8 +63,9 @@
this.$emit('input', this.img); this.$emit('input', this.img);
this.$emit('change', this.img); this.$emit('change', this.img);
}, },
handleImgUrlChange() { handleImgUrlChange(val) {
this.$emit('input', this.img); this.$emit('input', this.img);
this.$emit('change', this.img);
}, },
uploadQiniu() { uploadQiniu() {
var uploader = Qiniu.uploader({ var uploader = Qiniu.uploader({
...@@ -74,7 +75,7 @@ ...@@ -74,7 +75,7 @@
// uptoken_url: `${config.apiHost}/upload/getToken`, //Ajax请求upToken的Url,**强烈建议设置**(服务端提供) // uptoken_url: `${config.apiHost}/upload/getToken`, //Ajax请求upToken的Url,**强烈建议设置**(服务端提供)
save_key: true, // 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`,则开启,SDK会忽略对key的处理 save_key: true, // 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`,则开启,SDK会忽略对key的处理
domain: config.qiniuHost, // bucket 域名,下载资源时用到,**必需** domain: config.qiniuHost, // bucket 域名,下载资源时用到,**必需**
get_new_uptoken: false, // 设置上传文件的时候是否每次都重新获取新的token get_new_uptoken: true, // 设置上传文件的时候是否每次都重新获取新的token
container: this.containerId, // 上传区域DOM ID,默认是browser_button的父元素, container: this.containerId, // 上传区域DOM ID,默认是browser_button的父元素,
max_file_size: '10mb', // 最大文件体积限制 max_file_size: '10mb', // 最大文件体积限制
max_retries: 3, // 上传失败最大重试次数 max_retries: 3, // 上传失败最大重试次数
...@@ -173,6 +174,7 @@ ...@@ -173,6 +174,7 @@
right: 0; right: 0;
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
z-index: 3; z-index: 3;
i { i {
margin: 0 2px; margin: 0 2px;
color: #fff; color: #fff;
......
import {Component, Prop, Watch, Vue } from 'vue-property-decorator';
import ComponentSelect from '../ComponentSelect/index.vue';
import Upload from '../Upload/index.vue';
import ColorSelector from '../ColorSelector/index.vue';
import SwitchBtn from '../SwitchBtn/index.vue';
import BaseSelect from '../BaseSelect/index.vue';
@Component({ name: 'formListMixin', components: { ComponentSelect, Upload, ColorSelector, SwitchBtn, BaseSelect } })
export default class DynamicFormMixin extends Vue {
@Prop({ type: Array, default: () => [] }) value;
@Prop({ type: Array, default: () => [] }) formControl;
@Prop({ type: Object, default: () => ({}) }) formDefault;
@Prop(String) name;
list: object[] = [];
@Watch('list')
onListChange(val) {
this.$emit('input', val);
}
@Watch('value', { immediate: true })
onValueChange(val) {
this.list = val;
}
getComponent(type) {
let result = type;
switch (type) {
case 'text':
result = 'Input';
break;
case 'select':
result = 'BaseSelect';
break;
case 'checkbox':
result = 'Checkbox';
break;
case 'textarea':
result = 'Textarea';
break;
case 'number' :
result = 'Number';
break;
case 'switch':
result = 'SwitchBtn';
break;
}
return result;
}
add() {
const object = this.formControl.reduce((pre, cur) => {
pre[cur.key] = this.formDefault[cur.key] || '';
return pre;
}, {});
this.list.push(object);
}
del(index) {
this.list.splice(index, 1);
}
}
\ No newline at end of file
...@@ -22,6 +22,9 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix ...@@ -22,6 +22,9 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
@State(state => state.editor.curChildIndex) curChildIndex; @State(state => state.editor.curChildIndex) curChildIndex;
@Getter('pageData') pageData; @Getter('pageData') pageData;
prevCommonStyle: object = {
paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0
};
form: object = {}; form: object = {};
styleSchame: object = { styleSchame: object = {
curEle: [ curEle: [
...@@ -132,7 +135,23 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix ...@@ -132,7 +135,23 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
}, },
{ {
label: '背景颜色' label: '背景颜色'
} },
{
label: '上边距',
key: 'paddingTop'
},
{
label: '下边距',
key: 'paddingBottom'
},
{
label: '左边距',
key: 'paddingLeft'
},
{
label: '右边距',
key: 'paddingRight'
},
] ]
}; };
...@@ -145,7 +164,7 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix ...@@ -145,7 +164,7 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
} }
get commonStyle() { get commonStyle() {
let rs = { backgroundColor: '', backgroundImage: '' }; let rs = { backgroundColor: '', backgroundImage: '', paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0 };
if (this.curEleIndex || this.curEleIndex === 0) { if (this.curEleIndex || this.curEleIndex === 0) {
if (this.curChildIndex || this.curChildIndex === 0) { if (this.curChildIndex || this.curChildIndex === 0) {
rs = cloneDeep({ ...rs, ...this.pageData.elements[this.curEleIndex].child[this.curChildIndex].commonStyle }); rs = cloneDeep({ ...rs, ...this.pageData.elements[this.curEleIndex].child[this.curChildIndex].commonStyle });
...@@ -314,4 +333,14 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix ...@@ -314,4 +333,14 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
} }
this.updatePageInfo({ containerIndex: this.curEleIndex, childIndex: this.curChildIndex, data: { ...elements, commonStyle: { ...elements.commonStyle, left, top } } }); this.updatePageInfo({ containerIndex: this.curEleIndex, childIndex: this.curChildIndex, data: { ...elements, commonStyle: { ...elements.commonStyle, left, top } } });
} }
}
\ No newline at end of file changeMargin() {
const ele = this.getCurElement();
const prevCommonStyle = cloneDeep(ele.commonStyle || {});
// this.point.w = this.point.w - (this.commonStyle.paddingLeft || 0) - (this.commonStyle.paddingRight || 0);
this.point.h = this.point.h - (prevCommonStyle.paddingTop || 0) - (prevCommonStyle.paddingBottom || 0) + (this.commonStyle.paddingTop || 0) + (this.commonStyle.paddingBottom || 0);
this.updateCommonStyle({ containerIndex: this.curEleIndex, childIndex: this.curChildIndex, data: this.commonStyle });
this.updatePoint();
this.adjustHeight();
}
}
...@@ -48,6 +48,9 @@ ...@@ -48,6 +48,9 @@
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="point.w" @on-change="updatePoint($event, 'w')"></InputNumber> <InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="point.w" @on-change="updatePoint($event, 'w')"></InputNumber>
<InputNumber :max="667" :min="0" v-model="point.h" @on-change="updatePoint($event, 'h')"></InputNumber> <InputNumber :max="667" :min="0" v-model="point.h" @on-change="updatePoint($event, 'h')"></InputNumber>
</template> </template>
<template v-else-if="item.label.indexOf('边距') > -1">
<Slider v-model="commonStyle[item.key]" @on-input="changeMargin" />
</template>
<upload v-else-if="item.label === '背景图片'" v-model="commonStyle.backgroundImage" @change="updateStyle($event, 'backgroundImage')"></upload> <upload v-else-if="item.label === '背景图片'" v-model="commonStyle.backgroundImage" @change="updateStyle($event, 'backgroundImage')"></upload>
<ColorSelector v-else-if="item.label === '背景颜色'" v-model="commonStyle.backgroundColor" @input="updateStyle($event, 'backgroundColor')"></ColorSelector> <ColorSelector v-else-if="item.label === '背景颜色'" v-model="commonStyle.backgroundColor" @input="updateStyle($event, 'backgroundColor')"></ColorSelector>
<template v-else> <template v-else>
...@@ -63,80 +66,91 @@ ...@@ -63,80 +66,91 @@
</template> </template>
<script lang="ts" src="./index.ts"></script> <script lang="ts" src="./index.ts"></script>
<style lang="less" scoped> <style lang="less" scoped>
.dynamic-form { .dynamic-form {
padding: 0 0 16px; padding: 0 0 16px;
background: #fff; background: #fff;
&-header {
padding: 10px 0 5px; &-header {
border-bottom: 8px solid #F5F6FA; padding: 10px 0 5px;
text-align: center; border-bottom: 8px solid #f5f6fa;
div { text-align: center;
color: #A1A6B3;
padding: 5px; div {
font-size: 12px; color: #a1a6b3;
} padding: 5px;
font-size: 12px;
} }
}
// h2 {
// padding: 17px 0;
// border-bottom: 8px solid #F5F6FA;
// text-align: center;
// }
h3 {
padding: 10px 0;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
// h2 { &-component {
// padding: 17px 0; padding: 0 20px;
// border-bottom: 8px solid #F5F6FA; border-bottom: 8px solid #f5f6fa;
// text-align: center; }
// }
h3 { &-basic {
padding: 10px 0; padding: 0 20px;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5; .Df-basic-inputnumber {
} margin-right: 10px;
&-component {
padding: 0 20px;
border-bottom: 8px solid #F5F6FA;
} }
&-basic { }
padding: 0 20px;
.Df-basic-inputnumber { /deep/ .ivu-form-item {
margin-right: 10px; display: flex;
} align-items: center;
&::before,
&::after {
display: none;
} }
/deep/ .ivu-form-item { .ivu-form-item-label {
display: flex; width: 97px;
align-items: center; line-height: 18px;
&:before, &:after { font-size: 12px;
display: none; text-align: left;
} }
.ivu-form-item-label { .ivu-input-number {
width: 97px; width: 60px;
line-height: 18px; }
font-size: 12px;
text-align: left;
}
.ivu-input-number { .ivu-form-item-content {
width: 60px; margin-left: 0 !important;
} flex: 1;
.ivu-form-item-content { .ivu-tooltip {
margin-left: 0 !important; margin-right: 6px;
flex: 1;
.ivu-tooltip { .ivu-btn-ghost {
margin-right: 6px; padding: 2px 6px;
.ivu-btn-ghost { display: flex;
padding: 2px 6px; align-items: center;
display: flex; justify-content: center;
align-items: center;
justify-content: center; .ivu-icon {
.ivu-icon { font-size: 20px;
font-size: 20px;
}
} }
&:last-child { }
.ivu-icon {
transform: rotate(90deg); &:last-child {
} .ivu-icon {
transform: rotate(90deg);
} }
} }
} }
} }
} }
}
</style> </style>
\ No newline at end of file
import {Component, Vue, Prop, Watch, Emit} from 'vue-property-decorator'; import {Component, Vue, Prop, Watch, Emit} from 'vue-property-decorator';
import { Getter } from 'vuex-class';
import DynamicForm from '@editor/component/DynamicForm/index.vue'; import DynamicForm from '@editor/component/DynamicForm/index.vue';
import DynamicPageForm from '@editor/component/DynamicPageForm/index.vue'; import DynamicPageForm from '@editor/component/DynamicPageForm/index.vue';
import DynamicComponentSort from '@editor/component/DynamicComponentSort/index.vue';
// import EventBus from '@service/eventBus.service'; // import EventBus from '@service/eventBus.service';
@Component({ components: { DynamicPageForm, DynamicForm }, name: 'RecordModal' }) @Component({ components: { DynamicPageForm, DynamicForm, DynamicComponentSort }, name: 'RecordModal' })
export default class DynamicFormTabs extends Vue { export default class DynamicFormTabs extends Vue {
@Getter('curRightTabName') curRightTabName!: string | null;
@Emit('modProps') @Emit('modProps')
modProps(props, ele, type) { modProps(props, ele, type) {
// return props, ele, type; // return props, ele, type;
......
<template> <template>
<Tabs class="dynamic-form-tabs"> <Tabs class="dynamic-form-tabs" :value="curRightTabName">
<TabPane label="组件设置"> <TabPane name="组件设置" label="组件设置">
<dynamic-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-form> <dynamic-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-form>
</TabPane> </TabPane>
<!-- <TabPane label="事件">事件</TabPane> --> <!-- <TabPane label="事件">事件</TabPane> -->
<TabPane label="页面设置"> <TabPane name="页面设置" label="页面设置">
<dynamic-page-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-page-form> <dynamic-page-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-page-form>
</TabPane> </TabPane>
<TabPane name="组件管理" label="组件管理">
<dynamic-component-sort />
</TabPane>
</Tabs> </Tabs>
</template> </template>
<script lang="ts" src="./index.ts"></script> <script lang="ts" src="./index.ts"></script>
......
...@@ -8,9 +8,11 @@ import BaseSelect from '../DynamicForm/component/BaseSelect/index.vue'; ...@@ -8,9 +8,11 @@ import BaseSelect from '../DynamicForm/component/BaseSelect/index.vue';
import Textarea from '../DynamicForm/component/Textarea/index.vue'; import Textarea from '../DynamicForm/component/Textarea/index.vue';
import Number from '../DynamicForm/component/Number/index.vue'; import Number from '../DynamicForm/component/Number/index.vue';
import FormList from '../DynamicForm/component/FormList/index.vue'; import FormList from '../DynamicForm/component/FormList/index.vue';
import { resizeDiv, getStyle } from '@/service/utils.service'; import BackTopPicker from '../DynamicForm/component/BackTopPicker/index.vue';
import SwitchBtn from '../DynamicForm/component/SwitchBtn/index.vue';
import { SHOP_CART_CONFIG } from '@service/staticData.service';
@Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number, FormList }, name: 'DynamicPageForm' }) @Component({ components: { Upload, ColorSelector, BaseSelect, Textarea, Number, FormList, BackTopPicker, SwitchBtn }, name: 'DynamicPageForm' })
export default class DynamicPageForm extends Mixins(ContextMenuMixin) { export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
@Getter('pageData') pageData; @Getter('pageData') pageData;
...@@ -29,21 +31,22 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -29,21 +31,22 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
type: 'ColorSelector' type: 'ColorSelector'
} }
]; ];
titleSchema: object[] = [
propsSchame: object[] = [
{ {
key: 'titleBgColor', key: 'titleBgColor',
name: '标题栏 背景色', name: '背景色',
type: 'ColorSelector', type: 'ColorSelector',
props: { props: {
gradient: true, gradient: true,
alpha: false alpha: false
} }
}, },
];
bottomSchema: object[] = [
{ {
key: 'showPageBottomTip', key: 'showPageBottomTip',
name: '底部提示', name: '状态',
type: 'checkbox' type: 'SwitchBtn'
}, },
{ {
key: 'pageBottomTxt', key: 'pageBottomTxt',
...@@ -55,26 +58,41 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -55,26 +58,41 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
name: '提示颜色', name: '提示颜色',
type: 'ColorSelector' type: 'ColorSelector'
}, },
];
floatSchema: object[] = [
{ {
key: 'showBackTop', key: 'showBackTop',
name: '返回顶部', name: '返回顶部',
type: 'checkbox' type: 'SwitchBtn'
},
{
key: 'showShare',
name: '分享按钮',
type: 'checkbox'
}, },
{ {
key: 'btAttachVal', key: 'btAttachVal',
name: '添加按钮', name: '添加按钮',
desc: '添加按钮', desc: '添加按钮',
type: 'FormList', type: 'BackTopPicker',
formControl: [ formControl: [
{
key: 'persets',
name: '预设属性',
type: 'select',
options: ['购物车', '分享', '自定义']
},
{ {
key: 'name', key: 'name',
name: '名称', name: '名称',
type: 'text' type: 'text',
props: {
require: true,
placeholder: '(必填) 区分埋点'
}
},
{
key: 'txt',
name: '文字',
type: 'text',
props: {
placeholder: '(选填) 图标下方文字',
}
}, },
{ {
key: 'url', key: 'url',
...@@ -91,21 +109,57 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) { ...@@ -91,21 +109,57 @@ export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
name: '文字颜色', name: '文字颜色',
type: 'ColorSelector' type: 'ColorSelector'
}, },
{
key: 'iconColor',
name: '图标颜色',
type: 'ColorSelector'
},
{ {
key: 'background', key: 'background',
name: '背景颜色', name: '背景颜色',
type: 'ColorSelector' type: 'ColorSelector'
},
{
key: 'size',
name: '尺寸',
type: 'Slider',
props: {
min: 14,
max: 68
}
},
{
key: 'iconSize',
name: '图标尺寸',
type: 'Slider',
props: {
min: 14,
max: 68
}
},
{
key: 'radius',
name: '圆角',
type: 'checkbox'
},
{
key: 'shadow',
name: '阴影',
type: 'checkbox'
} }
] ],
}, formDefault: SHOP_CART_CONFIG
}
]; ];
get propsSchema() {
return [...this.titleSchema, ...this.bottomSchema, ...this.floatSchema];
}
@Watch('pageData', { immediate: true, deep: true }) @Watch('pageData', { immediate: true, deep: true })
onElementChange(newVal) { onElementChange() {
this.commonStyleSchame.forEach(schame => { this.commonStyleSchame.forEach(schame => {
this.$set(this.commonStyleForm, schame.key, this.pageData?.commonStyle[schame.key]); this.$set(this.commonStyleForm, schame.key, this.pageData?.commonStyle[schame.key]);
}); });
this.propsSchame.forEach(schame => { this.propsSchema.forEach(schame => {
this.$set(this.propsForm, schame.key, this.pageData?.props?.[schame.key]); this.$set(this.propsForm, schame.key, this.pageData?.props?.[schame.key]);
}); });
} }
......
<template> <template>
<div class="dynamic-form"> <div class="dynamic-form">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<Form class="dynamic-form-component" :label-width="80" :model="propsForm" @submit.native.prevent> <Form class="dynamic-form-component" :label-width="80" :model="propsForm" @submit.native.prevent>
<h3>基础属性</h3> <h3>基础属性</h3>
<template v-for="(item, index) in propsSchame"> <h4>标题栏</h4>
<FormItem class="Df-component-formitem" :label="item.name" > <template v-for="(item, index) in titleSchema">
<FormItem class="Df-component-formitem" :label="item.name" :key="'titleSchema_' + index">
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formControl="item.formControl" />
</FormItem>
</template>
<h4>底部提示</h4>
<template v-for="(item, index) in bottomSchema">
<FormItem class="Df-component-formitem" :label="item.name" :key="'bottomSchema_' + index">
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formControl="item.formControl" /> <component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formControl="item.formControl" />
</FormItem> </FormItem>
</template> </template>
</Form> <h4>悬浮窗</h4>
<Form class="dynamic-form-component" :label-width="80" :model="commonStyleForm" @submit.native.prevent> <template v-for="(item, index) in floatSchema">
<h3>基础样式</h3> <FormItem class="Df-component-formitem" :label="item.name" :key="'floatSchema_' + index">
<template v-for="(item, index) in commonStyleSchame"> <component :is="item.type" :options="item.options" v-bind="item.props" v-model="propsForm[item.key]" :formDefault="item.formDefault" :formControl="item.formControl" />
<FormItem class="Df-component-formitem" :label="item.name" >
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="commonStyleForm[item.key]" />
</FormItem> </FormItem>
</template> </template>
</Form>
<Form class="dynamic-form-component" :label-width="80" :model="commonStyleForm" @submit.native.prevent>
<h3>基础样式</h3>
<template v-for="(item, index) in commonStyleSchame">
<FormItem class="Df-component-formitem" :label="item.name" :key="index">
<component :is="item.type" :options="item.options" v-bind="item.props" v-model="commonStyleForm[item.key]" />
</FormItem>
</template>
</Form> </Form>
</div> </div>
</template> </template>
...@@ -28,7 +41,7 @@ ...@@ -28,7 +41,7 @@
border-bottom: 8px solid #F5F6FA; border-bottom: 8px solid #F5F6FA;
text-align: center; text-align: center;
} }
h3 { h3, h4 {
padding: 10px 0; padding: 10px 0;
margin-bottom: 10px; margin-bottom: 10px;
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5;
......
<template>
<cr-back-top :show-back-top="false" :list="list" class="back-top-preview" />
</template>
<script>
import { BACK_TOP_CONFIG } from '@service/staticData.service';
import { mapGetters } from 'vuex';
export default {
name: 'BackTopPreview',
data() {
return {
oldPersets: []
};
},
computed: {
...mapGetters(['pageData']),
list() {
const { btAttachVal, showBackTop } = this.pageData.props;
const btnGroup = btAttachVal.map(item => {
let it = JSON.parse(JSON.stringify(item));
delete it.url;
return it;
});
if (showBackTop) {
btnGroup.splice(0, 0, BACK_TOP_CONFIG);
}
return btnGroup;
}
},
}
</script>
<style lang="less" scoped>
.back-top-preview {
position: absolute;
}
</style>
\ No newline at end of file
<template>
<cr-nav-bar
class="nav-bar"
:title="title"
:left-arrow="false"
left-text=""
:fixed="false"
:style="titleStyle"
/>
</template>
<script>
import { hexToRgb } from '@service/color.service';
import { mapGetters } from 'vuex';
export default {
name: 'TitlePreview',
computed: {
...mapGetters(['pageData', 'pageInfo']),
title() {
return this.pageInfo.pageName || '标题';
},
titleStyle() {
const { titleBgColor } = this.pageData.props;
return this.getTitleStyle(titleBgColor);
}
},
methods: {
getTitleStyle(bgcolor = "#fff") {
if (!EASY_ENV_IS_BROWSER) return;
const DARK_COLOR = "#000";
const LIGHT_COLOR = "#fff";
const isGradient = Array.isArray(bgcolor);
let rgbColor = isGradient ? bgcolor[0] : bgcolor;
if (rgbColor.toLocaleLowerCase().indexOf("rgb") === -1) rgbColor = hexToRgb(rgbColor);
const isDarkContent = 0.213 * rgbColor[0] + 0.715 * rgbColor[1] + 0.072 * rgbColor[2] <= 255 / 2;
const color = isDarkContent ? LIGHT_COLOR : DARK_COLOR;
const background = isGradient ? `linear-gradient(90deg, ${bgcolor[0]}, ${bgcolor[1]})` : bgcolor;
return {
color,
background
};
}
}
}
</script>
<style lang="less" scoped>
@deep: ~'>>>';
.nav-bar {
@{deep} .cr-nav-bar {
z-index: inherit !important;
background-color: transparent;
&__title {
color: inherit;
font-weight: bold;
}
}
}
</style>
...@@ -4,8 +4,9 @@ import type { PageInfo, Page, GridLayout } from '@store/modules/editor/state'; ...@@ -4,8 +4,9 @@ import type { PageInfo, Page, GridLayout } from '@store/modules/editor/state';
import TransformStyleMixin from '@page/mixins/transformStyle.mixin'; import TransformStyleMixin from '@page/mixins/transformStyle.mixin';
import FreedomContainer from '@editor/component/FreedomContainer/index.vue'; import FreedomContainer from '@editor/component/FreedomContainer/index.vue';
import PageBottomTip from './component/PageBottomTip/index.vue'; import PageBottomTip from './component/PageBottomTip/index.vue';
import BackTopPreview from './component/BackTopPreview/index.vue';
@Component({ components: { FreedomContainer, PageBottomTip }, name: 'OperationPanel' }) import TitlePreview from './component/TitlePreview/index.vue';
@Component({ components: { FreedomContainer, PageBottomTip, BackTopPreview, TitlePreview }, name: 'OperationPanel' })
export default class OperationPanel extends Mixins(TransformStyleMixin) { export default class OperationPanel extends Mixins(TransformStyleMixin) {
@Getter('pageData') pageData; @Getter('pageData') pageData;
@State(state => state.editor.gridLayout) gridLayout?: GridLayout; @State(state => state.editor.gridLayout) gridLayout?: GridLayout;
...@@ -68,6 +69,11 @@ export default class OperationPanel extends Mixins(TransformStyleMixin) { ...@@ -68,6 +69,11 @@ export default class OperationPanel extends Mixins(TransformStyleMixin) {
// //
} }
@Emit('handlePageSetClick')
handlePageSetClick() {
//
}
@Emit('show') @Emit('show')
show(event, containerIndex, childIndex) { show(event, containerIndex, childIndex) {
// //
......
<template> <template>
<div :class="[{'Dcm-container-panel_in': isDragIn, 'Dcm-container-panel_draging': isDraging}, 'Dcm-container-panel']" @dragover.prevent @dragenter="dragenter" @dragover="dragover" <div class="Dcm-container-panel_container">
@dragleave="dragleave" @drop="drops"> <title-preview @click.native.stop="handlePageSetClick" />
<grid-layout <div :class="[{'Dcm-container-panel_in': isDragIn, 'Dcm-container-panel_draging': isDraging}, 'Dcm-container-panel']" @dragover.prevent @dragenter="dragenter" @dragover="dragover"
:layout.sync="layout" @dragleave="dragleave" @drop="drops" id="DcmContainerPanel">
:col-num="gridLayout.colNum" <grid-layout
:row-height="gridLayout.rowHeight" :layout.sync="layout"
:margin="[0, 0]" :col-num="gridLayout.colNum"
:is-draggable="gridLayout.draggable" :row-height="gridLayout.rowHeight"
:is-resizable="true" :margin="[0, 0]"
:is-mirrored="false" :is-draggable="gridLayout.draggable"
:vertical-compact="true" :is-resizable="true"
:use-css-transforms="true" :is-mirrored="false"
:style="transformStyle(pageData.commonStyle)" :vertical-compact="true"
@click.native.stop="toggle(false)" :use-css-transforms="true"
@layout-updated="layoutUpdatedEvent" :style="transformStyle(pageData.commonStyle)"
> @click.native.stop="toggle(false)"
<grid-item @click.native.stop="handleElementClick(index, null)" v-for="(item, index) in pageData.elements" @layout-updated="layoutUpdatedEvent"
:x="item.point.x"
:y="item.point.y"
:w="item.point.w"
:h="item.point.h"
:i="item.point.i"
:key="item.point.i + index"
@contextmenu.native.prevent="show($event, index)"
@resized="resizedEvent"
@moved="movedEvent"
:style="transformStyle(item.commonStyle, item.name)"
:class="{'Dcmcp-item_selected': curEleIndex === index && curChildIndex === null}">
<component ref="container" :id="item.id" class="Dcmcp-item-com" @handleElementClick="handleElementClick" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
</grid-item>
<grid-item
v-if="pageData.props.showPageBottomTip"
:x="bottomInfo.x"
:y="bottomInfo.y"
:w="bottomInfo.w"
:h="bottomInfo.h"
:i="bottomInfo.i"
:static="true"
:key="bottomInfo.i + pageData.elements.length"
> >
<page-bottom-tip /> <grid-item @click.native.stop="handleElementClick(index, null)" v-for="(item, index) in pageData.elements"
</grid-item> :x="item.point.x"
</grid-layout> :y="item.point.y"
:w="item.point.w"
:h="item.point.h"
:i="item.point.i"
:key="item.point.i + index"
@contextmenu.native.prevent="show($event, index)"
@resized="resizedEvent"
@moved="movedEvent"
:style="transformStyle(item.commonStyle, item.name)"
:class="{'Dcmcp-item_selected': curEleIndex === index && curChildIndex === null}">
<component ref="container" :id="item.id" class="Dcmcp-item-com" @handleElementClick="handleElementClick" :containerIndex="index" :childItem="item" :is="item.name" :key="item.point.i + index" v-bind="item.props"></component>
</grid-item>
<grid-item
v-if="pageData.props.showPageBottomTip"
:x="bottomInfo.x"
:y="bottomInfo.y"
:w="bottomInfo.w"
:h="bottomInfo.h"
:i="bottomInfo.i"
:static="true"
:key="bottomInfo.i + pageData.elements.length"
@click.native.stop="handlePageSetClick"
>
<page-bottom-tip @click.native.stop="handlePageSetClick" />
</grid-item>
</grid-layout>
</div>
<back-top-preview @click.native.stop="handlePageSetClick" />
</div> </div>
</template> </template>
<script lang="ts" src="./index.ts"></script> <script lang="ts" src="./index.ts"></script>
...@@ -55,21 +60,29 @@ ...@@ -55,21 +60,29 @@
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1; z-index: 102;
} }
} }
.Dcm-container-panel { .Dcm-container-panel {
margin: 30px auto; &_container {
position: relative;
width: 375px;
height: 667px;
margin: 30px auto;
box-shadow: 2px 3px 12px rgba(0, 0, 0, 0.116);
border-radius: 6px;
overflow: hidden;
}
width: 375px; width: 375px;
height: 667px; height: 619px;
min-height: 667px; min-height: 619px;
overflow-y: scroll; overflow-y: scroll;
background-color: rgb(244, 244, 244); background-color: rgb(244, 244, 244);
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout { /deep/ .vue-grid-layout {
min-height: 667px; min-height: 619px;
.vue-grid-item { .vue-grid-item {
display: flex; display: flex;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<Option :value="isNum(value, item.number)" :key="value" v-for="(label,value) in item.option" :label="label" /> <Option :value="isNum(value, item.number)" :key="value" v-for="(label,value) in item.option" :label="label" />
</Select> </Select>
<DatePicker v-else-if="item.type === 'date'" class="timeWidth" type="datetimerange" placeholder="" v-model="searchForm[item.key]"></DatePicker> <DatePicker v-else-if="item.type === 'date'" class="timeWidth" type="datetimerange" placeholder="" v-model="searchForm[item.key]"></DatePicker>
<treeselect v-else-if="item.type === 'treeSelect'" v-model.number="searchForm[item.key]" :multiple="false" :options="item.option" placeholder="请选择" :normalizer="normalizer" style="width:180px" /> <treeselect v-else-if="item.type === 'treeSelect'" v-model.number="searchForm[item.key]" :multiple="false" :options="item.option" placeholder="请选择" :normalizer="normalizer" style="width: 180px;" />
</FormItem> </FormItem>
<FormItem class="btnGroupStyle"> <FormItem class="btnGroupStyle">
<div> <div>
...@@ -26,18 +26,60 @@ ...@@ -26,18 +26,60 @@
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<Table :height="height" @on-select-cancel="selectionCancel" @on-selection-change="selectionChange" :columns="renderColumns" :data="tableData" class="tableStyle"></Table> <Tabs class="table-tabs" v-if="showSelected" :value="selectedTab">
<Page <TabPane name="1" :label="`全部${title}`">
:total="total" <Table
v-if="total > 0" :height="height"
show-elevator @on-select-cancel="selectionCancel"
show-sizer @on-selection-change="selectionChange"
class="pageStyle" :columns="renderColumns"
:current="searchForm.pageNo" :data="tableData"
:pageSize="searchForm.pageSize" class="tableStyle"
@on-change="changePageNo" />
@on-page-size-change="changePageSize" <Page
/> :total="total"
v-if="total > 0"
show-elevator
show-sizer
class="pageStyle"
:current="searchForm.pageNo"
:pageSize="searchForm.pageSize"
@on-change="changePageNo"
@on-page-size-change="changePageSize"
/>
</TabPane>
<TabPane name="2" :label="`已选${title}`">
<Table
:height="height"
@on-select-cancel="selectionCancel"
@on-selection-change="selectionChange"
:columns="renderColumns"
:data="tableData"
class="tableStyle"
/>
</TabPane>
</Tabs>
<template v-else>
<Table
:height="height"
@on-select-cancel="selectionCancel"
@on-selection-change="selectionChange"
:columns="renderColumns"
:data="tableData"
class="tableStyle"
/>
<Page
:total="total"
v-if="total > 0"
show-elevator
show-sizer
class="pageStyle"
:current="searchForm.pageNo"
:pageSize="searchForm.pageSize"
@on-change="changePageNo"
@on-page-size-change="changePageSize"
/>
</template>
</div> </div>
</div> </div>
</template> </template>
...@@ -53,7 +95,9 @@ export default { ...@@ -53,7 +95,9 @@ export default {
type: Array, type: Array,
default: () => ([]) default: () => ([])
}, },
title: String,
hideAdd: Boolean, hideAdd: Boolean,
// showSelected: false, // todo
request: Function, request: Function,
toolBar: Function, toolBar: Function,
height: Number, height: Number,
...@@ -67,7 +111,9 @@ export default { ...@@ -67,7 +111,9 @@ export default {
pageSize: 10, pageSize: 10,
pageNo: 1, pageNo: 1,
}, },
showSelected: false, // todo
searchCondition: [], searchCondition: [],
selectedTab: '1'
}; };
}, },
watch: { watch: {
...@@ -98,6 +144,7 @@ export default { ...@@ -98,6 +144,7 @@ export default {
this.$refs.searchForm.validate(async val => { this.$refs.searchForm.validate(async val => {
if (val) { if (val) {
this.searchForm.pageNo = page; this.searchForm.pageNo = page;
this.selectedTab = '1';
const getQueryData = (await this.request(this.searchForm)) || {}; const getQueryData = (await this.request(this.searchForm)) || {};
this.tableData = getQueryData.data || []; this.tableData = getQueryData.data || [];
this.total = getQueryData.total || 0; this.total = getQueryData.total || 0;
...@@ -191,6 +238,7 @@ export default { ...@@ -191,6 +238,7 @@ export default {
<style scoped lang="less"> <style scoped lang="less">
// @import '../../styles/comment.less'; // @import '../../styles/comment.less';
@padding: 25px; @padding: 25px;
.tableComStyle { .tableComStyle {
height: 100%; height: 100%;
background: #f5f5f5; background: #f5f5f5;
...@@ -199,50 +247,62 @@ export default { ...@@ -199,50 +247,62 @@ export default {
.searchFormStyle { .searchFormStyle {
text-align: left; text-align: left;
clear: both; clear: both;
background-color: #ffffff; background-color: #fff;
padding: @padding; padding: @padding;
min-height: 70px; min-height: 70px;
font-size: 0; font-size: 0;
.labelStyle { .labelStyle {
font-weight: bold !important; font-weight: bold !important;
display: inline-block; display: inline-block;
} }
/deep/ .ivu-form { /deep/ .ivu-form {
margin-bottom: -24px; margin-bottom: -24px;
} }
.required:before {
.required::before {
content: '* '; content: '* ';
color: #ed3f14; color: #ed3f14;
} }
} }
.btnStyle { .btnStyle {
margin-right: 10px; margin-right: 10px;
} }
.tableStyle { .tableStyle {
margin-top: 15px; margin-top: 15px;
} }
.comWidth { .comWidth {
width: 200px; width: 200px;
} }
.timeWidth { .timeWidth {
width: 320px; width: 320px;
} }
.btnGroupStyle { .btnGroupStyle {
text-align: left; text-align: left;
float: right; float: right;
} }
.inline { .inline {
display: inline-block; display: inline-block;
} }
.tableGroupStyle { .tableGroupStyle {
background: #ffffff; background: #fff;
padding: @padding; padding: @padding;
margin-top: 15px; margin-top: 15px;
.toolBarStyle { .toolBarStyle {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.pageStyle { .pageStyle {
margin-top: 20px; margin-top: 20px;
width: 100%; width: 100%;
...@@ -257,8 +317,13 @@ export default { ...@@ -257,8 +317,13 @@ export default {
font-weight: bold; font-weight: bold;
display: inline-block; display: inline-block;
} }
.ivu-form-item-content { .ivu-form-item-content {
display: inline-block; display: inline-block;
} }
} }
.table-tabs {
width: 1030px;
}
</style> </style>
...@@ -13,9 +13,13 @@ export default class AutoSaveMixin extends Vue { ...@@ -13,9 +13,13 @@ export default class AutoSaveMixin extends Vue {
hasCompared: boolean = false; hasCompared: boolean = false;
timer: NodeJS.Timeout | null = null; timer: NodeJS.Timeout | null = null;
get pageId() {
return this.pageInfo.uuid || 'new';
}
showRecord() { showRecord() {
// console.log('showRecord'); // console.log('showRecord');
const record = localStorage.get(`${this.account}-${this.pageInfo.id}`); const record = localStorage.get(`${this.account}-${this.pageId}`);
if (record && !isEqual(record, this.pageInfo)) { if (record && !isEqual(record, this.pageInfo)) {
this.showRecordModal = true; this.showRecordModal = true;
// console.log('showRecord', record, this.pageInfo); // console.log('showRecord', record, this.pageInfo);
...@@ -30,7 +34,7 @@ export default class AutoSaveMixin extends Vue { ...@@ -30,7 +34,7 @@ export default class AutoSaveMixin extends Vue {
// console.log('autoSave'); // console.log('autoSave');
if (this.account && this.pageInfo && this.hasCompared) { if (this.account && this.pageInfo && this.hasCompared) {
// console.log('autoSave in', this.pageInfo); // console.log('autoSave in', this.pageInfo);
localStorage.set(`${this.account}-${this.pageInfo.id}`, this.pageInfo); localStorage.set(`${this.account}-${this.pageId}`, this.pageInfo);
} }
}, interval); }, interval);
this.$once('hook:beforeDestroy', () => { this.$once('hook:beforeDestroy', () => {
...@@ -38,7 +42,7 @@ export default class AutoSaveMixin extends Vue { ...@@ -38,7 +42,7 @@ export default class AutoSaveMixin extends Vue {
}); });
} }
removeDefaultCache(id = 0) { removeDefaultCache(id = '') {
this.clearTimer(); this.clearTimer();
localStorage.remove(`${this.account}-${id}`); localStorage.remove(`${this.account}-${id}`);
this.autoSave(); this.autoSave();
...@@ -51,7 +55,7 @@ export default class AutoSaveMixin extends Vue { ...@@ -51,7 +55,7 @@ export default class AutoSaveMixin extends Vue {
recover() { recover() {
// console.log('recover'); // console.log('recover');
const record = localStorage.get(`${this.account}-${this.pageInfo.id}`); const record = localStorage.get(`${this.account}-${this.pageId}`);
this.setPageInfo(record); this.setPageInfo(record);
this.hasCompared = true; this.hasCompared = true;
} }
...@@ -59,6 +63,6 @@ export default class AutoSaveMixin extends Vue { ...@@ -59,6 +63,6 @@ export default class AutoSaveMixin extends Vue {
cancel() { cancel() {
// console.log('cancel'); // console.log('cancel');
this.hasCompared = true; this.hasCompared = true;
localStorage.remove(`${this.account}-${this.pageInfo.id}`); localStorage.remove(`${this.account}-${this.pageId}`);
} }
} }
\ No newline at end of file
import {Component, Vue } from 'vue-property-decorator'; import {Component, Vue } from 'vue-property-decorator';
import { Mutation } from 'vuex-class'; import { Mutation, Getter } from 'vuex-class';
import { getStyle } from '@/service/utils.service'; import { getStyle } from '@/service/utils.service';
@Component({ name: 'ContextMenuMixin' }) @Component({ name: 'ContextMenuMixin' })
export default class ContextMenuMixin extends Vue { export default class ContextMenuMixin extends Vue {
@Getter('pageData') pageData;
@Mutation('COPY_OR_DELETE_PAGE_INFO') updatePageData; @Mutation('COPY_OR_DELETE_PAGE_INFO') updatePageData;
@Mutation('UPDATE_PAGE_INFO') updatePageInfo; @Mutation('UPDATE_PAGE_INFO') updatePageInfo;
@Mutation('UPDATE_COMMON_STYLE') updateCommonStyle; @Mutation('UPDATE_COMMON_STYLE') updateCommonStyle;
show(event, containerIndex, childIndex) { show(event, containerIndex, childIndex) {
console.log('删除', event, containerIndex, childIndex); // console.log('删除', event, containerIndex, childIndex);
this.$contextmenu({ this.$contextmenu({
items: [ items: [
{ {
label: '复制', label: '复制',
onClick: () => { onClick: () => {
// PERF 可以抽象
const ComInfo = this.pageData.elements[containerIndex];
if (ComInfo.name === 'cs-search-bar') {
this.$Notice.warning({
title: '搜索框只能添加一个'
});
return;
}
if (ComInfo.name === 'cs-floor-nav') {
this.$Notice.warning({
title: '楼层导航只能添加一个'
});
return;
}
this.updatePageData({type: 'copy', containerIndex, childIndex}); this.updatePageData({type: 'copy', containerIndex, childIndex});
console.log('复制'); // console.log('复制');
} }
}, },
{ {
label: '删除', label: '删除',
onClick: () => { onClick: () => {
console.log('删除', event, containerIndex, childIndex); // console.log('删除', event, containerIndex, childIndex);
this.updatePageData({type: 'delete', containerIndex, childIndex}); this.updatePageData({type: 'delete', containerIndex, childIndex});
} }
}, },
...@@ -37,7 +52,9 @@ export default class ContextMenuMixin extends Vue { ...@@ -37,7 +52,9 @@ export default class ContextMenuMixin extends Vue {
const elements = this.pageData.elements[this.curEleIndex]; const elements = this.pageData.elements[this.curEleIndex];
const component = document.getElementById(elements.id); const component = document.getElementById(elements.id);
const height = component ? getStyle(component, 'height') : 0; const height = component ? getStyle(component, 'height') : 0;
console.log('adjustHeight', height); const paddingTop = elements.commonStyle.paddingTop || 0;
this.updatePageInfo({ containerIndex: this.curEleIndex, data: { ...elements, point: { ...elements.point, h: Math.ceil(+height || elements.point.h) } } }); const paddingBottom = elements.commonStyle.paddingBottom || 0;
console.log('adjustHeight', height, elements.commonStyle);
this.updatePageInfo({ containerIndex: this.curEleIndex, data: { ...elements, point: { ...elements.point, h: Math.ceil((+height || elements.point.h) + paddingTop + paddingBottom) } } });
} }
} }
\ No newline at end of file
...@@ -7,10 +7,19 @@ export default class GoodsTabsMixin extends Vue { ...@@ -7,10 +7,19 @@ export default class GoodsTabsMixin extends Vue {
@Getter('pageData') pageData; @Getter('pageData') pageData;
@Mutation('UPDATE_PAGE_INFO') updatePageInfo; @Mutation('UPDATE_PAGE_INFO') updatePageInfo;
@Mutation('COPY_OR_DELETE_PAGE_INFO') updatePageData; @Mutation('COPY_OR_DELETE_PAGE_INFO') updatePageData;
get hasSearchBarCom() {
return this.pageData.elements.some(item => item.name === 'cs-search-bar');
}
get hasFloorNavCom() {
return this.pageData.elements.some(item => item.name === 'cs-floor-nav');
}
get hasGoodsNavCom() {
return this.pageData.elements.some(item => item.name === 'cs-goods-tabs');
}
handleGoodsTabs() { handleGoodsTabs() {
const goodsTabs = {}; const goodsTabs = {};
const pageData = cloneDeep(this.pageData); const pageData = cloneDeep(this.pageData);
const hasMoreGoodsTabs = pageData.elements.filter(element => element.name === 'cs-goods-tabs').length > 1;
pageData.elements.forEach((element, idx) => { pageData.elements.forEach((element, idx) => {
if (element.name === 'cs-goods-tabs' && element?.props?.list.length) { if (element.name === 'cs-goods-tabs' && element?.props?.list.length) {
const childs = []; const childs = [];
...@@ -31,8 +40,10 @@ export default class GoodsTabsMixin extends Vue { ...@@ -31,8 +40,10 @@ export default class GoodsTabsMixin extends Vue {
}); });
goodsTabs[element.id] = { idx, childs, childIndexs }; goodsTabs[element.id] = { idx, childs, childIndexs };
} }
if ((this.hasFloorNavCom || hasMoreGoodsTabs) && element.name === 'cs-goods-tabs') {
element.props.anchor = false;
}
}); });
let indexs = []; let indexs = [];
Object.keys(goodsTabs).forEach(key => { Object.keys(goodsTabs).forEach(key => {
const { idx, childIndexs, childs } = goodsTabs[key]; const { idx, childIndexs, childs } = goodsTabs[key];
...@@ -68,4 +79,30 @@ export default class GoodsTabsMixin extends Vue { ...@@ -68,4 +79,30 @@ export default class GoodsTabsMixin extends Vue {
}); });
}); });
} }
} handleTabsRepaetCom() {
\ No newline at end of file const selectedComponentIds = [];
this.pageData.elements.forEach(element => {
if (element.name === 'cs-goods-tabs' || element.name === 'cs-floor-nav') {
element.props?.list.forEach(item => {
if (item.componentId && selectedComponentIds.includes(item.componentId)) {
throw new Error(`组件<${element.title}${element.id}>存在重复组件,请修改后继续操作`);
} else {
selectedComponentIds.push(item.componentId);
}
});
}
});
}
handleComAchorScrollEnable() {
const pageData = cloneDeep(this.pageData);
pageData.elements.forEach((element, idx) => {
if (element.name === 'cs-floor-nav') {
const idList = element.props.list.map(v => v.componentId);
if (idList.some(v => !v)) {
throw new Error('楼层导航组件不可为空');
}
}
});
return pageData;
}
}
...@@ -15,12 +15,15 @@ import localStorage from '@service/localStorage.service'; ...@@ -15,12 +15,15 @@ import localStorage from '@service/localStorage.service';
import EventBus from '@service/eventBus.service'; import EventBus from '@service/eventBus.service';
import { getStyle } from '@service/utils.service'; import { getStyle } from '@service/utils.service';
import OperationPanel from '@editor/component/OperationPanel/index.vue'; import OperationPanel from '@editor/component/OperationPanel/index.vue';
import type { PageInfo, Page, GridLayout } from '../../../store/modules/editor/state'; import type { PageInfo, Page, GridLayout } from '../../../store/modules/editor/state';
@Component({components: { GridLayout: VueGridLayout.GridLayout, @Component({components: { GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem, BasicPageFormModal, RecordModal, MaterialMenu, DynamicFormTabs, OperationPanel }, name: 'DashBoard'}) GridItem: VueGridLayout.GridItem, BasicPageFormModal, RecordModal, MaterialMenu, DynamicFormTabs, OperationPanel }, name: 'DashBoard'})
export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, TransformStyleMixin, AutoSaveMixin) { export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, TransformStyleMixin, AutoSaveMixin) {
get layout() {
return this.pageData.elements.map(v => v.point);
}
@Mutation('ADD_ELEMENTS') addElements; @Mutation('ADD_ELEMENTS') addElements;
@Mutation('SET_CUR_ELE_INDEX') setCurEleIndex; @Mutation('SET_CUR_ELE_INDEX') setCurEleIndex;
@Mutation('SET_CUR_CHILD_INDEX') setCurChildIndex; @Mutation('SET_CUR_CHILD_INDEX') setCurChildIndex;
...@@ -28,6 +31,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -28,6 +31,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
@Mutation('SET_PAGE_DATA') setPageData; @Mutation('SET_PAGE_DATA') setPageData;
@Mutation('UPDATE_PAGE_STYLE') setPageStyle; @Mutation('UPDATE_PAGE_STYLE') setPageStyle;
@Mutation('UPDATE_PAGE_PROPS') setPageProps; @Mutation('UPDATE_PAGE_PROPS') setPageProps;
@Mutation('SET_CUR_RIGHT_TAB_NAME') setRightTabName;
@Action('resetPageData') resetPageData; @Action('resetPageData') resetPageData;
@Action('savePageData') savePageData; @Action('savePageData') savePageData;
@Action('getPageDate') getPageDate; @Action('getPageDate') getPageDate;
...@@ -47,6 +51,11 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -47,6 +51,11 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
isDraging: boolean = false; isDraging: boolean = false;
showSubmitPopup: boolean = false; showSubmitPopup: boolean = false;
inTheSave: boolean = false; inTheSave: boolean = false;
searchBarComNum: number = 0;
get pageDataInject() {
return this.pageData;
}
async created() { async created() {
const { pageId, templateId } = this.$route.params; const { pageId, templateId } = this.$route.params;
...@@ -66,10 +75,6 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -66,10 +75,6 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
console.log('beforeRouteLeave'); console.log('beforeRouteLeave');
} }
get layout() {
return this.pageData.elements.map(v => v.point);
}
async save(type: 'preview' | 'save', pageConfig) { async save(type: 'preview' | 'save', pageConfig) {
try { try {
const user = localStorage.get('user'); const user = localStorage.get('user');
...@@ -79,7 +84,9 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -79,7 +84,9 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
} else { } else {
this.pageData.elements.sort((a, b) => a.point.y - b.point.y); this.pageData.elements.sort((a, b) => a.point.y - b.point.y);
// 处理商品标签组件 // 处理商品标签组件
const pageData = this.handleGoodsTabs(); let pageData = this.handleTabsRepaetCom();
pageData = this.handleComAchorScrollEnable();
pageData = this.handleGoodsTabs();
const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, shareCoverImage, shareOpenMethod } = pageConfig; const { pageName, pageDescribe, pageKeywords, coverImage, isPublish, isTemplate, shareCoverImage, shareOpenMethod } = pageConfig;
const pageInfo = { page: JSON.stringify(pageData), author: user?.account, isPublish, pageName, pageDescribe, pageKeywords, coverImage, isTemplate, shareCoverImage, shareOpenMethod } as pageInfo; const pageInfo = { page: JSON.stringify(pageData), author: user?.account, isPublish, pageName, pageDescribe, pageKeywords, coverImage, isTemplate, shareCoverImage, shareOpenMethod } as pageInfo;
if (this.uuid) { pageInfo.uuid = this.uuid; } if (this.uuid) { pageInfo.uuid = this.uuid; }
...@@ -96,7 +103,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -96,7 +103,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
} }
} catch (e) { } catch (e) {
this.showSubmitPopup = false; this.showSubmitPopup = false;
this.$Notice.error({ title: e?.message || '出现未知错误!' }); this.$Notice.error({ title: '提示', desc: e?.message || '出现未知错误!'});
} finally { } finally {
this.inTheSave = false; this.inTheSave = false;
} }
...@@ -116,6 +123,12 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -116,6 +123,12 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
this.toggle(false); this.toggle(false);
this.setCurEleIndex(curEleIndex); this.setCurEleIndex(curEleIndex);
this.setCurChildIndex(curChildIndex); this.setCurChildIndex(curChildIndex);
this.setRightTabName('组件设置');
}
handlePageSetClick() {
this.toggle(false);
this.setRightTabName('页面设置');
} }
toggle(val) { toggle(val) {
...@@ -184,7 +197,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -184,7 +197,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
// component // component
} else { } else {
const y = Math.floor(top / this.gridLayout.rowHeight); const y = Math.floor(top / this.gridLayout.rowHeight);
this.addElements({ data: {...data, point: { ...data.point, y } }}); this.addEleFilter({ data: {...data, point: { ...data.point, y } }});
this.handleElementClick(this.pageData.elements.length - 1, null); this.handleElementClick(this.pageData.elements.length - 1, null);
} }
// 调整组件高度 // 调整组件高度
...@@ -197,12 +210,51 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin, ...@@ -197,12 +210,51 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
this.handleElementClick(null, null); this.handleElementClick(null, null);
} else { } else {
const y = getStyle(document.querySelector('.vue-grid-layout'), 'height'); const y = getStyle(document.querySelector('.vue-grid-layout'), 'height');
this.addElements({ data: {...data, point: { ...data.point, y } }}); this.addEleFilter({ data: {...data, point: { ...data.point, y } }});
this.handleElementClick(this.pageData.elements.length - 1, null); this.handleElementClick(this.pageData.elements.length - 1, null);
} }
// 调整组件高度 // 调整组件高度
this.$nextTick(() => this.adjustHeight()); this.$nextTick(() => this.adjustHeight());
} }
// 限制组件添加
addEleFilter(el) {
// 限制searchNav只添加一个
if (el.data.name === 'cs-search-bar' && this.hasSearchBarCom) {
this.$Notice.warning({
title: '搜索框只能添加一个'
});
return;
}
// 限制floor-nav只添加一个,同时添加后goods-tabs会取消锚点滚动
if (el.data.name === 'cs-floor-nav') {
if (this.hasFloorNavCom) {
this.$Notice.warning({
title: '楼层导航目前只支持添加一个'
});
return;
} else {
this.$Message.error({
content: '楼层导航添加后,商品导航中的锚点滚动将会无效',
duration: 10
});
}
}
// 添加多个goods-tabs会取消锚点滚动
if (el.data.name === 'cs-goods-tabs') {
if (this.hasGoodsNavCom) {
this.$Message.error({
content: '添加多个商品导航后,商品导航中的锚点滚动将会无效',
duration: 10
});
} else if (this.hasFloorNavCom) {
this.$Message.error({
content: '已添加楼层导航后,商品导航中的锚点滚动将会无效',
duration: 10
});
}
}
this.addElements(el);
}
resizedEvent(i, h, w) { resizedEvent(i, h, w) {
const index = this.pageData.elements.findIndex(ele => ele.point.i === i); const index = this.pageData.elements.findIndex(ele => ele.point.i === i);
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<Col class="dashboard-container-middle" span="17"> <Col class="dashboard-container-middle" span="17">
<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)">
<OperationPanel :isDragIn.sync="isDragIn" :isDraging="isDraging" @dragover="dragover" @drops="drops" @handleElementClick="handleElementClick" @show="show" @resizedEvent="resizedEvent" @movedEvent="movedEvent" @toggle="toggle" /> <OperationPanel :isDragIn.sync="isDragIn" :isDraging="isDraging" @dragover="dragover" @drops="drops" @handleElementClick="handleElementClick" @show="show" @resizedEvent="resizedEvent" @movedEvent="movedEvent" @toggle="toggle" @handlePageSetClick="handlePageSetClick" />
</Col> </Col>
<Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']"> <Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']">
<DynamicFormTabs @modProps="modProps" @resizedChildEvent="resizedChildEvent" /> <DynamicFormTabs @modProps="modProps" @resizedChildEvent="resizedChildEvent" />
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
:request="query" :request="query"
@newBtnClick="addPage" @newBtnClick="addPage"
> >
<Button type="default" class="btnStyle" @click="refreshData()">更新缓存</Button>
</QGTable> </QGTable>
</template> </template>
<script> <script>
...@@ -75,7 +76,7 @@ export default { ...@@ -75,7 +76,7 @@ export default {
title: '链接', title: '链接',
hideSearch: true, hideSearch: true,
render: (h, params) => { render: (h, params) => {
return h('span', `${config.h5Host}/activity/${params.row.uuid}`) return h('span', `${config.h5Host}/activity/${params.row.uuid}?vccToken={token}`)
} }
}, },
{ {
...@@ -187,7 +188,16 @@ export default { ...@@ -187,7 +188,16 @@ export default {
}, },
addPage() { addPage() {
this.$router.push('/detail'); this.$router.push('/detail');
} },
async refreshData() {
try {
await editorApi.refreshCache();
this.$Notice.success({ title: '刷新成功!' });
} catch (error) {
console.log(error);
this.$Notice.warning({ title: '刷新失败!' });
}
},
}, },
mounted(){} mounted(){}
} }
......
...@@ -15,9 +15,10 @@ import { ...@@ -15,9 +15,10 @@ import {
SET_PAGE_DATA, SET_PAGE_DATA,
UPDATE_COMMON_STYLE, UPDATE_COMMON_STYLE,
UPDATE_PAGE_STYLE, UPDATE_PAGE_STYLE,
UPDATE_PAGE_PROPS UPDATE_PAGE_PROPS,
SET_CUR_RIGHT_TAB_NAME
} from './type'; } from './type';
import { v4 as uuid } from 'uuid';
import RootState from '../../state'; import RootState from '../../state';
import EditorState, { PageInfo, defaultState, Page, PageElement } from './state'; import EditorState, { PageInfo, defaultState, Page, PageElement } from './state';
...@@ -36,6 +37,9 @@ export default class EditorModule implements Module<EditorState, RootState> { ...@@ -36,6 +37,9 @@ export default class EditorModule implements Module<EditorState, RootState> {
}, },
pageInfo(state: EditorState): PageInfo { pageInfo(state: EditorState): PageInfo {
return state.pageInfo; return state.pageInfo;
},
curRightTabName(state: EditorState): string | null {
return state.curRightTabName;
} }
}; };
...@@ -111,8 +115,11 @@ export default class EditorModule implements Module<EditorState, RootState> { ...@@ -111,8 +115,11 @@ export default class EditorModule implements Module<EditorState, RootState> {
[SET_CUR_ELE_INDEX](state, curEleIndex) { [SET_CUR_ELE_INDEX](state, curEleIndex) {
state.curEleIndex = curEleIndex; state.curEleIndex = curEleIndex;
}, },
[SET_CUR_CHILD_INDEX](state, curChildIndex) { [SET_CUR_ELE_INDEX](state, curEleIndex) {
state.curChildIndex = curChildIndex; state.curEleIndex = curEleIndex;
},
[SET_CUR_RIGHT_TAB_NAME](state, curRightTabName) {
state.curRightTabName = curRightTabName;
}, },
[SET_TEMPLATE_LIST](state, data) { [SET_TEMPLATE_LIST](state, data) {
state.templateList = data; state.templateList = data;
...@@ -132,12 +139,17 @@ export default class EditorModule implements Module<EditorState, RootState> { ...@@ -132,12 +139,17 @@ export default class EditorModule implements Module<EditorState, RootState> {
state.curChildIndex = null; state.curChildIndex = null;
} else if (type === 'copy') { } else if (type === 'copy') {
let eleCopyed = {} as PageElement; let eleCopyed = {} as PageElement;
const newId = uuid().slice(19);
if (childIndex || childIndex === 0) { if (childIndex || childIndex === 0) {
eleCopyed = page[containerIndex].child[childIndex]; eleCopyed = cloneDeep(page[containerIndex].child[childIndex] || {});
eleCopyed.id = newId;
eleCopyed.point.i = newId;
const { left, top } = eleCopyed.commonStyle; const { left, top } = eleCopyed.commonStyle;
page[containerIndex].child.push({ ...eleCopyed, commonStyle: { ...eleCopyed.commonStyle, left: left + 10, top: top + 10 } }); page[containerIndex].child.push({ ...eleCopyed, commonStyle: { ...eleCopyed.commonStyle, left: left + 10, top: top + 10 } });
} else { } else {
eleCopyed = page[containerIndex]; eleCopyed = cloneDeep(page[containerIndex] || {});
eleCopyed.id = newId;
eleCopyed.point.i = newId;
page.push({ ...eleCopyed, point: { ...eleCopyed.point, i: page.length }}); page.push({ ...eleCopyed, point: { ...eleCopyed.point, i: page.length }});
} }
} }
......
...@@ -29,6 +29,7 @@ export interface GridLayout { ...@@ -29,6 +29,7 @@ export interface GridLayout {
export interface PageElement { export interface PageElement {
name: string; name: string;
title: string; title: string;
id: string;
schame: Schame[]; schame: Schame[];
props: object; props: object;
point: Point; point: Point;
...@@ -62,6 +63,7 @@ export default interface EditorState { ...@@ -62,6 +63,7 @@ export default interface EditorState {
pageInfo: PageInfo; pageInfo: PageInfo;
curEleIndex: number | null; curEleIndex: number | null;
curChildIndex: number | null; curChildIndex: number | null;
curRightTabName: string | null;
templateList: any[]; templateList: any[];
gridLayout: GridLayout; gridLayout: GridLayout;
} }
...@@ -69,6 +71,7 @@ export default interface EditorState { ...@@ -69,6 +71,7 @@ export default interface EditorState {
export const defaultState = { export const defaultState = {
curEleIndex: null, curEleIndex: null,
curChildIndex: null, curChildIndex: null,
curRightTabName: null,
pageInfo: { pageInfo: {
id: 0, id: 0,
pageName: '', pageName: '',
...@@ -87,9 +90,8 @@ export const defaultState = { ...@@ -87,9 +90,8 @@ export const defaultState = {
titleBgColor: '#fff', titleBgColor: '#fff',
showPageBottomTip: true, showPageBottomTip: true,
pageBottomTxt: '没有更多啦~', pageBottomTxt: '没有更多啦~',
pageBottomColor: '#fff', pageBottomColor: '#333',
showBackTop: true, showBackTop: true,
showShare: true,
btAttachVal: [ btAttachVal: [
{ {
persets: '购物车', persets: '购物车',
......
...@@ -12,3 +12,4 @@ export const SET_PAGE_DATA = 'SET_PAGE_DATA'; ...@@ -12,3 +12,4 @@ export const SET_PAGE_DATA = 'SET_PAGE_DATA';
export const UPDATE_COMMON_STYLE = 'UPDATE_COMMON_STYLE'; export const UPDATE_COMMON_STYLE = 'UPDATE_COMMON_STYLE';
export const UPDATE_PAGE_STYLE = 'UPDATE_PAGE_STYLE'; export const UPDATE_PAGE_STYLE = 'UPDATE_PAGE_STYLE';
export const UPDATE_PAGE_PROPS = 'UPDATE_PAGE_PROPS'; export const UPDATE_PAGE_PROPS = 'UPDATE_PAGE_PROPS';
export const SET_CUR_RIGHT_TAB_NAME = 'SET_CUR_RIGHT_TAB_NAME';
// tslint:disable
import { isApp } from './utils.service';
// RGB转HEX
export function rgbToHex(r, g, b) {
const hex = ((r << 16) | (g << 8) | b).toString(16);
return "#" + new Array(Math.abs(hex.length - 7)).join("0") + hex;
}
// HEX转RGB
export function hexToRgb(hex) {
var rgb = [];
for (let i = 1; i < 7; i += 2) {
rgb.push(parseInt("0x" + hex.slice(i, i + 2)));
}
return rgb;
}
// 计算RGB渐变色色值
export function gradient(startColor, endColor, step) {
// 将 hex 转换为rgb
let sColor = hexToRgb(startColor);
let eColor = hexToRgb(endColor);
// 计算R\G\B每一步的差值
let rStep = (eColor[0] - sColor[0]) / step;
let gStep = (eColor[1] - sColor[1]) / step;
let bStep = (eColor[2] - sColor[2]) / step;
let gradientColorArr = [];
for (let i = 0; i < step; i++) {
// 计算每一步的hex值
gradientColorArr.push(
rgbToHex(
parseInt(rStep * i + sColor[0]),
parseInt(gStep * i + sColor[1]),
parseInt(bStep * i + sColor[2])
)
);
}
return gradientColorArr;
}
export function colorToRgb(color) {
// 16进制颜色值的正则
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
// 把颜色值变成小写
color = color.toLowerCase();
if (reg.test(color)) {
// 如果只有三位的值,需变成六位,如:#fff => #ffffff
if (color.length === 4) {
let colorNew = "#";
for (let i = 1; i < 4; i += 1) {
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1));
}
color = colorNew;
}
// 处理六位的颜色值,转为RGB
let colorChange = [];
for (let i = 1; i < 7; i += 2) {
colorChange.push(parseInt("0x" + color.slice(i, i + 2)));
}
return "RGB(" + colorChange.join(",") + ")";
} else {
return color;
}
}
// RGB TO RGBA
export function rgbToRgba(rgb, opacity) {
return rgb.replace(")", `, ${opacity})`);
}
// 判断色值
export function isColor(color) {
var re1 = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
var re2 = /^rgb\(([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\\,([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\\,([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\)$/i;
var re3 = /^rgba\(([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\\,([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\\,([0-9]|[0-9][0-9]|25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9])\\,(1|1.0|0.[0-9])\)$/i;
return re2.test(color) || re1.test(color) || re3.test(color);
}
'use strict';
export const SHOP_CART_CONFIG = {
persets: '购物车',
txt: '',
name: '购物车',
icon: 'shopping-cart',
url: 'xyqb://shoppingCart?needLogin=1',
color: '#333',
iconColor: '#333',
background: '#fff',
shadow: true,
radius: true,
size: 46,
iconSize: 20
};
export const BACK_TOP_CONFIG = {
persets: '自定义',
name: '',
icon: 'back-top',
background: '#fff',
color: '#333',
size: 46,
iconSize: 20,
txt: ''
};
export const SHARE_CONFIG = {
persets: '分享',
name: '分享',
txt: '',
icon: 'share',
url: '',
color: '#333',
iconColor: '#333',
background: '#fff',
shadow: true,
radius: true,
size: 46,
iconSize: 20
};
export const DEFAULT_CONFIG = {
persets: '自定义',
name: '',
txt: '',
icon: '',
url: '',
color: '#333',
iconColor: '#333',
background: '#fff',
shadow: true,
radius: true,
size: 46,
iconSize: 20
};
\ No newline at end of file
...@@ -37,7 +37,7 @@ export default (appInfo: EggAppConfig) => { ...@@ -37,7 +37,7 @@ export default (appInfo: EggAppConfig) => {
}, },
client: { client: {
port: 32625, // Redis port port: 32625, // Redis port
host: '172.17.5.17', // Redis host host: '172.17.5.2', // Redis host
password: '', password: '',
db: 0 db: 0
} }
......
{ {
"name": "quantum-blocks", "name": "quantum-blocks",
"version": "0.0.1", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
...@@ -1481,9 +1481,9 @@ ...@@ -1481,9 +1481,9 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
}, },
"@popperjs/core": { "@popperjs/core": {
"version": "2.10.1", "version": "2.10.2",
"resolved": "http://npmprivate.quantgroups.com/@popperjs%2fcore/-/core-2.10.1.tgz", "resolved": "http://npmprivate.quantgroups.com/@popperjs%2fcore/-/core-2.10.2.tgz",
"integrity": "sha1-co7NlasgequKmk5CHwQi2zKSMr4=" "integrity": "sha1-B5jAM1Hw3qGlpMq93yalWny+5ZA="
}, },
"@qg/apollo-nodejs": { "@qg/apollo-nodejs": {
"version": "2.1.2", "version": "2.1.2",
...@@ -1491,9 +1491,9 @@ ...@@ -1491,9 +1491,9 @@
"integrity": "sha512-uOCUKu5mvX3PWee+7ZFXQSNIR1V5SN2JVE2yANmW9/wQOgpEct291gXGok8VMw0009HlTWB4JATURPwyOP2DFg==" "integrity": "sha512-uOCUKu5mvX3PWee+7ZFXQSNIR1V5SN2JVE2yANmW9/wQOgpEct291gXGok8VMw0009HlTWB4JATURPwyOP2DFg=="
}, },
"@qg/cherry-ui": { "@qg/cherry-ui": {
"version": "2.23.7", "version": "2.23.9",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcherry-ui/-/cherry-ui-2.23.7.tgz", "resolved": "http://npmprivate.quantgroups.com/@qg%2fcherry-ui/-/cherry-ui-2.23.9.tgz",
"integrity": "sha512-ZQ5valaspUkQfyUzoj7nzGqGW287NkK5WH7BB1q5LIb3kAaL/1YSF5J1j8Azn8aqtHq/xNqYMFIfb8YBsq00nw==", "integrity": "sha512-czpyl06OfKKnfUls4o5vX8chpeddc87l06E6lDiPks+mM47PJdhobGgvwDWZBIeYETBnlI+VqvUhAXeQ/ISTKg==",
"requires": { "requires": {
"@popperjs/core": "^2.5.4", "@popperjs/core": "^2.5.4",
"vue-lazyload": "^1.3.3", "vue-lazyload": "^1.3.3",
...@@ -1501,13 +1501,13 @@ ...@@ -1501,13 +1501,13 @@
} }
}, },
"@qg/citrus-ui": { "@qg/citrus-ui": {
"version": "0.2.29", "version": "0.3.7",
"resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.2.29.tgz", "resolved": "http://npmprivate.quantgroups.com/@qg%2fcitrus-ui/-/citrus-ui-0.3.7.tgz",
"integrity": "sha512-inMPAWHzFvNZSCqotDVlKnONmeYps98yGRck5V3cV/1dAiScBhWovyvxXGAnfCk0dfmjcm/5Tpw4pmjt04Xjnw==", "integrity": "sha512-BZw+Cr2rbPNocUGfE0rDiee74EOVmmfHZ8NwdGaYKnmMjKPTKmGEZx/cziuri+SBo7RlYNEAiYy5YG3CIPtgww==",
"requires": { "requires": {
"@better-scroll/core": "^2.1.1", "@better-scroll/core": "^2.1.1",
"@qg/cherry-ui": "^2.23.7", "@qg/cherry-ui": "^2.23.9",
"@qg/js-bridge": "^1.1.11", "@qg/js-bridge": "^1.1.12",
"axios": "^0.21.1", "axios": "^0.21.1",
"intersection-observer": "^0.12.0", "intersection-observer": "^0.12.0",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
......
{ {
"name": "quantum-blocks", "name": "quantum-blocks",
"version": "0.0.1", "version": "1.0.0",
"description": "低代码平台", "description": "低代码平台",
"scripts": { "scripts": {
"start": "cross-env NODE_ENV=production APOLLO_CLUSTER=RC egg-scripts start --port 9050 --workers 1", "start": "cross-env NODE_ENV=production APOLLO_CLUSTER=RC egg-scripts start --port 9050 --workers 1",
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
"@easy-team/easywebpack-vue": "^4.0.0", "@easy-team/easywebpack-vue": "^4.0.0",
"@hubcarl/json-typescript-mapper": "^2.0.0", "@hubcarl/json-typescript-mapper": "^2.0.0",
"@qg/apollo-nodejs": "^2.1.2", "@qg/apollo-nodejs": "^2.1.2",
"@qg/cherry-ui": "2.23.7", "@qg/cherry-ui": "2.23.9",
"@qg/citrus-ui": "0.2.29", "@qg/citrus-ui": "0.3.7",
"@riophae/vue-treeselect": "^0.4.0", "@riophae/vue-treeselect": "^0.4.0",
"@types/lodash": "^4.14.117", "@types/lodash": "^4.14.117",
"@types/node": "^10.12.0", "@types/node": "^10.12.0",
......
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