├── .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 | 
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 | 
30 |
31 | 
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 | 
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 | 
17 |
18 | 
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 | 
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 |
6 | - 不需要手写
Model Vo Controller Service 等等项目中重复的文件💩
7 |
8 | - 边写模版 边看结果 释放你的
CMD+R 💯
9 | - 项目配置 表配置 字段自定义属性配置 高度定制化🙌
10 | - 以
Model 为核心 结合模版 一键导出无限可能🙇
11 |
12 |
13 |
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 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 2017-2018 © Deboy
20 |
21 |
22 |
23 |
24 |
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 |
2 |
3 |
4 |
变量 (渲染数据)
5 |
{{code.variable}}
6 |
用法 (渲染模版)
7 |
{{code.template}}
8 |
结果 (渲染结果)
9 |
{{renderResult}}
10 |
11 |
12 |
13 |
33 |
68 |
--------------------------------------------------------------------------------
/src/renderer/components/config.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{showModal = false}" @on-cancel="()=>{showModal = false}">
4 |
19 |
20 | 日志记录
21 |
22 |
23 |
24 |
25 | 时间: {{log.time}} 类型: {{log.type}}
26 |
27 | 标题: {{log.title}}
28 | 内容: {{log.desc || log.content}}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 时间: {{log.time}} 类型: {{log.type}}
37 |
38 | 标题: {{log.title}}
39 | 内容: {{log.desc || log.content}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 时间: {{log.time}} 类型: {{log.type}}
48 |
49 | 标题: {{log.title}}
50 | 内容: {{log.desc || log.content}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 时间: {{log.time}} 类型: {{log.type}}
59 |
60 | 标题: {{log.title}}
61 | 内容: {{log.desc || log.content}}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 时间: {{log.time}} 类型: {{log.type}}
70 |
71 | 标题: {{log.title}}
72 | 内容: {{log.desc || log.content}}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 超过100条自动清理?
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
93 |
171 |
--------------------------------------------------------------------------------
/src/renderer/components/filter-editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {showModel = false}" @on-cancel="()=>{showModel = false}">
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
41 |
--------------------------------------------------------------------------------
/src/renderer/components/layout/app-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
38 |
39 |
64 |
96 |
--------------------------------------------------------------------------------
/src/renderer/components/layout/app-slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
61 |
62 |
{showAddModal = false}">
63 |
71 |
72 |
73 |
{showDeleteConfirm = false}">
74 | 确定删除该表 操作无法恢复 请三思?
75 |
76 |
77 |
78 |
79 |
80 |
81 |
116 |
205 |
--------------------------------------------------------------------------------
/src/renderer/components/layout/project-config.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
38 |
83 |
--------------------------------------------------------------------------------
/src/renderer/components/layout/table-config.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
40 |
75 |
--------------------------------------------------------------------------------
/src/renderer/components/multi-type-input.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{label}}
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
46 |
64 |
--------------------------------------------------------------------------------
/src/renderer/components/props-add.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{show = false}"
6 | @on-cancel="()=>{show = false}">
7 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
3 |
4 |
5 |
6 |
56 |
57 |
58 |
59 | 运行日志
60 |
61 |
Error
62 |
63 | {{error}}
64 |
65 |
66 |
67 |
StdErr
68 |
69 | {{stderr}}
70 |
71 |
72 |
73 |
StdOut
74 |
75 | {{stdout}}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
106 |
184 |
--------------------------------------------------------------------------------
/src/renderer/views/ExportPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 | 为避免Modal数据过多导致预览慢的问题,tables 仅显示第一个表格的 Model
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
127 |
237 |
--------------------------------------------------------------------------------
/src/renderer/views/FieldEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
请注意 自动保存功能已去除 切换表编辑之前记得按下 Ctrl+S 或 Ctrl+Shift+S 保存 否则会丢失数据 返回上一步按下:Ctrl+Z
4 |
5 |
6 |
104 |
105 |
106 |
107 | {{attrKey}} 属性
108 |
111 |
112 |
113 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
129 |
130 |
131 |
132 |
133 |
134 |
172 |
385 |
--------------------------------------------------------------------------------
/src/renderer/views/Helper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
以下是软件自带的几个帮助函数文档 (会逐步把第三方文档迁移到此处 方便查询)
13 |
14 |
15 |
16 | {{code.title}} ({{code.comment}})
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
79 |
--------------------------------------------------------------------------------
/src/renderer/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 本工具用于生成项目Model
4 | 使用步骤
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
当前版本: {{version}}
15 |
更新日志
16 |
17 |
18 |
19 | {{update.version}} {{update.date}}
20 |
21 |
{{update.tips.desc}}
22 |
23 | -
24 | {{index + 1}}. {{log}}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
45 |
196 |
--------------------------------------------------------------------------------
/src/renderer/views/NewProject.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
52 |
53 |
54 |
55 | Model信息概览
56 |
57 | -
58 |
{{table.name}}
59 | 表格字段列表: {{field.field}}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
83 |
166 |
--------------------------------------------------------------------------------
/src/renderer/views/Settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
34 |
36 |
--------------------------------------------------------------------------------
/src/renderer/views/Settings/Base.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
24 |
97 |
--------------------------------------------------------------------------------
/src/renderer/views/Settings/Logs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 日志记录
5 |
6 |
7 |
8 |
9 |
10 | 时间: {{log.time}} 类型: {{log.type}}
11 |
12 | 标题: {{log.title}}
13 | 内容: {{log.desc || log.content}}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 时间: {{log.time}} 类型: {{log.type}}
24 |
25 | 标题: {{log.title}}
26 | 内容: {{log.desc || log.content}}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 时间: {{log.time}} 类型: {{log.type}}
37 |
38 | 标题: {{log.title}}
39 | 内容: {{log.desc || log.content}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 时间: {{log.time}} 类型: {{log.type}}
50 |
51 | 标题: {{log.title}}
52 | 内容: {{log.desc || log.content}}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 时间: {{log.time}} 类型: {{log.type}}
63 |
64 | 标题: {{log.title}}
65 | 内容: {{log.desc || log.content}}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | 超过100条自动清理?
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
86 |
158 |
--------------------------------------------------------------------------------
/src/renderer/views/Settings/Template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 | 模版列表(任何数据异常 请点击右侧 同步模版)
18 |
19 |
20 |
21 |
22 |
23 |
60 |
61 |
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
--------------------------------------------------------------------------------