├── 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 | 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 | 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 | 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 | 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 | 6 | 7 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /lib/components/card/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 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 | 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 | 4 | 5 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /lib/page/viewport/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 16 | 17 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /lib/plugins/drag-menu-button/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /lib/plugins/main-tool-editor-type-box-select/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 21 | 22 | 49 | 50 | 58 | -------------------------------------------------------------------------------- /lib/plugins/main-tool-editor-type-box-color/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 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 | 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 | 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 | 4 | 5 | 81 | 82 | 91 | -------------------------------------------------------------------------------- /lib/plugins/main-tool-editor-type-box-number/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 11 | 12 | 105 | 106 | 129 | -------------------------------------------------------------------------------- /lib/page/index/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 3 | 11 | 12 | ); 13 | 14 | export const IconInlineBlock = (h, size = 20) => ( 15 | 16 | 25 | 29 | 30 | ); 31 | export const IconInline = (h, size = 20) => ( 32 | 33 | 37 | 38 | ); 39 | 40 | export const IconFlex = (h, size = 20) => ( 41 | 42 | 51 | 60 | 69 | 78 | 79 | ); 80 | 81 | export const IconNone = (h, size = 20) => ( 82 | 83 | 86 | 87 | ); 88 | 89 | export const IconflexDirectionEnd = (h, className, size = 20) => ( 90 | 91 | 92 | 93 | 94 | 95 | ); 96 | 97 | export const IconflexDirectionCenter = (h, className, size = 20) => ( 98 | 99 | 100 | 101 | 102 | 103 | 104 | ); 105 | export const IconflexSpaceBetween = (h, className, size = 20) => ( 106 | 107 | 110 | 113 | 116 | 119 | 120 | ); 121 | export const IconflexSpaceAround = (h, className, size = 20) => ( 122 | 123 | 126 | 129 | 132 | 135 | 136 | ); 137 | export const IconflexAlignStart = (h, className, size = 20) => ( 138 | 139 | 140 | 141 | 142 | 143 | ); 144 | export const IconflexAlignCenter = (h, className, size = 20) => ( 145 | 146 | 147 | 148 | 149 | 150 | 151 | ); 152 | export const IconflexAlignStretch = (h, className, size = 20) => ( 153 | 154 | 155 | 156 | 157 | 158 | 159 | ); 160 | export const IconflexBaseline = (h, className, size = 20) => ( 161 | 162 | 165 | 168 | 176 | 184 | 187 | 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 | --------------------------------------------------------------------------------