├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── karma.conf.js ├── package-lock.json ├── package.json ├── shot.png ├── src ├── App.vue ├── assets │ ├── 404.jpg │ ├── avatar.jpg │ ├── bg-1.svg │ ├── bg-2.svg │ ├── bg.jpeg │ ├── bg1.png │ ├── bg4.jpg │ ├── codepen.svg │ ├── logo.png │ ├── none.png │ ├── page.jpg │ ├── project.jpg │ ├── project1.jpg │ └── tx.jpg ├── components │ ├── Bottom.vue │ ├── ColorPicker.vue │ ├── CompCard.vue │ ├── HomeSelect.vue │ ├── ModuleCard.vue │ ├── PageCard.vue │ ├── ProjectCard.vue │ ├── TemplCard.vue │ ├── add │ │ ├── CompAdd.vue │ │ ├── PageAdd.vue │ │ ├── ProjectAdd.vue │ │ ├── TemplAdd.vue │ │ └── index.js │ └── radio │ │ ├── RadioBar.vue │ │ ├── RadioItem.vue │ │ └── index.js ├── directive.js ├── main.js ├── modules │ ├── attr-area │ │ ├── index.vue │ │ ├── info.vue │ │ └── struct.vue │ ├── code-area │ │ └── index.vue │ ├── edit-area │ │ ├── attr-box │ │ │ ├── config.vue │ │ │ ├── configTable.vue │ │ │ ├── container.vue │ │ │ ├── index.vue │ │ │ └── link.vue │ │ ├── custom-element.vue │ │ ├── index.vue │ │ ├── page.vue │ │ └── types │ │ │ ├── div │ │ │ ├── div00.vue │ │ │ ├── div01.vue │ │ │ ├── div10.vue │ │ │ └── div11.vue │ │ │ ├── index.js │ │ │ ├── preview.js │ │ │ ├── preview │ │ │ ├── div │ │ │ │ ├── div00.vue │ │ │ │ ├── div01.vue │ │ │ │ ├── div10.vue │ │ │ │ └── div11.vue │ │ │ ├── type-div.vue │ │ │ ├── type-flexbox.vue │ │ │ ├── type-grid.vue │ │ │ └── type-img.vue │ │ │ ├── type-button.vue │ │ │ ├── type-div.vue │ │ │ ├── type-flexbox.vue │ │ │ ├── type-grid.vue │ │ │ ├── type-h1.vue │ │ │ ├── type-icon.vue │ │ │ ├── type-icontext.vue │ │ │ ├── type-img.vue │ │ │ ├── type-navbar.vue │ │ │ ├── type-p.vue │ │ │ ├── type-radio.vue │ │ │ ├── type-rate.vue │ │ │ ├── type-search.vue │ │ │ ├── type-span.vue │ │ │ ├── type-swiper.vue │ │ │ ├── type-switch.vue │ │ │ ├── type-tabbar.vue │ │ │ └── type-tag.vue │ ├── head-area │ │ ├── index.vue │ │ ├── menu-bar.vue │ │ ├── mobile.vue │ │ ├── tool-bar.vue │ │ └── user-avatar.vue │ ├── me │ │ ├── Comp.vue │ │ ├── Menu.vue │ │ ├── Mod.vue │ │ ├── Project.vue │ │ └── Templ.vue │ ├── preview-area │ │ ├── custom-element.vue │ │ └── index.vue │ └── resource-area │ │ ├── comp.vue │ │ ├── compUse.vue │ │ ├── components.json │ │ ├── images.vue │ │ ├── index.vue │ │ ├── module.vue │ │ ├── templ.vue │ │ └── type.js ├── pages │ ├── edit.vue │ ├── home.vue │ ├── login.vue │ ├── me.vue │ ├── pageDetail.vue │ ├── preview.vue │ ├── projectDetail.vue │ └── register.vue ├── request.js ├── router │ └── index.js ├── store │ └── index.js └── utils │ ├── Bus.js │ ├── attrShow.js │ ├── config.js │ ├── help.js │ ├── modules.json │ ├── page.js │ ├── page.json │ ├── templs.json │ ├── transformStyle.js │ └── transformTree.js ├── static └── .gitkeep ├── styles ├── common.scss ├── element-variables.scss ├── normalize.min.css └── var.scss └── test ├── style.spec.js └── tree.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": [ 12 | "transform-vue-jsx", "transform-runtime" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | rules: { 20 | // allow async-await 21 | 'generator-star-spacing': 'off', 22 | 'no-useless-escape': 'off', 23 | // allow debugger during development 24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## H5 Editor 3 | 4 | 移动端页面 UI 生成器 5 | 6 | 用于将设计稿样式转化为组件化的代码 7 | 8 | [本地启动方式](https://github.com/hyhajnal/h5-editor/tree/server) 9 | 10 | > 有时间会持续更新维护的 😄 11 | 12 | ## 使用者 13 | * **设计师**:通过拖拽编辑,生成UI稿 14 | * **前端开发**:划分模块、组件、重命名 class ----> 代码 15 | 16 | ## 特性 17 | 18 | * **编辑**: 拖拽编辑,属性编辑 19 | * **预览**: 页面/模块/组件预览,导出对应的代码 `.vue文件` 20 | * **模块划分**: 从页面中框选元素,划分为模块,并将该模块分配给对应的开发者;从模块中框选元素,划分为组件,继而生成组件化的代码 21 | * **资源库**:元件`原生标签` ,组件`第三方UI组件库`,模版`拖拽生成的` 22 | * **图标库**:填入 `iconfont` 的链接,会在 `head` 中自动添加` link` 标签,目前只支持一个页面一个 icon 链接 23 | * **图片资源管理**:可以上传、删除图片、以及复制图片url,用于图片、轮播图组件 24 | 25 | ## 编辑区快捷键 26 | * 按 ⌘+d 删除当前元素 27 | * 按 ⌘+v 复制当前元素 28 | * 按 ⌘+z 撤销(暂时只能撤销一次) 29 | * 按 ⌘+s 暂存至本地 30 | * 按 e 切换模块框选模式,按 esc 取消当前框选 31 | 32 | ## Todo 33 | * 公共样式,变量 34 | * 导入 sketch 35 | * 操作历史 36 | * 删除重复样式(考虑到有模版or元素的复制) 37 | 38 | ## 局限性 39 | 40 | 目前只限于移动端页面以及 vue 的转换 41 | 42 | ## 灵感来源 43 | 部分灵感和UI设计来源于 44 | 45 | * 墨刀 46 | * Fusion Design 47 | * iconfont 48 | 49 | ## 参考 50 | 51 | * https://github.com/ascoders/gaea-editor 52 | * https://github.com/jaweii/Vue-Layout 53 | * https://github.com/xinyu198736/sketch-to-html 54 | * https://github.com/lzxb/flex.css/blob/master/docs/zh-ch.md 55 | * http://f2e-assets.souche.com/projects/antd-editor/index.html#/?_k=mxsz5w 56 | 57 | ## 相关文章 58 | * http://fex.baidu.com/blog/2014/04/realtime-collaboration/ 59 | * https://www.jianshu.com/p/840e0b0b2c6a 60 | * http://www.alloyteam.com/2016/03/using-react-to-write-a-simple-activity-pages-design-of-operating-system-article/ 61 | 62 | ## 编辑页截图 63 | 64 | ![编辑页截图](./shot.png) 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/build/logo.png -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass').concat( 64 | { 65 | loader: 'sass-resources-loader', 66 | options: { 67 | resources: path.resolve(__dirname, '../styles/var.scss') 68 | } 69 | } 70 | ), 71 | stylus: generateLoaders('stylus'), 72 | styl: generateLoaders('stylus') 73 | } 74 | } 75 | 76 | // Generate loaders for standalone style files (outside of .vue) 77 | exports.styleLoaders = function (options) { 78 | const output = [] 79 | const loaders = exports.cssLoaders(options) 80 | 81 | for (const extension in loaders) { 82 | const loader = loaders[extension] 83 | output.push({ 84 | test: new RegExp('\\.' + extension + '$'), 85 | use: loader 86 | }) 87 | } 88 | 89 | return output 90 | } 91 | 92 | exports.createNotifierCallback = () => { 93 | const notifier = require('node-notifier') 94 | 95 | return (severity, errors) => { 96 | if (severity !== 'error') return 97 | 98 | const error = errors[0] 99 | const filename = error.file && error.file.split('!').pop() 100 | 101 | notifier.notify({ 102 | title: packageConfig.name, 103 | message: severity + ': ' + error.name, 104 | subtitle: filename || '', 105 | icon: path.join(__dirname, 'logo.png') 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | const vuxLoader = require('vux-loader') 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | const webpackConfig = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: './src/main.js' 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: process.env.NODE_ENV === 'production' 32 | ? config.build.assetsPublicPath 33 | : config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue', '.json'], 37 | alias: { 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | '@': resolve('src') 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 54 | }, 55 | { 56 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 57 | loader: 'url-loader', 58 | options: { 59 | limit: 10000, 60 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 61 | } 62 | }, 63 | { 64 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 65 | loader: 'url-loader', 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 77 | } 78 | } 79 | ] 80 | }, 81 | node: { 82 | // prevent webpack from injecting useless setImmediate polyfill because Vue 83 | // source contains it (although only uses it if it's native). 84 | setImmediate: false, 85 | // prevent webpack from injecting mocks to Node native modules 86 | // that does not make sense for the client 87 | dgram: 'empty', 88 | fs: 'empty', 89 | net: 'empty', 90 | tls: 'empty', 91 | child_process: 'empty' 92 | } 93 | } 94 | 95 | module.exports = vuxLoader.merge(webpackConfig, { 96 | plugins: ['vux-ui'] 97 | }) 98 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://localhost:8360', 16 | changeOrigin: true //是否跨域 17 | }, 18 | '/static': { 19 | target: 'http://localhost:8360', 20 | changeOrigin: true //是否跨域 21 | } 22 | }, 23 | 24 | // Various Dev Server settings 25 | host: 'localhost', // can be overwritten by process.env.HOST 26 | port: 8888, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 27 | autoOpenBrowser: false, 28 | errorOverlay: true, 29 | notifyOnErrors: true, 30 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 31 | 32 | // Use Eslint Loader? 33 | // If true, your code will be linted during bundling and 34 | // linting errors and warnings will be shown in the console. 35 | useEslint: true, 36 | // If true, eslint errors and warnings will also be shown in the error overlay 37 | // in the browser. 38 | showEslintErrorsInOverlay: false, 39 | 40 | /** 41 | * Source Maps 42 | */ 43 | 44 | // https://webpack.js.org/configuration/devtool/#development 45 | devtool: 'cheap-module-eval-source-map', 46 | 47 | // If you have problems debugging vue-files in devtools, 48 | // set this to false - it *may* help 49 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 50 | cacheBusting: true, 51 | 52 | cssSourceMap: true 53 | }, 54 | 55 | build: { 56 | // Template for index.html 57 | index: path.resolve(__dirname, '../dist/index.html'), 58 | 59 | // Paths 60 | assetsRoot: path.resolve(__dirname, '../dist'), 61 | assetsSubDirectory: 'static', 62 | assetsPublicPath: '/', 63 | 64 | /** 65 | * Source Maps 66 | */ 67 | 68 | productionSourceMap: true, 69 | // https://webpack.js.org/configuration/devtool/#production 70 | devtool: '#source-map', 71 | 72 | // Gzip off by default as many popular static hosts such as 73 | // Surge or Netlify already gzip all static assets for you. 74 | // Before setting to `true`, make sure to: 75 | // npm install --save-dev compression-webpack-plugin 76 | productionGzip: false, 77 | productionGzipExtensions: ['js', 'css'], 78 | 79 | // Run the build command with an extra argument to 80 | // View the bundle analyzer report after build finishes: 81 | // `npm run build --report` 82 | // Set to `true` or `false` to always turn it on or off 83 | bundleAnalyzerReport: process.env.npm_config_report 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | H5 Editor 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sun Mar 04 2018 11:06:15 GMT+0800 (CST) 3 | 4 | const path = require('path') 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | 9 | // base path that will be used to resolve all patterns (eg. files, exclude) 10 | basePath: '', 11 | 12 | 13 | // frameworks to use 14 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 15 | frameworks: ['mocha', 'chai'], 16 | 17 | 18 | // list of files / patterns to load in the browser 19 | files: [ 20 | 'test/**/*.spec.js' 21 | ], 22 | 23 | 24 | // list of files / patterns to exclude 25 | exclude: [ 26 | ], 27 | 28 | 29 | // preprocess matching files before serving them to the browser 30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 31 | preprocessors: { 32 | 'test/**/*.spec.js': ['webpack'] 33 | }, 34 | 35 | webpack: { 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js$/, 40 | loader: 'babel-loader', 41 | exclude: path.resolve(__dirname, './node_modules') 42 | } 43 | ] 44 | }, 45 | watch: true 46 | }, 47 | webpackServer: { 48 | noInfo: true 49 | }, 50 | 51 | 52 | // test results reporter to use 53 | // possible values: 'dots', 'progress' 54 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 55 | reporters: ['progress'], 56 | 57 | 58 | // web server port 59 | port: 9876, 60 | 61 | 62 | // enable / disable colors in the output (reporters and logs) 63 | colors: true, 64 | 65 | 66 | // level of logging 67 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 68 | logLevel: config.LOG_INFO, 69 | 70 | 71 | // enable / disable watching file and executing tests whenever any file changes 72 | autoWatch: true, 73 | 74 | 75 | // start these browsers 76 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 77 | browsers: ['PhantomJS'], 78 | 79 | 80 | // Continuous Integration mode 81 | // if true, Karma captures browsers, runs the tests and exits 82 | singleRun: false, 83 | 84 | // Concurrency level 85 | // how many browser should be started simultaneous 86 | concurrency: Infinity 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "jinger ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js", 12 | "test": "karma start" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.18.0", 16 | "element-ui": "^2.2.0", 17 | "flex.css": "^1.1.7", 18 | "hotkeys-js": "^3.2.0", 19 | "jsoneditor": "^5.14.1", 20 | "pretty": "^2.0.0", 21 | "vant": "^1.6.21", 22 | "vue": "^2.5.2", 23 | "vue-color": "^2.4.5", 24 | "vue-highlightjs": "^1.3.3", 25 | "vue-router": "^3.0.1", 26 | "vuedraggable": "^2.16.0", 27 | "vuex": "^3.0.1", 28 | "vux": "^2.9.4" 29 | }, 30 | "devDependencies": { 31 | "autoprefixer": "^7.1.2", 32 | "babel-core": "^6.22.1", 33 | "babel-eslint": "^8.2.1", 34 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 35 | "babel-loader": "^7.1.1", 36 | "babel-plugin-import": "^1.6.7", 37 | "babel-plugin-syntax-jsx": "^6.18.0", 38 | "babel-plugin-transform-runtime": "^6.22.0", 39 | "babel-plugin-transform-vue-jsx": "^3.5.0", 40 | "babel-preset-env": "^1.3.2", 41 | "babel-preset-stage-2": "^6.22.0", 42 | "chai": "^4.1.2", 43 | "chalk": "^2.0.1", 44 | "copy-webpack-plugin": "^4.6.0", 45 | "css-loader": "^0.28.0", 46 | "eslint": "^4.18.2", 47 | "eslint-config-standard": "^10.2.1", 48 | "eslint-friendly-formatter": "^3.0.0", 49 | "eslint-loader": "^1.7.1", 50 | "eslint-plugin-html": "^4.0.2", 51 | "eslint-plugin-import": "^2.9.0", 52 | "eslint-plugin-node": "^5.2.1", 53 | "eslint-plugin-promise": "^3.4.0", 54 | "eslint-plugin-standard": "^3.0.1", 55 | "extract-text-webpack-plugin": "^3.0.2", 56 | "file-loader": "^1.1.4", 57 | "friendly-errors-webpack-plugin": "^1.6.1", 58 | "html-webpack-plugin": "^2.30.1", 59 | "karma": "^2.0.0", 60 | "karma-chai": "^0.1.0", 61 | "karma-mocha": "^1.3.0", 62 | "karma-phantomjs-launcher": "^1.0.4", 63 | "karma-webpack": "^2.0.13", 64 | "less": "^3.0.2", 65 | "less-loader": "^4.1.0", 66 | "mocha": "^5.0.1", 67 | "node-notifier": "^5.1.2", 68 | "node-sass": "^4.7.2", 69 | "optimize-css-assets-webpack-plugin": "^3.2.0", 70 | "ora": "^1.2.0", 71 | "phantomjs-prebuilt": "^2.1.16", 72 | "portfinder": "^1.0.13", 73 | "postcss-import": "^11.0.0", 74 | "postcss-loader": "^2.0.8", 75 | "postcss-url": "^7.2.1", 76 | "rimraf": "^2.6.0", 77 | "sass-loader": "^6.0.6", 78 | "sass-resources-loader": "^1.3.3", 79 | "semver": "^5.3.0", 80 | "shelljs": "^0.7.6", 81 | "uglifyjs-webpack-plugin": "^1.1.1", 82 | "url-loader": "^0.5.8", 83 | "vue-loader": "^13.3.0", 84 | "vue-style-loader": "^3.0.1", 85 | "vue-template-compiler": "^2.5.2", 86 | "vux-loader": "^1.2.9", 87 | "webpack": "^3.12.0", 88 | "webpack-bundle-analyzer": "^2.9.0", 89 | "webpack-dev-server": "^2.11.5", 90 | "webpack-merge": "^4.2.1" 91 | }, 92 | "engines": { 93 | "node": ">= 6.0.0", 94 | "npm": ">= 3.0.0" 95 | }, 96 | "browserslist": [ 97 | "> 1%", 98 | "last 2 versions", 99 | "not ie <= 8" 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/shot.png -------------------------------------------------------------------------------- /src/assets/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/404.jpg -------------------------------------------------------------------------------- /src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/avatar.jpg -------------------------------------------------------------------------------- /src/assets/bg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/bg.jpeg -------------------------------------------------------------------------------- /src/assets/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/bg1.png -------------------------------------------------------------------------------- /src/assets/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/bg4.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/none.png -------------------------------------------------------------------------------- /src/assets/page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/page.jpg -------------------------------------------------------------------------------- /src/assets/project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/project.jpg -------------------------------------------------------------------------------- /src/assets/project1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/project1.jpg -------------------------------------------------------------------------------- /src/assets/tx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/src/assets/tx.jpg -------------------------------------------------------------------------------- /src/components/Bottom.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 36 | 37 | 63 | -------------------------------------------------------------------------------- /src/components/ColorPicker.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 45 | 46 | 62 | -------------------------------------------------------------------------------- /src/components/HomeSelect.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 86 | 87 | 128 | 129 | -------------------------------------------------------------------------------- /src/components/ModuleCard.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 55 | 56 | 104 | -------------------------------------------------------------------------------- /src/components/PageCard.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 71 | 72 | 128 | -------------------------------------------------------------------------------- /src/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | 44 | 96 | -------------------------------------------------------------------------------- /src/components/TemplCard.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 123 | 124 | 181 | -------------------------------------------------------------------------------- /src/components/add/CompAdd.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 89 | 90 | 111 | -------------------------------------------------------------------------------- /src/components/add/PageAdd.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 58 | 59 | 77 | -------------------------------------------------------------------------------- /src/components/add/ProjectAdd.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 63 | 64 | 82 | -------------------------------------------------------------------------------- /src/components/add/TemplAdd.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 63 | 64 | 82 | -------------------------------------------------------------------------------- /src/components/add/index.js: -------------------------------------------------------------------------------- 1 | import ProjectAdd from './ProjectAdd' 2 | import TemplAdd from './TemplAdd' 3 | import CompAdd from './CompAdd' 4 | import PageAdd from './PageAdd' 5 | 6 | export { 7 | ProjectAdd, 8 | TemplAdd, 9 | CompAdd, 10 | PageAdd 11 | } 12 | -------------------------------------------------------------------------------- /src/components/radio/RadioBar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/radio/RadioItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/radio/index.js: -------------------------------------------------------------------------------- 1 | import RadioItem from './RadioItem.vue' 2 | import RadioBar from './RadioBar.vue' 3 | 4 | export { 5 | RadioBar, RadioItem 6 | } 7 | -------------------------------------------------------------------------------- /src/directive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * v-clickoutside 3 | * @desc 点击元素外面才会触发的事件 4 | * @example 5 | * ```vue 6 | *
7 | * ``` 8 | */ 9 | const clickoutsideContext = '@@clickoutsideContext' 10 | 11 | export default { 12 | bind (el, binding, vnode) { 13 | const documentHandler = function (e) { 14 | if (vnode.context && !el.contains(e.target)) { 15 | vnode.context[el[clickoutsideContext].methodName]() 16 | } 17 | } 18 | el[clickoutsideContext] = { 19 | documentHandler, 20 | methodName: binding.expression, 21 | arg: binding.arg || 'click' 22 | } 23 | document.addEventListener(el[clickoutsideContext].arg, documentHandler) 24 | }, 25 | 26 | update (el, binding) { 27 | el[clickoutsideContext].methodName = binding.expression 28 | }, 29 | 30 | unbind (el) { 31 | document.removeEventListener( 32 | el[clickoutsideContext].arg, 33 | el[clickoutsideContext].documentHandler) 34 | }, 35 | 36 | install (Vue) { 37 | Vue.directive('clickoutside', { 38 | bind: this.bind, 39 | unbind: this.unbind 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import ElementUI from 'element-ui' 7 | import 'element-ui/lib/theme-chalk/index.css' 8 | import '../styles/element-variables.scss' 9 | import store from '../src/store' 10 | import clickoutside from './directive' 11 | import axios from './request' 12 | import Vant from 'vant' 13 | import 'vant/lib/index.css' 14 | import VueHighlightJS from 'vue-highlightjs' 15 | 16 | Vue.use(Vant) 17 | Vue.use(VueHighlightJS) 18 | 19 | Vue.prototype.axios = axios 20 | 21 | Vue.use(ElementUI) 22 | 23 | Vue.config.productionTip = false 24 | 25 | Vue.directive('clickoutside', clickoutside) 26 | 27 | /* eslint-disable no-new */ 28 | new Vue({ 29 | el: '#app', 30 | router, 31 | store, 32 | components: { App }, 33 | template: '' 34 | }) 35 | 36 | // new Vue({ 37 | // router, 38 | // store, 39 | // render: h => h(App) 40 | // }).$mount('#app') 41 | -------------------------------------------------------------------------------- /src/modules/attr-area/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | 54 | -------------------------------------------------------------------------------- /src/modules/attr-area/info.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 93 | 94 | 124 | -------------------------------------------------------------------------------- /src/modules/attr-area/struct.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 69 | 70 | 90 | 91 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/modules/code-area/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 80 | 81 | 109 | 110 | 117 | 118 | -------------------------------------------------------------------------------- /src/modules/edit-area/attr-box/config.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 139 | 140 | 145 | 146 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/modules/edit-area/attr-box/configTable.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 63 | 64 | 89 | 90 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/modules/edit-area/attr-box/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 46 | 47 | 48 | 65 | 66 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/modules/edit-area/attr-box/link.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 58 | 59 | 64 | -------------------------------------------------------------------------------- /src/modules/edit-area/page.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 114 | 115 | 158 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/div/div00.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 61 | 62 | 64 | 65 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/div/div01.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 62 | 63 | 65 | 66 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/div/div10.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 81 | 82 | 84 | 85 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/div/div11.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 82 | 83 | 85 | 86 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/index.js: -------------------------------------------------------------------------------- 1 | import TypeDiv from './type-div' 2 | import TypeSpan from './type-span' 3 | import TypeH1 from './type-h1' 4 | import TypeP from './type-p' 5 | import TypeButton from './type-button' 6 | import TypeRadio from './type-radio' 7 | import TypeSearch from './type-search' 8 | import TypeSwitch from './type-switch' 9 | import TypeNavbar from './type-navbar' 10 | import TypeTabbar from './type-tabbar' 11 | import TypeTag from './type-tag' 12 | import TypeImg from './preview/type-img' 13 | import TypeFlexbox from './type-flexbox' 14 | import TypeGrid from './type-grid' 15 | import TypeSwiper from './type-swiper' 16 | import TypeIcon from './type-icon' 17 | import TypeIcontext from './type-icontext' 18 | import TypeRate from './type-rate' 19 | 20 | export default { 21 | TypeButton, 22 | TypeDiv, 23 | TypeSearch, 24 | TypeSwitch, 25 | TypeRadio, 26 | TypeSpan, 27 | TypeTabbar, 28 | TypeNavbar, 29 | TypeTag, 30 | TypeImg, 31 | TypeFlexbox, 32 | TypeGrid, 33 | TypeSwiper, 34 | TypeIcon, 35 | TypeP, 36 | TypeH1, 37 | TypeIcontext, 38 | TypeRate 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview.js: -------------------------------------------------------------------------------- 1 | import TypeDiv from './preview/type-div' 2 | import TypeSpan from './type-span' 3 | import TypeH1 from './type-h1' 4 | import TypeP from './type-p' 5 | import TypeButton from './type-button' 6 | import TypeRadio from './type-radio' 7 | import TypeSearch from './type-search' 8 | import TypeSwitch from './type-switch' 9 | import TypeNavbar from './type-navbar' 10 | import TypeTabbar from './type-tabbar' 11 | import TypeTag from './type-tag' 12 | import TypeImg from './preview/type-img' 13 | import TypeFlexbox from './preview/type-flexbox' 14 | import TypeGrid from './preview/type-grid' 15 | import TypeSwiper from './type-swiper' 16 | import TypeIcon from './type-icon' 17 | import TypeIcontext from './type-icontext' 18 | import TypeRate from './type-rate' 19 | 20 | export default { 21 | TypeButton, 22 | TypeDiv, 23 | TypeSearch, 24 | TypeSwitch, 25 | TypeRadio, 26 | TypeSpan, 27 | TypeTabbar, 28 | TypeNavbar, 29 | TypeTag, 30 | TypeImg, 31 | TypeFlexbox, 32 | TypeGrid, 33 | TypeSwiper, 34 | TypeIcon, 35 | TypeP, 36 | TypeH1, 37 | TypeIcontext, 38 | TypeRate 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/div/div00.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/div/div01.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/div/div10.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | 37 | 39 | 40 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/div/div11.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 41 | 42 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/type-div.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/type-flexbox.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 84 | 85 | 97 | 98 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/type-grid.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | 36 | 37 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/preview/type-img.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-div.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-flexbox.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 84 | 85 | 97 | 98 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-grid.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 72 | 73 | 83 | 84 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-h1.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-icon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-icontext.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-img.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 51 | 52 | 82 | 83 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-navbar.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-p.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-radio.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 21 | 22 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-rate.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | 34 | 36 | 37 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-search.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 34 | 35 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-span.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-swiper.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 29 | 30 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-switch.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 21 | 22 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-tabbar.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 55 | 56 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/modules/edit-area/types/type-tag.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /src/modules/head-area/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 119 | 120 | -------------------------------------------------------------------------------- /src/modules/head-area/menu-bar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 30 | 31 | -------------------------------------------------------------------------------- /src/modules/head-area/mobile.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 70 | 71 | 76 | 77 | 90 | 91 | -------------------------------------------------------------------------------- /src/modules/head-area/tool-bar.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 84 | 85 | -------------------------------------------------------------------------------- /src/modules/head-area/user-avatar.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 64 | 65 | 74 | 75 | -------------------------------------------------------------------------------- /src/modules/me/Comp.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | 33 | 34 | 55 | 56 | -------------------------------------------------------------------------------- /src/modules/me/Menu.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 52 | 53 | 62 | 63 | -------------------------------------------------------------------------------- /src/modules/me/Mod.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 27 | 34 | -------------------------------------------------------------------------------- /src/modules/me/Project.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 45 | -------------------------------------------------------------------------------- /src/modules/me/Templ.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | 28 | 35 | 36 | 67 | 68 | -------------------------------------------------------------------------------- /src/modules/preview-area/custom-element.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 42 | 43 | 73 | 74 | 75 | 77 | 78 | -------------------------------------------------------------------------------- /src/modules/preview-area/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 | 49 | -------------------------------------------------------------------------------- /src/modules/resource-area/comp.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 81 | 82 | 137 | -------------------------------------------------------------------------------- /src/modules/resource-area/compUse.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 84 | -------------------------------------------------------------------------------- /src/modules/resource-area/components.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "c1", 4 | "name": "按钮", 5 | "type": "button", 6 | "icon": "button", 7 | "style": "background:#fff;color:red;fontSize:12px;borderRadius:0px;", 8 | "config": [ 9 | { "name": "text", "label": "内容", "type": "input", "data": "按钮" } 10 | ] 11 | }, 12 | { 13 | "id": "c2", 14 | "name": "标签", 15 | "icon": "tag", 16 | "type": "tag", 17 | "style": "background:#fff;color:#F36D6E;fontSize:12px;border:1px solid #F36D6E;padding:2px 4px 2px 4px;borderRadius:2px;", 18 | "config": [ 19 | { "name": "text", "label": "文字", "type": "input", "data": "标签" } 20 | ] 21 | }, 22 | { 23 | "id": "c22", 24 | "name": "图文标", 25 | "icon": "icontext", 26 | "type": "icontext", 27 | "style": "color:#888;padding:2px;", 28 | "config": [ 29 | { "name": "icon", "label": "图标", "type": "input", "data": "icon-ziyuan" }, 30 | { "name": "label", "label": "文字", "type": "input", "data": "123万" }, 31 | { "name": "gutter", "label": "间距", "type": "number", "data": 0 } 32 | ] 33 | }, 34 | { 35 | "id": "c3", 36 | "name": "单选框", 37 | "icon": "radio", 38 | "type": "radio", 39 | "config": [] 40 | }, 41 | { 42 | "id": "c4", 43 | "name": "多选框", 44 | "icon": "checkbox", 45 | "type": "checkbox", 46 | "config": [] 47 | }, 48 | { 49 | "id": "c5", 50 | "name": "开关", 51 | "icon": "switch", 52 | "type": "switch", 53 | "config": [] 54 | }, 55 | { 56 | "id": "c6", 57 | "name": "搜索框", 58 | "icon": "search", 59 | "type": "search", 60 | "config": [] 61 | }, 62 | { 63 | "id": "c7", 64 | "name": "底部栏", 65 | "icon": "tabbar", 66 | "type": "tabbar", 67 | "style": "position:absolute;bottom:0;display:flex;", 68 | "config": [ 69 | { "name": "data", "label": "数据", "type": "table", "data": [ 70 | {"icon": "icon-paihangbang", "label": "排行榜", "info": "5"}, 71 | {"icon": "icon-yonghu", "label": "我的", "info": ""}, 72 | {"icon": "icon-faxian", "label": "发现", "info": ""} 73 | ]}, 74 | { "name": "normal", "label": "普通色", "type": "color", "data": "#666" }, 75 | { "name": "active", "label": "高亮色", "type": "color", "data": "#EC0E0E" } 76 | ] 77 | }, 78 | { 79 | "id": "c8", 80 | "name": "导航栏", 81 | "icon": "navbar", 82 | "type": "navbar", 83 | "style": "background:#171717;color:#fff;", 84 | "config": [ 85 | { "name": "title", "label": "标题", "type": "input", "data": "标题" }, 86 | { "name": "leftIcon", "label": "左图标", "type": "input", "data": "icon-fanhui" }, 87 | { "name": "leftText", "label": "左文字", "type": "input", "data": "返回" }, 88 | { "name": "rightIcon", "label": "右图标", "type": "input", "data": "icon-user" }, 89 | { "name": "rightText", "label": "右文字", "type": "input", "data": "" } 90 | ] 91 | }, 92 | { 93 | "id": "c9", 94 | "name": "轮播图", 95 | "icon": "swiper", 96 | "type": "swiper", 97 | "config": [ 98 | { "name": "image", "label": "图片", "type": "table", "data": [ 99 | {"url": "http://localhost/static/1.jpg"}, 100 | {"url": "http://localhost/static/2.jpg"} 101 | ]} 102 | ] 103 | }, 104 | { 105 | "id": "c10", 106 | "name": "布局盒子", 107 | "icon": "layout", 108 | "type": "flexbox", 109 | "config": [ 110 | { "name": "num", "label": "数量", "type": "number", "data": 3 }, 111 | { "name": "gutter", "label": "间隔", "type": "number", "data": 8 }, 112 | { "name": "orient", "label": "间隔", "type": "select", "data": "horizontal", "options": ["horizontal", "vertical"]}, 113 | { "name": "justify", "label": "水平排列", "type": "select", "data": "flex-start", 114 | "options": ["flex-start", "flex-end", "center", "space-between", "space-around"] 115 | }, 116 | { "name": "align", "label": "竖向排列", "type": "select", "data": "flex-start", 117 | "options": ["flex-start", "flex-end", "center", "space-between", "space-around", "stretch"] 118 | }, 119 | { "name": "wrap", "label": "间隔", "type": "select", "data": "row", 120 | "options": ["row", "row-reverse", "column", "column-reverse"] 121 | }, 122 | { "name": "direction", "label": "间隔", "type": "select", "data": "wrap", 123 | "options": ["wrap", "nowrap"] 124 | } 125 | ] 126 | }, 127 | { 128 | "id": "c11", 129 | "name": "网格布局", 130 | "icon": "9box", 131 | "type": "grid", 132 | "config": [ 133 | { "name": "cols", "label": "列数", "type": "number", "data": 3 }, 134 | { "name": "show-lr-borders", "label": "是否显示左右边框", "type": "bool", "data": true}, 135 | { "name": "show-vertical-dividers", "label": "是否显示分割线", "type": "bool", "data": true} 136 | ] 137 | } 138 | 139 | ] -------------------------------------------------------------------------------- /src/modules/resource-area/images.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 91 | 92 | 121 | -------------------------------------------------------------------------------- /src/modules/resource-area/module.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 82 | 83 | 154 | -------------------------------------------------------------------------------- /src/modules/resource-area/templ.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 95 | 96 | 160 | -------------------------------------------------------------------------------- /src/modules/resource-area/type.js: -------------------------------------------------------------------------------- 1 | const components = [ 2 | {icon: 'div', label: '容器', value: 'div'}, 3 | {icon: 'text', label: '文字', value: 'span'}, 4 | {icon: 'h1', label: '标题', value: 'h1'}, 5 | {icon: 'p', label: '描述', value: 'p'}, 6 | {icon: 'image', label: '图片', value: 'img'}, 7 | {icon: 'h-divider', label: '横分隔', value: 'h-divider'}, 8 | {icon: 'v-divider', label: '竖分隔', value: 'v-divider'}, 9 | {icon: 'info', label: '图标', value: 'icon'} 10 | ] 11 | 12 | const layouts = [ 13 | {icon: 'layout', label: '等分', value: 'flex'}, 14 | {icon: 'flex', label: '九宫格', value: 'ninebox'}, 15 | {icon: 'list', label: '列表', value: 'list'} 16 | ] 17 | 18 | export { components, layouts } 19 | -------------------------------------------------------------------------------- /src/pages/edit.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 119 | 120 | -------------------------------------------------------------------------------- /src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 70 | 71 | 188 | -------------------------------------------------------------------------------- /src/pages/me.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 114 | 115 | 206 | 207 | 218 | -------------------------------------------------------------------------------- /src/pages/pageDetail.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | 44 | -------------------------------------------------------------------------------- /src/pages/preview.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 72 | 73 | -------------------------------------------------------------------------------- /src/pages/projectDetail.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 91 | 92 | 131 | 132 | 142 | -------------------------------------------------------------------------------- /src/pages/register.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 69 | 70 | 187 | -------------------------------------------------------------------------------- /src/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Vue from 'vue' 3 | const vm = new Vue() 4 | axios.defaults.withCredentials = true 5 | // Add a request interceptor 6 | axios.interceptors.request.use(function (config) { 7 | // Do something before request is sent 8 | return config 9 | }, function (error) { 10 | // Do something with request error 11 | return Promise.reject(error) 12 | }) 13 | 14 | axios.interceptors.response.use(response => { 15 | // if (response.data.errno === undefined) return response 16 | const { data, errmsg, errno } = response.data 17 | const status = response.status 18 | 19 | if (status === 200) { 20 | if (errno !== 0) { 21 | vm.$message.error(errmsg || '操作失败') 22 | return 1000 // 用于判断是否错误 23 | } else { 24 | return data 25 | } 26 | } else { 27 | vm.$message.error('与服务器连接失败') 28 | return 1000 29 | } 30 | }, error => { 31 | if (error && error.response) { 32 | switch (error.response.status) { 33 | case 403: 34 | error.message = '你还未登录' 35 | location.href = 'http://localhost/#/login' 36 | break 37 | case 500: 38 | error.message = '服务器发生错误了' 39 | break 40 | case 502: 41 | error.message = '连接服务器超时' 42 | break 43 | default: 44 | } 45 | vm.$message.error(error.message) 46 | } 47 | return Promise.reject(error) 48 | }) 49 | 50 | export default axios 51 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Edit from '@/pages/edit' 4 | import Home from '@/pages/home' 5 | import Preview from '@/pages/preview' 6 | import PageDetail from '@/pages/pageDetail' 7 | import ProjectDetail from '@/pages/projectDetail' 8 | import Mobile from '@/modules/preview-area' 9 | import Me from '@/pages/me' 10 | import Login from '@/pages/login' 11 | import Register from '@/pages/register' 12 | 13 | Vue.use(Router) 14 | 15 | export default new Router({ 16 | routes: [ 17 | { 18 | path: '/', 19 | name: 'Welcome', 20 | component: Home 21 | }, 22 | { 23 | path: '/edit', 24 | name: 'Edit', 25 | component: Edit 26 | }, 27 | { 28 | path: '/home', 29 | name: 'Home', 30 | component: Home 31 | }, 32 | { 33 | path: '/preview', 34 | name: 'Preview', 35 | component: Preview, 36 | children: [{ 37 | path: 'mobile', 38 | component: Mobile 39 | }] 40 | }, 41 | { 42 | path: '/mobile', 43 | name: 'Mobile', 44 | component: Mobile 45 | }, 46 | { 47 | path: '/page', 48 | name: 'PageDetail', 49 | component: PageDetail 50 | }, 51 | { 52 | path: '/project', 53 | name: 'ProjectDetail', 54 | component: ProjectDetail 55 | }, 56 | { 57 | path: '/me', 58 | name: 'Me', 59 | component: Me 60 | }, 61 | { 62 | path: '/login', 63 | name: 'Login', 64 | component: Login 65 | }, 66 | { 67 | path: '/register', 68 | name: 'Register', 69 | component: Register 70 | } 71 | ] 72 | }) 73 | -------------------------------------------------------------------------------- /src/utils/Bus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const Bus = new Vue() 4 | 5 | export default Bus 6 | -------------------------------------------------------------------------------- /src/utils/attrShow.js: -------------------------------------------------------------------------------- 1 | export default { 2 | div: {padding: true, margin: true, width: true, height: true, background: true, borderRadius: true, border: true, position: true, display: true}, 3 | span: {padding: true, margin: true, width: true, height: true, background: true, borderRadius: true, border: true, position: true, display: true, fontSize: true, color: true, fontWeight: true}, 4 | h1: {padding: true, margin: true, position: true, display: true, fontSize: true, color: true, fontWeight: true, textAlign: true}, 5 | p: {padding: true, margin: true, position: true, display: true, fontSize: true, color: true, fontWeight: true, textAlign: true}, 6 | icon: {margin: true, position: true, display: true, fontSize: true, color: true, fontWeight: true}, 7 | img: {margin: true, width: true, height: true, borderRadius: true, border: true, position: true, display: true}, 8 | button: {fontSize: true, color: true, fontWeight: true, background: true, padding: true, margin: true, position: true, border: true, borderRadius: true}, 9 | flexbox: {padding: true, margin: true, position: true}, 10 | grid: {padding: true, margin: true, position: true}, 11 | tabbar: {border: true, background: true}, 12 | tag: {fontSize: true, color: true, fontWeight: true, background: true, padding: true, margin: true, position: true, borderRadius: true, border: true}, 13 | icontext: {color: true, margin: true, position: true}, 14 | navbar: {color: true, background: true} 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | // URL: 'http://localhost:8360' 3 | URL: '/api' 4 | } 5 | 6 | export default config 7 | -------------------------------------------------------------------------------- /src/utils/help.js: -------------------------------------------------------------------------------- 1 | function guid () { 2 | function S4 () { 3 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) 4 | } 5 | return (`e-${S4() + S4()}`) 6 | } 7 | 8 | function getInit (type) { 9 | let attr 10 | switch (type) { 11 | case 'div': 12 | attr = { 13 | style: 'height:100px;border:1px solid #ccc;', 14 | config: [ 15 | { name: 'p', label: '父元素', type: 'bool', data: false }, 16 | { name: 'dir', label: '主轴方向', type: 'select', options: ['top', 'right', 'bottom', 'left'], data: 'left', show: 1 }, 17 | { name: 'main', label: '主轴对齐方式', type: 'select', options: ['right', 'left', 'justify', 'center'], data: 'left', show: 1 }, 18 | { name: 'cross', label: '交叉轴对齐方式', type: 'select', options: ['top', 'bottom', 'baseline', 'center', 'stretch'], data: 'top', show: 1 }, 19 | { name: 'box', label: '子元素设置', type: 'select', options: ['', 'mean', 'first', 'last', 'justify'], data: '', show: 1 }, 20 | { name: 'c', label: '子元素', type: 'bool', data: false }, 21 | { name: 'flex-box', label: '子元素', type: 'number', data: 0, show: 6 } 22 | ] 23 | } 24 | break 25 | case 'span': 26 | attr = { 27 | style: 'color:#000;', 28 | config: [ 29 | { name: 'text', label: '文字', type: 'input', data: '文字' } 30 | ] 31 | } 32 | break 33 | case 'h1': 34 | attr = { 35 | style: 'color:#555;fontSize:16px;', 36 | config: [ 37 | { name: 'text', label: '文字', type: 'input', data: '标题' } 38 | ] 39 | } 40 | break 41 | case 'p': 42 | attr = { 43 | style: 'color:#888;fontSize:14px;', 44 | config: [ 45 | { name: 'text', label: '文字', type: 'input', data: '这是一段描述文字' } 46 | ] 47 | } 48 | break 49 | case 'img': 50 | attr = { 51 | style: 'width:100px;height:80px;', 52 | config: [ 53 | { name: 'url', label: 'url', type: 'input', data: '' } 54 | ] 55 | } 56 | break 57 | case 'icon': 58 | attr = { 59 | style: 'color:#000;', 60 | config: [ 61 | { name: 'name', label: '图标名', type: 'input', data: 'icon-info' } 62 | ] 63 | } 64 | break 65 | default: 66 | attr = {} 67 | } 68 | return attr 69 | } 70 | 71 | const mobiles = [ 72 | { 73 | label: 'iPhone7', 74 | width: 375, 75 | height: 667 76 | }, { 77 | label: 'iPhone7Plus', 78 | width: 414, 79 | height: 736 80 | }, { 81 | label: 'iPhone5s', 82 | width: 320, 83 | height: 568 84 | }, { 85 | label: 'iPhoneX', 86 | width: 375, 87 | height: 812 88 | } 89 | ] 90 | 91 | function px2rem (value) { 92 | if (value) { 93 | value = value / 75 94 | value = parseFloat(value).toFixed(6) 95 | return (value + 'rem') 96 | } 97 | return null 98 | } 99 | 100 | function createModule ({elements, pid, developer, name}) { 101 | let mod = { 102 | id: `m/${guid()}`, 103 | name, 104 | elements, 105 | pid, 106 | developer, 107 | classId: {}, 108 | components: {} 109 | } 110 | return mod 111 | } 112 | 113 | function createTempl ({elements, name}) { 114 | let templ = { 115 | id: `t/${guid()}`, 116 | name, 117 | elements 118 | } 119 | return templ 120 | } 121 | 122 | export { guid, getInit, mobiles, px2rem, createModule, createTempl } 123 | -------------------------------------------------------------------------------- /src/utils/modules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "m-1", 4 | "name": "模块1", 5 | "elements": ["e2", "e3"], 6 | "developer": "开发者1", 7 | "classId": {}, 8 | "components": { 9 | "id/comp1": { 10 | "name": "demo-comp1", 11 | "label": "组件1", 12 | "elements": ["e2"] 13 | }, 14 | "id/comp2": { 15 | "name": "demo-comp2", 16 | "label": "组件2", 17 | "elements": ["e3"] 18 | } 19 | } 20 | }, 21 | { 22 | "id": "m-2", 23 | "name": "模块2", 24 | "classId": {}, 25 | "elements": ["e4"], 26 | "developer": "开发者2", 27 | "components": { 28 | "id/comp1": { 29 | "name": "demo-comp1", 30 | "label": "组件1", 31 | "elements": ["e5"] 32 | } 33 | } 34 | }, 35 | { 36 | "id": "m-3", 37 | "name": "模块3", 38 | "classId": {}, 39 | "elements": ["e3", "e5"], 40 | "developer": "开发者2", 41 | "components": { 42 | "id/comp1": { 43 | "name": "demo-comp1", 44 | "label": "组件1", 45 | "elements": ["e5"] 46 | } 47 | } 48 | } 49 | ] -------------------------------------------------------------------------------- /src/utils/page.js: -------------------------------------------------------------------------------- 1 | import { toList } from '@/utils/transformTree' 2 | 3 | const DATA = [{ 4 | id: 'e1', 5 | label: '容器-e1', 6 | type: 'div', 7 | style: 'width:100px;height:100px;background:#F56C6C;', 8 | pid: 'root', 9 | children: [{ 10 | id: 'e2', 11 | label: '文字-e2', 12 | type: 'span', 13 | config: [ 14 | { name: 'text', lable: '文字', type: 'input', data: '监控设备' } 15 | ], 16 | style: 'width:50px;height:50px;color:pink;', 17 | pid: 'e1', 18 | children: [] 19 | }] 20 | }, { 21 | id: 'e4', 22 | label: '容器-e4', 23 | type: 'div', 24 | style: 'width:100px;height:50px;background:orange;', 25 | pid: 'root', 26 | children: [{ 27 | id: 'e5', 28 | label: '文字-e5', 29 | type: 'span', 30 | config: [ 31 | { name: 'text', label: '文字', type: 'input', data: 'sajfg' } 32 | ], 33 | style: 'color: #000;', 34 | pid: 'e4', 35 | children: [] 36 | }] 37 | }, { 38 | id: 'e7', 39 | label: '容器-e7', 40 | type: 'div', 41 | style: 'width:100%;height:100px;background:#409EFF;', 42 | children: [], 43 | pid: 'root' 44 | }, { 45 | id: 'e6', 46 | label: '文字-e6', 47 | type: 'span', 48 | config: [ 49 | { name: 'text', label: '文字', type: 'input', data: '数据库表' } 50 | ], 51 | style: 'width:50px;height:50px;color: #555;', 52 | pid: 'root', 53 | children: [] 54 | }] 55 | 56 | const LIST = toList(DATA) 57 | 58 | export { LIST, DATA } 59 | 60 | // export default DATA 61 | -------------------------------------------------------------------------------- /src/utils/page.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "id": "Page1", 4 | "name": "测试页面", 5 | "background": "#fff", 6 | "projectId": 5 7 | }, 8 | "elements": [ 9 | { 10 | "id": "e1", 11 | "type": "div", 12 | "className": "", 13 | "style": "width:150px;height:150px;border:1px solid #ccc;", 14 | "label": "容器-e1", 15 | "children": [{ 16 | "id": "e2", 17 | "className": "", 18 | "label": "容器-e2", 19 | "type": "div", 20 | "style": "width:50px;height:50px;background:#409EFF;", 21 | "pid": "e1", 22 | "children": [] 23 | },{ 24 | "id": "e3", 25 | "className": "", 26 | "label": "容器-e3", 27 | "type": "div", 28 | "style": "width:50px;height:50px;background:#555;", 29 | "pid": "e1", 30 | "children": [] 31 | }], 32 | "pid": "root" 33 | }, 34 | { 35 | "id": "e4", 36 | "label": "容器-e4", 37 | "type": "div", 38 | "className": "", 39 | "style": "width:100px;height:50px;background:#FED96E;", 40 | "pid": "root", 41 | "children": [{ 42 | "id": "e5", 43 | "label": "文字-e5", 44 | "type": "span", 45 | "className": "", 46 | "config": [ 47 | { "name": "text", "label": "文字", "type": "input", "data": "sajfg" } 48 | ], 49 | "style": "color: #000;", 50 | "pid": "e4", 51 | "children": [] 52 | }] 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /src/utils/templs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "t1", 4 | "name": "模版1", 5 | "image": "http://imgs.xueui.cn/wp-content/uploads/2018/01/fm110.jpg", 6 | "content": { 7 | "id": "t1", 8 | "type": "div", 9 | "style": "", 10 | "label": "模版1容器", 11 | "children": [ 12 | { 13 | "id": "e1", 14 | "type": "div", 15 | "style": "width:150px;height:150px;background:#4ED7EC;", 16 | "label": "容器-e1", 17 | "children": [{ 18 | "id": "e2", 19 | "label": "容器-e2", 20 | "type": "div", 21 | "style": "width:50px;height:50px;background:#409EFF;", 22 | "pid": "e1", 23 | "children": [] 24 | },{ 25 | "id": "e3", 26 | "label": "容器-e3", 27 | "type": "div", 28 | "style": "width:50px;height:50px;background:#555;", 29 | "pid": "e1", 30 | "children": [] 31 | }], 32 | "pid": "t1" 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "id": "t2", 39 | "name": "模版2", 40 | "image": "http://imgs.xueui.cn/wp-content/uploads/2017/10/Cover%EF%BC%9AArticle-2017.10.26-1.png", 41 | "content": { 42 | "id": "t2", 43 | "type": "div", 44 | "style": "", 45 | "label": "模版1容器", 46 | "children": [ 47 | { 48 | "id": "e1", 49 | "type": "div", 50 | "style": "width:150px;height:150px;background:#67c23a;borderRadius:100%", 51 | "label": "容器-e1", 52 | "children": [], 53 | "pid": "t2" 54 | } 55 | ] 56 | } 57 | }, 58 | { 59 | "id": "t3", 60 | "name": "模版3", 61 | "image": "http://imgs.xueui.cn/wp-content/uploads/2017/10/Cover%EF%BC%9AArticle-2017.10.24-2.png", 62 | "content": { 63 | "id": "t3", 64 | "type": "div", 65 | "style": "", 66 | "label": "模版1容器", 67 | "children": [ 68 | { 69 | "id": "e4", 70 | "label": "容器-e4", 71 | "type": "div", 72 | "style": "width:100px;height:50px;background:#fed96e;borderRadius: 4px;", 73 | "pid": "t3", 74 | "children": [{ 75 | "id": "e5", 76 | "label": "文字-e5", 77 | "type": "span", 78 | "config": [ 79 | { "name": "text", "label": "文字", "type": "input", "data": "文字" } 80 | ], 81 | "style": "color: #000;", 82 | "pid": "e4", 83 | "children": [] 84 | }] 85 | } 86 | ] 87 | } 88 | } 89 | ] -------------------------------------------------------------------------------- /src/utils/transformTree.js: -------------------------------------------------------------------------------- 1 | // import DATA from './page' 2 | import { guid } from '@/utils/help' 3 | 4 | function toTree (data) { 5 | let list = JSON.parse(JSON.stringify(data)) 6 | let idMap = {} 7 | let tree = [] 8 | 9 | list.forEach(item => { 10 | idMap[item.id] = item 11 | }) 12 | list.forEach(v => { 13 | let parent = idMap[v.pid] 14 | // debugger 15 | if (parent) { 16 | !parent.children && (parent.children = []) 17 | // parent.children.push(v) 18 | parent.children[v.idx] = v 19 | idMap[v.pid] = parent 20 | } else { 21 | // tree.push(v) 22 | tree[v.idx] = v 23 | } 24 | }) 25 | return tree 26 | } 27 | 28 | function handleList (tree, list, pid) { 29 | tree.forEach((item, idx) => { 30 | let newItem = { pid, idx, ...item } 31 | delete newItem.children 32 | list.push(newItem) 33 | if (item.children && item.children.length > 0) { 34 | handleList(item.children, list, item.id) 35 | } 36 | }) 37 | } 38 | 39 | function toList (tree) { 40 | let list = [] 41 | handleList(tree, list, 'root') 42 | return JSON.parse(JSON.stringify(list)) 43 | } 44 | 45 | /** 46 | * 根据 pid 返回 children 47 | * @param {*} tree 48 | * @param {*} pid 49 | */ 50 | function treeTravel (tree, pid) { 51 | if (pid === 'root') { 52 | return new Promise(resolve => resolve(tree)) 53 | } 54 | let q = [] 55 | tree.forEach(item => { 56 | q.push(item) 57 | }) 58 | // console.dir(q) 59 | while (q.length > 0) { 60 | let node = q.shift() 61 | if (node.id === pid) { 62 | return new Promise(resolve => resolve(node.children)) 63 | } 64 | if (node.children && node.children.length > 0) { 65 | node.children.forEach(item => { 66 | q.push(item) 67 | }) 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 根据 pid 返回 children 74 | * @param {*} tree 75 | * @param {*} id 76 | */ 77 | function treeTravelById (tree, id) { 78 | let q = [] 79 | tree.forEach(item => { 80 | q.push(item) 81 | }) 82 | while (q.length > 0) { 83 | let node = q.shift() 84 | if (node.id === id) { 85 | return new Promise(resolve => resolve(node)) 86 | } 87 | if (node.children && node.children.length > 0) { 88 | node.children.forEach(item => { 89 | q.push(item) 90 | }) 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * 模块编辑转换: 将ids -> element 97 | */ 98 | function moduleSelect (tree, ids) { 99 | let elements = [] 100 | let q = [] 101 | tree.forEach(item => { 102 | q.push(item) 103 | }) 104 | while (q.length > 0) { 105 | let node = q.shift() 106 | if (ids.indexOf(node.id) > -1) { 107 | node.pid = 'root' 108 | elements.push(node) 109 | } else if (node.children && node.children.length > 0) { 110 | node.children.forEach(item => { 111 | q.push(item) 112 | }) 113 | } 114 | } 115 | return new Promise(resolve => resolve(elements)) 116 | } 117 | 118 | /** 119 | * 复制包含多个子元素的元素,改变所有的id 120 | */ 121 | function copyElorTempl (el, pid) { 122 | const id = guid() 123 | el.id = id 124 | el.pid = pid 125 | el.label = `${el.type}/${id}` 126 | if (el.children && el.children.length > 0) { 127 | el.children.forEach(item => { 128 | copyElorTempl(item, id) 129 | }) 130 | } 131 | } 132 | 133 | export { toTree, toList, treeTravel, moduleSelect, treeTravelById, copyElorTempl } 134 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyhajnal/h5-editor/55d71c1915e204b9f03653d60078e7dc5ed05991/static/.gitkeep -------------------------------------------------------------------------------- /styles/common.scss: -------------------------------------------------------------------------------- 1 | 2 | .clearfix:before, 3 | .clearfix:after { 4 | display: table; 5 | content: ""; 6 | } 7 | 8 | .clearfix:after { 9 | clear: both 10 | } -------------------------------------------------------------------------------- /styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /* 改变主题色变量 */ 2 | $--color-primary: #FD7F6B; 3 | 4 | /* 改变 icon 字体路径变量,必需 */ 5 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 6 | 7 | @import "~element-ui/packages/theme-chalk/src/index"; -------------------------------------------------------------------------------- /styles/normalize.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*# sourceMappingURL=normalize.min.css.map */ -------------------------------------------------------------------------------- /styles/var.scss: -------------------------------------------------------------------------------- 1 | $themeColor: #FD7F6B; 2 | // $themeColor: #4FC5B6; 3 | $lightBg: #f2f2f2; -------------------------------------------------------------------------------- /test/style.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { toStyleString, toStyleObj } from '../src/utils/transformStyle' 3 | 4 | const egStyleObj = { 5 | width: 20, 6 | padding: [10, 10, 20, 30], 7 | border: [1, 'solid', '#ccc'] 8 | } 9 | 10 | const egStyleStr = 'width:20px;padding:10px 10px 20px 30px;border:1px solid #ccc;' 11 | 12 | describe('样式转换', () => { 13 | it('string to obj', () => { 14 | expect(toStyleObj(egStyleStr)).to.be.equal(egStyleObj) 15 | }) 16 | 17 | it('obj to string', () => { 18 | // console.log(toStyleString(egStyleObj)) 19 | expect(toStyleString(egStyleObj)) 20 | .to.be.equal(egStyleStr) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/tree.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { toTree, toList } from '../src/utils/transformTree' 3 | import egTree from '../src/utils/page' 4 | 5 | const egList = [{ 6 | id: 'e1', 7 | label: '容器', 8 | type: 'div', 9 | style: 'width:100px;height:100px;background:red;', 10 | pid: 'root', 11 | idx: 0 12 | }, { 13 | id: 'e2', 14 | label: '文字', 15 | type: 'span', 16 | text: 'hhhh', 17 | style: 'width:50px;height:50px;background:pink;', 18 | pid: 'e1', 19 | idx: 0 20 | }, { 21 | id: 'e3', 22 | label: '容器', 23 | type: 'div', 24 | style: 'width:100px;height:100px;background:blue;', 25 | idx: 1, 26 | pid: 'root' 27 | }] 28 | 29 | describe('树形结构转换', () => { 30 | it('tree to list', () => { 31 | console.log(toList(egTree)) 32 | // expect(toList(egTree)).to.be.equal(egList) 33 | }) 34 | 35 | it('list to tree', () => { 36 | console.log(toTree(egList)) 37 | // expect(toTree(egList)) 38 | // .to.be.equal(egTree) 39 | }) 40 | }) 41 | --------------------------------------------------------------------------------