├── lib
├── index.style.js
├── service
│ ├── eventbus.js
│ ├── dom.js
│ ├── main-tool-editor-type-mixin.js
│ ├── index.js
│ ├── mount.js
│ └── mount-mixin.js
├── plugins
│ ├── global-settings
│ │ ├── style.js
│ │ └── index.vue
│ ├── save
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-editor
│ │ ├── style.js
│ │ ├── box-editor
│ │ │ ├── style.js
│ │ │ └── index.vue
│ │ └── index.vue
│ ├── main-tool-editor-type-box-boolean
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-select
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-string
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-number
│ │ ├── style.js
│ │ └── index.vue
│ ├── drag-menu-button
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-tree
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-color
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor
│ │ ├── style.js
│ │ └── index.vue
│ ├── crumbs
│ │ ├── style.js
│ │ └── index.vue
│ ├── main-tool-editor-type-box-display
│ │ ├── style.js
│ │ ├── index.vue
│ │ └── icon.js
│ ├── drag-menu
│ │ ├── index.vue
│ │ └── style.js
│ ├── main-tool-editor-manager
│ │ ├── style.js
│ │ └── index.vue
│ └── viewport-guideline
│ │ └── index.vue
├── components
│ ├── index.js
│ ├── container
│ │ ├── index.vue
│ │ └── property.js
│ ├── card
│ │ ├── index.vue
│ │ └── property.js
│ └── button
│ │ ├── index.vue
│ │ └── property.js
├── page
│ ├── viewport
│ │ ├── style.js
│ │ ├── edit-helper
│ │ │ ├── style.js
│ │ │ └── index.vue
│ │ └── index.vue
│ └── index
│ │ ├── index.vue
│ │ └── index.style.js
├── store
│ ├── index.js
│ └── editor
│ │ ├── application.js
│ │ └── viewport.js
├── index.js
└── index.vue
├── .eslintignore
├── .gitignore
├── docs
├── 05acfdb568b3df49ad31355b19495d4a.woff
├── 24712f6c47821394fba7942fbb52c3b2.ttf
├── 2c2ae068be3b089e0a5b59abb1831550.eot
└── index.html
├── .babelrc
├── .editorconfig
├── example
├── src
│ ├── pages
│ │ └── default.vue
│ ├── App.vue
│ ├── store
│ │ └── index.js
│ ├── route
│ │ └── router.js
│ └── main.js
└── index.html
├── webpack.config.js
├── .eslintrc.json
├── README.md
└── package.json
/lib/index.style.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build
2 | docs
--------------------------------------------------------------------------------
/lib/service/eventbus.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | export default new Vue();
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules
3 | .project
4 | .vscode
5 | .history
6 | .DS_Store
7 | \.settings/
8 | build/env.js
9 |
--------------------------------------------------------------------------------
/lib/plugins/global-settings/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled.div`
4 | `;
5 |
--------------------------------------------------------------------------------
/docs/05acfdb568b3df49ad31355b19495d4a.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieLau/gaea-editor-vue/HEAD/docs/05acfdb568b3df49ad31355b19495d4a.woff
--------------------------------------------------------------------------------
/docs/24712f6c47821394fba7942fbb52c3b2.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieLau/gaea-editor-vue/HEAD/docs/24712f6c47821394fba7942fbb52c3b2.ttf
--------------------------------------------------------------------------------
/docs/2c2ae068be3b089e0a5b59abb1831550.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CharlieLau/gaea-editor-vue/HEAD/docs/2c2ae068be3b089e0a5b59abb1831550.eot
--------------------------------------------------------------------------------
/lib/plugins/save/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled.div`
4 | color: #666;
5 | `
6 | ;
7 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": ["transform-runtime","transform-vue-jsx","transform-object-rest-spread"],
4 | "comments": false
5 | }
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | charset = utf-8
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
--------------------------------------------------------------------------------
/lib/components/index.js:
--------------------------------------------------------------------------------
1 |
2 | import Button from './button';
3 | import Container from './container';
4 |
5 | import Card from './card';
6 |
7 | export default [Button, Container, Card];
8 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled.div`
4 | display:flex;
5 | flex-grow:1;
6 | overflow-x:hidden;
7 | `;
8 |
--------------------------------------------------------------------------------
/example/src/pages/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
16 |
--------------------------------------------------------------------------------
/lib/page/viewport/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | display:flex;
4 | flex-direction: column;
5 | flex-grow: 1;
6 | overflow-x: hidden;
7 | overflow-y: auto;
8 | `;
9 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-editor/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | display: flex;
4 | flex-grow: 1;
5 | justify-content: center;
6 | padding: 5px 0 5px 0;
7 | `;
8 |
--------------------------------------------------------------------------------
/lib/page/viewport/edit-helper/style.js:
--------------------------------------------------------------------------------
1 | import styled, { injectGlobal } from 'vue-styled-components';
2 |
3 | export const injectGlob = () => injectGlobal`
4 | .gaea-slot {
5 | border: 1px dotted #ccc;
6 | }
7 |
8 | .gaea-draggable {
9 | }
10 | `
11 | ;
12 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-boolean/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | padding: 5px 30px 5px 0;
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-end;
7 | flex-grow: 1;
8 | `;
9 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-select/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | padding: 5px 12px 5px 0;
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-end;
7 | flex-grow: 1;
8 | `;
9 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | let config = null;
3 |
4 | let env = process.env.NODE_ENV || 'dev';
5 | try {
6 | config = require(`./build/webpack.${env}.config`);
7 | } catch (error) {
8 | console.error('未找所选环境变量对应的配置文件');
9 | }
10 |
11 | module.exports = config;
12 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-string/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | display: flex;
4 | align-items: center;
5 | justify-content: flex-end;
6 | flex-grow: 1;
7 | flex-basis: 0;
8 | padding: 5px 10px;
9 | `;
10 |
--------------------------------------------------------------------------------
/example/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | Vue.use(Vuex);
5 |
6 | const store = new Vuex.Store({
7 | state: {
8 | //
9 | },
10 | mutations: {
11 | //
12 | },
13 | actions: {
14 |
15 | },
16 | modules: {
17 |
18 | }
19 | });
20 |
21 | export default store;
22 |
--------------------------------------------------------------------------------
/lib/plugins/save/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 保存
7 |
8 |
9 |
10 |
18 |
19 |
22 |
--------------------------------------------------------------------------------
/example/src/route/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import Demo from '../pages/default.vue';
4 |
5 | Vue.use(Router);
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'default',
12 | component: Demo
13 | }
14 | ]
15 | })
16 | ;
17 |
--------------------------------------------------------------------------------
/lib/plugins/global-settings/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 全局设置
7 |
8 |
9 |
10 |
18 |
19 |
22 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | gaea vue版本
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-number/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | padding: 5px 10px;
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-end;
7 | flex-grow: 1;
8 | flex-basis: 0;
9 | `;
10 |
11 | export const SliderContainer = styled.div`
12 | width: 100px;
13 | margin-right: 15px;
14 | `;
15 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | gaea vue版本
--------------------------------------------------------------------------------
/lib/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | import application from './modules/editor/application';
5 | import viewport from './modules/editor/viewport';
6 |
7 | Vue.use(Vuex);
8 |
9 | const store = new Vuex.Store({
10 | state: {
11 | //
12 | },
13 | mutations: {
14 | //
15 | },
16 | actions: {
17 |
18 | },
19 | modules: {
20 | application,
21 | viewport
22 | }
23 | });
24 |
25 | export default store;
26 |
--------------------------------------------------------------------------------
/lib/plugins/drag-menu-button/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled('div', {
4 | theme: Object
5 | })`
6 | ${(props) => props.theme.active && `
7 | background-color: white;
8 | box-shadow: inset 0 0 10px #cacaca;
9 | &:hover {
10 | background-color: white;
11 | }
12 | `}
13 | `;
14 |
15 | export const IconContainer = styled('div')`
16 | display:flex;
17 | height:40px;
18 | width:40px;
19 | align-items:center;
20 | justify-content:center;
21 | `;
22 |
--------------------------------------------------------------------------------
/example/src/main.js:
--------------------------------------------------------------------------------
1 |
2 | import Vue from 'vue';
3 | import App from './App';
4 | import iView from 'iview';
5 | import 'iview/dist/styles/iview.css';
6 | import store from './store/index';
7 | import router from './route/router';
8 |
9 | // import GaeaEditor from '../../lib/index';
10 | import GaeaEditor from '../../dist/index';
11 |
12 | Vue.use(iView);
13 | Vue.use(GaeaEditor, {store});
14 |
15 | Vue.config.productionTip = false;
16 | new Vue({
17 | el: '#app',
18 | router,
19 | store,
20 | template: '',
21 | components: { App }
22 | });
23 |
--------------------------------------------------------------------------------
/lib/components/container/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/lib/components/card/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
23 |
24 |
27 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-tree/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | position: relative;
4 | border-top: 1px solid #ddd;
5 | flex-grow: 1;
6 | width: 100%;
7 | display: flex;
8 | flex-direction: column;
9 | `;
10 |
11 | export const TreeContainer = styled.div`
12 | position: relative;
13 | width: 100%;
14 | overflow-y: auto;
15 | overflow-x: hidden;
16 | `;
17 |
18 | export const AbsoluteContainer = styled.div`
19 | position: absolute;
20 | bottom: 0;
21 | right: 0;
22 | padding: 5px;
23 | background-color: white;
24 | font-size: 12px;
25 | color: #666;
26 | `;
27 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard",
3 | "root": true,
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "env": {
9 | "browser": true,
10 | "node": true
11 | },
12 | "plugins": [ "html", "standard" ],
13 | "rules": {
14 | "indent": ["error", 4, { "SwitchCase": 1 }],
15 | "quotes": ["error", "single"],
16 | "semi": ["error", "always"],
17 | "no-console": ["error"],
18 | "no-empty": 2,
19 | "no-eq-null": 2,
20 | "no-new": 0,
21 | "no-fallthrough": 0,
22 | "no-unreachable": 0
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-color/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | padding: 5px 0 5px 0;
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-end;
7 | flex-grow: 1;
8 | margin-right: 10px;
9 | `
10 | ;
11 | export const ColorContainer = styled.div`
12 | display: flex;
13 | justify-content: center;
14 | align-items: center;
15 | width: 23px;
16 | height: 20px;
17 | border-radius: 3px;
18 | border: 1px solid #ddd;
19 | background-color: white;
20 | cursor: pointer;
21 | margin-right:15px;
22 | &:hover {
23 | border-color: #ccc;
24 | }
25 | `
26 | ;
27 |
--------------------------------------------------------------------------------
/lib/components/button/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
27 |
28 |
31 |
--------------------------------------------------------------------------------
/lib/service/dom.js:
--------------------------------------------------------------------------------
1 | export function hasClass (obj, cls) {
2 | return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
3 | }
4 |
5 | export function removeClass (obj, cls) {
6 | if (hasClass(obj, cls)) {
7 | const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
8 | obj.className = obj.className.replace(reg, ' ');
9 | }
10 | }
11 |
12 | export function addClass (obj, cls) {
13 | if (!hasClass(obj, cls)) {
14 | obj.className === '' ? obj.className = `${cls}` : obj.className += ` ${cls}`;
15 | }
16 | }
17 |
18 | export function getStyle (obj, oStyle) { // 获取元素的样式
19 | if (obj.currentStyle) {
20 | return obj.currentStyle[oStyle];
21 | } else {
22 | return getComputedStyle(obj, null)[oStyle];
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-string/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
30 |
31 |
34 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const EmptyContainer = styled.div`
4 | flex-grow: 1;
5 | `;
6 |
7 | export const EmptyTitle = styled.div`
8 | display: flex;
9 | justify-content: center;
10 | color: #666;
11 | font-weight: bold;
12 | padding: 10px;
13 | margin-top: 10px;
14 | font-size: 14px;
15 | `;
16 |
17 | export const EmptyDescription = styled.div`
18 | margin: 15px 10px 0 10px;
19 | padding: 20px;
20 | color: #999;
21 | border: 1px solid #ddd;
22 | border-radius: 3px;
23 | font-size: 13px;
24 | `;
25 |
26 | export const Container = styled.div`
27 | flex-grow: 1;
28 | overflow-y: auto;
29 | `;
30 |
31 | export const TabTitle = styled.div`
32 | padding: 5px 10px;
33 | font-size: 13px;
34 | color: #666;
35 | background-color: #eee;
36 | `;
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gaea-editor-vue
2 |
3 | ## demo
4 | > [demo](https://charlielau.github.io/gaea-editor-vue)
5 |
6 | ## Installation
7 |
8 | Install with yarn:
9 |
10 | ```sh
11 | $ yarn add gaea-editor-vue
12 | # or with npm:
13 | $ npm install gaea-editor-vue
14 | ```
15 |
16 | ## Usage:
17 |
18 | Main.js:
19 |
20 | ```javascript
21 | //依赖vuex
22 | import store from './store/index';
23 | import GaeaEditor from 'gaea-editor-vue';
24 |
25 | Vue.use(GaeaEditor, {store});
26 |
27 | ```
28 |
29 | component.vue:
30 |
31 | ```vue
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
42 | ```
43 | ## 扩展开发
44 |
45 | ```sh
46 | $ yarn run build
47 | ```
48 |
49 | ## Refrence
50 |
51 | > [gaea-editor](https://github.com/ascoders/gaea-editor)
52 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-boolean/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
36 |
39 |
--------------------------------------------------------------------------------
/lib/components/container/property.js:
--------------------------------------------------------------------------------
1 |
2 | export const editSetting = {
3 | key: 'gaea-container',
4 | name: 'Container',
5 | isContainer: true,
6 | slots: {
7 | default: []
8 | },
9 | editors: [
10 | 'Layout',
11 | {
12 | type: 'display'
13 | },
14 | {
15 | type: 'box-editor'
16 | },
17 | 'Style',
18 | {
19 | field: 'styles.backgroundColor',
20 | text: 'BackgroundColor',
21 | type: 'color'
22 | },
23 | {
24 | field: 'styles.opacity',
25 | text: 'Opacity',
26 | type: 'number',
27 | data: {
28 | useSlider: true,
29 | step: 1,
30 | inputRange: [0, 100],
31 | outputRange: [0, 1]
32 | }
33 | }
34 | ]
35 | };
36 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import component from './index.vue';
2 | import application from './store/editor/application';
3 | import viewport from './store/editor/viewport';
4 |
5 | import Plugins from './service';
6 |
7 | import Mixins from './service/mount';
8 |
9 | let GaeaEditor = {
10 | install (Vue, options) {
11 | let store = options.store;
12 | if (!store) {
13 | console.error('gaea editor 需要 vuex');
14 | return;
15 | }
16 | // 注册store 模块
17 | if (!store.state.application) {
18 | store.registerModule('application', application);
19 | }
20 | if (!store.state.viewport) {
21 | store.registerModule('viewport', viewport);
22 | }
23 | // 注册组件
24 | Vue.component('gaea-editor', component);
25 | Plugins(Vue);
26 | Mixins(Vue);
27 | }
28 | };
29 | export default GaeaEditor;
30 |
--------------------------------------------------------------------------------
/lib/components/card/property.js:
--------------------------------------------------------------------------------
1 |
2 | export const editSetting = {
3 | key: 'gaea-card',
4 | name: 'Card',
5 | // isContainer: true,
6 | slot: {
7 | default: []
8 | },
9 | editors: [
10 | 'Layout',
11 | {
12 | type: 'box-editor'
13 | },
14 | 'Style',
15 | {
16 | field: 'styles.backgroundColor',
17 | text: 'BackgroundColor',
18 | type: 'color'
19 | },
20 | {
21 | field: 'styles.opacity',
22 | text: 'Opacity',
23 | type: 'number',
24 | data: {
25 | useSlider: true,
26 | step: 1,
27 | inputRange: [0, 100],
28 | outputRange: [0, 1]
29 | }
30 | },
31 | 'aa',
32 | {
33 | field: 'title',
34 | text: 'Title',
35 | type: 'string'
36 | }
37 |
38 | ]
39 | };
40 |
--------------------------------------------------------------------------------
/lib/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/lib/page/viewport/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/lib/plugins/crumbs/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled.div`
4 | display: flex;
5 | align-items: stretch;
6 | flex-grow: 1;
7 | background-color: whitesmoke;
8 | `;
9 |
10 | export const AutoWidthContainer = styled.div`
11 | display: flex;
12 | align-items: stretch;
13 | `;
14 |
15 | export const FooterItem = styled.div`
16 | position: relative;
17 | display: flex;
18 | align-items: center;
19 | padding-left: 8px;
20 | color: #666;
21 | font-size: 13px;
22 | &:hover {
23 | cursor: pointer;
24 | color: #146f8c;
25 | transition: color 0.2s;
26 | }
27 | &:last-child {
28 | color: #146f8c;
29 | }
30 | `;
31 |
32 | export const rightIconContainer = styled.div`
33 | overflow: hidden;
34 | position: relative;
35 | width: 20px;
36 | height: 25px;
37 | margin-left: 5px;
38 | `;
39 |
40 | export const rightIcon = styled.div`
41 | position: absolute;
42 | width: 25px;
43 | height: 25px;
44 | transform: rotate(45deg);
45 | right: 7px;
46 | border: 1px solid #ddd;
47 | `;
48 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-display/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const Container = styled.div`
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | flex-grow: 1;
7 | margin: 10px 15px;
8 | padding: 5px;
9 | border: 1px solid #eee;
10 | border-radius: 5px;
11 |
12 | .rotate-45 {
13 | transform: rotate(45deg);
14 | }
15 |
16 | .rotate-90 {
17 | transform: rotate(90deg);
18 | }
19 |
20 | .rotate-135 {
21 | transform: rotate(135deg);
22 | }
23 |
24 | .rotate-180 {
25 | transform: rotate(180deg);
26 | }
27 |
28 | .rotate-270 {
29 | transform: rotate(270deg);
30 | }
31 | `;
32 |
33 | export const DisplayContainer = styled.div`
34 | display: flex;
35 | justify-content: space-between;
36 | `;
37 |
38 | export const FlexContainer = styled.div`
39 | display: flex;
40 | flex-direction: column;
41 | justify-content: center;
42 | `;
43 |
44 | export const FlexRow = styled.div`
45 | display: flex;
46 | justify-content: space-between;
47 | `;
48 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
33 |
34 |
43 |
--------------------------------------------------------------------------------
/lib/plugins/drag-menu-button/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-select/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
46 |
47 |
50 |
--------------------------------------------------------------------------------
/lib/service/main-tool-editor-type-mixin.js:
--------------------------------------------------------------------------------
1 |
2 | import * as _ from 'lodash';
3 | export const PropertyMixin = {
4 |
5 | watch: {
6 | currentEditInstanceKey (n, o) {
7 | // init
8 | this.initCurrtField();
9 | }
10 | },
11 | props: {
12 | instanceInfo: Object,
13 | editor: Object
14 | },
15 | data () {
16 | return {
17 | field: null,
18 | value: ''
19 | };
20 | },
21 | computed: {
22 | currentEditInstanceKey () {
23 | let currentEditInstanceKey = this.$store.state.viewport.currentEditInstanceKey;
24 | return currentEditInstanceKey;
25 | }
26 |
27 | },
28 | methods: {
29 | setInstanceProps (value) {
30 | this.$store.commit('viewport/setInstanceProps', {
31 | vm: this.instanceInfo.vm,
32 | key: this.field,
33 | value
34 | });
35 | },
36 | initCurrtField () {
37 | let props = this.instanceInfo.vm.$data;
38 | let val = _.get(props, this.field);
39 | if (val) {
40 | this.value = val;
41 | }
42 | }
43 | },
44 | mounted () {
45 | if (this.editor.field) {
46 | this.field = this.editor.field;
47 | }
48 | }
49 | }
50 | ;
51 |
--------------------------------------------------------------------------------
/lib/plugins/drag-menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 组件库
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{item[1].editSetting.name}}
12 |
13 |
14 |
15 |
16 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/lib/plugins/drag-menu/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 | export const GaeaComponent = styled.div`
3 | display: flex;
4 | align-items: center;
5 | height: 30px;
6 | padding: 0 10px;
7 | cursor: pointer;
8 | font-size: 14px;
9 | color: #666;
10 | background-color: #eee;
11 | transition: background-color .3s;
12 | &:hover {
13 | background-color: white;
14 | }
15 | border-bottom: 1px solid #ddd;
16 | `;
17 |
18 | export const Container = styled.div`
19 | display: flex;
20 | flex-direction: column;
21 | background-color: whitesmoke;
22 | flex-grow: 1;
23 | `;
24 |
25 | export const Title = styled.div`
26 | display: flex;
27 | justify-content: space-between;
28 | padding: 0 10px;
29 | height: 40px;
30 | font-size: 16px;
31 | align-items: center;
32 | color: #777;
33 | font-weight: bold;
34 | border-bottom: 1px solid #ddd;
35 | `;
36 |
37 | export const CloseContainer = styled.div`
38 | padding: 5px;
39 | cursor: pointer;
40 | fill: #999;
41 | align-items: center;
42 | &:hover {
43 | fill: #333;
44 | }
45 | `;
46 |
47 | export const SearchInput = styled.input`
48 | outline: none;
49 | padding: 5px 10px;
50 | font-size: 14px;
51 | color: #666;
52 | border-right: none;
53 | border-left: none;
54 | border-top: none;
55 | border-bottom: 1px solid #ddd;
56 | background-color: #fbfbfb;
57 | &:focus{
58 | background-color: white;
59 | color: #333;
60 | }
61 | &::-webkit-input-placeholder{
62 | color: #999;
63 | }
64 | `;
65 |
66 | export const ListContainer = styled.div`
67 | overflow-y: auto;
68 | flex-grow: 1;
69 | flex-basis: 0;
70 | `;
71 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 无编辑信息
9 |
10 |
11 | 该组件还未添加编辑信息。
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
49 |
50 |
58 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-color/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
68 |
69 |
72 |
--------------------------------------------------------------------------------
/lib/service/index.js:
--------------------------------------------------------------------------------
1 | let builtInPlugins = [];
2 | let sortedPlugins = {};
3 | let pluginsBelongKeys = {};
4 | let parseInputToOutRange = null;
5 |
6 | const context = require.context(
7 | '../plugins',
8 | true,
9 | /index\.(vue)$/
10 | );
11 | context.keys().forEach(item => {
12 | builtInPlugins.push(context(item).default);
13 | });
14 |
15 | sortedPlugins = builtInPlugins.reduce((prev, nxt) => {
16 | if (!nxt || !nxt.position) {
17 | return prev;
18 | }
19 | let componentName = `${nxt.position}-${nxt.name}`;
20 | if (!componentName) {
21 | return prev;
22 | }
23 | if (!pluginsBelongKeys[nxt.position]) {
24 | pluginsBelongKeys[nxt.position] = [];
25 | }
26 | pluginsBelongKeys[nxt.position].push(componentName);
27 | prev[componentName] = nxt;
28 | // Vue.component(componentName, nxt); // 全局注册
29 | return prev;
30 | }, {});
31 |
32 | // 根据 inputRange outputRange 转换值
33 | parseInputToOutRange = (value, inputRange, outputRange) => {
34 | if (value === undefined || value === null) {
35 | return null;
36 | }
37 |
38 | value = Number(value);
39 |
40 | if (inputRange[0] === outputRange[0] && inputRange[1] === outputRange[1]) {
41 | return value;
42 | }
43 |
44 | if (value >= inputRange[0] && value <= inputRange[1]) {
45 | // 给的值必须在 input 范围内
46 | // 转换成 0~1 的小数
47 | const percentage = (value - inputRange[0]) / (inputRange[1] - inputRange[0]);
48 | // 转换成 output 的长度
49 | const outputLength = (outputRange[1] - outputRange[0]) * percentage;
50 | // 数值是加上最小值
51 | value = outputLength + outputRange[0];
52 | }
53 | return value;
54 | };
55 |
56 | export default (Vue) => {
57 | Object.keys(sortedPlugins).forEach(item => {
58 | Vue.component(item, sortedPlugins[item]);
59 | });
60 | }
61 | ;
62 |
63 | export const SVC = {
64 | pluginsBelongKeys,
65 | sortedPlugins,
66 | parseInputToOutRange
67 | };
68 |
--------------------------------------------------------------------------------
/lib/store/editor/application.js:
--------------------------------------------------------------------------------
1 | const application = {
2 | namespaced: true,
3 | state: {
4 | navbarHeight: 40,
5 | isPreview: false,
6 | viewportContainerStyle: {},
7 | viewportStyle: {},
8 | plugins: [],
9 | componentClasses: new Map(),
10 | componentSetting: new Map(),
11 | componentDefaultProps: new Map(),
12 | defaultValue: null,
13 | rootComponentName: '',
14 | leftTool: null,
15 | rightTool: null,
16 | isShowModal: false,
17 | modalTitle: '',
18 | modalContentRender: null,
19 | pages: new Map(),
20 | currentCreatedPageKey: null,
21 | currentEditPageKey: null,
22 | currentViewportPageKey: null,
23 | preComponents: new Map(),
24 | pluginsBelongKeys: {},
25 | sortedPlugins: {},
26 | onComponentDragStart: () => {}
27 | },
28 | mutations: {
29 | // loadPluginByPosition (state, position) {
30 | // state.belongPlugins[position] = state.plugins.filter(plugin => plugin.position === position);
31 | // },
32 | addPlugin (state, plugin) {
33 | state.plugins.push(plugin);
34 | },
35 | setLeftTool (state, position) {
36 | state.leftTool = position;
37 | },
38 | setRightTool (state, position) {
39 | state.rightTool = position;
40 | },
41 | loadBuiltInPlugins (state, {pluginsBelongKeys, sortedPlugins}) {
42 | state.pluginsBelongKeys = pluginsBelongKeys;
43 | state.sortedPlugins = sortedPlugins;
44 | },
45 | addComponentClass (state, component) {
46 | state.componentClasses.set(component.name, component);
47 | state.componentSetting.set(component.name, component.editSetting);
48 | },
49 | setOnComponentDragStart (state, fn) {
50 | state.onComponentDragStart = fn;
51 | }
52 | }
53 | };
54 |
55 | export default application;
56 |
--------------------------------------------------------------------------------
/lib/page/viewport/edit-helper/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
62 |
63 |
66 |
--------------------------------------------------------------------------------
/lib/components/button/property.js:
--------------------------------------------------------------------------------
1 |
2 | export const editSetting = {
3 | key: 'gaea-button',
4 | name: 'Button',
5 | editors: [
6 | 'Layout',
7 | {
8 | type: 'box-editor'
9 | },
10 | 'Function',
11 | {
12 | field: 'text',
13 | text: 'Text',
14 | type: 'string'
15 | },
16 | {
17 | field: 'href',
18 | text: 'Href',
19 | type: 'string'
20 | },
21 | {
22 | field: 'target',
23 | text: 'Target',
24 | type: 'string'
25 | },
26 | 'Style',
27 | {
28 | field: 'type',
29 | text: 'Type',
30 | type: 'select',
31 | data: [{
32 | value: null,
33 | text: 'Default'
34 | }, {
35 | value: 'primary',
36 | text: 'Primary'
37 | }, {
38 | value: 'dashed',
39 | text: 'Dashed'
40 | }, {
41 | value: 'danger',
42 | text: 'Danger'
43 | }]
44 | }, {
45 | field: 'ghost',
46 | text: 'Transparent',
47 | type: 'boolean'
48 | }, {
49 | field: 'icon',
50 | text: 'Icon',
51 | type: 'string'
52 | }, {
53 | field: 'loading',
54 | text: 'Loading',
55 | type: 'boolean'
56 | }, {
57 | field: 'shape',
58 | text: 'Shape',
59 | type: 'select',
60 | data: [{
61 | value: null,
62 | text: 'Default'
63 | }, {
64 | value: 'circle',
65 | text: 'Circle'
66 | }]
67 | }, {
68 | field: 'size',
69 | text: 'Size',
70 | type: 'select',
71 | data: [{
72 | value: null,
73 | text: 'Default'
74 | }, {
75 | value: 'small',
76 | text: 'Small'
77 | }, {
78 | value: 'large',
79 | text: 'Large'
80 | }]
81 | }
82 | ],
83 | events: [{
84 | text: 'OnClick',
85 | field: 'onClick'
86 | }]
87 | };
88 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-manager/style.js:
--------------------------------------------------------------------------------
1 | import styled from 'vue-styled-components';
2 |
3 | export const Container = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | flex-grow: 1;
7 | background-color: whitesmoke;
8 | `;
9 |
10 | export const ComponentName = styled.div`
11 | display: flex;
12 | padding: 7px 10px;
13 | font-size: 14px;
14 | color: #666;
15 | font-weight: bold;
16 | border-bottom: 1px solid #ddd;
17 | `;
18 |
19 | export const TabTitle = styled.div`
20 | padding: 5px 10px;
21 | font-size: 14px;
22 | color: #666;
23 | background-color: #eee;
24 | width: 100%;
25 | font-weight: bold;
26 | `;
27 |
28 | export const EditorContainer = styled('div', {theme: Object})`
29 | display: flex;
30 | position: relative;
31 | justify-content: space-between;
32 | ${(props) => props.theme.isObjectType && `
33 | flex-direction: column;
34 | justify-content: flex-start;
35 | `}
36 | `;
37 |
38 | export const EditorBoxContainer = styled.div`
39 | display: flex;
40 | justify-content: flex-end;
41 | flex-grow: 1;
42 | `;
43 |
44 | export const Variable = styled('div', {theme: Object})`
45 | display: none;
46 | /* display: flex; */
47 | align-items: center;
48 | justify-content: center;
49 | position: absolute;
50 | right: 0;
51 | top: 0;
52 | width: 30px;
53 | height: 20px;
54 | background-color: #eee;
55 | border-left: 1px solid #ddd;
56 | border-bottom: 1px solid #ddd;
57 | border-top: 1px solid #ddd;
58 | border-bottom-left-radius: 5px;
59 | cursor: pointer;
60 | fill: #666;
61 | &:hover {
62 | background-color: white;
63 | fill: #333;
64 | }
65 | ${(props) => props.theme.isVariable && `
66 | background-color: #cef1ff;
67 | &:hover {
68 | background-color: #e4f7ff;
69 | }
70 | `}
71 | `;
72 |
73 | export const Label = styled('div', {theme: Object})`
74 | display: flex;
75 | flex-direction: row;
76 | align-items: center;
77 | font-size: 14px;
78 | color: #666;
79 | white-space: nowrap;
80 | padding: 5px 0;
81 | margin-left: 10px;
82 | ${(props) => props.theme.isObjectType && `
83 | align-items: flex-start;
84 | `}
85 | `;
86 |
87 | export const AddButton = styled.div`
88 | margin-left: 10px;
89 | fill: #666;
90 | cursor: pointer;
91 | transition: color .3s;
92 | &:hover {
93 | fill: #333;
94 | }
95 | `;
96 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-editor/box-editor/style.js:
--------------------------------------------------------------------------------
1 | import styled, {css} from 'vue-styled-components';
2 |
3 | const leftRightTopBottom = css`
4 | display: flex;
5 | position: absolute;
6 | `;
7 |
8 | const leftRight = css`
9 | flex-direction: row;
10 | cursor: ew-resize;
11 | `;
12 |
13 | const topBottom = css`
14 | flex-direction: column;
15 | cursor: ns-resize;
16 | `;
17 |
18 | export const Container = styled.div`
19 | position: relative;
20 | `;
21 |
22 | export const Left = styled.div`
23 | ${leftRightTopBottom}
24 | ${leftRight}
25 | `;
26 |
27 | export const Right = styled.div`
28 | ${leftRightTopBottom}
29 | ${leftRight}
30 | `;
31 |
32 | export const Top = styled.div`
33 | ${leftRightTopBottom}
34 | ${topBottom}
35 | `;
36 |
37 | export const Bottom = styled.div`
38 | ${leftRightTopBottom}
39 | ${topBottom}
40 | `;
41 |
42 | export const NumberBox = styled.div`
43 | display: flex;
44 | position: absolute;
45 | justify-content: center;
46 | align-items: center;
47 | user-select: none;
48 | `;
49 |
50 | export const Input = styled.input`
51 | outline: none;
52 | width: calc(100% - 5px);
53 | height: calc(100% - 8px);
54 | text-align: center;
55 | border: none;
56 | background-color: transparent;
57 | `;
58 |
59 | export const ButtonContainer = styled.div`
60 | overflow: hidden;
61 | `;
62 |
63 | export const ButtonTriangle = styled('div', {themes: Object})`
64 | transition: all .2s;
65 | user-select: none;
66 | ${(props) => {
67 | switch (props.themes.position) {
68 | case 'left':
69 | return `
70 | border-right-color: #666;
71 | &:hover {
72 | border-right-color: black;
73 | }
74 | `;
75 | case 'top':
76 | return `
77 | border-bottom-color: #666;
78 | &:hover {
79 | border-bottom-color: black;
80 | }
81 | `;
82 | case 'right':
83 | return `
84 | border-left-color: #666;
85 | &:hover {
86 | border-left-color: black;
87 | }
88 | `;
89 | case 'bottom':
90 | return `
91 | border-top-color: #666;
92 | &:hover {
93 | border-top-color: black;
94 | }
95 | `;
96 | }
97 | }}
98 | `;
99 |
--------------------------------------------------------------------------------
/lib/plugins/crumbs/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{item}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
62 |
63 |
66 |
--------------------------------------------------------------------------------
/lib/service/mount.js:
--------------------------------------------------------------------------------
1 | import {HelperClass} from './mount-mixin';
2 | let _Vue_ = null;
3 | export default (Vue) => {
4 | _Vue_ = Vue;
5 | };
6 |
7 | /**
8 | *
9 | * @param {*} $el 要挂载的元素
10 | * @param {*} component 组件类型
11 | * @param {*} isAppend 是否追加
12 | * @param {*} instanceKey 实例Id
13 | * @param {*} store store对象
14 | */
15 | export const mount = ($el, component, isAppend, instanceKey, store) => {
16 | return new Promise((resolve, reject) => {
17 | let $mountDom = null;
18 | if (isAppend) { // 如果id 存在 挂载元素下面
19 | let span = document.createElement('span');
20 | $el.appendChild(span);
21 | $mountDom = span;
22 | } else { // 如果元素不存在 替换元素
23 | $mountDom = $el;
24 | }
25 | let vm = new _Vue_({
26 | extends: component,
27 | mixins: [HelperClass],
28 | store
29 | });
30 | if (!vm.$store) {
31 | vm.$store = store;
32 | }
33 | // 挂载前 data赋值
34 | vm.__crrtInstanceKey__ = instanceKey;
35 | vm.$mount($mountDom);
36 | // 挂载之后操作
37 | resolve(vm);
38 | });
39 | };
40 | /**
41 | *
42 | * @param {*} $el 要挂载的元素
43 | * @param {*} component 组件类型
44 | * @param {*} isAppend 是否追加
45 | * @param {*} instanceKey 实例Id
46 | * @param {*} store store对象
47 | */
48 | export const mountSlot = (parentVm, component, slotName, instanceKey, store) => {
49 | return new Promise((resolve, reject) => {
50 | let WrapperComponent = {
51 | extends: component,
52 | mixins: [HelperClass, {
53 | props: {
54 | __crrtInstanceKey__: {
55 | type: String,
56 | default: instanceKey
57 | }
58 | }
59 | }],
60 | store
61 | };
62 | let elem = parentVm.$createElement(WrapperComponent);
63 | // if (Array.isArray(elem)) {
64 | // if (Array.isArray(parentVm.$slots[slotName])) {
65 | // parentVm.$slots[slotName] = [...parentVm.$slots[slotName], ...elem];
66 | // } else {
67 | // parentVm.$slots[slotName] = [...elem];
68 | // }
69 | // } else {
70 | if (Array.isArray(parentVm.$slots[slotName])) {
71 | parentVm.$slots[slotName].push(elem);
72 | } else {
73 | parentVm.$slots[slotName] = [elem];
74 | }
75 | // }
76 | // 先更新父级 子级vnode 才能获取到 对应的 componentInstance
77 | parentVm.$forceUpdate();
78 | parentVm.$nextTick(() => {
79 | // 挂载之后操作
80 | resolve(elem);
81 | });
82 | });
83 | };
84 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-manager/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{editor}}
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
66 |
67 |
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gaea-editor-vue",
3 | "version": "0.0.8",
4 | "description": "gaea-editor vue实现",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "cross-env NODE_ENV=dev webpack-dev-server --progress --hot ",
9 | "docs": " rimraf ./docs & cross-env NODE_ENV=docs webpack -p --progress ",
10 | "build": " rimraf ./dist && cross-env NODE_ENV=build webpack -p --progress "
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/CharlieLau/gaea-editor-vue.git"
15 | },
16 | "author": "charlielau",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/CharlieLau/gaea-editor-vue/issues"
20 | },
21 | "homepage": "https://github.com/CharlieLau/gaea-editor-vue#readme",
22 | "devDependencies": {
23 | "autoprefixer-loader": "^3.2.0",
24 | "babel": "^6.23.0",
25 | "babel-core": "^6.23.1",
26 | "babel-eslint": "^8.1.2",
27 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
28 | "babel-loader": "^7.1.2",
29 | "babel-plugin-syntax-jsx": "^6.18.0",
30 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
31 | "babel-plugin-transform-runtime": "^6.12.0",
32 | "babel-plugin-transform-vue-jsx": "^3.5.0",
33 | "babel-preset-env": "^1.6.1",
34 | "babel-preset-stage-3": "^6.24.1",
35 | "babel-runtime": "^6.11.6",
36 | "clean-webpack-plugin": "^0.1.17",
37 | "copy-webpack-plugin": "^4.3.1",
38 | "cross-env": "^5.1.3",
39 | "css-hot-loader": "^1.3.5",
40 | "css-loader": "^0.28.7",
41 | "eslint": "^4.14.0",
42 | "eslint-config-google": "^0.9.1",
43 | "eslint-config-standard": "^10.2.1",
44 | "eslint-plugin-html": "^4.0.1",
45 | "eslint-plugin-import": "^2.8.0",
46 | "eslint-plugin-node": "^5.2.1",
47 | "eslint-plugin-promise": "^3.6.0",
48 | "eslint-plugin-standard": "^3.0.1",
49 | "extract-text-webpack-plugin": "^3.0.2",
50 | "file-loader": "^1.1.6",
51 | "happypack": "^4.0.0",
52 | "html-loader": "^0.5.1",
53 | "html-webpack-plugin": "^2.28.0",
54 | "less": "^2.7.3",
55 | "less-loader": "^4.0.5",
56 | "semver": "^5.4.1",
57 | "style-loader": "^0.19.1",
58 | "unsupported": "^1.1.0",
59 | "url-loader": "^0.6.2",
60 | "vue-hot-reload-api": "^2.2.4",
61 | "vue-html-loader": "^1.2.3",
62 | "vue-i18n": "^5.0.3",
63 | "vue-loader": "^13.6.1",
64 | "vue-style-loader": "^3.0.3",
65 | "vue-template-compiler": "^2.5.13",
66 | "webpack": "^3.10.0",
67 | "webpack-dev-server": "2.9.7",
68 | "webpack-merge": "^4.1.1",
69 | "webpack-uglify-parallel": "^0.1.4"
70 | },
71 | "dependencies": {
72 | "iview": "^2.8.0",
73 | "sortablejs": "^1.7.0",
74 | "vue": "^2.5.13",
75 | "vue-router": "^3.0.1",
76 | "vue-styled-components": "^1.2.1",
77 | "vuex": "^3.0.1"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/plugins/viewport-guideline/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
81 |
82 |
91 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-number/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
102 |
103 |
106 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-editor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
118 |
119 |
122 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-tree/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 组件个数:{{instances.size}}
8 |
9 |
10 |
11 |
12 |
105 |
106 |
129 |
--------------------------------------------------------------------------------
/lib/page/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
92 |
93 |
96 |
--------------------------------------------------------------------------------
/lib/service/mount-mixin.js:
--------------------------------------------------------------------------------
1 | import {addClass} from './dom';
2 | import {mountSlot} from './mount';
3 | import eventbus from './eventbus';
4 | export const HelperClass = {
5 | data () {
6 | return {__setting__: null, __crrtInstanceInfo__: null};
7 | },
8 | methods: {
9 | handleMouseOver (event) {
10 | event.stopPropagation();
11 | this
12 | .$store
13 | .commit('viewport/setCurrentHoverInstanceKey', this.__crrtInstanceKey__);
14 | eventbus.$emit(this.$store.state.viewport.mouseHoveringComponent, {
15 | instanceKey: this.__crrtInstanceKey__,
16 | type: 'instance'
17 | });
18 | },
19 | handleClick (event) {
20 | event.stopPropagation();
21 | this
22 | .$store
23 | .commit('viewport/setCurrentEditInstanceKey', this.__crrtInstanceKey__);
24 | },
25 | registerEvent () {
26 | this
27 | .$el
28 | .removeEventListener('mouseover', this.handleMouseOver);
29 | this
30 | .$el
31 | .removeEventListener('click', this.handleClick);
32 | this
33 | .$el
34 | .addEventListener('mouseover', this.handleMouseOver);
35 | this
36 | .$el
37 | .addEventListener('click', this.handleClick);
38 | },
39 | __refresh__ (instanceKey, instanceDom, setting, instance) {
40 | this
41 | .$store
42 | .commit('viewport/setDomInstance', {
43 | key: instanceKey,
44 | instance: instanceDom
45 | });
46 | // 设置可拖拽类样式
47 | addClass(instanceDom, 'gaea-draggable');
48 | this.__setLayoutClassIfCanDragIn__(setting, instance, instanceDom);
49 | let slotsDom = instanceDom.querySelectorAll('.gaea-slot');
50 |
51 | // sortable
52 | if (this.__setting__.isContainer) {
53 | this
54 | .$store
55 | .commit('viewport/registerInnerDrag', {
56 | parentInstanceKey: instanceKey,
57 | dragParentDom: instanceDom,
58 | params: {
59 | draggable: '.gaea-draggable'
60 | },
61 | // 注册新增组件回调
62 | onDragAdd: this.__handleDragAdd__,
63 | onDragUpdate: this.__handleDragUpdate__,
64 | onDragRemove: this.__handleDragRemove__
65 | });
66 | } else if (slotsDom.length) {
67 | slotsDom.forEach(dom => {
68 | this
69 | .$store
70 | .commit('viewport/registerInnerDrag', {
71 | parentInstanceKey: instanceKey,
72 | dragParentDom: dom,
73 | params: {
74 | draggable: '.gaea-draggable'
75 | },
76 | // 注册新增组件回调
77 | onDragAdd: this.__handleDragAdd__,
78 | onDragUpdate: this.__handleDragUpdate__,
79 | onDragRemove: this.__handleDragRemove__
80 | });
81 | });
82 | }
83 | },
84 | __handleDragUpdate__ (event) {},
85 | // 新增组件
86 | __handleDragAdd__ (e, parentInstanceKey, gaeaKey, instanceKey, slotName) {
87 | let _parentInstance = this
88 | .$store
89 | .state
90 | .viewport
91 | .instances
92 | .get(parentInstanceKey);
93 | let componentClass = this
94 | .$store
95 | .state
96 | .application
97 | .componentClasses
98 | .get(gaeaKey);
99 | let crrtInstance = this
100 | .$store
101 | .state
102 | .viewport
103 | .instances
104 | .get(instanceKey);
105 | crrtInstance.vm = this;
106 | mountSlot(_parentInstance.vm, componentClass, 'default', instanceKey, this.$store);
107 | },
108 | __handleDragRemove__ (event) {},
109 | __setLayoutClassIfCanDragIn__ (setting, instance, instanceDom) {
110 | if (setting.isContainer && instance.parentInstanceKey !== null) {
111 | addClass(instanceDom, 'gaea-container');
112 | }
113 | }
114 | },
115 | mounted () {
116 | if (!this.__crrtInstanceKey__) {
117 | return;
118 | }
119 | // 注册 当前dom 事件
120 | this.registerEvent();
121 | this.__crrtInstanceInfo__ = this
122 | .$store
123 | .state
124 | .viewport
125 | .instances
126 | .get(this.__crrtInstanceKey__);
127 | // 设置当前实例vm对象是自己
128 | this.__crrtInstanceInfo__.vm = this;
129 | // 同步instance的属性 $data
130 | this.__crrtInstanceInfo__.data = this.$data;
131 | this.__setting__ = this
132 | .$store
133 | .state
134 | .application
135 | .componentSetting
136 | .get(this.__crrtInstanceInfo__.gaeaKey);
137 | // 绑定 子可拖拽 设置 state key-$el Map对象
138 | this.__refresh__(this.__crrtInstanceKey__, this.$el, this.__setting__, this.__crrtInstanceInfo__);
139 | }
140 | };
141 |
--------------------------------------------------------------------------------
/lib/page/index/index.style.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'vue-styled-components';
2 |
3 | const NavbarContainerLeftAndNavbarContainerRight = css`
4 | & > div:not(.no-style) {
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | padding: 0 10px;
9 | font-size: 14px;
10 | cursor: pointer;
11 | user-select: none;
12 | color: #666;
13 | &:hover {
14 | background-color: white;
15 | color: #333;
16 | }
17 | }
18 | `;
19 |
20 | const ViewportContainerBoxAndPreviewContainer = css`
21 | display: flex;
22 | position: relative;
23 | flex-basis: 0%;
24 | flex-grow: 1;
25 | ${(props) => props.theme.hidden && `
26 | display: none;
27 | `}
28 | `;
29 |
30 | export const Container = styled('div', { componentId: 'Container' })`
31 | display: flex;
32 | width: 100%;
33 | height: 100%;
34 | overflow: hidden;
35 | `;
36 |
37 | export const LeftContainer = styled('div', { componentId: 'LeftContainer' })`
38 | display: flex;
39 | flex-direction: column;
40 | flex-grow: 1;
41 | width: 0;
42 | `;
43 |
44 | export const RightContainer = styled('div', { componentId: 'RightContainer', theme: Object })`
45 | display: flex;
46 | width: 300px;
47 | z-index: 2;
48 | background-color: white;
49 | overflow: hidden;
50 | border-left: 1px solid #ddd;
51 |
52 | ${(props) => props.theme.hidden && `
53 | display: none;
54 | `}
55 | `;
56 |
57 | export const NavbarContainer = styled.div`
58 | display: flex;
59 | justify-content: space-between;
60 | background-color: whitesmoke;
61 | border-bottom: 1px solid #ddd;
62 | `;
63 |
64 | export const NavbarContainerLeft = styled.div`
65 | display: flex;
66 | & > div:not(.no-style) {
67 | border-right: 1px solid #ddd;
68 | }
69 | ${NavbarContainerLeftAndNavbarContainerRight}
70 | `;
71 |
72 | export const NavbarContainerRight = styled.div`
73 | display: flex;
74 | justify-content: flex-end;
75 | & > div:not(.no-style) {
76 | border-left: 1px solid #ddd;
77 | }
78 | ${NavbarContainerLeftAndNavbarContainerRight}
79 | `;
80 |
81 | export const ViewportContainer = styled.div`
82 | display: flex;
83 | flex-grow: 1;
84 | height: 0;
85 | `;
86 |
87 | export const ViewportContainerLeft = styled('div', {theme: Object})`
88 | display: flex;
89 | flex-direction: column;
90 | justify-content: space-between;
91 | width: 40px;
92 | background-color: white;
93 | z-index: 1;
94 | background-color: whitesmoke;
95 | border-right: 1px solid #ddd;
96 | ${(props) => props.theme.hidden && `
97 | display: none;
98 | `}
99 | `;
100 |
101 | export const ViewportContainerLeftTop = styled.div`
102 | width: 100%;
103 | & > div {
104 | display: flex;
105 | justify-content: center;
106 | align-items: center;
107 | font-size: 14px;
108 | height: 40px;
109 | fill: #666;
110 | cursor: pointer;
111 | user-select: none;
112 | border-bottom: 1px solid #ddd;
113 | &:hover {
114 | background-color: white;
115 | }
116 | }
117 | `;
118 |
119 | export const ViewportContainerLeftBottom = styled.div`
120 | width: 100%;
121 | & > div {
122 | display: flex;
123 | justify-content: center;
124 | align-items: center;
125 | font-size: 14px;
126 | height: 30px;
127 | cursor: pointer;
128 | user-select: none;
129 | &:hover {
130 | background-color: #eaeaea;
131 | }
132 | }
133 | `;
134 |
135 | export const ViewportContainerRight = styled('div', {theme: Object})`
136 | display: flex;
137 | margin-left: -300px;
138 | flex-grow: 1;
139 | width: 0;
140 | transition: all .15s;
141 | ${(props) => props.theme.transparent && `
142 | //background-image: url('../images/transparent.png');
143 | `}
144 | ${(props) => props.theme.showLeft && `
145 | margin-left: 0;
146 | `}
147 | `;
148 |
149 | export const FooterContainer = styled.div`
150 | display: flex;
151 | height: 30px;
152 | border-top: 1px solid #ddd;
153 | background-color: whitesmoke;
154 | `;
155 |
156 | export const ToolsContainer = styled('div', {theme: Object})`
157 | display: flex;
158 | flex-direction: row;
159 | background-color: whitesmoke;
160 | position: relative;
161 | width: 300px;
162 | ${(props) => props.theme.fullScreen && `
163 | width: 100%;
164 | `}
165 | `;
166 |
167 | export const ToolsContainerLeft = styled.div`
168 | display: flex;
169 | flex-direction: column;
170 | width: 300px;
171 | border-right: 1px solid #ddd;
172 | `;
173 |
174 | export const ToolsContainerRight = styled('div', {theme: Object})`
175 | display: flex;
176 | flex-direction: column;
177 | display: none;
178 | flex-grow: 1;
179 | flex-basis: 0%;
180 | ${(props) => props.theme.show && `
181 | display: block;
182 | `}
183 | `;
184 |
185 | export const ViewportContainerBox = styled('div', {theme: Object})`
186 | ${ViewportContainerBoxAndPreviewContainer}
187 | overflow-x: hidden;
188 | `;
189 |
190 | export const PreviewContainer = styled('div', {theme: Object})`
191 | ${ViewportContainerBoxAndPreviewContainer}
192 | overflow-y: auto;
193 | `;
194 |
195 | export const ModalTitleContainer = styled.div`
196 | display: flex;
197 | flex-direction: row;
198 | justify-content: space-between;
199 | height: 45px;
200 | padding: 0 15px;
201 | border-bottom: 1px solid #ddd;
202 | `;
203 |
204 | export const ModalTitle = styled.div`
205 | display: flex;
206 | align-items: center;
207 | font-size: 18px;
208 | color: #666;
209 | font-weight: bold;
210 | `;
211 |
212 | export const ModalTitleClose = styled.div`
213 | display: flex;
214 | align-items: center;
215 | fill: #999;
216 | cursor: pointer;
217 | &:hover {
218 | fill: #333;
219 | }
220 | `;
221 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-display/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
307 |
308 |
311 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-display/icon.js:
--------------------------------------------------------------------------------
1 | export const IconBlock = (h, size = 20) => (
2 |
12 | );
13 |
14 | export const IconInlineBlock = (h, size = 20) => (
15 |
30 | );
31 | export const IconInline = (h, size = 20) => (
32 |
38 | );
39 |
40 | export const IconFlex = (h, size = 20) => (
41 |
79 | );
80 |
81 | export const IconNone = (h, size = 20) => (
82 |
87 | );
88 |
89 | export const IconflexDirectionEnd = (h, className, size = 20) => (
90 |
95 | );
96 |
97 | export const IconflexDirectionCenter = (h, className, size = 20) => (
98 |
104 | );
105 | export const IconflexSpaceBetween = (h, className, size = 20) => (
106 |
120 | );
121 | export const IconflexSpaceAround = (h, className, size = 20) => (
122 |
136 | );
137 | export const IconflexAlignStart = (h, className, size = 20) => (
138 |
143 | );
144 | export const IconflexAlignCenter = (h, className, size = 20) => (
145 |
151 | );
152 | export const IconflexAlignStretch = (h, className, size = 20) => (
153 |
159 | );
160 | export const IconflexBaseline = (h, className, size = 20) => (
161 |
188 | );
189 |
--------------------------------------------------------------------------------
/lib/plugins/main-tool-editor-type-box-editor/box-editor/index.vue:
--------------------------------------------------------------------------------
1 |
427 |
428 |
431 |
--------------------------------------------------------------------------------
/lib/store/editor/viewport.js:
--------------------------------------------------------------------------------
1 | import * as Sortable from 'sortablejs';
2 | import * as _ from 'lodash';
3 | // import eventbus from '../../../views/editor/service/eventbus';
4 |
5 | let common = {
6 | createNewInstanceKey () {
7 | return _.uniqueId('gaea_instance_');
8 | }
9 | };
10 |
11 | const viewport = {
12 | namespaced: true,
13 | state: {
14 | viewportDOM: null,
15 | rootInstanceKey: null,
16 | instances: new Map(),
17 | instanceDoms: new Map(),
18 | currentDragInfo: null,
19 | currentHoverInstanceKey: null,
20 | currentEditInstanceKey: null,
21 | dragStartDataReady: false,
22 | /**
23 | * 鼠标离开视图区域
24 | */
25 | mouseLeaveViewport: 'mouseLeaveViewport',
26 | /**
27 | * 鼠标移动到某个组件上
28 | */
29 | mouseHoveringComponent: 'mouseHoveringComponent',
30 | /**
31 | * 鼠标移动到面包屑
32 | */
33 | mouseHoveringCrumbItem: 'mouseHoveringCrumbItem',
34 | /**
35 | * 鼠标移动到面包屑
36 | */
37 | mouseLeaveCrumbItem: 'mouseLeaveCrumbItem',
38 | /**
39 | * 视图区域发生更新
40 | */
41 | viewportUpdated: 'viewportUpdated',
42 | /**
43 | * 刷新某个实例
44 | */
45 | instanceUpdate: 'instanceUpdate',
46 | /**
47 | * 页面冲渲染
48 | */
49 | refreshPage: 'refreshPage',
50 | /**
51 | * 触发编辑器回调
52 | */
53 | emitEditorCallback: 'emitEditorCallback',
54 | /**
55 | * 当前点击选组件改变
56 | */
57 | currentEditInstanceChange: 'currentEditInstanceChange'
58 | },
59 | mutations: {
60 | startDrag (state, dragInfo) {
61 | state.currentDragInfo = dragInfo;
62 | },
63 | endDrag (state) {
64 | state.currentDragInfo = null;
65 | },
66 | setViewportDOM (state, viewportDOM) {
67 | state.viewportDOM = viewportDOM;
68 | },
69 | initViewport (state) {
70 | this.commit('viewport/addInstance', {
71 | gaeaKey: 'gaea-container',
72 | parentInstanceKey: null,
73 | indexPosition: null
74 | });
75 | let rootInstance = Array.from(state.instances);
76 | const rootInstanceKey = rootInstance[0][0];
77 | this.commit('viewport/setRootInstanceKey', rootInstanceKey);
78 | },
79 | addInstance (state, params) {
80 | const newInstanceKey = common.createNewInstanceKey();
81 | state
82 | .instances
83 | .set(newInstanceKey, {
84 | gaeaKey: params.gaeaKey,
85 | data: {
86 | props: null
87 | },
88 | slots: {},
89 | parentInstanceKey: params.parentInstanceKey,
90 | preGaeaKey: params.preGaeaKey,
91 | vm: null
92 | });
93 | // 先执行 callback 挂载 vm 再执行 修改属性样式
94 | state.instances = new Map(state.instances);
95 | if (params.cb) {
96 | params
97 | .cb
98 | .call(null, newInstanceKey);
99 | }
100 | // 然后再执行修改样式
101 | if (params.parentInstanceKey !== null) {
102 | const parentInstance = state
103 | .instances
104 | .get(params.parentInstanceKey);
105 | if (!parentInstance.slots[params.slotName]) {
106 | parentInstance.slots[params.slotName] = [];
107 | }
108 | parentInstance
109 | .slots[params.slotName]
110 | .splice(params.indexPosition, 0, newInstanceKey);
111 | // 如果父级和自身都是 gaea-container,且父级是 display:flex,那么子元素默认 flexDirection 与父级元素相反
112 | if (parentInstance.gaeaKey === 'gaea-container' && params.gaeaKey === 'gaea-container') {
113 | // let crrtInstance = state
114 | // .instances
115 | // .get(newInstanceKey);
116 | // if (parentInstance.vm.$el.style.display === 'flex' && crrtInstance.vm.$el.style.display === 'flex') {
117 | // switch (parentInstance.vm.$el.style.flexDirection) {
118 | // case 'column':
119 | // this.commit('viewport/setInstanceProps', {
120 | // vm: crrtInstance.vm,
121 | // key: 'styles',
122 | // value: {
123 | // flexDirection: 'row'
124 | // }
125 | // });
126 | // break;
127 | // case 'row':
128 | // default:
129 | // this.commit('viewport/setInstanceProps', {
130 | // vm: crrtInstance.vm,
131 | // key: 'styles',
132 | // value: {
133 | // flexDirection: 'column'
134 | // }
135 | // });
136 | // break;
137 | // }
138 | // }
139 | }
140 | }
141 | },
142 | setInstanceProps (state, {vm, key, value}) {
143 | if (Object.prototype.toString.call(value) === '[object Object]') {
144 | let obj = _.get(vm.$data, key);
145 | _.set(vm.$data, key, {
146 | ...obj,
147 | ...value
148 | });
149 | } else {
150 | _.set(vm.$data, key, value);
151 | }
152 | vm.$forceUpdate();
153 | },
154 | setRootInstanceKey (state, key) {
155 | state.rootInstanceKey = key;
156 | },
157 | setDomInstance (state, {key, instance}) {
158 | state
159 | .instanceDoms
160 | .set(key, instance);
161 | },
162 | setCurrentHoverInstanceKey (state, instanceKey) {
163 | state.currentHoverInstanceKey = instanceKey;
164 | },
165 | moveInstance (state, {sourceTargetKey, targetParentKey, targetIndex, fromSlotName, toSlotName}) {
166 | const sourceTargetInstance = state
167 | .instances
168 | .get(sourceTargetKey);
169 | const sourceParentInstance = state
170 | .instances
171 | .get(sourceTargetInstance.parentInstanceKey);
172 | const targetParentInstance = state
173 | .instances
174 | .get(targetParentKey);
175 |
176 | if (sourceTargetInstance.parentInstanceKey !== targetParentKey) { // 跨越层级
177 | // 修改拖拽元素的 parentMapUniqueKey
178 | sourceTargetInstance.parentInstanceKey = targetParentKey;
179 |
180 | // 拖拽目标添加 instance
181 | if (!targetParentInstance
182 | .slots[toSlotName]) {
183 | targetParentInstance
184 | .slots[toSlotName] = [];
185 | }
186 | targetParentInstance
187 | .slots[toSlotName]
188 | .splice(targetIndex, 0, sourceTargetKey);
189 |
190 | // 拖拽源删除 instance
191 | sourceParentInstance.slots[fromSlotName] = sourceParentInstance
192 | .slots[fromSlotName]
193 | .filter(childKey => childKey !== sourceTargetKey);
194 | } else { // 同层级
195 | this.commit('viewport/horizontalMoveInstance', {
196 | parentKey: targetParentKey,
197 | beforeIndex: sourceParentInstance
198 | .slots[fromSlotName]
199 | .findIndex(childKey => childKey === sourceTargetKey),
200 | afterIndex: targetIndex
201 | });
202 | }
203 | // 触发 VueX 对map的检查
204 | state.instances = new Map(state.instances);
205 | },
206 | /**
207 | * 同层级拖拽,不需要主动调用,直接调用 moveInstance 即可
208 | */
209 | horizontalMoveInstance (state, {parentKey, beforeIndex, afterIndex, slotName}) {
210 | const parentInstance = state
211 | .instances
212 | .get(parentKey);
213 | if (beforeIndex < afterIndex) {
214 | // 从左到右
215 | for (let index = beforeIndex; index < afterIndex; index++) {
216 | const beforeUniqueKey = parentInstance.slots[slotName][index];
217 | const afterUniqueKey = parentInstance.slots[slotName][index + 1];
218 | parentInstance.slots[slotName][index] = afterUniqueKey;
219 | parentInstance.slots[slotName][index + 1] = beforeUniqueKey;
220 | }
221 | } else {
222 | // 从右到左
223 | for (let index = beforeIndex; index > afterIndex; index--) {
224 | const beforeUniqueKey = parentInstance.slots[slotName][index];
225 | const afterUniqueKey = parentInstance.slots[slotName][index - 1];
226 | parentInstance.slots[slotName][index] = afterUniqueKey;
227 | parentInstance.slots[slotName][index - 1] = beforeUniqueKey;
228 | }
229 | }
230 | // 触发 VueX 对map的检查
231 | state.instances = new Map(state.instances);
232 | },
233 | setDragInfo (state, {mapUniqueKey, index}) {
234 | const newInfo = state.currentDragInfo.info;
235 | newInfo.targetInstanceKey = mapUniqueKey;
236 | newInfo.targetIndex = index;
237 | },
238 | registerOuterDrag (state, dragParentDom) {
239 | // 上次拖拽的位置
240 | let lastDragStartIndex = -1;
241 | Sortable.create(dragParentDom, {
242 | animation: 50,
243 | // 放在一个组里,可以跨组拖拽
244 | group: {
245 | name: 'gaea-container',
246 | pull: 'clone',
247 | put: false
248 | },
249 | sort: false,
250 | delay: 0,
251 | onStart: (event) => {
252 | lastDragStartIndex = event.oldIndex;
253 | if (event.item.dataset.source) {
254 | // this.startDrag({ type: 'combo', dragStartParentElement:
255 | // dragParentDom, dragStartIndex: event.oldIndex , comboInfo: { source:
256 | // event.item.dataset.source } })
257 | } else if (event.item.dataset.gaeaKey) {
258 | state.dragStartDataReady = false;
259 | this.commit('viewport/startDrag', {
260 | type: 'new',
261 | dragStartParentDom: dragParentDom,
262 | dragStartIndex: event.oldIndex,
263 | info: {
264 | gaeaKey: event.item.dataset.gaeaKey,
265 | props: event.item.dataset.props,
266 | preGaeaKey: event.item.dataset.preGaeaKey
267 | }
268 | });
269 |
270 | // 开始拖拽完毕
271 | state.dragStartDataReady = true;
272 | }
273 | },
274 | onEnd: (event) => {
275 | this.commit('viewport/endDrag');
276 | // 因为是 clone 方式, 拖拽成功的话元素会重复, 没成功拖拽会被添加到末尾 所以先移除 clone 的元素(吐槽下, 拖走的才是真的, 留下的才是
277 | // clone 的) 有 parentNode, 说明拖拽完毕还是没有被清除, 说明被拖走了, 因为如果没真正拖动成功, clone 元素会被删除
278 | if (event.clone.parentNode) {
279 | // 有 clone, 说明已经真正拖走了
280 | dragParentDom.removeChild(event.clone);
281 | // 再把真正移过去的弄回来
282 | if (lastDragStartIndex === dragParentDom.childNodes.length) {
283 | // 如果拖拽的是最后一个
284 | dragParentDom.appendChild(event.item);
285 | } else {
286 | // 拖拽的不是最后一个
287 | dragParentDom.insertBefore(event.item, dragParentDom.childNodes[lastDragStartIndex]);
288 | }
289 | } else {
290 | // 没拖走, 只是晃了一下, 不用管了
291 | }
292 | }
293 | });
294 | },
295 | registerInnerDrag (state, {
296 | parentInstanceKey,
297 | dragParentDom,
298 | params,
299 | groupName = 'gaea-container',
300 | onDragAdd
301 | }) {
302 | const instance = state
303 | .instances
304 | .get(parentInstanceKey);
305 | Sortable.create(dragParentDom, {
306 | ...params,
307 | animation: 50,
308 | // 放在一个组里,可以跨组拖拽
309 | group: {
310 | name: groupName,
311 | pull: true,
312 | put: true
313 | },
314 | onStart: (event) => {
315 | let slotName = event.from.dataset.slotName;
316 | this.commit('viewport/startDrag', {
317 | type: 'viewport',
318 | dragStartParentDom: dragParentDom,
319 | dragStartIndex: event.oldIndex,
320 | info: {
321 | instanceKey: instance.slots[slotName][event.oldIndex]
322 | }
323 | });
324 | },
325 | onEnd: (event) => {
326 | this.commit('viewport/endDrag');
327 |
328 | // 在 viewport 中元素拖拽完毕后, 为了防止 outer-move-box 在原来位置留下残影, 先隐藏掉
329 | this.commit('viewport/setCurrentHoverInstanceKey', null);
330 | },
331 | onAdd: (event) => {
332 | // 如果数据还没有 ready,很尴尬,什么都没有发生 此时一定是 new,不用担心 dom 节点脏掉
333 | if (!state.dragStartDataReady) {
334 | return;
335 | }
336 | switch (state.currentDragInfo.type) {
337 | case 'new':
338 | // 是新拖进来的, 不用管, 因为工具栏会把它收回去 为什么不删掉? 因为这个元素不论是不是 clone, 都被移过来了, 不还回去 react 在更新
339 | // dom 时会无法找到
340 | const newInfo = state.currentDragInfo.info;
341 | const slotName = event.to.dataset.slotName;
342 | this.commit('viewport/addInstance', {
343 | gaeaKey: newInfo.gaeaKey,
344 | parentInstanceKey,
345 | indexPosition: event.newIndex,
346 | preGaeaKey: newInfo.preGaeaKey,
347 | slotName,
348 | cb: newInstanceKey => {
349 | if (onDragAdd) {
350 | // e --event parentInstanceKey --父instanceKey gaeaKey -->component type
351 | // newInstanceKey -> new instace Key
352 | onDragAdd.call(this, event, parentInstanceKey, newInfo.gaeaKey, newInstanceKey, slotName);
353 | }
354 | }
355 | });
356 | break;
357 | case 'viewport':
358 | // 设置新增时拖拽源信息
359 | this.commit('viewport/setDragInfo', {
360 | mapUniqueKey: parentInstanceKey,
361 | index: event.newIndex
362 | });
363 | break;
364 |
365 | case 'combo':
366 | // this.addComboComponentBySource(mapUniqueKey,
367 | // this.viewport.currentDragComponentInfo.comboInfo.source, event.newIndex )
368 | // TODO 发布新增组合事件 this.props.viewport.saveOperate({ type: 'addCombo',
369 | // mapUniqueKey, addCombo: { parentMapUniqueKey:
370 | // this.props.mapUniqueKey, index: event.newIndex , componentInfo:
371 | // component } })
372 | break;
373 | }
374 | },
375 | onUpdate: (event) => {
376 | let slotName = event.from.dataset.slotName;
377 | // 同一个父级下子元素交换父级 // 取消 srotable 对 dom 的修改, 让元素回到最初的位置即可复原
378 | this.commit('viewport/horizontalMoveInstance', {
379 | parentKey: parentInstanceKey,
380 | beforeIndex: event.oldIndex,
381 | afterIndex: event.newIndex,
382 | slotName
383 | });
384 | },
385 | onRemove: (event) => {
386 | // onEnd 在其之后执行,会清除拖拽目标的信息 减少了一个子元素,一定是发生在 viewport 区域元素发生跨父级拖拽时
387 | let toSlotName = event.to.dataset.slotName;
388 | let fromSlotName = event.from.dataset.slotName;
389 | const dragTargetKey = state
390 | .instances
391 | .get(parentInstanceKey)
392 | .slots[toSlotName][state.currentDragInfo.dragStartIndex];
393 | const dragViewportInfo = state.currentDragInfo.info;
394 | this.commit('viewport/moveInstance', {
395 | sourceTargetKey: dragTargetKey,
396 | targetParentKey: dragViewportInfo.targetInstanceKey,
397 | targetIndex: dragViewportInfo.targetIndex,
398 | fromSlotName,
399 | toSlotName
400 | });
401 | }
402 | });
403 | },
404 | getInstancePath (state, {instanceKey, cb}) {
405 | const finderPath = [state.currentEditInstanceKey];
406 | if (state.currentEditInstanceKey === null) {
407 | if (cb) {
408 | let result = [];
409 | cb(result);
410 | }
411 | return;
412 | }
413 | let instance = state
414 | .instances
415 | .get(state.currentEditInstanceKey);
416 | // 如果已经是根元素, 直接返回空数组
417 | if (instance.parentInstanceKey === null) {
418 | if (cb) {
419 | let result = [state.rootInstanceKey];
420 | cb(result);
421 | }
422 | return;
423 | }
424 |
425 | // 直到父级是根元素为止
426 | while (state.instances.get(instance.parentInstanceKey).parentInstanceKey !== null) {
427 | finderPath.unshift(instance.parentInstanceKey);
428 | instance = state
429 | .instances
430 | .get(instance.parentInstanceKey);
431 | }
432 | finderPath.unshift(state.rootInstanceKey);
433 | if (cb) {
434 | cb(finderPath);
435 | }
436 | },
437 | setCurrentEditInstanceKey (state, instanceKey) {
438 | // 如果和当前正在编辑元素相同,不做操作
439 | if (state.currentEditInstanceKey === instanceKey) {
440 | return;
441 | }
442 | // 修改 mapUniqueKey
443 | state.currentEditInstanceKey = instanceKey;
444 | }
445 | },
446 | actions: {}
447 | };
448 |
449 | export default viewport;
450 |
--------------------------------------------------------------------------------