Browse Source

init

master
1549469775 4 years ago
commit
bb4a06b526
  1. 2
      .browserslistrc
  2. 18
      .eslintrc.js
  3. 21
      .gitignore
  4. 0
      .gitkeep
  5. 12
      babel.config.js
  6. 16
      build/config.js
  7. 45
      build/webpack.dll.config.js
  8. 13738
      package-lock.json
  9. 49
      package.json
  10. 5
      postcss.config.js
  11. BIN
      public/favicon.ico
  12. 47
      public/index.html
  13. 1
      public/vendor/ant-manifest.json
  14. 289
      public/vendor/ant.8e3712ed.dll.js
  15. 1
      public/vendor/css-manifest.json
  16. 23392
      public/vendor/css.8e3712ed.dll.css
  17. 1
      public/vendor/css.8e3712ed.dll.js
  18. 1
      public/vendor/plugins-manifest.json
  19. 9
      public/vendor/plugins.8e3712ed.dll.js
  20. 1
      public/vendor/vue-manifest.json
  21. 7
      public/vendor/vue.8e3712ed.dll.js
  22. 1
      public/vendor/vuerouter-manifest.json
  23. 1
      public/vendor/vuerouter.8e3712ed.dll.js
  24. 1
      public/vendor/vuex-manifest.json
  25. 7
      public/vendor/vuex.8e3712ed.dll.js
  26. 14
      readme.md
  27. 22
      scripts/zip.js
  28. 12
      src/App.vue
  29. 0
      src/api/index.js
  30. 56
      src/api/request/index.js
  31. 33
      src/api/request/interceptors.js
  32. BIN
      src/assets/logo.png
  33. 3
      src/assets/style/global.scss
  34. 0
      src/assets/style/utils.scss
  35. 23
      src/components/base/index.js
  36. 26
      src/components/toast/index.js
  37. 29
      src/components/toast/toast.vue
  38. 3
      src/config/development.js
  39. 19
      src/config/index.js
  40. 3
      src/config/production.js
  41. 130
      src/data/component-list.js
  42. 35
      src/main.js
  43. 32
      src/router/.invoke/router.js
  44. 33
      src/router/index.js
  45. 33
      src/store/index.js
  46. 41
      src/store/modules/components.js
  47. 30
      src/store/modules/user.js
  48. 10
      src/store/root.js
  49. 54
      src/ui/detail/detail.vue
  50. 184
      src/ui/editor/MakeLine.vue
  51. 24
      src/ui/editor/Shape.vue
  52. 31
      src/ui/editor/customComponent.vue
  53. 146
      src/ui/editor/editor.vue
  54. 5
      src/ui/header/header.vue
  55. 37
      src/ui/panel/panel.vue
  56. 7
      src/vendor/ant.js
  57. 5
      src/views/NotFound.vue
  58. 19
      src/views/about/index.vue
  59. 2
      src/views/about/meta.yml
  60. 49
      src/views/home/index.vue
  61. 123
      vue.config.js

2
.browserslistrc

@ -0,0 +1,2 @@
> 1%
last 2 versions

18
.eslintrc.js

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unused-vars': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: 'babel-eslint'
}
}

21
.gitignore

@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

0
.gitkeep

12
babel.config.js

@ -0,0 +1,12 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
// ['import', {
// libraryName: 'ant-design-vue',
// libraryDirectory: 'es',
// style: true
// }, 'vant'],
]
}

16
build/config.js

@ -0,0 +1,16 @@
const path = require("path");
let config = {
modules_root: path.join(__dirname, '../public'),
modules_dir: './vendor',
modules: {
vue: ['vue'],
ant: ['ant-design-vue'],
css: ['ant-design-vue/dist/antd.css'],
vuex: ['vuex'],
vuerouter: ['vue-router'],
plugins: ['lodash', 'axios', 'vuex-persistedstate'],
}
};
module.exports = config;

45
build/webpack.dll.config.js

@ -0,0 +1,45 @@
var path = require("path");
var webpack = require("webpack");
const $config = require("./config.js")
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const root = path.resolve($config.modules_root, $config.modules_dir);
module.exports = {
mode: "production",
// 你想要打包的模块的数组
entry: $config.modules,
module: {
rules: [{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: true,
},
},
'css-loader',
]
}]
},
output: {
path: root, // 打包后文件输出的位置
filename: '[name].[hash:8].dll.js',
library: '[name]_library'
// vendor.dll.js中暴露出的全局变量名。
// 主要是给DllPlugin中的name使用,
// 故这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// all options are optional
filename: '[name].[hash:8].dll.css',
chunkFilename: '[id].css',
ignoreOrder: false, // Enable to remove warnings about conflicting order
}),
new webpack.DllPlugin({
path: path.join(root, '[name]-manifest.json'),
name: '[name]_library',
context: process.cwd()
}),
]
};

13738
package-lock.json

File diff suppressed because it is too large

49
package.json

@ -0,0 +1,49 @@
{
"name": "VisiblePlatform",
"version": "0.1.0",
"private": true,
"scripts": {
"dll": "webpack --config ./build/webpack.dll.config.js",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"zip": "node ./scripts/zip.js",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.3.2",
"vue": "^2.6.10",
"vue-router": "^3.1.3",
"vuex": "^3.0.1",
"lodash": "^4.17.15",
"ant-design-vue": "^1.3.8",
"babel-plugin-import": "^1.13.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-vuex": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"normalize.css": "^8.0.1",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.0",
"vue-template-compiler": "^2.6.10",
"webpack-bundle-analyzer": "^3.4.1",
"compression-webpack-plugin": "^3.0.0",
"webpack-cli": "^3.3.10",
"add-asset-html-webpack-plugin": "^3.1.3",
"compressing": "^1.5.0",
"vue-router-invoke-webpack-plugin": "^0.4.3",
"vuex-persistedstate": "^2.7.0",
"axios": "^0.19.0",
"mini-css-extract-plugin": "^0.9.0",
"vue-cli-plugin-qrcode": "*",
"qrcode-terminal": "^0.12.0",
"chalk": "^2.4.2",
"internal-ip": "^4.2.0"
},
"externals": {}
}

5
postcss.config.js

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
}
}

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

47
public/index.html

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 是否启动webapp功能,会删除默认的苹果工具栏和菜单栏。 -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- 这个主要是根据实际的页面设计的主体色为搭配来进行设置。 -->
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- 忽略页面中的数字识别为电话号码,email识别 -->
<meta name="format-detection" content="telephone=no, email=no" />
<!-- 启用360浏览器的极速模式(webkit) -->
<meta name="renderer" content="webkit">
<!-- 避免IE使用兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" href="favicon.ico">
<title>可视化页面布局</title>
</head>
<body>
<noscript>
<strong>We're sorry but VisiblePlatform doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

1
public/vendor/ant-manifest.json

File diff suppressed because one or more lines are too long

289
public/vendor/ant.8e3712ed.dll.js

File diff suppressed because one or more lines are too long

1
public/vendor/css-manifest.json

@ -0,0 +1 @@
{"name":"css_library","content":{"./node_modules/ant-design-vue/dist/antd.css":{"id":970,"buildMeta":{"providedExports":true}}}}

23392
public/vendor/css.8e3712ed.dll.css

File diff suppressed because it is too large

1
public/vendor/css.8e3712ed.dll.js

@ -0,0 +1 @@
var css_library=function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}return t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=969)}({969:function(e,r,t){e.exports=t},970:function(e,r,t){}});

1
public/vendor/plugins-manifest.json

@ -0,0 +1 @@
{"name":"plugins_library","content":{"./node_modules/webpack/buildin/global.js":{"id":51,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/utils.js":{"id":59,"buildMeta":{"providedExports":true}},"./node_modules/process/browser.js":{"id":95,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/module.js":{"id":156,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/bind.js":{"id":607,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/buildURL.js":{"id":608,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/isCancel.js":{"id":609,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/defaults.js":{"id":610,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/adapters/xhr.js":{"id":611,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/createError.js":{"id":612,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/mergeConfig.js":{"id":613,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/Cancel.js":{"id":614,"buildMeta":{"providedExports":true}},"./node_modules/lodash/lodash.js":{"id":976,"buildMeta":{"providedExports":true}},"./node_modules/axios/index.js":{"id":977,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/axios.js":{"id":978,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/Axios.js":{"id":979,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/InterceptorManager.js":{"id":980,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/dispatchRequest.js":{"id":981,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/transformData.js":{"id":982,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/normalizeHeaderName.js":{"id":983,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/settle.js":{"id":984,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/enhanceError.js":{"id":985,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/core/buildFullPath.js":{"id":986,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isAbsoluteURL.js":{"id":987,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/combineURLs.js":{"id":988,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/parseHeaders.js":{"id":989,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/isURLSameOrigin.js":{"id":990,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/cookies.js":{"id":991,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/cancel/CancelToken.js":{"id":992,"buildMeta":{"providedExports":true}},"./node_modules/axios/lib/helpers/spread.js":{"id":993,"buildMeta":{"providedExports":true}},"./node_modules/vuex-persistedstate/dist/vuex-persistedstate.es.js":{"id":994,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}}}}

9
public/vendor/plugins.8e3712ed.dll.js

File diff suppressed because one or more lines are too long

1
public/vendor/vue-manifest.json

@ -0,0 +1 @@
{"name":"vue_library","content":{"./node_modules/vue/dist/vue.runtime.esm.js":{"id":21,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}},"./node_modules/webpack/buildin/global.js":{"id":51,"buildMeta":{"providedExports":true}},"./node_modules/process/browser.js":{"id":95,"buildMeta":{"providedExports":true}},"./node_modules/timers-browserify/main.js":{"id":299,"buildMeta":{"providedExports":true}},"./node_modules/setimmediate/setImmediate.js":{"id":300,"buildMeta":{"providedExports":true}}}}

7
public/vendor/vue.8e3712ed.dll.js

File diff suppressed because one or more lines are too long

1
public/vendor/vuerouter-manifest.json

@ -0,0 +1 @@
{"name":"vuerouter_library","content":{"./node_modules/vue-router/dist/vue-router.esm.js":{"id":974,"buildMeta":{"exportsType":"namespace","providedExports":["default"]}}}}

1
public/vendor/vuerouter.8e3712ed.dll.js

File diff suppressed because one or more lines are too long

1
public/vendor/vuex-manifest.json

@ -0,0 +1 @@
{"name":"vuex_library","content":{"./node_modules/webpack/buildin/global.js":{"id":51,"buildMeta":{"providedExports":true}},"./node_modules/vuex/dist/vuex.esm.js":{"id":972,"buildMeta":{"exportsType":"namespace","providedExports":["default","Store","createLogger","createNamespacedHelpers","install","mapActions","mapGetters","mapMutations","mapState"]}}}}

7
public/vendor/vuex.8e3712ed.dll.js

File diff suppressed because one or more lines are too long

14
readme.md

@ -0,0 +1,14 @@
* 克隆拖拽
* 拖拽位置不变
* 快速拖动不会失去焦点
* 拖动节点时不触发点击事件
## 预想
* 框选部分组件
* 导出代码
* 删除组件
* 组件插槽以及布局
https://woai3c.github.io/visual-drag-demo
https://github.com/woai3c/visual-drag-demo

22
scripts/zip.js

@ -0,0 +1,22 @@
const compressing = require('compressing');
var path = require('path')
//格式化data时间
function timeStamp2String(time) {
var datetime = new Date();
datetime.setTime(time);
var year = datetime.getFullYear();
var month = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
var hour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours();
var minute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes();
var second = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds();
return year + "-" + month + "-" + date + "_" + hour + "-" + minute + "-" + second;
}
let name = `dist_${timeStamp2String(new Date())}.zip`;
compressing.zip.compressDir('dist', name)
.then(() => {
console.log('success');
})
.catch(err => {
console.error(err);
});

12
src/App.vue

@ -0,0 +1,12 @@
<template>
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.alive" />
</keep-alive>
<router-view v-if="!$route.meta.alive" />
</div>
</template>
<style lang="scss">
@import "~@/assets/style/global.scss";
</style>

0
src/api/index.js

56
src/api/request/index.js

@ -0,0 +1,56 @@
import axios from "axios";
import qs from "qs";
import toast from "@/components/toast";
import config from "@/config";
const checkERR = (err, type) => {
console.log(err);
switch (type) {
case 0:
// 来自执行的错误
break;
case 1:
//来自于接口的错误
break;
}
};
// 静默执行
export function request_(type = "GET", url, data = {},contentType=0,sendToken = true) {
var __data = {};
if (type.toUpperCase() == "GET") {
__data.params = data;
}
if (type.toUpperCase() == "POST") {
__data.data = data; // qs.stringify(data)
}
return new Promise((resolve, reject) => {
var reg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)/;
var realUrl = url;
if (!reg.test(url)) {
realUrl = config.BASEURL + url;
}
axios({
url: realUrl,
method: type,
...__data,
headers:{
$type: contentType,
$token: sendToken,
},
responseType: "json",
})
.then((res) => {
if (res.status == 200 && res.data && res.data.error == 0) {
resolve(res.data);
} else {
checkERR(res, 1);
reject(res);
}
})
.catch((err) => {
checkERR(err, 0);
reject(err);
});
});
}

33
src/api/request/interceptors.js

@ -0,0 +1,33 @@
import axios from "axios";
import store from "@/store";
axios.interceptors.request.use(
(config) => {
if (!config.headers["Content-Type"]) {
config.headers = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
};
}
// if (store.getters.token) {
// config.headers = {
// 'Authorization': store.getters.token
// }
// }
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
(res) => {
//对错误代码做处理
return res;
},
(error) => {
return Promise.reject(error);
}
);

BIN
src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

3
src/assets/style/global.scss

@ -0,0 +1,3 @@
html,body,#app{
height: 100%;
}

0
src/assets/style/utils.scss

23
src/components/base/index.js

@ -0,0 +1,23 @@
// 引入vue 及 lodash
import Vue from 'vue'
import {
upperFirst
} from 'lodash' // 首字线大写
import {
camelCase
} from 'lodash' // 驼峰命名大法
// 把 /component/base/ 下的所有 vue 组件 require 进来
// path: 要引入的组件所在相对路径(相对于当前文件)
// deep: 是否检索子文件夹
// matchFile: 匹配的文件名称
// require.context(path, deep, matchFile)
const requireComponent = require.context('./', true, /base-[\w-]+\.vue$/)
// 遍历 require 进来的组件并注册
requireComponent.keys().forEach((fileName) => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(camelCase(fileName.replace(/^\.\/_/, '').replace(/\.\w+$/, '')))
// 全局注册组件
Vue.component(componentName, componentConfig.default || componentConfig)
})

26
src/components/toast/index.js

@ -0,0 +1,26 @@
import Toast from "./toast.vue";
import Vue from "vue";
function createToast(text, duration = 2000) {
let VueToast = Vue.extend({
render(h) {
let props = {
text: text,
};
return h(Toast, {
props,
});
},
});
let newToast = new VueToast();
let vm = newToast.$mount();
document.body.append(vm.$el);
setTimeout(() => {
vm.$el.remove();
vm = null;
}, duration);
}
createToast.install = function(Vue, defaultOptions = {}) {
Vue.prototype.$toast = createToast;
};
export default createToast;

29
src/components/toast/toast.vue

@ -0,0 +1,29 @@
<template>
<div class="base-toast">
{{ text }}
</div>
</template>
<script>
export default {
props: ['text'],
methods: {
show() {}
}
}
</script>
<style lang="scss" scoped>
.base-toast {
position: fixed;
max-width: 60%;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.733);
color: white;
padding: 10px;
border-radius: 10px;
z-index: 999;
}
</style>

3
src/config/development.js

@ -0,0 +1,3 @@
module.exports = {
BASEURL: ''
}

19
src/config/index.js

@ -0,0 +1,19 @@
let isProd = process.env.NODE_ENV == "production";
let config = {}
if (isProd) {
const prodConfig = require("./production");
config = {
...config,
...prodConfig
}
}
if (!isProd) {
const devConfig = require("./development");
config = {
...config,
...devConfig
}
}
export default config;

3
src/config/production.js

@ -0,0 +1,3 @@
module.exports = {
BASEURL: ''
}

130
src/data/component-list.js

@ -0,0 +1,130 @@
let list = [
{
name: "a-button",
describe: "按钮",
type: "component",
slot: [{ type: "text", text: "按钮" }],
props: {},
id: "customID",
style: {
left: 0,
top: 0,
width: 80,
height: 32,
},
},
{
name: "a-avatar",
describe: "图片",
type: "component",
slot: [],
props: {
icon: "user",
},
id: "badgeID",
style: {
left: 0,
top: 0,
width: 32,
height: 32,
},
},
{
name: "a-input",
describe: "输入框",
type: "component",
slot: [],
props: {},
id: "inputID",
style: {
height: 32,
width: 120,
left: 0,
top: 0,
},
},
{
name: "a-tooltip",
describe: "tooltip",
type: "component",
slot: [
{
name: "a-button",
type: "component",
slot: [{ type: "text", text: "阿萨大" }],
props: {},
style: {
left: 0,
top: 0,
width: 80,
height: 32,
},
},
],
props: { placement: "topLeft", title: "Prompt Text" },
id: "tooltipID",
style: {
height: 32,
width: 80,
left: 0,
top: 0,
},
},
{
name: "a-cascader",
describe: "级联选择",
type: "component",
slot: [],
props: {
options: [
{
value: "zhejiang",
label: "Zhejiang",
children: [
{
value: "hangzhou",
label: "Hangzhou",
children: [
{
value: "xihu",
label: "West Lake",
},
],
},
],
},
{
value: "jiangsu",
label: "Jiangsu",
children: [
{
value: "nanjing",
label: "Nanjing",
children: [
{
value: "zhonghuamen",
label: "Zhong Hua Men",
},
],
},
],
},
],
placeholder: "Please select",
},
methods: {
onChange(value) {
console.log(value);
},
},
id: "cascaderID",
style: {
width: 120,
height: 32,
left: 0,
top: 0,
},
},
];
export default list;

35
src/main.js

@ -0,0 +1,35 @@
import "normalize.css"
import Vue from 'vue'
import App from '@/App.vue'
import router from '@/router';
import store from '@/store'
import '@/components/base'
import toast from '@/components/toast'
import '@/vendor/ant'
import {cloneDeep} from "lodash"
//http://kazupon.github.io/vue-i18n/api/#constructor-options
Vue.prototype.$event = new Vue()
Vue.config.productionTip = process.env.NODE_ENV === 'production' ? true : false;
Vue.use(toast);
Vue.prototype.$computeStyle= function(style){
let myStyle = cloneDeep(style);
let attr = ["left", "width", "height", "top", "right", "bottom"];
let attrStr = ["backgroundColor", "color","position",'zIndex'];
Object.keys(style).forEach((v) => {
if (attr.indexOf(v) != -1 && typeof style[v] == "number") {
myStyle[v] = style[v] + "px";
}
if (attrStr.indexOf(v) != -1) {
myStyle[v] = style[v];
}
});
return myStyle
},
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

32
src/router/.invoke/router.js

@ -0,0 +1,32 @@
import Vue from 'vue';
import Router from 'vue-router';;
Vue.use(Router);
export const routes = [{
component: () => import('@/views/about/index.vue'),
name: 'about',
meta: {
alive: true,
},
path: '/about',
},
{
component: () => import('@/views/home/index.vue'),
name: 'home',
path: '/home',
},
{
path: '/',
redirect: '/home'
},
{
name: 'notFound',
path: '*',
component: () => import('@/views/NotFound.vue')
},
];
const router = new Router({
mode: 'hash',
routes,
});
export default router;

33
src/router/index.js

@ -0,0 +1,33 @@
// import Vue from 'vue'
// import VueRouter from 'vue-router'
// import Home from '../views/home'
// Vue.use(VueRouter)
// const routes = [{
// path: '/home',
// alias:'/',
// name: 'home',
// meta:{alive:true},
// component: Home
// },{
// path: '/about',
// name: 'about',
// meta:{alive:true},
// component: ()=>import('@/views/about')
// }
// ]
// const router = new VueRouter({
// mode: 'history',
// base: process.env.BASE_URL,
// routes
// })
// export default router
import router from './.invoke/router';
router.beforeEach((to, from, next) => {
next();
})
export default router;
// https://zhuanlan.zhihu.com/p/63079674

33
src/store/index.js

@ -0,0 +1,33 @@
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import root from './root'
Vue.use(Vuex);
const modules = {};
const local = [];
const requireComponent = require.context('./modules/', true, /^(.*?).js$/);
// 遍历 require 进来的组件并注册
requireComponent.keys().forEach((fileName) => {
const key = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '');
const componentConfig = requireComponent(fileName)
const module = componentConfig.default || componentConfig;
modules[key] = module;
if (module.local == true) {
local.push(key);
}
if (Array.isArray(module.local) && module.local.length > 0) {
module.local.forEach(v => {
local.push(key + '.' + v);
})
}
});
const vuexStore = new Vuex.Store({
modules,
...root,
plugins: [createPersistedState({
paths: local
})]
});
export default vuexStore

41
src/store/modules/components.js

@ -0,0 +1,41 @@
const state = {
components: [],
};
const getters = {
components(state) {
return state.components;
},
};
const mutations = {
addComponent(state, payload) {
state.components.push(payload);
},
editComponent(state, payload, index) {
state.components.splice(index, 1, payload);
},
replaceComponents(state,payload){
state.components = payload
},
};
const actions = {
addComponent({ commit }, payload) {
commit("addComponent", payload);
},
editComponent({ commit }, payload, index) {
commit("editComponent", payload, index);
},
replaceComponents({ commit }, payload) {
commit("replaceComponents", payload);
},
};
export default {
namespaced: true,
local: false,
state,
getters,
mutations,
actions,
};

30
src/store/modules/user.js

@ -0,0 +1,30 @@
const state = {
token: "",
userinfo: {}
}
const getters = {
token(state) {
return state.token
},
userinfo(state) {
return state.userinfo
},
}
const mutations = {
}
const actions = {
}
export default {
namespaced: true,
local: true,
state,
getters,
mutations,
actions
}

10
src/store/root.js

@ -0,0 +1,10 @@
export default {
state: {},
getters: {
components(state){
return state.components.components
}
},
mutations: {},
actions: {}
}

54
src/ui/detail/detail.vue

@ -0,0 +1,54 @@
<template>
<div class="detail">
<div v-if="currentDetail">
<a-input v-model.number="currentDetail.style.left" @blur="blur" />
<a-input v-model.number="currentDetail.style.top" @blur="blur" />
</div>
{{ currentDetail }}--{{ currentIndex }}
</div>
</template>
<script>
import {cloneDeep} from "lodash"
export default {
data() {
return {
currentDetail: null,
currentIndex: -1,
};
},
created() {
//
this.$event.$on("@editor:publish:activeOne", (item, index) => {
this.currentDetail = cloneDeep(item);
this.currentIndex = index;
});
//
this.$event.$on("@editor:notice:update", (item, index) => {
//
this.currentDetail = cloneDeep(item);
this.currentIndex = index;
});
},
beforeDestroy(){
this.$event.$off("@editor:publish:activeOne")
this.$event.$off("@editor:notice:update")
},
methods: {
blur() {
console.log(this.currentDetail,this.currentIndex);
this.$event.$emit("@editor:publish:updateOne",this.currentDetail,this.currentIndex)
},
},
};
</script>
<style lang="scss" scoped>
.detail {
height: 100%;
width: 200px;
border-left: 1px solid blue;
border-top: 1px solid blue;
padding: 10px;
}
</style>

184
src/ui/editor/MakeLine.vue

@ -0,0 +1,184 @@
<template>
<div class="makeline">
<slot></slot>
<div
v-for="(item, index) in lineTop"
:key="index"
:style="$computeStyle(item)"
></div>
<div
v-for="(item, index) in lineLeft"
:key="index"
:style="$computeStyle(item)"
></div>
</div>
</template>
<script>
import { cloneDeep } from "lodash";
let value = 4;
let lines = [];
let curIndex = 0;
let temp1 = {
id: "",
position: "absolute",
left: 0,
right: 0,
top: 0,
height: 1,
backgroundColor: "red",
};
let temp2 = {
id: "",
position: "absolute",
left: 0,
bottom: 0,
top: 0,
width: 1,
backgroundColor: "red",
};
export default {
data() {
return {
lineLeft: [],
lineTop: [],
};
},
methods: {
removeAll(){
this.lineLeft = [];
this.lineTop = [];
},
listenerChange(list, index,cb) {
const activeStyle = list[index].style;
list.forEach((item, jndex) => {
if (jndex !== index) {
let curStyle = item.style;
let curBottom = curStyle.top+curStyle.height
let curRight = curStyle.left+curStyle.width
let actRight = activeStyle.left+activeStyle.width
let actBottom = activeStyle.top+activeStyle.height
this.removeLine(curStyle.top, curStyle.left);
this.removeLine(curBottom, curRight);
// TODO
// --
if (Math.abs(activeStyle.top - curStyle.top) <= value) {
activeStyle.top = curStyle.top;
this.makeTop2Bottom(curStyle.top);
cb&&cb(list[index],index)
}
// --
if (Math.abs(actBottom - curBottom) <= value) {
activeStyle.top = curBottom - activeStyle.height;
this.makeTop2Bottom(curBottom);
cb&&cb(list[index],index)
}
// --
if (Math.abs(activeStyle.top - curBottom) <= value) {
activeStyle.top = curBottom;
this.makeTop2Bottom(curBottom);
cb&&cb(list[index],index)
}
// --
if (Math.abs(activeStyle.left - curStyle.left) <= value) {
activeStyle.left = curStyle.left;
this.makeLeft2Right(curStyle.left);
cb&&cb(list[index],index)
}
// --
if (Math.abs(activeStyle.left - curRight) <= value) {
activeStyle.left = curRight
this.makeLeft2Right(curRight);
cb&&cb(list[index],index)
}
// --
if (Math.abs(actRight - curRight) <= value) {
activeStyle.left = curRight - activeStyle.width;
this.makeLeft2Right(curRight);
cb&&cb(list[index],index)
}
// --
if (Math.abs(actBottom - curStyle.top) <= value) {
activeStyle.top = curStyle.top - activeStyle.height;
this.makeTop2Bottom(curStyle.top);
cb&&cb(list[index],index)
}
// --
if (Math.abs(actRight - curStyle.left) <= value) {
activeStyle.left = curStyle.left - activeStyle.width;
this.makeLeft2Right(curStyle.left);
cb&&cb(list[index],index)
}
}
});
},
removeLine(top, left) {
let isHave = -1;
this.lineTop.forEach((line, index) => {
if (line.top == top) {
isHave = index;
}
});
if (isHave != -1) {
this.lineTop.splice(isHave, 1);
}
let isHaveLeft = -1;
this.lineLeft.forEach((line, index) => {
if (line.left == left) {
isHaveLeft = index;
}
});
if (isHaveLeft != -1) {
this.lineLeft.splice(isHaveLeft, 1);
}
},
// 线
makeTop2Bottom(top) {
const line = cloneDeep(temp1);
curIndex++;
line.top = top;
line.id = curIndex;
let isHave = false;
this.lineTop.forEach((line) => {
if (Math.abs(line.top - top) <= value) {
isHave = true;
}
});
if (!isHave) {
this.lineTop.push(line);
}
},
// 线
makeLeft2Right(left) {
const line = cloneDeep(temp2);
curIndex++;
line.id = curIndex;
line.left = left;
let isHave = false;
this.lineLeft.forEach((line) => {
if (Math.abs(line.left - left) <= value) {
isHave = true;
}
});
if (!isHave) {
this.lineLeft.push(line);
}
},
},
};
</script>
<style lang="scss" scoped>
.makeline {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
</style>

24
src/ui/editor/Shape.vue

@ -0,0 +1,24 @@
<template>
<div class="shape">
<slot></slot>
</div>
</template>
<style lang="scss" scoped>
.shape {
position: absolute;
transition: none;
&.moving {
z-index: 99;
pointer-events: none;
user-select: none;
}
&.staying {
pointer-events: none;
user-select: none;
}
> .component{
width: 100%;
height: 100%;
}
}
</style>

31
src/ui/editor/customComponent.vue

@ -0,0 +1,31 @@
<template>
<component
class="component"
:is="detail.name"
v-if="detail.type == 'component'"
v-bind="detail.props"
v-on="detail.methods"
>
<template v-for="(child, childIndex) in detail.slot">
<template v-if="child.type == 'text'"> {{ child.text }}</template>
<customComponent v-if="child.type == 'component'" :detail="child" :key="childIndex" />
</template>
</component>
</template>
<script>
export default {
name: "customComponent",
props: {
detail: {
type: Object,
default: () => {},
},
},
};
</script>
<style lang="scss" scoped>
.component {
// width: 100%;
// height: 100%;
}
</style>

146
src/ui/editor/editor.vue

@ -0,0 +1,146 @@
<template>
<div
class="editor"
:class="[isEnter ? 'dragging' : '']"
@dragenter="dragenter"
@dragleave="dragleave"
@dragover="dragover"
@drop="drop"
>
<Shape
v-for="(item, index) in componentList"
ref="shape"
@mousedown.native="mousedown($event, index)"
:class="[
movingIndex == index ? 'moving' : '',
movingIndex != -1 && movingIndex != index ? 'staying' : '',
]"
:key="index"
:style="[$computeStyle(item.style)]"
>
<customComponent :detail="item" :key="index" />
</Shape>
<MakeLine ref="makeline"> </MakeLine>
</div>
</template>
<script>
import componentList from "@/data/component-list";
import { cloneDeep } from "lodash";
import Shape from "./Shape";
import customComponent from "./customComponent";
import MakeLine from "./MakeLine";
export default {
components: {
Shape,
customComponent,
MakeLine,
},
data() {
return {
isEnter: false,
movingIndex: -1,
activeIndex: -1,
componentList: [],
};
},
created () {
//
this.$event.$on("@editor:publish:updateOne",(item,index)=>{
this.componentList.splice(index, 1, item);
//
//
this.$event.$emit("@editor:notice:update",item,index);
})
},
beforeDestroy(){
this.$event.$off("@editor:publish:updateOne")
},
methods: {
mousedown(ev, index) {
let startX = ev.clientX;
let startY = ev.clientY;
let startW = this.$refs.shape[index].$el.offsetLeft;
let startH = this.$refs.shape[index].$el.offsetTop;
this.activeIndex = index
this.$event.$emit("@editor:publish:activeOne",this.componentList[index],index);
let isMoving = false;
document.onmousemove = (ex) => {
if (!isMoving && Math.abs(ex.clientX - startX) > 1) {
isMoving = true;
}
if (isMoving) {
this.$refs.shape[index].$el.onmousemove = null;
//
this.movingIndex = index;
let el = this.componentList[index];
let offsetX = ex.clientX - startX + startW;
let offsetY = ex.clientY - startY + startH;
el.style.left = offsetX;
el.style.top = offsetY;
this.componentList.splice(index, 1, el);
//
this.$event.$emit("@editor:notice:update",el,index);
this.$refs.makeline.listenerChange(this.componentList, index,(item,index)=>{
this.$event.$emit("@editor:notice:update",item,index);
});
}
};
document.onmouseup = (ex) => {
//
this.$refs.makeline.removeAll();
isMoving = false;
this.movingIndex = -1;
document.onmousemove = null;
document.onmouseup = null;
};
},
drag(e) {
console.log(e);
},
dragenter() {
this.isEnter = true;
},
dragleave() {
this.isEnter = false;
},
drop(ev) {
ev.stopPropagation();
try {
this.isEnter = false;
var { id, x, y } = JSON.parse(
decodeURIComponent(ev.dataTransfer.getData("component-transfer"))
);
let offsetX = ev.offsetX - x;
let offsetY = ev.offsetY - y;
let el = cloneDeep(componentList.filter((v) => v.id === id)[0]);
el.style.left = offsetX;
el.style.top = offsetY;
this.componentList.push(el);
} catch (error) {
console.warn(error);
}
},
dragover(ev) {
ev.preventDefault();
},
},
};
</script>
<style lang="scss" scoped>
.editor {
width: 200px;
height: 100%;
border-top: 1px solid blue;
position: relative;
overflow: hidden;
&.dragging {
border-color: red;
* {
pointer-events: none;
user-select: none;
}
}
}
</style>

5
src/ui/header/header.vue

@ -0,0 +1,5 @@
<template>
<header>
header
</header>
</template>

37
src/ui/panel/panel.vue

@ -0,0 +1,37 @@
<template>
<div class="panel">
<a-button v-for="(item,index) in componentList" :key="index" @dragstart="drag($event,item)" @drag="drago" draggable>{{item.describe}}</a-button>
</div>
</template>
<script>
import data from "@/data/component-list"
export default {
data () {
return {
componentList : data
}
},
methods: {
drago(event){
},
drag(event,row){
event.dataTransfer.setData("component-transfer",encodeURIComponent(JSON.stringify({id:row.id,x:event.layerX,y:event.layerY})));
}
}
}
</script>
<style lang="scss" scoped>
.panel{
height: 100%;
width: 200px;
border-right: 1px solid blue;
border-top: 1px solid blue;
padding: 10px;
>*{
margin: 5px 10px;
}
}
</style>

7
src/vendor/ant.js

@ -0,0 +1,7 @@
import Vue from 'vue';
// 通用采用了按需引入
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
console.log(Antd)
Vue.use(Antd);

5
src/views/NotFound.vue

@ -0,0 +1,5 @@
<template>
<div>
404
</div>
</template>

19
src/views/about/index.vue

@ -0,0 +1,19 @@
<template>
<div>
<router-link to="/home" replace>Go to home</router-link>
<input type="text" />
</div>
</template>
<script>
export default {
beforeRouteLeave(to, from, next) {
next();
},
beforeRouteEnter(to, from, next) {
next(vw => {
//
vw.$route.meta.alive = false;
});
}
};
</script>

2
src/views/about/meta.yml

@ -0,0 +1,2 @@
meta:
- alive: true

49
src/views/home/index.vue

@ -0,0 +1,49 @@
<template>
<div class="home">
<x-header></x-header>
<div class="layout">
<panel class="panel"></panel>
<editor class="editor"></editor>
<detail class="detail"></detail>
</div>
</div>
</template>
<script>
import detail from "@/ui/detail/detail";
import editor from "@/ui/editor/editor";
import header from "@/ui/header/header";
import panel from "@/ui/panel/panel";
export default {
name: "home",
components: {
detail,
editor,
panel,
xHeader:header,
},
mounted () {
setTimeout(() => {
this.$toast("哈哈")
}, 200);
}
};
</script>
<style lang="scss" scoped>
.home{
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.layout{
flex: 1;
display: flex;
align-content: center;
height: 100%;
.editor{
flex: 1;
width: 0;
}
}
</style>

123
vue.config.js

@ -0,0 +1,123 @@
const path = require('path')
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const CompressionPlugin = require("compression-webpack-plugin");
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
const VueRouterInvokeWebpackPlugin = require('vue-router-invoke-webpack-plugin');
const $config = require("./build/config")
const root = path.resolve($config.modules_root, $config.modules_dir);
const isProd = () => {
return process.env.NODE_ENV === 'production';
};
let plugins = [];
plugins.push(
// 将 dll 注入到 生成的 html 模板中
new AddAssetHtmlPlugin({
// dll文件位置
filepath: path.resolve(root, '*.js'),
// dll 引用路径
publicPath: $config.modules_dir,
// dll最终输出的目录
outputPath: $config.modules_dir
})
)
plugins.push(
// 将 dll 注入到 生成的 html 模板中
new AddAssetHtmlPlugin({
// dll文件位置
filepath: path.resolve(root, '*.css'),
// dll 引用路径
publicPath: $config.modules_dir,
// dll最终输出的目录
typeOfAsset: 'css',
outputPath: $config.modules_dir
})
)
let modules = Object.keys($config.modules);
modules.forEach(module => {
plugins.push(new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(path.resolve(root, `${module}-manifest.json`))
}), )
})
if (isProd()) {
plugins.push(new BundleAnalyzerPlugin())
plugins.push(new CompressionPlugin({
test: /\.js$|\.html$|\.css/,
threshold: 10240,
deleteOriginalAssets: false
}));
}
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ?
'./' : '/',
assetsDir: 'static',
productionSourceMap: false,
// 以下是必须的,如果你要编译完成之后打印二维码的话
devServer: {
open: true,
host: '0.0.0.0',
// 要保证不会冲突,否则二维码不对
port: 3200,
https: false,
compress: true,
hotOnly: false,
overlay: {
warnings: false,
errors: true
}
},
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: isProd() ? true : false,
// 开启 CSS source maps?
sourceMap: isProd() ? true : false,
// css预设器配置项
loaderOptions: {
scss: {
prependData: `
@import "~@/assets/style/utils.scss";
`
},
},
// requireModuleExtension: false,
},
chainWebpack: config => {},
configureWebpack: (config) => {
return {
resolve: {
alias: {
'@': path.resolve('src')
}
},
plugins: [
new webpack.ProvidePlugin({
// _: "lodash",
// $: "jquery"
}),
new webpack.BannerPlugin('文件头声明'),
// https://github.com/cklwblove/vue-preset/blob/master/generator/index.js
// https://zhuanlan.zhihu.com/p/63079674
new VueRouterInvokeWebpackPlugin({
dir: 'src/views',
alias: '@/views',
mode: 'hash',
base: process.env.BASE_URL,
ignore: ['images', 'components', 'NotFound.vue', /\.scss$/],
notFound: '@/views/NotFound.vue',
routerDir: 'src/router',
redirect: [{
redirect: '/home',
path: '/'
}]
}),
...plugins
],
externals: {}
}
}
}
Loading…
Cancel
Save