├── .babelrc ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── build └── icons │ ├── 256x256.png │ ├── icon.icns │ ├── icon.ico │ └── logo.png ├── docs ├── .nojekyll ├── README.md ├── download.md ├── images │ ├── screen │ │ ├── export.png │ │ └── index.png │ └── son.jpg ├── index.html └── landing.html ├── package.json ├── src ├── index.ejs ├── main │ ├── index.dev.js │ └── index.js └── renderer │ ├── App.vue │ ├── assets │ ├── .gitkeep │ ├── logo.png │ └── styles │ │ └── tree-view.less │ ├── components │ ├── code-block.vue │ ├── config.vue │ ├── filter-editor.vue │ ├── layout │ │ ├── app-header.vue │ │ ├── app-slider.vue │ │ ├── project-config.vue │ │ └── table-config.vue │ ├── multi-type-input.vue │ └── props-add.vue │ ├── config │ ├── helper │ │ ├── index.js │ │ ├── my-helper.js │ │ └── nei-toolkit.js │ └── tool.js │ ├── main.js │ ├── mixins │ └── base.js │ ├── project-configs │ ├── README.md │ ├── built-in-field-options.js │ ├── index.js │ ├── laravel │ │ ├── auto.js │ │ ├── field-options.js │ │ ├── fields.js │ │ └── index.js │ └── springBoot │ │ ├── auto.js │ │ ├── field-options.js │ │ ├── fields.js │ │ └── index.js │ ├── router │ └── index.js │ ├── services │ ├── project.js │ └── sql.js │ ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── localstorage-keys.js │ ├── modules │ │ ├── index.js │ │ ├── projects.js │ │ ├── settings.js │ │ ├── styles.js │ │ └── template.js │ └── mutation-types.js │ ├── utils │ ├── electron.js │ ├── hbs-helpers │ │ ├── built-in-hbs-helper.js │ │ ├── huntbao-hbs-helper.js │ │ └── index.js │ ├── helper.js │ └── localstorage.js │ └── views │ ├── CreateModel.vue │ ├── ExportPage.vue │ ├── FieldEditor.vue │ ├── Helper.vue │ ├── Home.vue │ ├── NewProject.vue │ ├── Settings.vue │ └── Settings │ ├── Base.vue │ ├── Logs.vue │ └── Template.vue ├── static └── .gitkeep ├── test ├── .eslintrc ├── e2e │ ├── index.js │ ├── specs │ │ └── Launch.spec.js │ └── utils.js └── unit │ ├── index.js │ ├── karma.conf.js │ └── specs │ └── LandingPage.spec.js ├── wechat.jpg └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "test": { 5 | "presets": [ 6 | ["env", { 7 | "targets": { "node": 7 } 8 | }], 9 | "stage-0" 10 | ], 11 | "plugins": ["istanbul"] 12 | }, 13 | "main": { 14 | "presets": [ 15 | ["env", { 16 | "targets": { "node": 7 } 17 | }], 18 | "stage-0" 19 | ] 20 | }, 21 | "renderer": { 22 | "presets": [ 23 | ["env", { 24 | "modules": false 25 | }], 26 | "stage-0" 27 | ] 28 | }, 29 | "web": { 30 | "presets": [ 31 | ["env", { 32 | "modules": false 33 | }], 34 | "stage-0" 35 | ] 36 | } 37 | }, 38 | "plugins": ["transform-runtime"] 39 | } 40 | -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | const webConfig = require('./webpack.web.config') 16 | 17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 20 | const isCI = process.env.CI || false 21 | 22 | if (process.env.BUILD_TARGET === 'clean') clean() 23 | else if (process.env.BUILD_TARGET === 'web') web() 24 | else build() 25 | 26 | function clean () { 27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 28 | console.log(`\n${doneLog}\n`) 29 | process.exit() 30 | } 31 | 32 | function build () { 33 | greeting() 34 | 35 | del.sync(['dist/electron/*', '!.gitkeep']) 36 | 37 | const tasks = ['main', 'renderer'] 38 | const m = new Multispinner(tasks, { 39 | preText: 'building', 40 | postText: 'process' 41 | }) 42 | 43 | let results = '' 44 | 45 | m.on('success', () => { 46 | process.stdout.write('\x1B[2J\x1B[0f') 47 | console.log(`\n\n${results}`) 48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 49 | process.exit() 50 | }) 51 | 52 | pack(mainConfig).then(result => { 53 | results += result + '\n\n' 54 | m.success('main') 55 | }).catch(err => { 56 | m.error('main') 57 | console.log(`\n ${errorLog}failed to build main process`) 58 | console.error(`\n${err}\n`) 59 | process.exit(1) 60 | }) 61 | 62 | pack(rendererConfig).then(result => { 63 | results += result + '\n\n' 64 | m.success('renderer') 65 | }).catch(err => { 66 | m.error('renderer') 67 | console.log(`\n ${errorLog}failed to build renderer process`) 68 | console.error(`\n${err}\n`) 69 | process.exit(1) 70 | }) 71 | } 72 | 73 | function pack (config) { 74 | return new Promise((resolve, reject) => { 75 | webpack(config, (err, stats) => { 76 | if (err) reject(err.stack || err) 77 | else if (stats.hasErrors()) { 78 | let err = '' 79 | 80 | stats.toString({ 81 | chunks: false, 82 | colors: true 83 | }) 84 | .split(/\r?\n/) 85 | .forEach(line => { 86 | err += ` ${line}\n` 87 | }) 88 | 89 | reject(err) 90 | } else { 91 | resolve(stats.toString({ 92 | chunks: false, 93 | colors: true 94 | })) 95 | } 96 | }) 97 | }) 98 | } 99 | 100 | function web () { 101 | del.sync(['dist/web/*', '!.gitkeep']) 102 | webpack(webConfig, (err, stats) => { 103 | if (err || stats.hasErrors()) console.log(err) 104 | 105 | console.log(stats.toString({ 106 | chunks: false, 107 | colors: true 108 | })) 109 | 110 | process.exit() 111 | }) 112 | } 113 | 114 | function greeting () { 115 | const cols = process.stdout.columns 116 | let text = '' 117 | 118 | if (cols > 85) text = 'lets-build' 119 | else if (cols > 60) text = 'lets-|build' 120 | else text = false 121 | 122 | if (text && !isCI) { 123 | say(text, { 124 | colors: ['yellow'], 125 | font: 'simple3d', 126 | space: false 127 | }) 128 | } else console.log(chalk.yellow.bold('\n lets-build')) 129 | console.log() 130 | } 131 | -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | */ 7 | if (event.action === 'reload') { 8 | window.location.reload() 9 | } 10 | 11 | /** 12 | * Notify `mainWindow` when `main` process is compiling, 13 | * giving notice for an expected reload of the `electron` process 14 | */ 15 | if (event.action === 'compiling') { 16 | document.body.innerHTML += ` 17 | 30 | 31 |
32 | Compiling Main Process... 33 |
34 | ` 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats (proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer () { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.plugin('compilation', compilation => { 52 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.plugin('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | setup (app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain () { 81 | return new Promise((resolve, reject) => { 82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) 83 | 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.plugin('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron () { 117 | electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')]) 118 | 119 | electronProcess.stdout.on('data', data => { 120 | electronLog(data, 'blue') 121 | }) 122 | electronProcess.stderr.on('data', data => { 123 | electronLog(data, 'red') 124 | }) 125 | 126 | electronProcess.on('close', () => { 127 | if (!manualRestart) process.exit() 128 | }) 129 | } 130 | 131 | function electronLog (data, color) { 132 | let log = '' 133 | data = data.toString().split(/\r?\n/) 134 | data.forEach(line => { 135 | log += ` ${line}\n` 136 | }) 137 | if (/[0-9A-z]+/.test(log)) { 138 | console.log( 139 | chalk[color].bold('┏ Electron -------------------') + 140 | '\n\n' + 141 | log + 142 | chalk[color].bold('┗ ----------------------------') + 143 | '\n' 144 | ) 145 | } 146 | } 147 | 148 | function greeting () { 149 | const cols = process.stdout.columns 150 | let text = '' 151 | 152 | if (cols > 104) text = 'electron-vue' 153 | else if (cols > 76) text = 'electron-|vue' 154 | else text = false 155 | 156 | if (text) { 157 | say(text, { 158 | colors: ['yellow'], 159 | font: 'simple3d', 160 | space: false 161 | }) 162 | } else console.log(chalk.yellow.bold('\n electron-vue')) 163 | console.log(chalk.blue(' getting ready...') + '\n') 164 | } 165 | 166 | function init () { 167 | greeting() 168 | 169 | Promise.all([startRenderer(), startMain()]) 170 | .then(() => { 171 | startElectron() 172 | }) 173 | .catch(err => { 174 | console.error(err) 175 | }) 176 | } 177 | 178 | init() 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js') 14 | }, 15 | externals: [ 16 | ...Object.keys(dependencies || {}) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js)$/, 22 | enforce: 'pre', 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'eslint-loader', 26 | options: { 27 | formatter: require('eslint-friendly-formatter') 28 | } 29 | } 30 | }, 31 | { 32 | test: /\.js$/, 33 | use: 'babel-loader', 34 | exclude: /node_modules/ 35 | }, 36 | { 37 | test: /\.node$/, 38 | use: 'node-loader' 39 | } 40 | ] 41 | }, 42 | node: { 43 | __dirname: process.env.NODE_ENV !== 'production', 44 | __filename: process.env.NODE_ENV !== 'production' 45 | }, 46 | output: { 47 | filename: '[name].js', 48 | libraryTarget: 'commonjs2', 49 | path: path.join(__dirname, '../dist/electron') 50 | }, 51 | plugins: [ 52 | new webpack.NoEmitOnErrorsPlugin() 53 | ], 54 | resolve: { 55 | extensions: ['.js', '.json', '.node'] 56 | }, 57 | target: 'electron-main' 58 | } 59 | 60 | /** 61 | * Adjust mainConfig for development settings 62 | */ 63 | if (process.env.NODE_ENV !== 'production') { 64 | mainConfig.plugins.push( 65 | new webpack.DefinePlugin({ 66 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 67 | }) 68 | ) 69 | } 70 | 71 | /** 72 | * Adjust mainConfig for production settings 73 | */ 74 | if (process.env.NODE_ENV === 'production') { 75 | mainConfig.plugins.push( 76 | new BabiliWebpackPlugin(), 77 | new webpack.DefinePlugin({ 78 | 'process.env.NODE_ENV': '"production"' 79 | }) 80 | ) 81 | } 82 | 83 | module.exports = mainConfig 84 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | 14 | /** 15 | * List of node_modules to include in webpack bundle 16 | * 17 | * Required for specific packages like Vue UI libraries 18 | * that provide pure *.vue files that need compiling 19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 20 | */ 21 | let whiteListedModules = ['vue'] 22 | 23 | let rendererConfig = { 24 | devtool: '#cheap-module-eval-source-map', 25 | entry: { 26 | renderer: path.join(__dirname, '../src/renderer/main.js') 27 | }, 28 | externals: [ 29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.(js|vue)$/, 35 | enforce: 'pre', 36 | exclude: /node_modules/, 37 | use: { 38 | loader: 'eslint-loader', 39 | options: { 40 | formatter: require('eslint-friendly-formatter') 41 | } 42 | } 43 | }, 44 | { 45 | test: /\.css$/, 46 | use: ExtractTextPlugin.extract({ 47 | fallback: 'style-loader', 48 | use: 'css-loader' 49 | }) 50 | }, 51 | { 52 | test: /\.html$/, 53 | use: 'vue-html-loader' 54 | }, 55 | { 56 | test: /\.js$/, 57 | use: 'babel-loader', 58 | exclude: /node_modules/ 59 | }, 60 | { 61 | test: /\.node$/, 62 | use: 'node-loader' 63 | }, 64 | { 65 | test: /\.vue$/, 66 | use: { 67 | loader: 'vue-loader', 68 | options: { 69 | extractCSS: process.env.NODE_ENV === 'production', 70 | loaders: { 71 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 72 | scss: 'vue-style-loader!css-loader!sass-loader' 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 79 | use: { 80 | loader: 'url-loader', 81 | query: { 82 | limit: 10000, 83 | name: 'imgs/[name]--[folder].[ext]' 84 | } 85 | } 86 | }, 87 | { 88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | name: 'media/[name]--[folder].[ext]' 93 | } 94 | }, 95 | { 96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 97 | use: { 98 | loader: 'url-loader', 99 | query: { 100 | limit: 10000, 101 | name: 'fonts/[name]--[folder].[ext]' 102 | } 103 | } 104 | } 105 | ] 106 | }, 107 | node: { 108 | __dirname: process.env.NODE_ENV !== 'production', 109 | __filename: process.env.NODE_ENV !== 'production' 110 | }, 111 | plugins: [ 112 | new ExtractTextPlugin('styles.css'), 113 | new HtmlWebpackPlugin({ 114 | filename: 'index.html', 115 | template: path.resolve(__dirname, '../src/index.ejs'), 116 | minify: { 117 | collapseWhitespace: true, 118 | removeAttributeQuotes: true, 119 | removeComments: true 120 | }, 121 | nodeModules: process.env.NODE_ENV !== 'production' 122 | ? path.resolve(__dirname, '../node_modules') 123 | : false 124 | }), 125 | new webpack.HotModuleReplacementPlugin(), 126 | new webpack.NoEmitOnErrorsPlugin() 127 | ], 128 | output: { 129 | filename: '[name].js', 130 | libraryTarget: 'commonjs2', 131 | path: path.join(__dirname, '../dist/electron') 132 | }, 133 | resolve: { 134 | alias: { 135 | '@': path.join(__dirname, '../src/renderer'), 136 | 'vue$': 'vue/dist/vue.esm.js' 137 | }, 138 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 139 | }, 140 | target: 'electron-renderer' 141 | } 142 | 143 | /** 144 | * Adjust rendererConfig for development settings 145 | */ 146 | if (process.env.NODE_ENV !== 'production') { 147 | rendererConfig.plugins.push( 148 | new webpack.DefinePlugin({ 149 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 150 | }) 151 | ) 152 | } 153 | 154 | /** 155 | * Adjust rendererConfig for production settings 156 | */ 157 | if (process.env.NODE_ENV === 'production') { 158 | rendererConfig.devtool = '' 159 | 160 | rendererConfig.plugins.push( 161 | new BabiliWebpackPlugin(), 162 | new CopyWebpackPlugin([ 163 | { 164 | from: path.join(__dirname, '../static'), 165 | to: path.join(__dirname, '../dist/electron/static'), 166 | ignore: ['.*'] 167 | } 168 | ]), 169 | new webpack.DefinePlugin({ 170 | 'process.env.NODE_ENV': '"production"' 171 | }), 172 | new webpack.LoaderOptionsPlugin({ 173 | minimize: true 174 | }) 175 | ) 176 | } 177 | 178 | module.exports = rendererConfig 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.web.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'web' 4 | 5 | const path = require('path') 6 | const webpack = require('webpack') 7 | 8 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | 13 | let webConfig = { 14 | devtool: '#cheap-module-eval-source-map', 15 | entry: { 16 | web: path.join(__dirname, '../src/renderer/main.js') 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js|vue)$/, 22 | enforce: 'pre', 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'eslint-loader', 26 | options: { 27 | formatter: require('eslint-friendly-formatter') 28 | } 29 | } 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: ExtractTextPlugin.extract({ 34 | fallback: 'style-loader', 35 | use: 'css-loader' 36 | }) 37 | }, 38 | { 39 | test: /\.html$/, 40 | use: 'vue-html-loader' 41 | }, 42 | { 43 | test: /\.js$/, 44 | use: 'babel-loader', 45 | include: [ path.resolve(__dirname, '../src/renderer') ], 46 | exclude: /node_modules/ 47 | }, 48 | { 49 | test: /\.vue$/, 50 | use: { 51 | loader: 'vue-loader', 52 | options: { 53 | extractCSS: true, 54 | loaders: { 55 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 56 | scss: 'vue-style-loader!css-loader!sass-loader' 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 63 | use: { 64 | loader: 'url-loader', 65 | query: { 66 | limit: 10000, 67 | name: 'imgs/[name].[ext]' 68 | } 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | use: { 74 | loader: 'url-loader', 75 | query: { 76 | limit: 10000, 77 | name: 'fonts/[name].[ext]' 78 | } 79 | } 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | new ExtractTextPlugin('styles.css'), 85 | new HtmlWebpackPlugin({ 86 | filename: 'index.html', 87 | template: path.resolve(__dirname, '../src/index.ejs'), 88 | minify: { 89 | collapseWhitespace: true, 90 | removeAttributeQuotes: true, 91 | removeComments: true 92 | }, 93 | nodeModules: false 94 | }), 95 | new webpack.DefinePlugin({ 96 | 'process.env.IS_WEB': 'true' 97 | }), 98 | new webpack.HotModuleReplacementPlugin(), 99 | new webpack.NoEmitOnErrorsPlugin() 100 | ], 101 | output: { 102 | filename: '[name].js', 103 | path: path.join(__dirname, '../dist/web') 104 | }, 105 | resolve: { 106 | alias: { 107 | '@': path.join(__dirname, '../src/renderer'), 108 | 'vue$': 'vue/dist/vue.esm.js' 109 | }, 110 | extensions: ['.js', '.vue', '.json', '.css'] 111 | }, 112 | target: 'web' 113 | } 114 | 115 | /** 116 | * Adjust webConfig for production settings 117 | */ 118 | if (process.env.NODE_ENV === 'production') { 119 | webConfig.devtool = '' 120 | 121 | webConfig.plugins.push( 122 | new BabiliWebpackPlugin(), 123 | new CopyWebpackPlugin([ 124 | { 125 | from: path.join(__dirname, '../static'), 126 | to: path.join(__dirname, '../dist/web/static'), 127 | ignore: ['.*'] 128 | } 129 | ]), 130 | new webpack.DefinePlugin({ 131 | 'process.env.NODE_ENV': '"production"' 132 | }), 133 | new webpack.LoaderOptionsPlugin({ 134 | minimize: true 135 | }) 136 | ) 137 | } 138 | 139 | module.exports = webConfig 140 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/unit/coverage/** 2 | test/unit/*.js 3 | test/e2e/*.js 4 | src/renderer/config/** 5 | src/renderer/utils/** 6 | src/renderer/project-configs/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: 'standard', 12 | globals: { 13 | __static: true 14 | }, 15 | plugins: [ 16 | 'html' 17 | ], 18 | 'rules': { 19 | // allow paren-less arrow functions 20 | 'arrow-parens': 0, 21 | // allow async-await 22 | 'generator-star-spacing': 0, 23 | // allow debugger during development 24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | coverage 7 | node_modules/ 8 | npm-debug.log 9 | npm-debug.log.* 10 | thumbs.db 11 | !.gitkeep 12 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Commented sections below can be used to run tests on the CI server 2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing 3 | osx_image: xcode8.3 4 | sudo: required 5 | dist: trusty 6 | language: c 7 | matrix: 8 | include: 9 | - os: osx 10 | - os: linux 11 | env: CC=clang CXX=clang++ npm_config_clang=1 12 | compiler: clang 13 | cache: 14 | directories: 15 | - node_modules 16 | - "$HOME/.electron" 17 | - "$HOME/.cache" 18 | addons: 19 | apt: 20 | packages: 21 | - libgnome-keyring-dev 22 | - icnsutils 23 | #- xvfb 24 | before_install: 25 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 26 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 27 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 28 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 29 | install: 30 | #- export DISPLAY=':99.0' 31 | #- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 32 | - nvm install 7 33 | - curl -o- -L https://yarnpkg.com/install.sh | bash 34 | - source ~/.bashrc 35 | - npm install -g xvfb-maybe 36 | - yarn 37 | script: 38 | #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js 39 | #- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e 40 | - yarn run build 41 | branches: 42 | only: 43 | - master 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Deboy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [Document](https://modelmakerpro.github.io/model-maker-pro/#/) 2 | ## !注意! 3 | 该软件需要配合模版使用 [templates](https://github.com/ModelMakerPro/templates) 4 | 5 | 我近期还会出一份具体的使用教程 原谅我这段时间比较忙 可以先`star`🙏 6 | 7 | 等我把目前发现的 `bug` 修复后会在 `掘金` 和`laravel中文社区` 发文章推广 届时再使用体验更佳 8 | 9 | 想现在就加入开发的可以联系我 10 | 11 | 微信 12 | ![wechat](./wechat.jpg) 13 | 14 | ## 软件介绍 15 | `ModelMakerPro` 一款以设计软件表结构为核心 通过与模版结合 生成各种文件的工具 16 | 模版可以是`Java`项目的 17 | `Domain` `Repository` `Controller` `Service` 18 | 也可以是`Laravel`项目的 19 | `routes` `Controller` `Model` 20 | 如果希望支持其他项目模版 可以参考 一个标准模版的[编写示范](https://github.com/ModelMakerPro/model-maker-pro/blob/master/src/renderer/config/default-row/laravel.js)提交你们的模版 21 | 等待你们发掘 22 | 23 | ## 开发技术栈: 24 | - [Vue.js](https://cn.vuejs.org/) 25 | - 以及了解 [electron](https://electron.atom.io/docs/) API 调用 就这么简单 26 | - 使用到的UI库: [iview](https://www.iviewui.com/) 27 | 28 | ## 截图 29 | ![首页](docs/images/screen/index.png) 30 | 31 | ![导出配置](docs/images/screen/export.png) 32 | 33 | ## Build Setup 34 | ``` bash 35 | # clone this rep 36 | git clone https://github.com/ModelMakerPro/model-maker-pro.git 37 | 38 | # install dependencies 39 | npm install 40 | # 国内请注意 electron 的源基本都下载不了 41 | # 参照这个替换国内源安装 42 | # https://npm.taobao.org/mirrors 43 | # http://blog.tomyail.com/install-electron-slow-in-china/ 44 | 45 | # serve with hot reload at localhost:9080 46 | npm run dev 47 | 48 | # build electron app for production 49 | npm run build 50 | 51 | # lint all JS/Vue component files in `app/src` 52 | npm run lint 53 | 54 | # run webpack in production 55 | npm run pack 56 | ``` 57 | 58 | ## 相关文档 59 | 60 | - electron: [https://electron.atom.io/docs/](https://electron.atom.io/docs/) 61 | 62 | - electron-vue: [https://github.com/SimulatedGREG/electron-vue](https://github.com/SimulatedGREG/electron-vue) 63 | 64 | - vue.js: [https://cn.vuejs.org/](https://cn.vuejs.org/) 65 | 66 | ## Q&A 67 | ### Q: 这玩意有什么用? 68 | A: 启动一个项目(带数据库) 如果是`java` 以 `SpringBoot` 框架为例 69 | 你需要为每个表都编写一份 `Domain` `Repository` `Controller` `Service` 70 | 几乎都是重复的工作 为何不将这些工作交给机器? 71 | 72 | ### Q: 怎么用 73 | A: 74 | 1. 你可以根据操作系统不同直接下载我们为你打包好的软件 然后通过遵循文档来使用 75 | 2. `clone` 这个 `repository` 然后自己修改想要适应你们项目的部分 打包 内部使用 76 | 3. 如果你觉得这个功能(可以是某个字段应该是默认的或者某个配置应该是默认的)应该是普遍 或者你发现并解决了软件的BUG 77 | 可以通过提交 `pull request` 或者 `issue` 来参与我们软件的开发 让这款软件更加完美 78 | 79 | ### Q: 我的项目用XXX框架的能用吗? 80 | A: 当然可以 81 | 如果希望支持其他项目模版 可以参考 82 | 一个标准模版的[编写示范](https://github.com/deboyblog/model-maker-pro/blob/master/src/renderer/config/default-row/springboot.js) 83 | 强烈欢迎你们提交不同框架的模版 这会让软件更加强大 你也可以维护原有的模版 增强原模版的扩展性 84 | 85 | ### Q: 为什么是 Pro 版? 86 | A: 因为非 Pro 版 我和我司已经踩够坑了 87 | 我司主要后端是`Java`(`SpringBoot`) 我个人后端更偏爱 `PHP`(`Laravel`) 其实我最爱`js` 哈哈 88 | 刚开始写这个项目是结合我司其他方案整合一整套`CMS`生成系统的 89 | 后来我又改成了`Laravel`适用的方案(`ModelMakerForLaravel`) 90 | 但是我发现字段等配置可以独立出来成为配置 于是就 合体 合并成为Pro 版了 91 | 经过我司同意(这个软件是我一人编写的), 我决定将其开源, 我希望它能在开源的环境下得到成长. 92 | 93 | ### Q: 我能放心使用吗 94 | A: 经过我司实践 已经生成了一个项目 95 | 但是呢 软件目前为止(2017-6-17) 96 | 还有以下工作等待我去完成 97 | - [] 目录重新划分 98 | - [] 配置抽离 99 | - [] 完善使用文档 100 | 101 | 你们可以尝试使用, 如果可以的话, 请把你们遇到的问题和觉得不合理的交互提交到[issues](https://github.com/deboyblog/model-maker-pro/issues)中 102 | 我会抽时间完善它 103 | 毕竟是亲生的 104 | ![](docs/images/son.jpg) 105 | ### Q: 需要付费吗? 106 | A: 完全不需要 107 | ## License 108 | [MIT](https://github.com/ModelMakerPro/model-maker-pro/blob/master/LICENSE) 109 | 110 | ## [Document](https://deboyblog.github.io/model-maker-pro/#/) 111 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Commented sections below can be used to run tests on the CI server 2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing 3 | version: 0.1.{build} 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | image: Visual Studio 2017 10 | platform: 11 | - x64 12 | 13 | cache: 14 | - node_modules 15 | - '%APPDATA%\npm-cache' 16 | - '%USERPROFILE%\.electron' 17 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 18 | 19 | init: 20 | - git config --global core.autocrlf input 21 | 22 | install: 23 | - ps: Install-Product node 8 x64 24 | - choco install yarn --ignore-dependencies 25 | - git reset --hard HEAD 26 | - yarn 27 | - node --version 28 | 29 | build_script: 30 | #- yarn test 31 | - yarn build 32 | 33 | test: off 34 | -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/build/icons/icon.ico -------------------------------------------------------------------------------- /build/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/build/icons/logo.png -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## 软件介绍 2 | `ModelMakerPro` 一款以设计软件表结构为核心 通过与模版结合 生成各种文件的工具 3 | 模版可以是`Java`项目的 4 | `Domain` `Repository` `Controller` `Service` 5 | 也可以是`Laravel`项目的 6 | `routes` `Controller` `Model` 7 | 如果希望支持其他项目模版 可以参考 一个标准模版的[编写示范](https://github.com/ModelMakerPro/model-maker-pro/blob/master/src/renderer/config/default-row/laravel.js)提交你们的模版 8 | 等待你们发掘 9 | 10 | ## 开发技术栈: 11 | - [Vue.js](https://cn.vuejs.org/) 12 | - 以及了解 [electron](https://electron.atom.io/docs/) API 调用 就这么简单 13 | - 使用到的UI库: [iview](https://www.iviewui.com/) 14 | 15 | ## 截图 16 | ![首页](images/screen/index.png) 17 | 18 | ![导出配置](images/screen/export.png) 19 | 20 | ## 如何新增 语言/框架 支持 21 | 1. `fork` 项目 22 | 2. 下载到本地 到 `/src/renderer/config/default-row` 目录下拷贝一份非`index.js`的文件 23 | 3. 重命名为你的框架的名字 后缀必须是.js 放到同级目录 24 | 4. 提交 然后 创建 `pull request` 25 | 26 | ## 模版编写规范(导出才用到的) 27 | 28 | [点我点我: https://github.com/ModelMakerPro/templates](https://github.com/ModelMakerPro/templates) 29 | 30 | ### 模版帮助函数(helper) 31 | > 在软件中也可以看到 并且是最新的 32 | 33 | - [assemble](http://assemble.io/helpers) 34 | - [Handlebars辅助函数集](https://github.com/NEYouFan/nei-toolkit/blob/master/doc/Handlebars%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0%E9%9B%86.md) 35 | 36 | ## Build Setup 37 | ``` bash 38 | # clone this rep 39 | git clone https://github.com/deboyblog/model-maker-pro.git 40 | 41 | # install dependencies 42 | npm install 43 | # 国内请注意 electron 的源基本都下载不了 44 | # 参照这个替换国内源安装 45 | # https://npm.taobao.org/mirrors 46 | # http://blog.tomyail.com/install-electron-slow-in-china/ 47 | 48 | # serve with hot reload at localhost:9080 49 | npm run dev 50 | 51 | # build electron app for production 52 | npm run build 53 | 54 | # lint all JS/Vue component files in `app/src` 55 | npm run lint 56 | 57 | # run webpack in production 58 | npm run pack 59 | ``` 60 | 61 | ## 相关文档 62 | 63 | - electron: [https://electron.atom.io/docs/](https://electron.atom.io/docs/) 64 | 65 | - electron-vue: [https://github.com/SimulatedGREG/electron-vue](https://github.com/SimulatedGREG/electron-vue) 66 | 67 | - vue.js: [https://cn.vuejs.org/](https://cn.vuejs.org/) 68 | 69 | ## Q&A 70 | ### Q: 这玩意有什么用? 71 | A: 启动一个项目(带数据库) 如果是`java` 以 `SpringBoot` 框架为例 72 | 你需要为每个表都编写一份 `Domain` `Repository` `Controller` `Service` 73 | 几乎都是重复的工作 为何不将这些工作交给机器? 74 | 75 | ### Q: 怎么用 76 | A: 77 | 1. 你可以根据操作系统不同直接下载我们为你打包好的软件 然后通过遵循文档来使用 78 | 2. `clone` 这个 `repository` 然后自己修改想要适应你们项目的部分 打包 内部使用 79 | 3. 如果你觉得这个功能(可以是某个字段应该是默认的或者某个配置应该是默认的)应该是普遍 或者你发现并解决了软件的BUG 80 | 可以通过提交 `pull request` 或者 `issue` 来参与我们软件的开发 让这款软件更加完美 81 | 82 | ### Q: 我的项目用XXX框架的能用吗? 83 | A: 当然可以 84 | 如果希望支持其他项目模版 可以参考 85 | 一个标准模版的[编写示范](https://github.com/deboyblog/model-maker-pro/blob/master/src/renderer/config/default-row/springboot.js) 86 | 强烈欢迎你们提交不同框架的模版 这会让软件更加强大 你也可以维护原有的模版 增强原模版的扩展性 87 | 88 | ### Q: 为什么是 Pro 版? 89 | A: 因为非 Pro 版 我和我司已经踩够坑了 90 | 我司主要后端是`Java`(`SpringBoot`) 我个人后端更偏爱 `PHP`(`Laravel`) 其实我最爱`js` 哈哈 91 | 刚开始写这个项目是结合我司其他方案整合一整套`CMS`生成系统的 92 | 后来我又改成了`Laravel`适用的方案(`ModelMakerForLaravel`) 93 | 但是我发现字段等配置可以独立出来成为配置 于是就 合体 合并成为Pro 版了 94 | 经过我司同意(这个软件是我一人编写的), 我决定将其开源, 我希望它能在开源的环境下得到成长. 95 | 96 | ### Q: 我能放心使用吗 97 | A: 经过我司实践 已经生成了一个项目 98 | 但是呢 软件目前为止(2017-6-17) 99 | 还有以下工作等待我去完成 100 | - [] 目录重新划分 101 | - [] 配置抽离 102 | - [] 完善使用文档 103 | 104 | 你们可以尝试使用, 如果可以的话, 请把你们遇到的问题和觉得不合理的交互提交到[issues](https://github.com/deboyblog/model-maker-pro/issues)中 105 | 我会抽时间完善它 106 | 毕竟是亲生的 107 | ![](images/son.jpg) 108 | ### Q: 需要付费吗? 109 | A: 完全不需要 110 | ## License 111 | [MIT](https://en.wikipedia.org/wiki/MIT_License) 112 | -------------------------------------------------------------------------------- /docs/download.md: -------------------------------------------------------------------------------- 1 | # Mac 2 | [ModelMakerPro](https://github.com/deboyblog/model-maker-pro) 3 | # Window 4 | [ModelMakerPro](https://github.com/deboyblog/model-maker-pro) 5 | # Linux 6 | [ModelMakerPro](https://github.com/deboyblog/model-maker-pro) 7 | 8 | # look fro template ? 9 | [Official templates rep](https://github.com/ModelMakerPro/templates) 10 | 11 | # looking for admin panel ? 12 | [vue-admin-panel](https://github.com/ModelMakerPro/vue-admin-panel) -------------------------------------------------------------------------------- /docs/images/screen/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/docs/images/screen/export.png -------------------------------------------------------------------------------- /docs/images/screen/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/docs/images/screen/index.png -------------------------------------------------------------------------------- /docs/images/son.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/docs/images/son.jpg -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ModelMakerPro-一个模型+模版 一键生成RESTful接口 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/landing.html: -------------------------------------------------------------------------------- 1 |

Model Maker Pro

2 | 3 |

项目模型 + 各种模版 = 完整RESTful接口.

4 | 5 | 12 | 13 |
14 | 15 | 源码 16 | 17 | 18 | 下载(等我的MBP到了再发布版本) 19 | 20 | 21 | 文档 22 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "model-maker-pro", 3 | "version": "0.3.0", 4 | "author": "Deboy <55631825@qq.com>", 5 | "description": "Automatic generation of models based on user-defined templates + data structures", 6 | "license": null, 7 | "main": "./dist/electron/main.js", 8 | "scripts": { 9 | "build": "node .electron-vue/build.js && electron-builder", 10 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 11 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 12 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 13 | "dev": "node .electron-vue/dev-runner.js", 14 | "e2e": "npm run pack && mocha test/e2e", 15 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test", 16 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src test", 17 | "pack": "npm run pack:main && npm run pack:renderer", 18 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 19 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 20 | "test": "npm run unit && npm run e2e", 21 | "unit": "karma start test/unit/karma.conf.js", 22 | "postinstall": "npm run lint:fix" 23 | }, 24 | "build": { 25 | "productName": "ModelMakerPro", 26 | "appId": "cn.deboy.model-maker-pro", 27 | "directories": { 28 | "output": "build" 29 | }, 30 | "files": [ 31 | "dist/electron/**/*" 32 | ], 33 | "dmg": { 34 | "contents": [ 35 | { 36 | "x": 410, 37 | "y": 150, 38 | "type": "link", 39 | "path": "/Applications" 40 | }, 41 | { 42 | "x": 130, 43 | "y": 150, 44 | "type": "file" 45 | } 46 | ] 47 | }, 48 | "mac": { 49 | "icon": "build/icons/icon.icns" 50 | }, 51 | "win": { 52 | "icon": "build/icons/icon.ico" 53 | }, 54 | "linux": { 55 | "icon": "build/icons" 56 | } 57 | }, 58 | "dependencies": { 59 | "axios": "^0.16.1", 60 | "child_process": "^1.0.2", 61 | "handlebars": "^4.0.11", 62 | "handlebars-helpers": "^0.9.8", 63 | "highlight.js": "^9.12.0", 64 | "iview": "^2.6.0", 65 | "javascript-stringify": "^1.6.0", 66 | "open": "^0.0.5", 67 | "require-from-string": "^2.0.1", 68 | "sequelize": "^4.19.0", 69 | "vue": "^2.3.3", 70 | "vue-electron": "^1.0.6", 71 | "vue-json-tree-view": "^2.1.1", 72 | "vue-router": "^2.5.3", 73 | "vuex": "^2.3.1" 74 | }, 75 | "devDependencies": { 76 | "babel-core": "^6.25.0", 77 | "babel-eslint": "^7.2.3", 78 | "babel-loader": "^7.1.1", 79 | "babel-plugin-istanbul": "^4.1.1", 80 | "babel-plugin-transform-runtime": "^6.23.0", 81 | "babel-preset-env": "^1.6.0", 82 | "babel-preset-stage-0": "^6.24.1", 83 | "babel-register": "^6.24.1", 84 | "babili-webpack-plugin": "^0.1.2", 85 | "cfonts": "^1.1.3", 86 | "chai": "^4.0.0", 87 | "chalk": "^2.1.0", 88 | "copy-webpack-plugin": "^4.0.1", 89 | "cross-env": "^5.0.5", 90 | "css-loader": "^0.28.4", 91 | "del": "^3.0.0", 92 | "devtron": "^1.4.0", 93 | "electron": "^1.8.1", 94 | "electron-builder": "^19.19.1", 95 | "electron-debug": "^1.4.0", 96 | "electron-devtools-installer": "^2.2.0", 97 | "eslint": "^4.4.1", 98 | "eslint-config-standard": "^10.2.1", 99 | "eslint-friendly-formatter": "^3.0.0", 100 | "eslint-loader": "^1.9.0", 101 | "eslint-plugin-html": "^3.1.1", 102 | "eslint-plugin-import": "^2.7.0", 103 | "eslint-plugin-node": "^5.1.1", 104 | "eslint-plugin-promise": "^3.5.0", 105 | "eslint-plugin-standard": "^3.0.1", 106 | "extract-text-webpack-plugin": "^3.0.0", 107 | "file-loader": "^0.11.2", 108 | "html-webpack-plugin": "^2.30.1", 109 | "inject-loader": "^3.0.0", 110 | "karma": "^1.3.0", 111 | "karma-chai": "^0.1.0", 112 | "karma-coverage": "^1.1.1", 113 | "karma-electron": "^5.1.1", 114 | "karma-mocha": "^1.2.0", 115 | "karma-sourcemap-loader": "^0.3.7", 116 | "karma-spec-reporter": "^0.0.31", 117 | "karma-webpack": "^2.0.1", 118 | "less": "^2.7.3", 119 | "less-loader": "^4.0.5", 120 | "mocha": "^3.0.2", 121 | "multispinner": "^0.2.1", 122 | "node-loader": "^0.6.0", 123 | "require-dir": "^0.3.0", 124 | "spectron": "^3.7.1", 125 | "style-loader": "^0.18.2", 126 | "url-loader": "^0.5.9", 127 | "vue-html-loader": "^1.2.4", 128 | "vue-loader": "^13.0.5", 129 | "vue-style-loader": "^3.0.1", 130 | "vue-template-compiler": "^2.4.2", 131 | "webpack": "^3.5.2", 132 | "webpack-dev-server": "^2.7.1", 133 | "webpack-hot-middleware": "^2.18.2", 134 | "webpack-merge": "^4.1.0" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ModelMakerPro 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Set environment for development 11 | process.env.NODE_ENV = 'development' 12 | 13 | // Install `electron-debug` with `devtron` 14 | require('electron-debug')({ showDevTools: true }) 15 | 16 | // Install `vue-devtools` 17 | require('electron').app.on('ready', () => { 18 | let installExtension = require('electron-devtools-installer') 19 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 20 | .then(() => {}) 21 | .catch(err => { 22 | console.log('Unable to install `vue-devtools`: \n', err) 23 | }) 24 | }) 25 | 26 | // Require `main` process to boot app 27 | require('./index') 28 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app, BrowserWindow, Menu } from 'electron' 4 | 5 | /** 6 | * Set `__static` path to static files in production 7 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 8 | */ 9 | if (process.env.NODE_ENV !== 'development') { 10 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') 11 | } 12 | 13 | let mainWindow 14 | const winURL = process.env.NODE_ENV === 'development' 15 | ? `http://localhost:9080` 16 | : `file://${__dirname}/index.html` 17 | 18 | function createWindow () { 19 | /** 20 | * Initial window options 21 | */ 22 | mainWindow = new BrowserWindow({ 23 | height: 800, 24 | useContentSize: true, 25 | width: 1700 26 | }) 27 | 28 | mainWindow.loadURL(winURL) 29 | 30 | mainWindow.on('closed', () => { 31 | mainWindow = null 32 | }) 33 | mainWindow.on('ready-to-show', () => { 34 | mainWindow.show() 35 | mainWindow.focus() 36 | }) 37 | // 创建菜单 38 | let template = [{ 39 | label: '应用', 40 | submenu: [ 41 | { 42 | label: '关于ModelMakerPro', 43 | click: function (e) {} 44 | }, 45 | {type: 'separator'}, 46 | {label: '退出', accelerator: 'Command+Q', click: function () { app.quit() }} 47 | ] 48 | }, { 49 | label: '编辑', 50 | submenu: [ 51 | {label: '返回', accelerator: 'CmdOrCtrl+Z', selector: 'undo:'}, 52 | {label: '重新执行', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:'}, 53 | {type: 'separator'}, 54 | {label: '剪切', accelerator: 'CmdOrCtrl+X', selector: 'cut:'}, 55 | {label: '复制', accelerator: 'CmdOrCtrl+C', selector: 'copy:'}, 56 | {label: '粘贴', accelerator: 'CmdOrCtrl+V', selector: 'paste:'}, 57 | {label: '全选', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:'} 58 | ] 59 | } 60 | ] 61 | 62 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 63 | // eslint-disable-next-line no-console 64 | console.log('mainWindow opened') 65 | } 66 | 67 | app.on('ready', createWindow) 68 | 69 | app.on('window-all-closed', () => { 70 | if (process.platform !== 'darwin') { 71 | app.quit() 72 | } 73 | }) 74 | 75 | app.on('activate', () => { 76 | if (mainWindow === null) { 77 | createWindow() 78 | } 79 | }) 80 | 81 | /** 82 | * Auto Updater 83 | * 84 | * Uncomment the following code below and install `electron-updater` to 85 | * support auto updating. Code Signing with a valid certificate is required. 86 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 87 | */ 88 | 89 | /* 90 | import { autoUpdater } from 'electron-updater' 91 | 92 | autoUpdater.on('update-downloaded', () => { 93 | autoUpdater.quitAndInstall() 94 | }) 95 | 96 | app.on('ready', () => { 97 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 98 | }) 99 | */ 100 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 25 | 79 | 113 | 163 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/assets/styles/tree-view.less: -------------------------------------------------------------------------------- 1 | @booleancolor: #ae81ff; 2 | @stringcolor: #e6db74; 3 | .tree-view-wrapper{ 4 | background: #23241f; 5 | color: #e6db74; 6 | line-height: 1.5; 7 | min-height: 100%; 8 | .tree-view-item-key, .tree-view-item-value{ 9 | font-family: Consolas,Menlo,Courier,monospace; 10 | font-size: 1.2em; 11 | font-weight: lighter !important; 12 | } 13 | .tree-view-item-key-with-chevron::before{ 14 | color: white !important; 15 | } 16 | .tree-view-item-value{ 17 | &-null, &-boolean, &-number{ 18 | color: @booleancolor; 19 | } 20 | } 21 | .tree-view-item-hint{ 22 | font-weight: 600; 23 | font-size: 1.1em; 24 | } 25 | } -------------------------------------------------------------------------------- /src/renderer/components/code-block.vue: -------------------------------------------------------------------------------- 1 | 13 | 33 | 68 | -------------------------------------------------------------------------------- /src/renderer/components/config.vue: -------------------------------------------------------------------------------- 1 | 91 | 93 | 171 | -------------------------------------------------------------------------------- /src/renderer/components/filter-editor.vue: -------------------------------------------------------------------------------- 1 | 17 | 19 | 41 | -------------------------------------------------------------------------------- /src/renderer/components/layout/app-header.vue: -------------------------------------------------------------------------------- 1 | 39 | 64 | 96 | -------------------------------------------------------------------------------- /src/renderer/components/layout/app-slider.vue: -------------------------------------------------------------------------------- 1 | 81 | 116 | 205 | -------------------------------------------------------------------------------- /src/renderer/components/layout/project-config.vue: -------------------------------------------------------------------------------- 1 | 36 | 38 | 83 | -------------------------------------------------------------------------------- /src/renderer/components/layout/table-config.vue: -------------------------------------------------------------------------------- 1 | 38 | 40 | 75 | -------------------------------------------------------------------------------- /src/renderer/components/multi-type-input.vue: -------------------------------------------------------------------------------- 1 | 26 | 46 | 64 | -------------------------------------------------------------------------------- /src/renderer/components/props-add.vue: -------------------------------------------------------------------------------- 1 | 27 | 29 | 74 | -------------------------------------------------------------------------------- /src/renderer/config/helper/index.js: -------------------------------------------------------------------------------- 1 | const files = require.context('.', false, /\.js$/) 2 | let helpers = [] 3 | files.keys().forEach((key) => { 4 | if (key === './index.js') { 5 | return 6 | } 7 | helpers = [...helpers, ...files(key).default] 8 | }) 9 | 10 | export default helpers 11 | -------------------------------------------------------------------------------- /src/renderer/config/helper/my-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/16. 3 | */ 4 | export default [ 5 | { 6 | title: 'dashToCamel', 7 | comment: '下划线转驼峰', 8 | highlight: false, 9 | variable: { 10 | name: 'user_address' 11 | }, 12 | template: `class {{dashToCamel name}}Controller extend Controller { 13 | // code somethings 14 | }` 15 | }, 16 | { 17 | title: 'camelToDash', 18 | comment: '驼峰转下划线', 19 | highlight: false, 20 | variable: { 21 | name: 'userAddress' 22 | }, 23 | template: `{{camelToDash name}}` 24 | }, 25 | { 26 | title: 'json', 27 | comment: 'JSON格式化变量', 28 | highlight: false, 29 | variable: { 30 | address: { 31 | text: 'addresstext' 32 | }, 33 | user: { 34 | name: 'username' 35 | } 36 | }, 37 | template: `{{{json this true}}}` 38 | }, 39 | { 40 | title: 'multiple helper', 41 | comment: '同时使用多个Helper', 42 | highlight: false, 43 | variable: { 44 | name: 'user_address' 45 | }, 46 | template: `{{upperFirst (dashToCamel name)}}` 47 | } 48 | ] -------------------------------------------------------------------------------- /src/renderer/config/helper/nei-toolkit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/16. 3 | */ 4 | // https://github.com/NEYouFan/nei-toolkit/blob/master/doc/Handlebars%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0%E9%9B%86.md 5 | export default [ 6 | { 7 | title: 'raw', 8 | comment: '用它包裹的内容按原样输出', 9 | highlight: false, 10 | variable: { 11 | name: 'myname' 12 | }, 13 | template: `{{{{raw}}}} 14 | {{name}} 15 | {{{{/raw}}}}` 16 | }, 17 | { 18 | title: 'lowerFirst', 19 | comment: '将首字母小写', 20 | highlight: false, 21 | variable: { 22 | name: 'ProjectGroup' 23 | }, 24 | template: `{{lowerFirst name}}` 25 | }, 26 | { 27 | title: 'lowerFirst', 28 | comment: '将首字母小写', 29 | highlight: false, 30 | variable: { 31 | name: 'ProjectGroup' 32 | }, 33 | template: `{{lowerFirst name}}` 34 | }, 35 | { 36 | title: 'upperFirst', 37 | comment: '将首字母大写', 38 | highlight: false, 39 | variable: { 40 | name: 'id' 41 | }, 42 | template: `{{upperFirst name}}` 43 | } 44 | ] -------------------------------------------------------------------------------- /src/renderer/config/tool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/12. 3 | */ 4 | import projectConfigs from '../project-configs' 5 | import _ from 'lodash' 6 | 7 | /** 8 | * 根据类型 来合并某个字段 并返回合并后的字段列表 9 | * @param type 10 | * @param rows 11 | * @returns {Array} 12 | */ 13 | export const mergeDefaultRow = (type, rows) => { 14 | if (rows instanceof Object) { 15 | return _.merge({}, projectConfigs[type].fields, rows) 16 | } else { 17 | let list = [] 18 | rows.forEach((row) => { 19 | list.push(_.merge({}, projectConfigs[type].fields, row)) 20 | }) 21 | return list 22 | } 23 | } 24 | 25 | /** 26 | * 获取指定类型的的 字段配置 27 | * @param type 28 | * @returns {*} 29 | */ 30 | export const getDefaultRow = (type = 'laravel') => { 31 | return projectConfigs[type].fields 32 | } 33 | 34 | // 将属性的值直接赋给属性 不用多一层属性 减少项目结构 35 | let setValue = (obj = {}) => { 36 | for (let key in obj) { 37 | if (!key || obj[key].value === undefined) { 38 | continue 39 | } 40 | obj[key] = obj[key].value 41 | } 42 | return obj 43 | } 44 | // [{key: 'host', value: '127.0.0.1'}] => {host: '127.0.0.1'} 45 | let arrayToObject = (list = []) => { 46 | let obj = {} 47 | list.forEach(item => { 48 | obj[item.key] = item.value 49 | }) 50 | return obj 51 | } 52 | let splitToArray = (str) => { 53 | if (!str) { 54 | return null 55 | } 56 | let props = (str && str.split(';')) || [] 57 | let obj = {} 58 | props.forEach(prop => { 59 | let propArr = prop.split(':') 60 | obj[propArr[0]] = propArr[1] 61 | }) 62 | return obj 63 | } 64 | /** 65 | * 遍历一个字段属性 是数组的都转成 key -> value 对象 66 | * @param field 67 | * @returns {*} 68 | */ 69 | let setObjectPropToKeyValue = (field) => { 70 | Object.keys(field).forEach((key) => { 71 | if (field[key] instanceof Object && key !== 'options') { 72 | field[key] = setValue(field[key]) 73 | } 74 | }) 75 | return field 76 | } 77 | let dealWithFields = (field) => { 78 | field = setObjectPropToKeyValue(field) 79 | if (field.options) { 80 | field.options = splitToArray(field.options) 81 | } 82 | return field 83 | } 84 | // 查找关联表并设置one属性 85 | export const transformProjectInfo = (project) => { 86 | let originProject = JSON.parse(JSON.stringify(project)) 87 | project.props = arrayToObject(project.props) 88 | project.tables.forEach(table_1 => { 89 | // 将项目的属性也插入到表中 90 | table_1.projectProps = project.props 91 | table_1.props = arrayToObject(table_1.props) 92 | table_1.fields.forEach(originField => { 93 | let field 94 | // 将db vue 及项目自有属性都转化为 key=>vlaue 形式 方便模版渲染调用 95 | field = dealWithFields(originField) 96 | if (field.associationTable) { 97 | originProject.tables.forEach(table_2 => { 98 | if (field.associateAttributes) { 99 | return 100 | } 101 | if (table_2.id === field.associationTable) { 102 | let newTable = JSON.parse(JSON.stringify(table_2)) 103 | // 删除关联表的关联信息 防止死循环关联 104 | delete newTable.associateAttributes 105 | delete newTable.associationTable 106 | newTable.fields.map(_field => { 107 | return dealWithFields(_field) 108 | }) 109 | field.associateAttributes = table_2.fields 110 | field.associationTable = table_2.name 111 | return 112 | } 113 | }) 114 | } 115 | }) 116 | }) 117 | return project 118 | } 119 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import BaseMixin from './mixins/base' 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import iview from 'iview' 8 | import 'iview/dist/styles/iview.css' 9 | import { remote } from 'electron' 10 | import TreeView from 'vue-json-tree-view' 11 | 12 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 13 | Vue.use(iview) 14 | Vue.mixin(BaseMixin) 15 | Vue.use(require('./services/project')) 16 | Vue.use(TreeView) 17 | 18 | Vue.http = Vue.prototype.$http = axios 19 | Vue.config.productionTip = false 20 | Vue.config.debug = true 21 | 22 | remote.globalShortcut.register('CommandOrControl+Shift+K', () => { 23 | remote.BrowserWindow.getFocusedWindow().webContents.openDevTools() 24 | }) 25 | 26 | window.addEventListener('beforeunload', () => { 27 | remote.globalShortcut.unregisterAll() 28 | }) 29 | 30 | /* eslint-disable no-new */ 31 | new Vue({ 32 | router, 33 | store, 34 | ...App 35 | }).$mount('#app') 36 | -------------------------------------------------------------------------------- /src/renderer/mixins/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/3/25. 3 | */ 4 | export default { 5 | computed: { 6 | fullHeight () { 7 | return { 8 | height: this.$store.state.styles.fullHeight 9 | } 10 | }, 11 | contentWarpHeight () { 12 | return { 13 | height: this.$store.state.styles.contentWarpHeight, 14 | overflow: 'auto' 15 | } 16 | } 17 | }, 18 | methods: { 19 | clone (val) { 20 | let result = null 21 | try { 22 | let newVal = JSON.stringify(val) 23 | result = JSON.parse(newVal) 24 | } catch (e) { 25 | console.error('转换错误!请注意传值是否正常') 26 | } 27 | return result 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/project-configs/README.md: -------------------------------------------------------------------------------- 1 | ## 文件夹说明 2 | 3 | - 本文件放置软件创建项目时选择的 `框架/语言` 类型对应的配置文件 (以下以 `配置` 代指 `框架/语言`) 4 | 5 | - 如果新增配置 请按照以下步骤新增 否则将导致无法正常解析出你新增的配置 6 | 7 | - 新建配置对应的文件夹 命名规则 `作者-语言-框架` 例如: `deboy-php-laravel` 要求全部小写 并使用中划线分隔 8 | 9 | - 新建对应的 `index.js` 并在该文件中导入 相应的 默认字段配置(`fields.js`) 选项配置(可选 参照其中一个官方配置) 10 | 11 | - 完成上述操作后 将 上一步 `index.js` 中导入的配置 参照已存在的配置中 导出 以供下一步使用 12 | 13 | - 在 `project-configs` 下的 index.js 中新增相应配置 同样参照已存在的配置项 如: `deboy-php-laravel` -------------------------------------------------------------------------------- /src/renderer/project-configs/built-in-field-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/12. 3 | * 默认的字段下来选项 4 | */ 5 | export const DB_FIELD_TYPES = [ 6 | {name: '不设置', value: ''}, 7 | {name: 'int', value: 'int'}, 8 | {name: 'decimal', value: 'decimal'}, 9 | {name: 'double', value: 'double'}, 10 | {name: 'date', value: 'date'}, 11 | {name: 'datetime', value: 'datetime'}, 12 | {name: 'varchar', value: 'varchar'}, 13 | {name: 'text', value: 'text'} 14 | ] 15 | 16 | 17 | export const FORM_VALID_TYPES = [ 18 | {name: '无校验', value: ''}, 19 | {name: 'Phone', value: '^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$'}, 20 | {name: 'Email', value: '^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$'}, 21 | {name: '数字', value: '^(-?\d+)(\.\d+)?$'}, 22 | {name: '整数', value: '^-?\d+$'}, 23 | {name: '金钱', value: '^\d+(\.\d+)?$'}, 24 | {name: '百分比', value: '^\\d+\\.?\\d*\\%?$'}, 25 | {name: '密码', value: '[a-zA-Z]\w{5,17}$'}, 26 | {name: '强密码', value: '^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$'}, 27 | {name: '身份证号', value: '^\d{15}|\d{18}$'}, 28 | {name: 'IP地址', value: '^\d{15}|\d{18}$'}, 29 | {name: '中国邮政编码', value: '[1-9]\d{5}(?!\d)'} 30 | ] 31 | export const VUE_DATA_TYPES = [ 32 | {name: '字符串', value: 'string'}, 33 | {name: '数字(单选)', value: 'number'}, 34 | {name: '数组(多选)', value: 'array'} 35 | ] 36 | -------------------------------------------------------------------------------- /src/renderer/project-configs/index.js: -------------------------------------------------------------------------------- 1 | import Laravel from './laravel' 2 | import SpringBoot from './springBoot' 3 | 4 | export default { 5 | Laravel, 6 | SpringBoot 7 | } -------------------------------------------------------------------------------- /src/renderer/project-configs/laravel/auto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 创建新表格的时候 默认添加的字段 3 | * @type {Array} 4 | */ 5 | export default [] -------------------------------------------------------------------------------- /src/renderer/project-configs/laravel/field-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一些属性需要下拉选项 但是不能每次都复制一遍 可以单独抽出来 而选项中只需记录 使用的选项的key即可 3 | */ 4 | import { DB_FIELD_TYPES, FORM_VALID_TYPES, VUE_DATA_TYPES } from '../built-in-field-options' 5 | 6 | export default { 7 | DB_FIELD_TYPES, 8 | FORM_VALID_TYPES, 9 | VUE_DATA_TYPES, 10 | // 自定义的下拉选项 11 | ASSOCIATION_PATTERNS: [ 12 | {name: '无关联', value: ''}, 13 | {name: '一对一', value: 'hasOne'}, 14 | {name: '一对多', value: 'hasMany'}, 15 | {name: '多对一', value: 'belongsTo'}, 16 | {name: '多对多', value: 'belongsToMany'} 17 | ] 18 | } -------------------------------------------------------------------------------- /src/renderer/project-configs/laravel/fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/12. 3 | */ 4 | export default { 5 | title: null, 6 | field: null, 7 | sort: false, 8 | isShow: true, 9 | edit: true, 10 | required: false, 11 | type: 'text', 12 | associationTable: null, 13 | // 关联表列表地址 14 | selectUrl: null, 15 | displayField: null, 16 | options: null, 17 | db: { 18 | isDbField: { 19 | type: 'boolean', 20 | name: '数据库字段', 21 | value: true 22 | }, 23 | dbType: { 24 | type: 'select', 25 | name: '字段类型', 26 | value: 'varchar', 27 | optionKey: 'DB_FIELD_TYPES' 28 | }, 29 | isPrimaryKey: { 30 | type: 'boolean', 31 | name: '是否主键', 32 | value: false 33 | }, 34 | generatedValue: { 35 | type: 'boolean', 36 | name: '是否自增', 37 | value: false 38 | }, 39 | allowEmpty: { 40 | type: 'boolean', 41 | name: '允许为空', 42 | value: true 43 | }, 44 | defaultVal: { 45 | type: 'text', 46 | name: '默认值', 47 | value: null 48 | }, 49 | maxlength: { 50 | type: 'text', 51 | name: '最大长度', 52 | value: null 53 | } 54 | }, 55 | // 统一字段名为 custom 56 | custom: { 57 | searchable: { 58 | type: 'boolean', 59 | name: '允许搜索', 60 | value: true 61 | }, 62 | fillable: { 63 | type: 'boolean', 64 | name: '可批量填充', 65 | value: true 66 | }, 67 | associateAttribute: { 68 | type: 'boolean', 69 | name: '关联属性', 70 | value: false 71 | }, 72 | associateTable: { 73 | type: 'text', 74 | name: '关联表', 75 | value: null 76 | }, 77 | relevance: { 78 | type: 'select', 79 | name: '关联模式', 80 | value: '', 81 | optionKey: 'ASSOCIATION_PATTERNS' 82 | }, 83 | verify: { 84 | type: 'select', 85 | name: '校验', 86 | value: '', 87 | optionKey: 'FORM_VALID_TYPES' 88 | }, 89 | maxlength: { 90 | type: 'number', 91 | name: '最大长度', 92 | value: '255' 93 | }, 94 | minlength: { 95 | type: 'number', 96 | name: '最小长度', 97 | value: '' 98 | }, 99 | format: { 100 | type: 'text', 101 | name: '格式化', 102 | value: '' 103 | } 104 | }, 105 | vue: { 106 | // 框架校验值 107 | validType: { 108 | type: 'select', 109 | name: '表单校验格式', 110 | value: 'string', 111 | optionKey: 'VUE_DATA_TYPES' 112 | }, 113 | // 是否是数组 决定是否在读取/提交的时候做数组/字符串的转换 114 | isArray: { 115 | type: 'boolean', 116 | name: '数组/字符串转换', 117 | value: false 118 | }, 119 | // 是否是数组 决定是否在读取/提交的时候做数组/字符串的转换 120 | multiple: { 121 | type: 'boolean', 122 | name: '是否是多选', 123 | value: false 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/renderer/project-configs/laravel/index.js: -------------------------------------------------------------------------------- 1 | import fields from './fields' 2 | import autoFields from './auto' 3 | import fieldOptions from './field-options' 4 | export default { 5 | name: 'deboy-java-laravel', 6 | version: 1.0, 7 | fields, 8 | autoFields, 9 | fieldOptions 10 | } -------------------------------------------------------------------------------- /src/renderer/project-configs/springBoot/auto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 创建新表格的时候 默认添加的字段 3 | * @type {Array} 4 | */ 5 | export default [] -------------------------------------------------------------------------------- /src/renderer/project-configs/springBoot/field-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 一些属性需要下拉选项 但是不能每次都复制一遍 可以单独抽出来 而选项中只需记录 使用的选项的key即可 3 | */ 4 | import { DB_FIELD_TYPES, FORM_VALID_TYPES, VUE_DATA_TYPES } from '../built-in-field-options' 5 | 6 | export default { 7 | DB_FIELD_TYPES, 8 | FORM_VALID_TYPES, 9 | VUE_DATA_TYPES, 10 | // 自定义的下拉选项 11 | ASSOCIATION_PATTERNS: [ 12 | {name: '无关联', value: ''}, 13 | {name: '一对一', value: 'oneToOne'}, 14 | {name: '一对多', value: 'oneToMany'}, 15 | {name: '多对一', value: 'manyToOne'}, 16 | {name: '多对多', value: 'manyToMany'} 17 | ], 18 | FIELD_TYPES: [ 19 | {name: 'Integer', value: 'Integer'}, 20 | {name: 'Double', value: 'Double'}, 21 | {name: 'Long', value: 'Long'}, 22 | {name: 'String', value: 'String'}, 23 | {name: 'Date', value: 'Date'}, 24 | {name: 'Set', value: 'Set'}, 25 | {name: 'List', value: 'List'} 26 | ] 27 | } -------------------------------------------------------------------------------- /src/renderer/project-configs/springBoot/fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/5/12. 3 | */ 4 | export default { 5 | title: null, 6 | field: null, 7 | sort: false, 8 | isShow: true, 9 | edit: true, 10 | required: false, 11 | type: 'text', 12 | associationTable: null, 13 | // 关联表列表地址 14 | selectUrl: null, 15 | displayField: null, 16 | options: null, 17 | db: { 18 | isDbField: { 19 | type: 'boolean', 20 | name: '数据库字段', 21 | value: true 22 | }, 23 | dbType: { 24 | type: 'select', 25 | name: '字段类型', 26 | value: 'varchar', 27 | optionKey: 'DB_FIELD_TYPES' 28 | }, 29 | isPrimaryKey: { 30 | type: 'boolean', 31 | name: '是否主键', 32 | value: false 33 | }, 34 | generatedValue: { 35 | type: 'boolean', 36 | name: '是否自增', 37 | value: false 38 | }, 39 | allowEmpty: { 40 | type: 'boolean', 41 | name: '允许为空', 42 | value: true 43 | }, 44 | defaultVal: { 45 | type: 'text', 46 | name: '默认值', 47 | value: null 48 | }, 49 | maxlength: { 50 | type: 'text', 51 | name: '最大长度', 52 | value: null 53 | } 54 | }, 55 | springBoot: { 56 | transient: { 57 | type: 'boolean', 58 | name: '非数据库字段', 59 | value: false 60 | }, 61 | type: { 62 | type: 'select', 63 | name: '字段类型', 64 | value: 'String', 65 | optionKey: 'FIELD_TYPES' 66 | }, 67 | updateble: { 68 | type: 'boolean', 69 | name: '是否更新数据库', 70 | value: true 71 | }, 72 | insertable: { 73 | type: 'boolean', 74 | name: '是否插入数据库', 75 | value: true 76 | }, 77 | relevance: { 78 | type: 'select', 79 | name: '关联模式', 80 | value: '', 81 | optionKey: 'ASSOCIATION_PATTERNS' 82 | }, 83 | relevanceObject: { 84 | type: 'text', 85 | name: '关联对象', 86 | value: '', 87 | }, 88 | verify1: { 89 | type: 'select', 90 | name: '校验', 91 | value: '', 92 | optionKey: 'FORM_VALID_TYPES' 93 | }, 94 | maxlength: { 95 | type: 'number', 96 | name: '最大长度', 97 | value: '255' 98 | }, 99 | minlength: { 100 | type: 'number', 101 | name: '最小长度', 102 | value: '' 103 | }, 104 | format: { 105 | type: 'text', 106 | name: '格式化', 107 | value: '' 108 | } 109 | }, 110 | vue: { 111 | // 框架校验值 112 | validType: { 113 | type: 'select', 114 | name: '表单校验格式', 115 | value: 'string', 116 | optionKey: 'VUE_DATA_TYPES' 117 | }, 118 | // 是否是数组 决定是否在读取/提交的时候做数组/字符串的转换 119 | isArray: { 120 | type: 'boolean', 121 | name: '数组/字符串转换', 122 | value: false 123 | }, 124 | // 是否是数组 决定是否在读取/提交的时候做数组/字符串的转换 125 | multiple: { 126 | type: 'boolean', 127 | name: '是否是多选', 128 | value: false 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/renderer/project-configs/springBoot/index.js: -------------------------------------------------------------------------------- 1 | import fields from './fields' 2 | import autoFields from './auto' 3 | import fieldOptions from './field-options' 4 | export default { 5 | name: 'deboy-java-springBoot', 6 | version: 1.0, 7 | fields, 8 | autoFields, 9 | fieldOptions 10 | } -------------------------------------------------------------------------------- /src/renderer/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | export default new Router({ 7 | routes: [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: require('../views/Home.vue').default 12 | }, 13 | { 14 | path: '/NewProject', 15 | name: 'NewProject', 16 | component: require('../views/NewProject.vue').default 17 | }, 18 | { 19 | path: '/CreateModel', 20 | name: 'CreateModel', 21 | component: require('../views/CreateModel.vue').default 22 | }, 23 | { 24 | path: '/FieldEditor', 25 | name: 'FieldEditor', 26 | component: require('../views/FieldEditor.vue').default 27 | }, 28 | { 29 | path: '/Settings', 30 | name: 'Settings', 31 | component: require('../views/Settings.vue').default, 32 | redirect: to => { 33 | // 方法接收 目标路由 作为参数 34 | return '/Settings/base' 35 | }, 36 | children: [ 37 | { 38 | path: '/Settings/base', 39 | name: 'baseSettings', 40 | component: require('../views/Settings/Base.vue').default 41 | }, 42 | { 43 | path: '/Settings/template', 44 | name: 'templateSettings', 45 | component: require('../views/Settings/Template.vue').default 46 | }, 47 | { 48 | path: '/Settings/logs', 49 | name: 'logsSettings', 50 | component: require('../views/Settings/Logs.vue').default 51 | } 52 | ] 53 | }, 54 | { 55 | path: '/ExportPage', 56 | name: 'ExportPage', 57 | component: require('../views/ExportPage.vue').default 58 | }, 59 | { 60 | path: '/Helper', 61 | name: 'Helper', 62 | component: require('../views/Helper.vue').default 63 | }, 64 | { 65 | path: '*', 66 | redirect: '/' 67 | } 68 | ] 69 | }) 70 | -------------------------------------------------------------------------------- /src/renderer/services/project.js: -------------------------------------------------------------------------------- 1 | import {openDialog, openFileExplorer} from '../utils/electron' 2 | import store from '../store' 3 | import * as types from '../store/mutation-types' 4 | import * as customHelper from '../utils/helper' 5 | import * as tools from '../config/tool' 6 | 7 | const fs = require('fs') 8 | const path = require('path') 9 | let Handlebars = require('handlebars') 10 | // custom 11 | require('../utils/hbs-helpers') 12 | require('handlebars-helpers')({ 13 | handlebars: Handlebars 14 | }) 15 | 16 | const projectConfPostfix = '.project.json' 17 | 18 | /** 19 | * 写入本地文件 20 | * @param fullpath 21 | * @param data 22 | * @param needOpenDir 23 | * @param callbackFn 24 | */ 25 | function writeToLocalFile (fullpath, data, needOpenDir = true, callbackFn) { 26 | fs.open(fullpath, 'a', (err, fd) => { 27 | if (err) { 28 | store.dispatch('showNotice', {type: 'error', title: '导出项目失败', desc: err}) 29 | return 30 | } 31 | let content = typeof data === 'object' ? JSON.stringify(data) : data 32 | fs.writeFile(fullpath, content, (err) => { 33 | if (err) { 34 | store.dispatch('showNotice', {type: 'error', title: '写入文件失败', desc: err}) 35 | return 36 | } 37 | if (typeof callbackFn === 'function') { 38 | callbackFn() 39 | } else { 40 | store.dispatch('showNotice', '导出成功') 41 | } 42 | if (needOpenDir) { 43 | openFileExplorer(fullpath) 44 | } 45 | }) 46 | fs.close(fd) 47 | }) 48 | } 49 | 50 | export function install (Vue) { 51 | Vue.prototype.$project = { 52 | // 读取模版文件夹下所有的模版 53 | listTpl (templateDir) { 54 | let res = [] 55 | let files = fs.readdirSync(templateDir) 56 | files.forEach((file) => { 57 | let fullpath = path.resolve(templateDir, file) 58 | res.push({ 59 | name: file, 60 | path: fullpath 61 | }) 62 | }) 63 | return res 64 | }, 65 | /** 66 | * 根据传过来的变量和字符串传渲染 67 | * @param variables 68 | * @param template 69 | */ 70 | renderTemplate (variables, template) { 71 | try { 72 | let tpl = Handlebars.compile(template) 73 | return tpl(variables) 74 | } catch (e) { 75 | store.dispatch('showNotice', {type: 'error', title: '编译模版文件失败', desc: e.message, duration: 0}) 76 | return null 77 | } 78 | }, 79 | /** 80 | * 为项目文件做兼容性处理 81 | */ 82 | polyfillProject (targetProject) { 83 | targetProject.tables.forEach((table) => { 84 | table.fields = tools.mergeDefaultRow(targetProject.type, JSON.parse(JSON.stringify(table.fields))) 85 | }) 86 | return targetProject 87 | }, 88 | getTablePreview (index = 0, exportType = 'list') { 89 | let project = store.getters.projectList[store.getters.projectIndex] 90 | if (project && project.id) { 91 | let exportProjectObj = JSON.parse(JSON.stringify(project)) 92 | exportProjectObj = tools.transformProjectInfo(exportProjectObj) 93 | console.log(exportProjectObj) 94 | if (exportType === 'list') { 95 | return exportProjectObj.tables[index] 96 | } else { 97 | delete exportProjectObj.defaultFields 98 | return exportProjectObj 99 | } 100 | } else { 101 | return '没有选中项目' 102 | } 103 | }, 104 | /** 105 | * 循环表导出文件 106 | */ 107 | exportFileByCustomTemplate (tplInfo, previewMode = false, customTableIndex = 0) { 108 | let project = store.getters.projectList[store.getters.projectIndex] 109 | if (project && project.id) { 110 | let exportProjectObj = JSON.parse(JSON.stringify(project)) 111 | exportProjectObj = tools.transformProjectInfo(exportProjectObj) 112 | let template = fs.readFileSync(tplInfo.path).toString() 113 | try { 114 | let tpl = Handlebars.compile(template) 115 | // 如果是预览模式 默认取第一个表来预览 116 | if (previewMode) { 117 | // 如果导出类型是list 118 | if (tplInfo.type === 'list') { 119 | return tpl(exportProjectObj.tables[customTableIndex]) 120 | } else { 121 | return tpl(exportProjectObj) 122 | } 123 | } else { 124 | openDialog((res) => { 125 | if (res && res.length > 0) { 126 | let filename = '' 127 | if (tplInfo.type === 'list') { 128 | exportProjectObj.tables.forEach((table, index) => { 129 | if (tplInfo.filenameFormat) { 130 | filename = customHelper[tplInfo.filenameFormat](table.name) 131 | } else { 132 | filename = table.name 133 | } 134 | writeToLocalFile(path.resolve(res[0], `./${filename + tplInfo.suffix}`), tpl(table), () => { 135 | }) 136 | if (index === exportProjectObj.tables.length - 1) { 137 | openFileExplorer(res[0]) 138 | } 139 | }) 140 | } else { 141 | filename = tplInfo.suffix || tplInfo.name || exportProjectObj.name 142 | let content = tpl(exportProjectObj) 143 | writeToLocalFile(path.resolve(res[0], `./${filename}`), content, false) 144 | openFileExplorer(res[0]) 145 | } 146 | } 147 | }) 148 | } 149 | } catch (e) { 150 | store.dispatch('showNotice', {type: 'error', title: '编译模版文件失败', desc: e.message, duration: 0}) 151 | } 152 | } 153 | }, 154 | /** 155 | * 导出项目的整个文件 只能被ModelMaker识别 请勿用其他软件识别修改 否则将损坏该文件 156 | */ 157 | exportProjectFile () { 158 | let project = store.getters.projectList[store.getters.projectIndex] 159 | let successCount = 0 160 | let errorCount = 0 161 | if (project && project.id) { 162 | openDialog((res) => { 163 | if (res && res.length > 0) { 164 | let showNotice = () => { 165 | if (successCount + errorCount === res.length) { 166 | store.dispatch('showNotice', { 167 | type: 'info', 168 | title: '导出完成', 169 | desc: `导出成功: ${successCount} \n 导出失败:${errorCount}` 170 | }) 171 | } 172 | } 173 | fs.open(path.resolve(res[0], `./${project.name}${projectConfPostfix}`), 'a', (err, fd) => { 174 | if (err) { 175 | store.dispatch('showNotice', {type: 'error', title: '导出项目失败', desc: err}) 176 | return 177 | } 178 | let writeBuffer = Buffer.from(JSON.stringify(project)) 179 | let offset = 0 180 | let len = writeBuffer.length 181 | let filePostion = null 182 | fs.write(fd, writeBuffer, offset, len, filePostion, (err, readByte) => { 183 | if (err) { 184 | errorCount++ 185 | showNotice() 186 | return 187 | } 188 | successCount++ 189 | showNotice() 190 | fs.close(fd) 191 | }) 192 | }) 193 | } 194 | }) 195 | } else { 196 | store.dispatch('showNotice', {type: 'info', title: '没有项目可以导出'}) 197 | } 198 | }, 199 | /** 200 | * 从本地读取文件 同步到软件中 201 | * @param showNotice 202 | * @param workspace 203 | */ 204 | syncProject (showNotice = true, workspace = null, repeat = true) { 205 | const syncWorkspace = workspace || store.getters.workspace || null 206 | if (!syncWorkspace) { 207 | store.dispatch('showNotice', {type: 'warning', title: '同步项目失败', desc: '请先点击右上角的齿轮设置 工作空间'}) 208 | } else { 209 | let files = fs.readdirSync(syncWorkspace) 210 | let count = { 211 | success: 0, 212 | fail: 0 213 | } 214 | files.forEach((filename) => { 215 | let fullname = path.join(syncWorkspace, filename) 216 | if (filename.indexOf(projectConfPostfix) < 0) { 217 | return 218 | } 219 | let stats = fs.statSync(fullname) 220 | if (!stats.isDirectory()) { 221 | let fileContent = fs.readFileSync(fullname, 'UTF-8') 222 | let project = null 223 | try { 224 | project = JSON.parse(fileContent) 225 | } catch (e) { 226 | count.fail++ 227 | } 228 | if (project && project.id) { 229 | let afterPolyfillProject = this.polyfillProject(project) 230 | store.commit(types.MERGE_PROJECT_LIST, afterPolyfillProject) 231 | count.success++ 232 | } else { 233 | store.dispatch('showNotice', {type: 'warning', title: `${filename}文件导入失败`, desc: '不是标准的项目文件!请检查后重试'}) 234 | } 235 | } 236 | }) 237 | if (showNotice) { 238 | store.dispatch('showNotice', { 239 | type: 'success', 240 | title: '同步工作空间项目完成', 241 | desc: `成功: ${count.success} 失败:${count.fail}` 242 | }) 243 | } 244 | } 245 | }, 246 | /** 247 | * 将已存在的项目写入本地文件 248 | * @param projectList 249 | * @param callbackFn function 250 | */ 251 | syncToLocalFile (projectList, callbackFn = () => { 252 | }) { 253 | let workspace = store.getters.workspace || null 254 | if (workspace && projectList.length > 0) { 255 | projectList.forEach((project, index) => { 256 | let filename = project.name + projectConfPostfix 257 | let fullpath = path.resolve(workspace, filename) 258 | fs.open(fullpath, 'w+', (err, fd) => { 259 | if (err) { 260 | store.dispatch('showNotice', {type: 'error', title: '保存项目失败', desc: err}) 261 | return 262 | } 263 | let writeBuffer = Buffer.from(JSON.stringify(project)) 264 | let offset = 0 265 | let len = writeBuffer.length 266 | let filePostion = null 267 | fs.write(fd, writeBuffer, offset, len, filePostion, (err, readByte) => { 268 | if (err) { 269 | store.dispatch('showNotice', {type: 'error', title: '保存项目失败', desc: err}) 270 | return 271 | } else { 272 | console.log('同步完成 项目文件大小:', readByte) 273 | } 274 | fs.close(fd, () => { 275 | if (index === projectList.length - 1 && typeof callbackFn === 'function') { 276 | callbackFn(true) 277 | } 278 | }) 279 | }) 280 | }) 281 | }) 282 | } else if (projectList.length === 0) { 283 | console.info('当前没有项目 快点新建啊') 284 | if (typeof callbackFn === 'function') { 285 | callbackFn(false) 286 | } 287 | } else { 288 | store.dispatch('showNotice', {type: 'error', title: '保存失败', desc: '请先点击右上角齿轮按钮设置工作空间 否则项目无法保存!'}) 289 | if (typeof callbackFn === 'function') { 290 | callbackFn(false) 291 | } 292 | } 293 | }, 294 | /** 295 | * 同步删除项目 296 | * @param project 297 | * @param workspace 298 | */ 299 | syncDeleteProject (project, workspace) { 300 | let projectFile = path.resolve(workspace || store.getters.workspace, project.name + projectConfPostfix) 301 | try { 302 | fs.unlinkSync(projectFile) 303 | } catch (e) { 304 | } 305 | }, 306 | /** 307 | * 重命名项目 308 | * @param oldName 309 | * @param newName 310 | * @param callbackFn 311 | */ 312 | reNameProject (oldName, newName, callbackFn) { 313 | const workspace = store.getters.workspace 314 | if (workspace) { 315 | let oldPath = path.resolve(workspace, oldName + projectConfPostfix) 316 | let newPath = path.resolve(workspace, newName + projectConfPostfix) 317 | if (fs.renameSync(oldPath, newPath)) { 318 | callbackFn && callbackFn() 319 | } 320 | } else { 321 | store.dispatch('showNotice', {type: 'error', title: '保存失败', desc: '请先点击右上角齿轮按钮设置工作空间 否则项目无法保存到本地!'}) 322 | callbackFn && callbackFn() 323 | } 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/renderer/services/sql.js: -------------------------------------------------------------------------------- 1 | import * as projectConfigs from '../project-configs' 2 | const Sequelize = require('sequelize') 3 | const requireFromString = require('require-from-string') 4 | const path = require('path') 5 | const fs = require('fs') 6 | 7 | let isSelect = (comment = '') => { 8 | return comment && comment.indexOf('{') >= 0 && comment.indexOf('}') >= 0 9 | } 10 | let getType = (type, comment = '') => { 11 | if (typeof type === 'function') { 12 | return 'datetime' 13 | } else { 14 | // TODO 这里类型判断没有统一的写法 只是为了临时生成 需要改进 15 | if (isSelect(comment)) { 16 | return 'select' 17 | } else { 18 | return 'text' 19 | } 20 | } 21 | } 22 | let getTitle = (comment = '') => { 23 | let titleSplitStrLastIndex = comment.indexOf(']') 24 | let titleStr = comment.substring(0, titleSplitStrLastIndex) 25 | return titleStr && titleStr.replace('[', '').replace(']', '') 26 | } 27 | let getFilter = (comment = '') => { 28 | if (isSelect(comment)) { 29 | let titleIndex = comment.indexOf(']') 30 | let optionsStr = comment.substring(titleIndex + 1, comment.length) 31 | let afterRemoveSplitStr = optionsStr.replace('{', '').replace('}', '') 32 | return afterRemoveSplitStr.replace(',', ';') 33 | } else { 34 | return null 35 | } 36 | } 37 | let excludedFields = [ 38 | 'del', 39 | 'delDtm' 40 | ] 41 | export const transform = (modelPath = '', projectType = 'springboot') => { 42 | let tables = [] 43 | if (modelPath) { 44 | let myTransform = { 45 | define: (name, model) => { 46 | let fields = [] 47 | for (let fieldKey in model) { 48 | if (excludedFields.indexOf(fieldKey) < 0) { 49 | let field = model[fieldKey] 50 | // 取用户选中的项目类型相对应的默认字段配置合并 51 | fields.push(Object.assign({}, projectConfigs[projectType], { 52 | title: getTitle(field.comment), 53 | field: fieldKey, 54 | displayField: fieldKey, 55 | options: getFilter(field.comment), 56 | type: getType(field.type, field.comment) 57 | })) 58 | } 59 | } 60 | tables.push({ 61 | fields: fields, 62 | name: name, 63 | comment: name 64 | }) 65 | }, 66 | literal: (type) => {} 67 | } 68 | let files = fs.readdirSync(modelPath) 69 | files.forEach((file) => { 70 | let filePath = path.resolve(modelPath, file) 71 | let fileContent = fs.readFileSync(filePath).toString() 72 | let tableMigration = requireFromString(fileContent) 73 | tableMigration(myTransform, Sequelize.DataTypes) 74 | }) 75 | } 76 | return tables 77 | } 78 | -------------------------------------------------------------------------------- /src/renderer/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as iview from 'iview' 2 | import * as types from './mutation-types' 3 | function saveLog (log, context) { 4 | let nowTime = new Date().toLocaleDateString() + new Date().toLocaleTimeString() 5 | let logObj = { 6 | time: nowTime + ': ', 7 | type: 'info', 8 | title: '通知', 9 | content: '内容', 10 | createdAt: new Date().getTime() 11 | } 12 | if (typeof log === 'string') { 13 | logObj.content = log 14 | } else { 15 | logObj = Object.assign({}, logObj, log) 16 | } 17 | context.commit(types.SAVE_LOG, logObj) 18 | } 19 | export const showNotice = function (context, payload) { 20 | if (typeof payload === 'string') { 21 | iview.Notice.success({ 22 | title: payload, 23 | duration: 3 24 | }) 25 | } else { 26 | iview.Notice[payload.type](Object.assign({}, { 27 | title: '通知', 28 | content: null, 29 | duration: 3 30 | }, payload)) 31 | } 32 | saveLog(payload, context) 33 | } 34 | -------------------------------------------------------------------------------- /src/renderer/store/getters.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/src/renderer/store/getters.js -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import * as actions from './actions' 4 | import * as getters from './getters' 5 | import modules from './modules' 6 | Vue.use(Vuex) 7 | let store = new Vuex.Store({ 8 | actions, 9 | getters, 10 | modules, 11 | strict: process.env.NODE_ENV !== 'production' 12 | }) 13 | export default store 14 | -------------------------------------------------------------------------------- /src/renderer/store/localstorage-keys.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/2. 3 | */ 4 | // projects 5 | export const PROJECTS = 'PROJECTS' 6 | export const PROJECTS_INDEX = 'PROJECTS_INDEX' 7 | export const ON_EDIT_TABLE_INDEX = 'ON_EDIT_TABLE_INDEX' 8 | // global setting 9 | export const SETTINGS = 'SETTINGS' 10 | export const TEMPLATE_SETTINGS = 'TEMPLATE_SETTINGS' 11 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | const files = require.context('.', false, /\.js$/) 2 | const modules = {} 3 | 4 | files.keys().forEach((key) => { 5 | if (key === './index.js') { 6 | return 7 | } 8 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 9 | }) 10 | 11 | export default modules 12 | -------------------------------------------------------------------------------- /src/renderer/store/modules/projects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/2. 3 | */ 4 | import * as types from '../mutation-types' 5 | import UUID from 'uuid' 6 | import { PROJECTS, PROJECTS_INDEX, ON_EDIT_TABLE_INDEX } from '../localstorage-keys' 7 | import { get } from '@/utils/localstorage' 8 | const state = { 9 | list: get({key: PROJECTS, defaultVal: []}), 10 | index: parseInt(get({key: PROJECTS_INDEX, isObject: false, defaultVal: null})), 11 | editTableIndex: parseInt(get({key: ON_EDIT_TABLE_INDEX, isObject: false, defaultVal: null})) 12 | } 13 | const getters = { 14 | projectList (state) { 15 | return state.list 16 | }, 17 | // 当起那选中项目的索引 18 | projectIndex (state) { 19 | return state.index 20 | }, 21 | // 当前编辑的项目 22 | onEditProject () { 23 | if (state.list.length > 0 && state.index !== null) { 24 | return state.list[state.index] 25 | } else { 26 | return null 27 | } 28 | }, 29 | projectTableIndexAndLabels (state) { 30 | let tables = state.list.length ? (state.index !== null && state.list[state.index] && state.list[state.index].tables) : [] 31 | let indexs = [] 32 | if (tables && tables.length > 0) { 33 | tables.forEach((table, index) => { 34 | indexs.push({ 35 | index: index, 36 | name: table.name, 37 | comment: table.comment 38 | }) 39 | }) 40 | } 41 | return indexs 42 | }, 43 | // 当前项目所有表 44 | projectTables (state) { 45 | return state.list.length ? (state.index !== null && state.list[state.index] && state.list[state.index].tables) : [] 46 | }, 47 | // 当前编辑的表格索引 48 | onEditTableIndex (state) { 49 | return state.editTableIndex 50 | }, 51 | // 当前编辑的表格 52 | onEditTable (state) { 53 | return state.list.length ? (state.index !== null && state.list[state.index] && state.list[state.index].tables[state.editTableIndex || 0]) : {} 54 | } 55 | } 56 | const mutations = { 57 | // 新建项目 58 | [types.ADD_PROJECT] (state, payload) { 59 | state.list.push(Object.assign({}, { 60 | id: UUID.v1(), 61 | tables: [], 62 | type: payload.type, 63 | props: [ 64 | {key: 'host', value: 'localhost', name: '数据库主机', extend: false, editable: true}, 65 | {key: 'port', value: '3306', name: '数据库端口', extend: false, editable: true}, 66 | {key: 'user', value: 'root', name: '数据库用户名', extend: false, editable: true}, 67 | {key: 'password', value: '', name: '数据库密码', extend: false, editable: true}, 68 | {key: 'database', value: payload.name, name: '数据库名称', extend: false, editable: true} 69 | ], 70 | defaultFields: [] 71 | }, payload)) 72 | }, 73 | // 更新选中的项目索引 74 | [types.UPDATE_SELECT_INDEX] (state, index) { 75 | state.index = index 76 | }, 77 | // 删除某个项目 78 | [types.DELETE_PROJECT] (state, payload) { 79 | state.list.splice(payload, 1) 80 | }, 81 | // 更新某个项目 82 | [types.UPDATE_PROJECT] (state, payload) { 83 | Object.keys(state.list[state.index]).forEach(key => { 84 | state.list[state.index][key] = payload[key] 85 | }) 86 | }, 87 | // 添加表 88 | [types.ADD_PROJECT_TABLE] (state, payload) { 89 | let table = Object.assign({}, { 90 | id: UUID.v1(), 91 | props: [ 92 | {key: 'deleteBatch', value: 0, name: '是否允许批量删除'} 93 | ] 94 | }, payload) 95 | state.list[state.index].tables.push(table) 96 | }, 97 | [types.UPDATE_PROJECT_TABLE] (state, payload) { 98 | let tables = state.list[state.index].tables 99 | tables.forEach((table, index) => { 100 | if (table.id === payload.id) { 101 | tables[index] = Object.assign({}, table, payload) 102 | return true 103 | } 104 | }) 105 | state.list[state.index].tables = Object.assign([], state.list[state.index].tables, tables) 106 | }, 107 | [types.UPDATE_ON_EDIT_TABLE_INDEX] (state, id) { 108 | state.list[state.index].tables.forEach((table, index) => { 109 | if (table.id === id) { 110 | state.editTableIndex = index 111 | return true 112 | } 113 | }) 114 | }, 115 | [types.DELETE_PROJECT_TABLE] (state, index) { 116 | state.list[state.index].tables.splice(index, 1) 117 | }, 118 | [types.MERGE_PROJECT_LIST] (state, payload) { 119 | let isExit = false 120 | let findIndex = -1 121 | state.list.forEach((item, index) => { 122 | if (item.id === payload.id) { 123 | findIndex = index 124 | isExit = true 125 | } 126 | }) 127 | if (!isExit) { 128 | state.list.push(payload) 129 | } else { 130 | state.list.splice(findIndex, 1) 131 | state.list.splice(findIndex, 0, payload) 132 | } 133 | } 134 | } 135 | export default { 136 | state, 137 | getters, 138 | mutations 139 | } 140 | -------------------------------------------------------------------------------- /src/renderer/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/3/26. 3 | */ 4 | import * as types from '../mutation-types' 5 | import { get } from '../../utils/localstorage' 6 | import { SETTINGS } from '../localstorage-keys' 7 | const defaultSettings = { 8 | workspace: null, 9 | // 模版存放文件夹 10 | templateDir: null, 11 | // 日志记录 12 | logs: [], 13 | // 在新建表的时候自动添加字段 14 | autoAddFieldWhenTableCreate: true, 15 | // 超过100条自动清理日志 16 | autoClearLogs: true 17 | } 18 | const state = get({ 19 | key: SETTINGS, 20 | defaultVal: defaultSettings 21 | }) 22 | const getters = { 23 | allSettings () { 24 | return state 25 | }, 26 | workspace (state) { 27 | return state.workspace 28 | }, 29 | autoClearLogs () { 30 | return state.autoClearLogs 31 | }, 32 | autoAddFieldWhenTableCreate () { 33 | return state.autoAddFieldWhenTableCreate 34 | }, 35 | logs (state) { 36 | return state.logs 37 | } 38 | } 39 | const mutations = { 40 | [types.UPDATE_SETTINGS] (state, payload) { 41 | Object.keys(payload).forEach(key => { 42 | state[key] = payload[key] 43 | }) 44 | }, 45 | [types.SAVE_LOG] (state, payload) { 46 | state.logs.push(payload) 47 | // 保留前100条日志 48 | if (state.autoClearLogs && state.logs.length > 100) { 49 | state.logs = state.logs.slice(state.logs.length - 100) 50 | } 51 | }, 52 | [types.CLEAR_LOG] (state) { 53 | state.logs = [] 54 | }, 55 | [types.RESET_SETTINGS] (state) { 56 | Object.keys(defaultSettings).forEach((keys) => { 57 | state[keys] = defaultSettings[keys] 58 | }) 59 | } 60 | } 61 | export default { 62 | state, 63 | getters, 64 | mutations 65 | } 66 | -------------------------------------------------------------------------------- /src/renderer/store/modules/styles.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const state = { 4 | fullHeight: window.document.body.clientHeight + 'px', 5 | fullWidth: window.document.body.clientWidth + 'px', 6 | contentWarpHeight: window.document.body.clientHeight - 150 + 'px' 7 | } 8 | 9 | const mutations = { 10 | [types.WINDOW_ON_RESIZE] (state) { 11 | state.fullHeight = window.document.body.clientHeight + 'px' 12 | state.fullWidth = window.document.body.clientWidth + 'px' 13 | state.contentWarpHeight = window.document.body.clientHeight - 150 + 'px' 14 | } 15 | } 16 | 17 | export default { 18 | state, 19 | mutations 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/store/modules/template.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | import { get, save } from '../../utils/localstorage' 3 | import { TEMPLATE_SETTINGS } from '../localstorage-keys' 4 | const defaultConfigs = { 5 | dir: null, 6 | list: get(TEMPLATE_SETTINGS, []) 7 | } 8 | const state = get({ 9 | key: TEMPLATE_SETTINGS, 10 | defaultVal: defaultConfigs 11 | }) 12 | const getters = { 13 | templateDir () { 14 | return state.dir 15 | }, 16 | templateList (state) { 17 | return state.list 18 | }, 19 | templateSettings () { 20 | return state 21 | } 22 | } 23 | const mutations = { 24 | [types.UPDATE_TEMPLATE_SETTINGS] (state, payload) { 25 | Object.keys(payload).forEach(key => { 26 | state[key] = payload[key] 27 | }) 28 | save({key: TEMPLATE_SETTINGS, value: state}) 29 | }, 30 | [types.UPDATE_TEMPLATE_LIST] (state, payload) { 31 | state.list = payload 32 | save({key: TEMPLATE_SETTINGS, value: state}) 33 | }, 34 | [types.MERGE_TEMPLATE_LIST] (state, payload) { 35 | payload.forEach((template, index) => { 36 | payload[index] = Object.assign({}, { 37 | // 导出文件的后缀 38 | suffix: '', 39 | // 首字母大写 40 | filenameFormat: '', 41 | // 默认的导出类型 42 | type: 'list' 43 | }, template) 44 | }) 45 | state.list = payload 46 | save({key: TEMPLATE_SETTINGS, value: state}) 47 | } 48 | } 49 | export default { 50 | state, 51 | getters, 52 | mutations 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const WINDOW_ON_RESIZE = 'WINDOW_ON_RESIZE' 2 | // settings 3 | export const UPDATE_SETTINGS = 'UPDATE_SETTINGS' 4 | export const SAVE_LOG = 'SAVE_LOG' 5 | export const CLEAR_LOG = 'CLEAR_LOG' 6 | export const RESET_SETTINGS = 'RESET_SETTINGS' 7 | // template 8 | export const UPDATE_TEMPLATE_SETTINGS = 'UPDATE_TEMPLATE_SETTINGS' 9 | export const UPDATE_TEMPLATE_LIST = 'UPDATE_TEMPLATE_LIST' 10 | export const MERGE_TEMPLATE_LIST = 'MERGE_TEMPLATE_LIST' 11 | // project 12 | export const ADD_PROJECT = 'ADD_PROJECT' 13 | export const DELETE_PROJECT = 'DELETE_PROJECT' 14 | export const UPDATE_PROJECT = 'UPDATE_PROJECT' 15 | export const MERGE_PROJECT_LIST = 'MERGE_PROJECT_LIST' 16 | export const UPDATE_SELECT_INDEX = 'UPDATE_SELECT_INDEX' 17 | 18 | // project tables action 19 | export const ADD_PROJECT_TABLE = 'ADD_PROJECT_TABLE' 20 | export const DELETE_PROJECT_TABLE = 'DELETE_PROJECT_TABLE' 21 | export const UPDATE_PROJECT_TABLE = 'UPDATE_PROJECT_TABLE' 22 | export const UPDATE_ON_EDIT_TABLE_INDEX = 'UPDATE_ON_EDIT_TABLE_INDEX' 23 | -------------------------------------------------------------------------------- /src/renderer/utils/electron.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/2. 3 | * TODO 重新规划插件目录 规范化插件封装 4 | */ 5 | const {dialog} = require('electron').remote 6 | const {shell} = require('electron') 7 | export function openDialog (callback) { 8 | dialog.showOpenDialog({ 9 | properties: [ 10 | 'openDirectory' 11 | ] 12 | }, callback) 13 | } 14 | export function openFileExplorer (target) { 15 | return shell.showItemInFolder(target) 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/utils/hbs-helpers/built-in-hbs-helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/24. 3 | */ 4 | let javascriptStringify = require('javascript-stringify') 5 | let hbs = require('handlebars') 6 | hbs.registerHelper('expression', function () { 7 | let exps = [] 8 | try { 9 | // 最后一个参数作为展示内容,也就是平时的options。不作为逻辑表达式部分 10 | var arg_len = arguments.length 11 | var len = arg_len - 1 12 | for (var j = 0; j < len; j++) { 13 | exps.push(arguments[j]) 14 | } 15 | var result = eval(exps.join(' ')) 16 | if (result) { 17 | return arguments[len].fn(this) 18 | } else { 19 | return arguments[len].inverse(this) 20 | } 21 | } catch (e) { 22 | throw new Error('Handlerbars Helper "expression" can not deal with wrong expression:' + exps.join(' ') + '.') 23 | } 24 | }) 25 | hbs.registerHelper('upperFirstAndDashToCamel', function (str) { 26 | if (!str) { 27 | return console.error('错误: upperFirstAndDashToCamel 方法需要传入字符串类型的值') 28 | } 29 | return str.split('_').map(x => { 30 | return x.charAt(0).toUpperCase() + x.slice(1) 31 | }).join('') 32 | }) 33 | hbs.registerHelper('dashToCamel', function (str) { 34 | if (!str) { 35 | return console.error('错误: dashToCamel 方法需要传入字符串类型的值') 36 | } 37 | return str.split('_').map((x, index) => { 38 | return index > 0 ? (x.charAt(0).toUpperCase() + x.slice(1)) : x 39 | }).join('') 40 | }) 41 | hbs.registerHelper('json', function (obj, withSpace) { 42 | try { 43 | return JSON.stringify(obj, null, withSpace ? 4 : 0) 44 | } catch (e) { 45 | return null 46 | } 47 | }) 48 | hbs.registerHelper('jsToStr', function (tables) { 49 | tables.forEach(table => { 50 | delete table.db 51 | delete table.springBoot 52 | }) 53 | let str = javascriptStringify(tables, null, 2) 54 | // TODO 不生效 会有转义字符出现 55 | str = str.replace(new RegExp("&" + "#" + "x27;", "g"), "'") 56 | console.log(str) 57 | return str 58 | }) 59 | hbs.registerHelper('toUpperCase', function (str) { 60 | return str.toUpperCase() 61 | }) 62 | hbs.registerHelper('camelToDash', function (str) { 63 | if (str === '') { 64 | return '' 65 | } 66 | 67 | str = str[0].toLowerCase() + str.substr(1) 68 | 69 | return str.replace(/([A-Z])/g, function ($1) { 70 | return '_' + $1.toLowerCase() 71 | }) 72 | }) -------------------------------------------------------------------------------- /src/renderer/utils/hbs-helpers/huntbao-hbs-helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Handlebars 辅助函数集 3 | * @author huntbao 4 | */ 5 | 'use strict' 6 | let Handlebars = require('handlebars') 7 | 8 | /** 9 | * 不翻译被包裹的内容 10 | * @param {Object} options 11 | * 12 | * 13 | */ 14 | function raw (options) { 15 | return options.fn() 16 | } 17 | 18 | Handlebars.registerHelper('raw', raw) 19 | 20 | /** 21 | * 一些逻辑运算: '=='、'==='、'<'、'<='、'>'、'>='、'&&'、'||' 22 | */ 23 | Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) { 24 | switch (operator) { 25 | case '==': 26 | return (v1 == v2) ? options.fn(this) : options.inverse(this) 27 | case '===': 28 | return (v1 === v2) ? options.fn(this) : options.inverse(this) 29 | case '<': 30 | return (v1 < v2) ? options.fn(this) : options.inverse(this) 31 | case '<=': 32 | return (v1 <= v2) ? options.fn(this) : options.inverse(this) 33 | case '>': 34 | return (v1 > v2) ? options.fn(this) : options.inverse(this) 35 | case '>=': 36 | return (v1 >= v2) ? options.fn(this) : options.inverse(this) 37 | case '&&': 38 | return (v1 && v2) ? options.fn(this) : options.inverse(this) 39 | case '||': 40 | return (v1 || v2) ? options.fn(this) : options.inverse(this) 41 | default: 42 | return options.inverse(this) 43 | } 44 | }) 45 | 46 | /** 47 | * 获取以点号分隔字符串后的最后一项, 相当于根据路径取文件的扩展名 48 | * @param {string} str 49 | * @return {string} 50 | * 51 | * @example 52 | * Handlebars.compile('{{extname "hello.js"}}')() // => "js" 53 | * Handlebars.compile('{{extname "hellojs"}}')() // => "" 54 | * Handlebars.compile('{{extname "hellojs."}}')() // => "" 55 | * Handlebars.compile('{{extname "hello.util.js"}}')() // => "js" 56 | */ 57 | function extname (str) { 58 | if (!str) { 59 | return console.error('错误: extname 方法需要传入字符串类型的值') 60 | } 61 | return str.substr(str.lastIndexOf('.')).substr(1) 62 | } 63 | 64 | Handlebars.registerHelper('extname', extname) 65 | 66 | /** 67 | * 获取模型名称, 如果它是数组, 默认使用 `[]` 嵌套, 比如二维数组: String[][] 68 | * 或者使用 `List` 嵌套, 比如二维数组: List> 69 | */ 70 | Handlebars.registerHelper('typeName', function (model, options) { 71 | let type 72 | let arrDim 73 | let useList 74 | if (model.hasOwnProperty('type')) { 75 | // 模型中的用法: `{{typeName this useList=true}}` 76 | type = model.type 77 | arrDim = model.arrDim 78 | if (options.hasOwnProperty('hash')) { 79 | useList = options.hash.useList 80 | } 81 | } else if (model.hasOwnProperty('hash')) { 82 | // 接口返回参数中的用法: `{{typeName type=outputModel arrDim=outputModelArrDim useList=true}}` 83 | type = model.hash.type 84 | arrDim = model.hash.arrDim 85 | useList = model.hash.useList 86 | } 87 | if (arrDim) { 88 | if (useList === true) { 89 | let name = [] 90 | let len = arrDim 91 | while (len--) { 92 | name.push('List<') 93 | } 94 | name.push(type) 95 | len = arrDim 96 | while (len--) { 97 | name.push('>') 98 | } 99 | } else { 100 | let name = [type] 101 | let len = arrDim 102 | while (len--) { 103 | name.push('[]') 104 | } 105 | } 106 | return name.join('') 107 | } else { 108 | return type 109 | } 110 | }) 111 | 112 | /** 113 | * 中划线'-' 转下划线'_' 114 | * @param {string} str 115 | * @return {string} 116 | * 117 | * @example 118 | * Handlebars.compile('{{hyphenToUnderline "a---b-c"}}')() // => "a_b_c" 119 | * Handlebars.compile('{{hyphenToUnderline "---a-b-c"}}')() // => "_a_b_c" 120 | * Handlebars.compile('{{hyphenToUnderline "a-b-c--"}}')() // => "a_b_c_" 121 | */ 122 | function hyphenToUnderline (str) { 123 | if (!str) { 124 | return console.error('错误: hyphenToUnderline 方法需要传入字符串类型的值') 125 | } 126 | return str.split(/-+/).join('_') 127 | } 128 | 129 | Handlebars.registerHelper('hyphenToUnderline', hyphenToUnderline) 130 | 131 | /** 132 | * 中划线'-'后的字符转大写 133 | * @param {string} str 134 | * @return {string} 135 | * @example 136 | * Handlebars.compile('{{hyphenToCamel "--a-b-c--"}}')() // => "ABC" 137 | * Handlebars.compile('{{hyphenToCamel "a---b---c"}}')() // => "ABC" 138 | * 139 | */ 140 | function hyphenToCamel (str) { 141 | if (!str) { 142 | return console.error('错误: hyphenToCamel 方法需要传入字符串类型的值') 143 | } 144 | return str.split('-').map(x => { 145 | return x.charAt(0).toUpperCase() + x.slice(1) 146 | }).join('') 147 | } 148 | 149 | Handlebars.registerHelper('hyphenToCamel', hyphenToCamel) 150 | 151 | /** 152 | * hyphenToCamel的反函数,将大写字符转为小写并以中划线'-'分开 153 | * @param {string} str 154 | * @return {string} 155 | * 156 | * @example 157 | * Handlebars.compile('{{camelToHyphen "AbcDefGhi"}}')() // => "abc-def-ghi" 158 | * Handlebars.compile('{{camelToHyphen "abcDEF"}}')() // => "abc-d-e-f" 159 | * Handlebars.compile('{{camelToHyphen "abc--DEF"}}')() // => "abc-d-e-f" 160 | * Handlebars.compile('{{camelToHyphen "abc--DEF--"}}')() // => "abc-d-e-f-" 161 | * 162 | */ 163 | function camelToHyphen (str) { 164 | if (!str) { 165 | return console.error('错误: camelToHyphen 方法需要传入字符串类型的值') 166 | } 167 | return str.split(/([A-Z][^A-Z]*)/g).filter(x => { 168 | return x != '' 169 | }) 170 | .map(x => { 171 | return x.toLowerCase() 172 | }).join('-').replace(/-+/g, '-') 173 | } 174 | 175 | Handlebars.registerHelper('camelToHyphen', camelToHyphen) 176 | 177 | /** 178 | * 首字母小写 179 | * @param {string} str 180 | * @return {string} 181 | * 182 | * @example 183 | * Handlebars.compile('{{lowerFirst "abcDEF"}}')() // => "abcDEF" 184 | * Handlebars.compile('{{lowerFirst "AbcDEF"}}')() // => "abcDEF" 185 | * Handlebars.compile('{{lowerFirst "-AbcDEF"}}')() // => "-AbcDEF" 186 | * Handlebars.compile('{{lowerFirst "A"}}')() // => "a" 187 | * Handlebars.compile('{{lowerFirst ""}}')() // => "" 188 | */ 189 | function lowerFirst (str) { 190 | if (!str) { 191 | return console.error('错误: lowerFirst 方法需要传入字符串类型的值') 192 | } 193 | return str.charAt(0).toLowerCase() + str.slice(1) 194 | } 195 | 196 | Handlebars.registerHelper('lowerFirst', lowerFirst) 197 | 198 | /** 199 | * 将换行替换为逗号(默认)或者自定义分隔符 200 | */ 201 | Handlebars.registerHelper('noLineBreak', function (str, options) { 202 | let sep = options.hash.sep || ',' 203 | return str.replace(/\n/g, sep) 204 | }) 205 | 206 | /** 207 | * 格式化注释, 在每一行的前面添加 ` * ` 208 | */ 209 | Handlebars.registerHelper('prettifyComment', function (str) { 210 | if (!str) { 211 | return console.error('错误: prettifyComment 方法需要传入字符串类型的值') 212 | } 213 | let lines = str.split('\n') 214 | return ' * ' + lines.join('\n * ') 215 | }) 216 | 217 | /** 218 | * 首字母大写 219 | * @param {string} str 220 | * @return {string} 221 | * 222 | * @example 223 | * Handlebars.compile('{{upperFirst "abcDEF"}}')() // => "AbcDEF" 224 | * Handlebars.compile('{{upperFirst "AbcDEF"}}')() // => "AbcDEF" 225 | * Handlebars.compile('{{upperFirst "-abcDEF"}}')() // => "-abcDEF" 226 | * Handlebars.compile('{{upperFirst "a"}}')() // => "A" 227 | * Handlebars.compile('{{upperFirst ""}}')() // => "" 228 | */ 229 | function upperFirst (str) { 230 | if (!str) { 231 | return console.error('错误: upperFirst 方法需要传入字符串类型的值') 232 | } 233 | return str.charAt(0).toUpperCase() + str.slice(1) 234 | } 235 | 236 | Handlebars.registerHelper('upperFirst', upperFirst) 237 | 238 | Handlebars.registerHelper('iosProperty', function (datatype, options) { 239 | let isObject = function (obj) { 240 | return ({}).toString.call(obj).toLowerCase() === '[object object]' 241 | } 242 | let helper = function (field, prefix) { 243 | let noStar = null 244 | let refName = null 245 | let type 246 | if (field.format === neiDbConst.MDL_FMT_HASH && options.hash && options.hash.hasPrefix) { 247 | type = prefix + field.type 248 | } else { 249 | type = field.type 250 | } 251 | if (field.itemIsArray || field == undefined) { 252 | noStar = 0 253 | refName = 'strong' 254 | /** 255 | * 规则: 256 | * 取数组元素的类型, 如果是多维数组, 则取最终一层的元素类型。 257 | * a. 如果元素类型不是数组: 258 | * 1. 如果数组元素的类型为 Number 或者 Boolean, 那么需要转为 NSNumber,类型格式为:NSArray; 259 | * 2. 否则类型格式为:NSArray<${prefix}${field.type} *>。 260 | * b. 否则类型格式为:NSArray 261 | * */ 262 | if (options.hash.datatypes) { 263 | // 取数组元素的类型, 如果是多维数组, 则取最终一层的元素类型 264 | let arrEleDatatype = options.hash.datatypes.find((dt) => dt.name === field.originalType) 265 | if (arrEleDatatype.format !== neiDbConst.MDL_FMT_ARRAY) { 266 | // 如果元素类型不是数组: 267 | // 1. 如果数组元素的类型为 Number 或者 Boolean, 那么需要转为 NSNumber 268 | if (arrEleDatatype.format === neiDbConst.MDL_FMT_BOOLEAN || arrEleDatatype.format === neiDbConst.MDL_FMT_NUMBER) { 269 | type = `NSArray` 270 | } else if (arrEleDatatype.format === neiDbConst.MDL_FMT_STRING) { 271 | type = `NSArray` 272 | } else if (arrEleDatatype.format === neiDbConst.MDL_FMT_HASH) { 273 | // 2. 否则使用下述形式 274 | type = `NSArray<${prefix}${field.type} *>` 275 | } 276 | } else { 277 | type = 'NSArray' 278 | } 279 | } 280 | } else { 281 | switch (field.format) { 282 | case neiDbConst.MDL_FMT_NUMBER: 283 | case neiDbConst.MDL_FMT_BOOLEAN: 284 | noStar = 1 285 | refName = 'assign' 286 | break 287 | case neiDbConst.MDL_FMT_STRING: 288 | refName = 'copy' 289 | noStar = 0 290 | break 291 | case neiDbConst.MDL_FMT_HASH: 292 | refName = 'strong' 293 | noStar = 0 294 | break 295 | default: 296 | break 297 | } 298 | } 299 | let header = `/**\n * ${field.description}\n */\n` 300 | return header + `@property (nonatomic, ${refName}) ${type} ${noStar ? '' : '*'}${field.name}` 301 | } 302 | 303 | if (isObject(datatype)) { 304 | return new Handlebars.SafeString(datatype.fields && datatype.fields.map(x => { 305 | return helper(x, this.args.prefix) 306 | }).join('\n')) 307 | } else if (Array.isArray(datatype)) { 308 | return new Handlebars.SafeString(datatype.map(x => { 309 | return helper(x, this.args.prefix) 310 | }).join('\n')) 311 | } 312 | }) 313 | 314 | /** 315 | * 把 json 对象转为 string 形式 316 | */ 317 | Handlebars.registerHelper('JSONStringify', function (obj) { 318 | return JSON.stringify(obj, null, '\t') 319 | }) 320 | -------------------------------------------------------------------------------- /src/renderer/utils/hbs-helpers/index.js: -------------------------------------------------------------------------------- 1 | require('./built-in-hbs-helper') 2 | require('./huntbao-hbs-helper') -------------------------------------------------------------------------------- /src/renderer/utils/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/4. 3 | */ 4 | const swapItems = function (arr, index1, index2) { 5 | arr[index1] = arr.splice(index2, 1, arr[index1])[0] 6 | return arr 7 | } 8 | 9 | export function clone (val) { 10 | let newVal = JSON.stringify(val) 11 | return JSON.parse(newVal) 12 | } 13 | 14 | export function moveUp (arr, $index) { 15 | if ($index === 0) { 16 | return 17 | } 18 | swapItems(arr, $index, $index - 1) 19 | } 20 | 21 | export function moveDown (arr, $index) { 22 | if ($index === arr.length - 1) { 23 | return 24 | } 25 | swapItems(arr, $index, $index + 1) 26 | } 27 | 28 | export function insert (arr, position, item) { 29 | return arr.splice(position, 0, item) 30 | } 31 | 32 | export function upperFirstAndDashToCamel (str) { 33 | if (!str) { 34 | return str 35 | } 36 | return str.split('_').map(x => { 37 | return x.charAt(0).toUpperCase() + x.slice(1) 38 | }).join('') 39 | } 40 | 41 | export function dashToCamel (str) { 42 | if (!str) { 43 | return str 44 | } 45 | return str.split('_').map(x => { 46 | return x.charAt(0).toUpperCase() + x.slice(1) 47 | }).join('') 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/renderer/utils/localstorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Deboy on 2017/4/2. 3 | */ 4 | /** 5 | * 保存到本地储存 6 | * key: 保存的key值 7 | * value: 对应的value 8 | * saveAsSession: 是否保存到session 9 | * isObject: 需要储存的值 是否是对象 默认是 10 | * @param args 11 | */ 12 | export function save (args) { 13 | let params = Object.assign({}, {key: '', value: null, saveAsSession: false, isObject: true}, args) 14 | let saveString = params.isObject ? JSON.stringify(params.value) : params.value 15 | if (params.saveAsSession) { 16 | window.sessionStorage.setItem(params.key, saveString) 17 | } else { 18 | window.localStorage.setItem(params.key, saveString) 19 | } 20 | } 21 | 22 | /** 23 | * 从本地储存中区 24 | * key: 保存的key值 25 | * value: 对应的value 26 | * getFromSession: 是否从session中获取的 27 | * isObject: 需要储存的值 是否是对象 默认是 28 | * defaultVal: 如果获取到时undefined或者空 则默认返回的值 默认是null 29 | * @param args 30 | */ 31 | export function get (args) { 32 | let params = Object.assign({}, {key: '', getFromSession: false, isObject: true, defaultVal: null}, args) 33 | let val = null 34 | if (params.getFromSession) { 35 | val = window.sessionStorage.getItem(params.key) 36 | } else { 37 | val = window.localStorage.getItem(params.key) 38 | } 39 | return params.isObject ? (JSON.parse(val) ? JSON.parse(val) : params.defaultVal) : (val === undefined ? params.defaultVal : val) 40 | } 41 | -------------------------------------------------------------------------------- /src/renderer/views/CreateModel.vue: -------------------------------------------------------------------------------- 1 | 2 | 82 | 106 | 184 | -------------------------------------------------------------------------------- /src/renderer/views/ExportPage.vue: -------------------------------------------------------------------------------- 1 | 76 | 127 | 237 | -------------------------------------------------------------------------------- /src/renderer/views/FieldEditor.vue: -------------------------------------------------------------------------------- 1 | 134 | 172 | 385 | -------------------------------------------------------------------------------- /src/renderer/views/Helper.vue: -------------------------------------------------------------------------------- 1 | 26 | 31 | 79 | -------------------------------------------------------------------------------- /src/renderer/views/Home.vue: -------------------------------------------------------------------------------- 1 | 33 | 45 | 196 | -------------------------------------------------------------------------------- /src/renderer/views/NewProject.vue: -------------------------------------------------------------------------------- 1 | 2 | 66 | 83 | 166 | -------------------------------------------------------------------------------- /src/renderer/views/Settings.vue: -------------------------------------------------------------------------------- 1 | 23 | 34 | 36 | -------------------------------------------------------------------------------- /src/renderer/views/Settings/Base.vue: -------------------------------------------------------------------------------- 1 | 22 | 24 | 97 | -------------------------------------------------------------------------------- /src/renderer/views/Settings/Logs.vue: -------------------------------------------------------------------------------- 1 | 84 | 86 | 158 | -------------------------------------------------------------------------------- /src/renderer/views/Settings/Template.vue: -------------------------------------------------------------------------------- 1 | 62 | 95 | 169 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/static/.gitkeep -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "assert": true, 7 | "expect": true, 8 | "should": true, 9 | "__static": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/e2e/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Set BABEL_ENV to use proper env config 4 | process.env.BABEL_ENV = 'test' 5 | 6 | // Enable use of ES6+ on required files 7 | require('babel-register')({ 8 | ignore: /node_modules/ 9 | }) 10 | 11 | // Attach Chai APIs to global scope 12 | const { expect, should, assert } = require('chai') 13 | global.expect = expect 14 | global.should = should 15 | global.assert = assert 16 | 17 | // Require all JS files in `./specs` for Mocha to consume 18 | require('require-dir')('./specs') 19 | -------------------------------------------------------------------------------- /test/e2e/specs/Launch.spec.js: -------------------------------------------------------------------------------- 1 | import utils from '../utils' 2 | 3 | describe('Launch', function () { 4 | beforeEach(utils.beforeEach) 5 | afterEach(utils.afterEach) 6 | 7 | it('shows the proper application title', function () { 8 | return this.app.client.getTitle() 9 | .then(title => { 10 | expect(title).to.equal('model-maker-pro') 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/e2e/utils.js: -------------------------------------------------------------------------------- 1 | import electron from 'electron' 2 | import { Application } from 'spectron' 3 | 4 | export default { 5 | afterEach () { 6 | this.timeout(10000) 7 | 8 | if (this.app && this.app.isRunning()) { 9 | return this.app.stop() 10 | } 11 | }, 12 | beforeEach () { 13 | this.timeout(10000) 14 | this.app = new Application({ 15 | path: electron, 16 | args: ['dist/electron/main.js'], 17 | startTimeout: 10000, 18 | waitTimeout: 10000 19 | }) 20 | 21 | return this.app.start() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | Vue.config.devtools = false 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src/renderer', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const merge = require('webpack-merge') 5 | const webpack = require('webpack') 6 | 7 | const baseConfig = require('../../.electron-vue/webpack.renderer.config') 8 | const projectRoot = path.resolve(__dirname, '../../src/renderer') 9 | 10 | // Set BABEL_ENV to use proper preset config 11 | process.env.BABEL_ENV = 'test' 12 | 13 | let webpackConfig = merge(baseConfig, { 14 | devtool: '#inline-source-map', 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env.NODE_ENV': '"testing"' 18 | }) 19 | ] 20 | }) 21 | 22 | // don't treat dependencies as externals 23 | delete webpackConfig.entry 24 | delete webpackConfig.externals 25 | delete webpackConfig.output.libraryTarget 26 | 27 | // apply vue option to apply isparta-loader on js 28 | webpackConfig.module.rules 29 | .find(rule => rule.use.loader === 'vue-loader').use.options.loaders.js = 'babel-loader' 30 | 31 | module.exports = config => { 32 | config.set({ 33 | browsers: ['visibleElectron'], 34 | client: { 35 | useIframe: false 36 | }, 37 | coverageReporter: { 38 | dir: './coverage', 39 | reporters: [ 40 | { type: 'lcov', subdir: '.' }, 41 | { type: 'text-summary' } 42 | ] 43 | }, 44 | customLaunchers: { 45 | 'visibleElectron': { 46 | base: 'Electron', 47 | flags: ['--show'] 48 | } 49 | }, 50 | frameworks: ['mocha', 'chai'], 51 | files: ['./index.js'], 52 | preprocessors: { 53 | './index.js': ['webpack', 'sourcemap'] 54 | }, 55 | reporters: ['spec', 'coverage'], 56 | singleRun: true, 57 | webpack: webpackConfig, 58 | webpackMiddleware: { 59 | noInfo: true 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /test/unit/specs/LandingPage.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import LandingPage from '@/components/LandingPage' 3 | 4 | describe('LandingPage.vue', () => { 5 | it('should render correct contents', () => { 6 | const vm = new Vue({ 7 | el: document.createElement('div'), 8 | render: h => h(LandingPage) 9 | }).$mount() 10 | 11 | expect(vm.$el.querySelector('.title').textContent).to.contain('Welcome to your new project!') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModelMakerPro/model-maker-pro/ba64ce5358799ec5ee241046f16a3b0b6bcc9d63/wechat.jpg --------------------------------------------------------------------------------