├── .babelrc ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── assets ├── alipay.png ├── commitMessageStandard.md ├── home.jpg ├── login.png ├── logo.png ├── tree.txt ├── treeNodeCli.md ├── vueDevtools.md └── wechatpay.png ├── build └── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico ├── dist ├── electron │ └── .gitkeep └── web │ └── .gitkeep ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── assets │ ├── icon.ico │ └── logo.png ├── changelog.md ├── en │ ├── README.md │ └── _sidebar.md ├── faq.md ├── index.html └── sw.js ├── package-lock.json ├── package.json ├── screenshot-app.png ├── screenshot-desktop.png ├── screenshot-frontend.png ├── screenshot-home-online.png ├── screenshot-web.png ├── sentry-symbols.js ├── sentry.properties ├── snap └── snapcraft.yaml ├── src ├── api │ ├── assets.js │ ├── category.js │ ├── incomeAndExpenditure.js │ └── user.js ├── datastore │ ├── README.md │ └── index.js ├── index.ejs ├── main │ ├── index.dev.js │ ├── index.js │ └── startOnBoot.js └── renderer │ ├── App.vue │ ├── assets │ ├── .gitkeep │ └── logo.png │ ├── components │ ├── Layout │ │ └── Layout.vue │ └── add │ │ └── add.vue │ ├── main.js │ ├── router │ └── index.js │ ├── store │ ├── index.js │ ├── modules │ │ ├── Counter.js │ │ ├── dialogAdd.js │ │ ├── index.js │ │ └── todo.js │ └── plugins │ │ └── todo.js │ └── views │ ├── Assets │ └── Assets.vue │ ├── Category │ └── Category.vue │ ├── Help │ └── Help.vue │ ├── Home │ ├── Home.vue │ └── components │ │ ├── add │ │ └── add.vue │ │ ├── history │ │ └── history.vue │ │ ├── statsCurve │ │ └── statsCurve.vue │ │ ├── statsIcon │ │ └── statsIcon.vue │ │ └── todo │ │ ├── todo.vue │ │ └── todoItem.vue │ ├── IncomeAndExpenditure │ └── IncomeAndExpenditure.vue │ ├── Login │ └── Login.vue │ ├── Notes │ └── Notes.vue │ ├── Password │ └── Password.vue │ └── Settings │ └── Settings.vue ├── static ├── .gitkeep ├── icon.ico ├── iconMessage.ico ├── iconMessage.png ├── iconMessageNotWin.png ├── iconMessageNotWin@2x.png ├── iconMessageNotWin@3x.png ├── iconTransparent.ico ├── iconTransparent.png ├── iconTransparentNotWin.png ├── iconTransparentNotWin@2x.png ├── iconTransparentNotWin@3x.png ├── logo.ico ├── logo.png ├── logoNotWin.png ├── logoNotWin@2x.png └── logoNotWin@3x.png └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | [ 7 | "env", 8 | { 9 | "targets": { 10 | "node": 7 11 | } 12 | } 13 | ], 14 | "stage-0" 15 | ] 16 | }, 17 | "renderer": { 18 | "presets": [ 19 | [ 20 | "env", 21 | { 22 | "modules": false 23 | } 24 | ], 25 | "stage-0" 26 | ] 27 | }, 28 | "web": { 29 | "presets": [ 30 | [ 31 | "env", 32 | { 33 | "modules": false 34 | } 35 | ], 36 | "stage-0" 37 | ] 38 | } 39 | }, 40 | "plugins": [ 41 | "transform-runtime", 42 | [ 43 | "component", 44 | [ 45 | { 46 | "libraryName": "element-ui", 47 | "styleLibraryName": "theme-chalk" 48 | } 49 | ] 50 | ] 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.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 | config.mode = 'production' 76 | webpack(config, (err, stats) => { 77 | if (err) reject(err.stack || err) 78 | else if (stats.hasErrors()) { 79 | let err = '' 80 | 81 | stats.toString({ 82 | chunks: false, 83 | colors: true 84 | }) 85 | .split(/\r?\n/) 86 | .forEach(line => { 87 | err += ` ${line}\n` 88 | }) 89 | 90 | reject(err) 91 | } else { 92 | resolve(stats.toString({ 93 | chunks: false, 94 | colors: true 95 | })) 96 | } 97 | }) 98 | }) 99 | } 100 | 101 | function web () { 102 | del.sync(['dist/web/*', '!.gitkeep']) 103 | webConfig.mode = 'production' 104 | webpack(webConfig, (err, stats) => { 105 | if (err || stats.hasErrors()) console.log(err) 106 | 107 | console.log(stats.toString({ 108 | chunks: false, 109 | colors: true 110 | })) 111 | 112 | process.exit() 113 | }) 114 | } 115 | 116 | function greeting () { 117 | const cols = process.stdout.columns 118 | let text = '' 119 | 120 | if (cols > 85) text = 'lets-build' 121 | else if (cols > 60) text = 'lets-|build' 122 | else text = false 123 | 124 | if (text && !isCI) { 125 | say(text, { 126 | colors: ['yellow'], 127 | font: 'simple3d', 128 | space: false 129 | }) 130 | } else console.log(chalk.yellow.bold('\n lets-build')) 131 | console.log() 132 | } -------------------------------------------------------------------------------- /.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 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /.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 | rendererConfig.mode = 'development' 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.hooks.compilation.tap('compilation', compilation => { 52 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.hooks.done.tap('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 | before (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 | mainConfig.mode = 'development' 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.hooks.watchRun.tapAsync('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 | var args = [ 118 | '--inspect=5858', 119 | // 为了方便调试chrome://inspect,本地调试用,此代码不能提交 120 | // '--inspect-brk=5858', 121 | path.join(__dirname, '../dist/electron/main.js') 122 | ] 123 | 124 | // detect yarn or npm and process commandline args accordingly 125 | if (process.env.npm_execpath.endsWith('yarn.js')) { 126 | args = args.concat(process.argv.slice(3)) 127 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { 128 | args = args.concat(process.argv.slice(2)) 129 | } 130 | 131 | electronProcess = spawn(electron, args) 132 | 133 | electronProcess.stdout.on('data', data => { 134 | electronLog(data, 'blue') 135 | }) 136 | electronProcess.stderr.on('data', data => { 137 | electronLog(data, 'red') 138 | }) 139 | 140 | electronProcess.on('close', () => { 141 | if (!manualRestart) process.exit() 142 | }) 143 | } 144 | 145 | function electronLog (data, color) { 146 | let log = '' 147 | data = data.toString().split(/\r?\n/) 148 | data.forEach(line => { 149 | log += ` ${line}\n` 150 | }) 151 | if (/[0-9A-z]+/.test(log)) { 152 | console.log( 153 | chalk[color].bold('┏ Electron -------------------') + 154 | '\n\n' + 155 | log + 156 | chalk[color].bold('┗ ----------------------------') + 157 | '\n' 158 | ) 159 | } 160 | } 161 | 162 | function greeting () { 163 | const cols = process.stdout.columns 164 | let text = '' 165 | 166 | if (cols > 104) text = 'electron-vue' 167 | else if (cols > 76) text = 'electron-|vue' 168 | else text = false 169 | 170 | if (text) { 171 | say(text, { 172 | colors: ['yellow'], 173 | font: 'simple3d', 174 | space: false 175 | }) 176 | } else console.log(chalk.yellow.bold('\n electron-vue')) 177 | console.log(chalk.blue(' getting ready...') + '\n') 178 | } 179 | 180 | function init () { 181 | greeting() 182 | 183 | Promise.all([startRenderer(), startMain()]) 184 | .then(() => { 185 | startElectron() 186 | }) 187 | .catch(err => { 188 | console.error(err) 189 | }) 190 | } 191 | 192 | init() 193 | -------------------------------------------------------------------------------- /.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 | const BabiliWebpackPlugin = require('babel-minify-webpack-plugin') 11 | 12 | let mainConfig = { 13 | entry: { 14 | main: path.join(__dirname, '../src/main/index.js') 15 | }, 16 | externals: [ 17 | ...Object.keys(dependencies || {}) 18 | ], 19 | // 为了方便调试chrome://inspect,本地调试用,此代码不能提交 20 | // devtool:`inline-source-map`, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | use: 'babel-loader', 26 | exclude: /node_modules/ 27 | }, 28 | { 29 | test: /\.node$/, 30 | use: 'node-loader' 31 | } 32 | ] 33 | }, 34 | node: { 35 | __dirname: process.env.NODE_ENV !== 'production', 36 | __filename: process.env.NODE_ENV !== 'production' 37 | }, 38 | output: { 39 | filename: '[name].js', 40 | libraryTarget: 'commonjs2', 41 | path: path.join(__dirname, '../dist/electron') 42 | }, 43 | plugins: [ 44 | new webpack.NoEmitOnErrorsPlugin() 45 | ], 46 | resolve: { 47 | extensions: ['.js', '.json', '.node'] 48 | }, 49 | target: 'electron-main' 50 | } 51 | 52 | /** 53 | * Adjust mainConfig for development settings 54 | */ 55 | if (process.env.NODE_ENV !== 'production') { 56 | mainConfig.plugins.push( 57 | new webpack.DefinePlugin({ 58 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 59 | }) 60 | ) 61 | } 62 | 63 | /** 64 | * Adjust mainConfig for production settings 65 | */ 66 | if (process.env.NODE_ENV === 'production') { 67 | mainConfig.plugins.push( 68 | new BabiliWebpackPlugin(), 69 | new webpack.DefinePlugin({ 70 | 'process.env.NODE_ENV': '"production"' 71 | }) 72 | ) 73 | } 74 | 75 | module.exports = mainConfig 76 | -------------------------------------------------------------------------------- /.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 BabiliWebpackPlugin = require('babel-minify-webpack-plugin') 11 | const CopyWebpackPlugin = require('copy-webpack-plugin') 12 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 13 | const HtmlWebpackPlugin = require('html-webpack-plugin') 14 | const { VueLoaderPlugin } = require('vue-loader') 15 | 16 | /** 17 | * List of node_modules to include in webpack bundle 18 | * 19 | * Required for specific packages like Vue UI libraries 20 | * that provide pure *.vue files that need compiling 21 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 22 | */ 23 | let whiteListedModules = ['vue', 'vuetify'] 24 | 25 | let rendererConfig = { 26 | devtool: '#cheap-module-eval-source-map', 27 | entry: { 28 | renderer: path.join(__dirname, '../src/renderer/main.js') 29 | }, 30 | externals: [ 31 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 32 | ], 33 | module: { 34 | rules: [ 35 | { 36 | test: /\.scss$/, 37 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 38 | }, 39 | { 40 | test: /\.sass$/, 41 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 42 | }, 43 | { 44 | test: /\.less$/, 45 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 46 | }, 47 | { 48 | test: /\.css$/, 49 | use: ['vue-style-loader', 'css-loader'] 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 | less: 'vue-style-loader!css-loader!less-loader' 74 | } 75 | } 76 | } 77 | }, 78 | { 79 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 80 | use: { 81 | loader: 'url-loader', 82 | query: { 83 | limit: 10000, 84 | name: 'imgs/[name]--[folder].[ext]' 85 | } 86 | } 87 | }, 88 | { 89 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 90 | loader: 'url-loader', 91 | options: { 92 | limit: 10000, 93 | name: 'media/[name]--[folder].[ext]' 94 | } 95 | }, 96 | { 97 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 98 | use: { 99 | loader: 'url-loader', 100 | query: { 101 | limit: 10000, 102 | name: 'fonts/[name]--[folder].[ext]' 103 | } 104 | } 105 | } 106 | ] 107 | }, 108 | node: { 109 | __dirname: process.env.NODE_ENV !== 'production', 110 | __filename: process.env.NODE_ENV !== 'production' 111 | }, 112 | plugins: [ 113 | new VueLoaderPlugin(), 114 | new MiniCssExtractPlugin({filename: 'styles.css'}), 115 | // new HtmlWebpackPlugin({ 116 | // filename: 'index.html', 117 | // template: path.resolve(__dirname, '../src/index.ejs'), 118 | // minify: { 119 | // collapseWhitespace: true, 120 | // removeAttributeQuotes: true, 121 | // removeComments: true 122 | // }, 123 | // nodeModules: process.env.NODE_ENV !== 'production' 124 | // ? path.resolve(__dirname, '../node_modules') 125 | // : false 126 | // }), 127 | new HtmlWebpackPlugin({ 128 | filename: 'index.html', 129 | template: path.resolve(__dirname, '../src/index.ejs'), 130 | templateParameters(compilation, assets, options) { 131 | return { 132 | compilation: compilation, 133 | webpack: compilation.getStats().toJson(), 134 | webpackConfig: compilation.options, 135 | htmlWebpackPlugin: { 136 | files: assets, 137 | options: options 138 | }, 139 | process, 140 | }; 141 | }, 142 | minify: { 143 | collapseWhitespace: true, 144 | removeAttributeQuotes: true, 145 | removeComments: true 146 | }, 147 | nodeModules: process.env.NODE_ENV !== 'production' 148 | ? path.resolve(__dirname, '../node_modules') 149 | : false 150 | }), 151 | new webpack.HotModuleReplacementPlugin(), 152 | new webpack.NoEmitOnErrorsPlugin() 153 | ], 154 | output: { 155 | filename: '[name].js', 156 | libraryTarget: 'commonjs2', 157 | path: path.join(__dirname, '../dist/electron') 158 | }, 159 | resolve: { 160 | alias: { 161 | '@': path.join(__dirname, '../src/renderer'), 162 | 'vue$': 'vue/dist/vue.esm.js', 163 | '~': path.join(__dirname, '../src'), 164 | }, 165 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 166 | }, 167 | target: 'electron-renderer' 168 | } 169 | 170 | /** 171 | * Adjust rendererConfig for development settings 172 | */ 173 | if (process.env.NODE_ENV !== 'production') { 174 | rendererConfig.plugins.push( 175 | new webpack.DefinePlugin({ 176 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 177 | }) 178 | ) 179 | } 180 | 181 | /** 182 | * Adjust rendererConfig for production settings 183 | */ 184 | if (process.env.NODE_ENV === 'production') { 185 | rendererConfig.devtool = '' 186 | 187 | rendererConfig.plugins.push( 188 | new BabiliWebpackPlugin(), 189 | new CopyWebpackPlugin([ 190 | { 191 | from: path.join(__dirname, '../static'), 192 | to: path.join(__dirname, '../dist/electron/static'), 193 | ignore: ['.*'] 194 | } 195 | ]), 196 | new webpack.DefinePlugin({ 197 | 'process.env.NODE_ENV': '"production"' 198 | }), 199 | new webpack.LoaderOptionsPlugin({ 200 | minimize: true 201 | }) 202 | ) 203 | } 204 | 205 | module.exports = rendererConfig 206 | -------------------------------------------------------------------------------- /.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 BabiliWebpackPlugin = require('babel-minify-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | const { VueLoaderPlugin } = require('vue-loader') 14 | 15 | let webConfig = { 16 | devtool: '#cheap-module-eval-source-map', 17 | entry: { 18 | web: path.join(__dirname, '../src/renderer/main.js') 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.scss$/, 24 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 25 | }, 26 | { 27 | test: /\.sass$/, 28 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 29 | }, 30 | { 31 | test: /\.less$/, 32 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 33 | }, 34 | { 35 | test: /\.css$/, 36 | use: ['vue-style-loader', 'css-loader'] 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 | less: 'vue-style-loader!css-loader!less-loader' 58 | } 59 | } 60 | } 61 | }, 62 | { 63 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 64 | use: { 65 | loader: 'url-loader', 66 | query: { 67 | limit: 10000, 68 | name: 'imgs/[name].[ext]' 69 | } 70 | } 71 | }, 72 | { 73 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 74 | use: { 75 | loader: 'url-loader', 76 | query: { 77 | limit: 10000, 78 | name: 'fonts/[name].[ext]' 79 | } 80 | } 81 | } 82 | ] 83 | }, 84 | plugins: [ 85 | new VueLoaderPlugin(), 86 | new MiniCssExtractPlugin({filename: 'styles.css'}), 87 | // new HtmlWebpackPlugin({ 88 | // filename: 'index.html', 89 | // template: path.resolve(__dirname, '../src/index.ejs'), 90 | // minify: { 91 | // collapseWhitespace: true, 92 | // removeAttributeQuotes: true, 93 | // removeComments: true 94 | // }, 95 | // nodeModules: false 96 | // }), 97 | new HtmlWebpackPlugin({ 98 | filename: 'index.html', 99 | template: path.resolve(__dirname, '../src/index.ejs'), 100 | templateParameters(compilation, assets, options) { 101 | return { 102 | compilation: compilation, 103 | webpack: compilation.getStats().toJson(), 104 | webpackConfig: compilation.options, 105 | htmlWebpackPlugin: { 106 | files: assets, 107 | options: options 108 | }, 109 | process, 110 | }; 111 | }, 112 | minify: { 113 | collapseWhitespace: true, 114 | removeAttributeQuotes: true, 115 | removeComments: true 116 | }, 117 | nodeModules: false 118 | }), 119 | new webpack.DefinePlugin({ 120 | 'process.env.IS_WEB': 'true' 121 | }), 122 | new webpack.HotModuleReplacementPlugin(), 123 | new webpack.NoEmitOnErrorsPlugin() 124 | ], 125 | output: { 126 | filename: '[name].js', 127 | path: path.join(__dirname, '../dist/web') 128 | }, 129 | resolve: { 130 | alias: { 131 | '@': path.join(__dirname, '../src/renderer'), 132 | 'vue$': 'vue/dist/vue.esm.js' 133 | }, 134 | extensions: ['.js', '.vue', '.json', '.css'] 135 | }, 136 | target: 'web' 137 | } 138 | 139 | /** 140 | * Adjust webConfig for production settings 141 | */ 142 | if (process.env.NODE_ENV === 'production') { 143 | webConfig.devtool = '' 144 | 145 | webConfig.plugins.push( 146 | new BabiliWebpackPlugin(), 147 | new CopyWebpackPlugin([ 148 | { 149 | from: path.join(__dirname, '../static'), 150 | to: path.join(__dirname, '../dist/web/static'), 151 | ignore: ['.*'] 152 | } 153 | ]), 154 | new webpack.DefinePlugin({ 155 | 'process.env.NODE_ENV': '"production"' 156 | }), 157 | new webpack.LoaderOptionsPlugin({ 158 | minimize: true 159 | }) 160 | ) 161 | } 162 | 163 | module.exports = webConfig 164 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | node_modules/ 7 | npm-debug.log 8 | npm-debug.log.* 9 | thumbs.db 10 | !.gitkeep 11 | .idea/ 12 | yarn-error.log 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: osx 4 | osx_image: xcode10.2 5 | language: node_js 6 | node_js: "10" 7 | env: 8 | - ELECTRON_CACHE=$HOME/.cache/electron 9 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 10 | 11 | - os: linux 12 | services: docker 13 | language: generic 14 | 15 | cache: 16 | directories: 17 | - node_modules 18 | - $HOME/.cache/electron 19 | - $HOME/.cache/electron-builder 20 | 21 | script: 22 | - | 23 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then 24 | docker run --rm \ 25 | --env-file <(env | grep -vE '\r|\n' | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS_TAG|TRAVIS|TRAVIS_REPO_|TRAVIS_BUILD_|TRAVIS_BRANCH|TRAVIS_PULL_REQUEST_|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \ 26 | -v ${PWD}:/project \ 27 | -v ~/.cache/electron:/root/.cache/electron \ 28 | -v ~/.cache/electron-builder:/root/.cache/electron-builder \ 29 | electronuserland/builder:wine \ 30 | /bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn release --linux --win" 31 | else 32 | yarn release 33 | fi 34 | before_cache: 35 | - rm -rf $HOME/.cache/electron-builder/wine 36 | 37 | branches: 38 | except: 39 | - "/^v\\d+\\.\\d+\\.\\d+$/" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lan Miao 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 | > 特别提醒 2 | - *online* 分支是网络版,基于 [electron-forge](https://github.com/electron-userland/electron-forge) 开发,需要配合我的另一个开源项目 [招财猫](https://github.com/hilanmiao/smart-miao) 进行开发 3 | - *master* 分支是离线版,基于 [elctron-vue](https://github.com/SimulatedGREG/electron-vue) 开发 4 | 5 | ![首页](screenshot-home-online.png) 6 | 7 | 招财猫系列应用截图: 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 23 | 26 | 29 | 30 |
网页端移动端桌面端营销网站
18 | 19 | 21 | 22 | 24 | 25 | 27 | 28 |
31 | 32 | *** 33 | 34 | # PocketBook 35 | 36 |

37 | 38 |

39 |

40 | 41 | 42 | 43 | 44 | 45 | 46 |

47 | 48 | ## 概述 49 | 50 | **使用Electron开发一套记账软件,几乎每一个实现都会有一片博文说明。** 51 | 52 | 53 | ## 下载安装 54 | 55 | macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。 56 | 57 | 点击此处下载[应用](https://github.com/hilanmiao/LanMiaoDesktop/releases)。 58 | 59 | 安装完毕,点击此处[尝试打开](PocketBook:?a=1&b=2)。 60 | 61 | [PocketBook:?a=1&b=2](PocketBook:?a=1&b=2 "PocketBook:?a=1&b=2") 62 | 63 | ## 应用截图 64 | 65 | ![首页](https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/master/assets/home.jpg) 66 | 67 | ![登录](https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/master/assets/login.png) 68 | 69 | ## 开发说明 70 | 71 | > 目前仅针对Windows。Mac、Linux平台并未测试。 72 | 73 | ``` bash 74 | # install dependencies 75 | npm install 76 | 77 | # serve with hot reload at localhost:9080 78 | npm run dev 79 | 80 | # build electron application for production 81 | npm run build 82 | 83 | # lint all JS/Vue component files in `src/` 84 | npm run lint 85 | ``` 86 | 87 | 88 | ## 其他相关 89 | 90 | - [NSIS-UI](https://github.com/hilanmiao/NSIS-UI) windows exe安装包美化程序。 91 | 92 | 93 | ## 赞助 94 | 95 | 如果你喜欢它,不妨给它点个star或者请我喝杯咖啡 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 109 | 110 |
支付宝微信
104 | 105 | 107 | 108 |
111 | 112 | ## 开源协议 113 | 114 | 本项目基于 [MIT](http://opensource.org/licenses/MIT) 协议,请自由地享受和参与开源。 115 | 116 | 117 | ## 贡献 118 | 119 | 如果你有好的意见或建议,欢迎给我们提 [issue] 或 [PR],为优化 [LanMiaoDesktop] 贡献力量 120 | 121 | [PR]: https://github.com/hilanmiao/LanMiaoDesktop/pulls 122 | 123 | [issue]: https://github.com/hilanmiao/LanMiaoDesktop/issues 124 | 125 | [LanMiaoDesktop]: https://github.com/youzan/vant-weapp 126 | 127 | ## 教程 128 | 129 | - [Electron开发实战之记账软件1——开篇](https://my.oschina.net/u/3667677/blog/3035515) 130 | 131 | - [Electron开发实战之记账软件2——使用electron-vue初始化项目并升级部分包](https://my.oschina.net/u/3667677/blog/3035513) 132 | 133 | - [Electron开发实战之记账软件3——使用UI框架vuetify实现页面](https://my.oschina.net/u/3667677/blog/3040223) 134 | 135 | - [Electron开发实战之记账软件4——无边框、窗口关闭、最大最小化、隐藏右键菜单](https://my.oschina.net/u/3667677/blog/3041467) 136 | 137 | - [Electron开发实战之记账软件5——制作ico格式的logo](https://my.oschina.net/u/3667677/blog/3041685) 138 | 139 | - [Electron开发实战之记账软件6——自定义系统托盘菜单、闪烁](https://my.oschina.net/u/3667677/blog/3041851) 140 | 141 | - [Electron开发实战之记账软件7——开机自动启动](https://my.oschina.net/u/3667677/blog/3042628) 142 | 143 | - [Electron开发实战之记账软件8——高级通知](https://my.oschina.net/u/3667677/blog/3043443) 144 | 145 | - [Electron开发实战之记账软件9——禁用一些事件](https://my.oschina.net/u/3667677/blog/3043516) 146 | 147 | - [Electron开发实战之记账软件10——保证只有一个实例](https://my.oschina.net/u/3667677/blog/3043529) 148 | 149 | - [Electron开发实战之记账软件11——打包及美化](https://my.oschina.net/u/3667677/blog/3043986) 150 | 151 | - [Electron开发实战之记账软件12——通过CI系统自动构建与发布](https://my.oschina.net/u/3667677/blog/3044025) 152 | 153 | - [Electron开发实战之记账软件13——添加Github徽章](https://my.oschina.net/u/3667677/blog/3044255) 154 | 155 | - [Electron开发实战之记账软件14——自动更新](https://my.oschina.net/u/3667677/blog/3046786) 156 | 157 | - [Electron开发实战之记账软件15——崩溃报告](https://my.oschina.net/u/3667677/blog/3047628) 158 | 159 | - [Electron开发实战之记账软件16——使用Docsify生成文档网站](https://my.oschina.net/u/3667677/blog/3048374) 160 | 161 | - [Electron开发实战之记账软件17——使用Lowdb存储数据](https://my.oschina.net/u/3667677/blog/3054535) 162 | 163 | - [Electron开发实战之记账软件18——备份、恢复、导入、导出](https://my.oschina.net/u/3667677/blog/3073660) 164 | 165 | - [Electron开发实战之记账软件19——通过协议唤起Electron应用](https://my.oschina.net/u/3667677/blog/3074681) 166 | 167 | Electron开发实战之记账软件1——迷你程序 168 | 169 | Electron开发实战之记账软件99——完结 170 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | image: Visual Studio 2019 8 | platform: 9 | - x64 10 | 11 | cache: 12 | - node_modules 13 | - '%APPDATA%\npm-cache' 14 | - '%USERPROFILE%\.electron' 15 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 16 | 17 | init: 18 | - git config --global core.autocrlf input 19 | 20 | install: 21 | - ps: Install-Product node 12 x64 22 | - git reset --hard HEAD 23 | - yarn 24 | - node --version 25 | 26 | build_script: 27 | #- yarn build 28 | - npm run release 29 | 30 | test: off 31 | -------------------------------------------------------------------------------- /assets/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/assets/alipay.png -------------------------------------------------------------------------------- /assets/commitMessageStandard.md: -------------------------------------------------------------------------------- 1 | #优雅的提交Commit信息 2 | 主要有以下组成 3 | 4 | - 标题行: 必填, 描述主要修改类型和内容 5 | - 主题内容: 描述为什么修改, 做了什么样的修改, 以及开发的思路等等 6 | - 页脚注释: 放 Breaking Changes 或 Closed Issues 7 | 8 | 常用的修改项 9 | 10 | - type: commit 的类型 11 | - feat: 新特性 12 | - fix: 修改问题 13 | - refactor: 代码重构 14 | - docs: 文档修改 15 | - style: 代码格式修改, 注意不是 css 修改 16 | - test: 测试用例修改 17 | - chore: 其他修改, 比如构建流程, 依赖管理. 18 | - scope: commit 影响的范围, 比如: route, component, utils, build... 19 | - subject: commit 的概述 20 | - body: commit 具体修改内容, 可以分为多行 21 | - footer: 一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接. 22 | 23 | https://gitmoji.carloscuesta.me/ 24 | 25 | 26 | -------------------------------------------------------------------------------- /assets/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/assets/home.jpg -------------------------------------------------------------------------------- /assets/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/assets/login.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/assets/logo.png -------------------------------------------------------------------------------- /assets/tree.txt: -------------------------------------------------------------------------------- 1 | ├── .electron-vue // 配置及构建脚本 2 | │ ├── build.js // 生产环境构建脚本 3 | │ ├── dev-client.js // 开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新 4 | │ ├── dev-runner.js // 运行本地开发服务器 5 | │ ├── webpack.main.config.js // 主进程webpack配置文件 6 | │ ├── webpack.renderer.config.js // 渲染进程webpack配置文件 7 | │ └── webpack.web.config.js // 8 | ├── assets // 其他资源 9 | ├── build // 构建 10 | │ └── icons // 图标 11 | │ └── win-unpacked // 未打包资源 12 | ├── dist // 静态资源 13 | │ ├── electron 14 | │ │ └── main.js 15 | │ └── web 16 | ├── src // 源码目录 17 | │ ├── main // 主进程 18 | │ │ ├── index.dev.js 19 | │ │ └── index.js // 入口文件 20 | │ ├── renderer // 渲染进程 21 | │ │ ├── assets // 资源 22 | │ │ ├── components // 公共组件目录 23 | │ │ ├── router // 前端路由 24 | │ │ ├── store // 状态管理 25 | │ │ ├── views // 页面目录 26 | │ │ ├── App.vue // 根组件 27 | │ │ └── main.js // 入口文件 28 | │ └── index.ejs 29 | ├── static // 纯静态资源 30 | ├── .babelrc 31 | ├── .travis.yml // Windows和Linux持续集成配置文件 32 | ├── appveyor.yml // Mac持续集成配置文件 33 | ├── package-lock.json 34 | ├── package.json // 包配置文件 35 | └── README.md // 项目介绍 36 | -------------------------------------------------------------------------------- /assets/treeNodeCli.md: -------------------------------------------------------------------------------- 1 | # 安装tree-node-cli 2 | `npm install -g tree-node-cli` 3 | 4 | # 忽略文件夹 5 | `tree -I "node_modules"` 6 | 7 | # 只看三级目录 8 | `tree -L 3` 9 | 10 | # 使用实例 Note: Use the command treee on Windows and Linux to avoid conflicts with built-in tree command. 11 | ``` 12 | tree [options] 13 | treee -L 3 -I "node_modules|.idea|objects|.git" -a --dirs-first 14 | ``` 15 | 16 | # 卸载 17 | `npm uninstall -g tree-node-cli` -------------------------------------------------------------------------------- /assets/vueDevtools.md: -------------------------------------------------------------------------------- 1 | 举个例子:如果你用了ipc,在这里打断点平常是跑不到的,加上devtools才可以。 2 | ``` 3 | ipcRenderer.on('getAutoStartValue', (event, result) => { 4 | this.autoStart = result 5 | }) 6 | ``` 7 | 8 | 0.局部或全局安装 9 | ``` 10 | npm install -g @vue/devtools 11 | npm install --save-dev @vue/devtools 12 | ``` 13 | 14 | 详细信息可查看 15 | 16 | https://github.com/vuejs/vue-devtools/blob/master/shells/electron/README.md 17 | 18 | 1.在vue前引入devtools 19 | ``` 20 | import devtools from '@vue/devtools' 21 | import Vue from 'vue' 22 | ``` 23 | 24 | 2.设置(暂时没用到) 25 | ``` 26 | if (process.env.NODE_ENV === 'development') { 27 | devtools.connect('localhost', '9080') 28 | } 29 | ``` 30 | 31 | 3.在index.ejs文件中添加 32 | ``` 33 | 34 | ``` 35 | 36 | 4.执行cmd文件(有两个文件vue-devtools,vue-devtools.cmd,因为我是windows,所以找到文件位置直接双击运行即可) 37 | ``` 38 | ./node_modules/.bin/vue-devtools.cmd 39 | ``` 40 | -------------------------------------------------------------------------------- /assets/wechatpay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/assets/wechatpay.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/build/icons/icon.ico -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/dist/electron/.gitkeep -------------------------------------------------------------------------------- /dist/web/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/dist/web/.gitkeep -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # PocketBook 2 | 3 |

4 | 5 |

6 |

7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 | ## 概述 16 | 17 | **使用Electron开发一套记账软件,几乎每一个实现都会有一片博文说明。** 18 | 19 | 20 | ## 下载安装 21 | 22 | macOS用户请下载最新版本的`dmg`文件,windows用户请下载最新版本的`exe`文件,linux用户请下载`AppImage`文件。 23 | 24 | 点击此处下载[应用](https://github.com/hilanmiao/LanMiaoDesktop/releases)。 25 | 26 | 安装完毕,点击此处[尝试打开](PocketBook:?a=1&b=2)。 27 | 28 | [PocketBook:?a=1&b=2](PocketBook:?a=1&b=2 "PocketBook:?a=1&b=2") 29 | 30 | ## 应用截图 31 | 32 | ![首页](https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/master/assets/home.jpg) 33 | 34 | ![登录](https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/master/assets/login.png) 35 | 36 | ## 开发说明 37 | 38 | > 目前仅针对Windows。Mac、Linux平台并未测试。 39 | 40 | ``` bash 41 | # install dependencies 42 | npm install 43 | 44 | # serve with hot reload at localhost:9080 45 | npm run dev 46 | 47 | # build electron application for production 48 | npm run build 49 | 50 | # lint all JS/Vue component files in `src/` 51 | npm run lint 52 | ``` 53 | 54 | 55 | ## 其他相关 56 | 57 | - [NSIS-UI](https://github.com/hilanmiao/NSIS-UI):exe安装包美化程序。 58 | 59 | 60 | ## 赞助 61 | 62 | 如果你喜欢它,不妨给它点个star或者请我喝杯咖啡 63 | 64 | 支付宝: 65 | 66 | 67 | 68 | 微信: 69 | 70 | 71 | 72 | 73 | ## 开源协议 74 | 75 | 本项目基于 [MIT](http://opensource.org/licenses/MIT)协议,请自由地享受和参与开源。 76 | 77 | 78 | ## 贡献 79 | 80 | 如果你有好的意见或建议,欢迎给我们提 [issue] 或 [PR],为优化 [LanMiaoDesktop] 贡献力量 81 | 82 | [PR]: https://github.com/hilanmiao/LanMiaoDesktop/pulls 83 | 84 | [issue]: https://github.com/hilanmiao/LanMiaoDesktop/issues 85 | 86 | [LanMiaoDesktop]: https://github.com/youzan/vant-weapp 87 | 88 | ## 教程 89 | 90 | - [Electron开发实战之记账软件1——开篇](https://my.oschina.net/u/3667677/blog/3035515) 91 | 92 | - [Electron开发实战之记账软件2——使用electron-vue初始化项目并升级部分包](https://my.oschina.net/u/3667677/blog/3035513) 93 | 94 | - [Electron开发实战之记账软件3——使用UI框架vuetify实现页面](https://my.oschina.net/u/3667677/blog/3040223) 95 | 96 | - [Electron开发实战之记账软件4——无边框、窗口关闭、最大最小化、隐藏右键菜单](https://my.oschina.net/u/3667677/blog/3041467) 97 | 98 | - [Electron开发实战之记账软件5——制作ico格式的logo](https://my.oschina.net/u/3667677/blog/3041685) 99 | 100 | - [Electron开发实战之记账软件6——自定义系统托盘菜单、闪烁](https://my.oschina.net/u/3667677/blog/3041851) 101 | 102 | - [Electron开发实战之记账软件7——开机自动启动](https://my.oschina.net/u/3667677/blog/3042628) 103 | 104 | - [Electron开发实战之记账软件8——高级通知](https://my.oschina.net/u/3667677/blog/3043443) 105 | 106 | - [Electron开发实战之记账软件9——禁用一些事件](https://my.oschina.net/u/3667677/blog/3043516) 107 | 108 | - [Electron开发实战之记账软件10——保证只有一个实例](https://my.oschina.net/u/3667677/blog/3043529) 109 | 110 | - [Electron开发实战之记账软件11——打包及美化](https://my.oschina.net/u/3667677/blog/3043986) 111 | 112 | - [Electron开发实战之记账软件12——通过CI系统自动构建与发布](https://my.oschina.net/u/3667677/blog/3044025) 113 | 114 | - [Electron开发实战之记账软件13——添加Github徽章](https://my.oschina.net/u/3667677/blog/3044255) 115 | 116 | - [Electron开发实战之记账软件14——自动更新](https://my.oschina.net/u/3667677/blog/3046786) 117 | 118 | - [Electron开发实战之记账软件15——崩溃报告](https://my.oschina.net/u/3667677/blog/3047628) 119 | 120 | - [Electron开发实战之记账软件16——使用Docsify生成文档网站](https://my.oschina.net/u/3667677/blog/3048374) 121 | 122 | - [Electron开发实战之记账软件17——使用Lowdb存储数据](https://my.oschina.net/u/3667677/blog/3054535) 123 | 124 | - [Electron开发实战之记账软件18——备份、恢复、导入、导出](https://my.oschina.net/u/3667677/blog/3073660) 125 | 126 | - [Electron开发实战之记账软件19——通过协议唤起Electron应用](https://my.oschina.net/u/3667677/blog/3074681) 127 | 128 | Electron开发实战之记账软件1——迷你程序 129 | 130 | Electron开发实战之记账软件99——完结 131 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PocketBook 0.0.1 4 | 5 | > A simple electron application 6 | 7 | [Star Me](https://github.com/hilanmiao/LanMiaoDesktop) 8 | [Get Started](#main) 9 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | - [中文](/) 2 | - [English](/en/) 3 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [**介绍**]() 2 | - [**常见问题**](faq) 3 | - [**更新日志**](changelog) -------------------------------------------------------------------------------- /docs/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/docs/assets/icon.ico -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ## 4.9.2(2019-04-21) 2 | 3 | 4 | ### Bug Fixes(Just an example) 5 | 6 | * re-render gitalk when router changed ([11ea1f8](https://github.com/docsifyjs/docsify/commit/11ea1f8)) 7 | 8 | 9 | ### Features 10 | 11 | * allows relative path, fixed [#590](https://github.com/docsifyjs/docsify/issues/590) ([31654f1](https://github.com/docsifyjs/docsify/commit/31654f1)) 12 | -------------------------------------------------------------------------------- /docs/en/README.md: -------------------------------------------------------------------------------- 1 | # building -------------------------------------------------------------------------------- /docs/en/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [**Introduction**](en/) 2 | - [**FAQ**](en/faq) 3 | - [**Changelog**](en/changelog) -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | building -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PocketBook - An electron-vue project 6 | 7 | 8 | 10 | 11 | 12 | 26 | 27 | 28 |
Loading...
29 | 30 | 31 | 32 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/sw.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * docsify sw.js 3 | * =========================================================== 4 | * Copyright 2016 @huxpro 5 | * Licensed under Apache 2.0 6 | * Register service worker. 7 | * ========================================================== */ 8 | 9 | const RUNTIME = 'docsify' 10 | const HOSTNAME_WHITELIST = [ 11 | self.location.hostname, 12 | 'fonts.gstatic.com', 13 | 'fonts.googleapis.com', 14 | 'unpkg.com' 15 | ] 16 | 17 | // The Util Function to hack URLs of intercepted requests 18 | const getFixedUrl = (req) => { 19 | var now = Date.now() 20 | var url = new URL(req.url) 21 | 22 | // 1. fixed http URL 23 | // Just keep syncing with location.protocol 24 | // fetch(httpURL) belongs to active mixed content. 25 | // And fetch(httpRequest) is not supported yet. 26 | url.protocol = self.location.protocol 27 | 28 | // 2. add query for caching-busting. 29 | // Github Pages served with Cache-Control: max-age=600 30 | // max-age on mutable content is error-prone, with SW life of bugs can even extend. 31 | // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. 32 | // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 33 | if (url.hostname === self.location.hostname) { 34 | url.search += (url.search ? '&' : '?') + 'cache-bust=' + now 35 | } 36 | return url.href 37 | } 38 | 39 | /** 40 | * @Lifecycle Activate 41 | * New one activated when old isnt being used. 42 | * 43 | * waitUntil(): activating ====> activated 44 | */ 45 | self.addEventListener('activate', event => { 46 | event.waitUntil(self.clients.claim()) 47 | }) 48 | 49 | /** 50 | * @Functional Fetch 51 | * All network requests are being intercepted here. 52 | * 53 | * void respondWith(Promise r) 54 | */ 55 | self.addEventListener('fetch', event => { 56 | // Skip some of cross-origin requests, like those for Google Analytics. 57 | if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { 58 | // Stale-while-revalidate 59 | // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale 60 | // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 61 | const cached = caches.match(event.request) 62 | const fixedUrl = getFixedUrl(event.request) 63 | const fetched = fetch(fixedUrl, { cache: 'no-store' }) 64 | const fetchedCopy = fetched.then(resp => resp.clone()) 65 | 66 | // Call respondWith() with whatever we get first. 67 | // If the fetch fails (e.g disconnected), wait for the cache. 68 | // If there’s nothing in cache, wait for the fetch. 69 | // If neither yields a response, return offline pages. 70 | event.respondWith( 71 | Promise.race([fetched.catch(_ => cached), cached]) 72 | .then(resp => resp || fetched) 73 | .catch(_ => { /* eat any errors */ }) 74 | ) 75 | 76 | // Update the cache with the version we fetched (only for ok status) 77 | event.waitUntil( 78 | Promise.all([fetchedCopy, caches.open(RUNTIME)]) 79 | .then(([response, cache]) => response.ok && cache.put(event.request, response)) 80 | .catch(_ => { /* eat any errors */ }) 81 | ) 82 | } 83 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PocketBook", 3 | "version": "1.0.0", 4 | "author": "hilanmiao ", 5 | "description": "An electron-vue project", 6 | "license": "MIT", 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 | "pack": "npm run pack:main && npm run pack:renderer", 15 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 16 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 17 | "postinstall": "electron-builder install-app-deps", 18 | "release": "node .electron-vue/build.js && electron-builder", 19 | "dist": "node .electron-vue/build.js && electron-builder" 20 | }, 21 | "build": { 22 | "productName": "PocketBook", 23 | "appId": "com.electron.PocketBook", 24 | "copyright": "Copyright © 2019 lanmiao", 25 | "directories": { 26 | "output": "build" 27 | }, 28 | "publish": { 29 | "provider": "github", 30 | "repo": "LanMiaoDesktop", 31 | "owner": "hilanmiao", 32 | "releaseType": "release", 33 | "private": false 34 | }, 35 | "files": [ 36 | "dist/electron/**/*" 37 | ], 38 | "dmg": { 39 | "contents": [ 40 | { 41 | "x": 410, 42 | "y": 150, 43 | "type": "link", 44 | "path": "/Applications" 45 | }, 46 | { 47 | "x": 130, 48 | "y": 150, 49 | "type": "file" 50 | } 51 | ] 52 | }, 53 | "mac": { 54 | "icon": "build/icons/icon.icns" 55 | }, 56 | "win": { 57 | "icon": "build/icons/icon.ico", 58 | "requestedExecutionLevel": "highestAvailable", 59 | "target": "nsis" 60 | }, 61 | "nsis": { 62 | "oneClick": false, 63 | "allowToChangeInstallationDirectory": true 64 | }, 65 | "linux": { 66 | "icon": "build/icons", 67 | "target": [ 68 | "AppImage" 69 | ] 70 | } 71 | }, 72 | "dependencies": { 73 | "@sentry/electron": "^0.17.1", 74 | "axios": "^0.21.1", 75 | "electron-updater": "^4.3.9", 76 | "element-ui": "^2.9.1", 77 | "exceljs": "^3.5.0", 78 | "fs-extra": "^8.0.1", 79 | "lodash": "^4.17.21", 80 | "lodash-id": "^0.14.0", 81 | "lowdb": "^1.0.0", 82 | "material-design-icons-iconfont": "^4.0.5", 83 | "moment": "^2.24.0", 84 | "vue": "^2.6.10", 85 | "vue-electron": "^1.0.6", 86 | "vue-router": "^3.0.3", 87 | "vuetify": "^1.5.11", 88 | "vuex": "^3.1.0", 89 | "vuex-electron": "^1.0.3", 90 | "winreg": "^1.2.4" 91 | }, 92 | "devDependencies": { 93 | "@vue/devtools": "^5.0.9", 94 | "ajv": "^6.10.0", 95 | "babel-core": "^6.26.3", 96 | "babel-loader": "^7.1.4", 97 | "babel-minify-webpack-plugin": "^0.3.1", 98 | "babel-plugin-component": "^1.1.1", 99 | "babel-plugin-transform-runtime": "^6.23.0", 100 | "babel-preset-env": "^1.7.0", 101 | "babel-preset-stage-0": "^6.24.1", 102 | "babel-register": "^6.26.0", 103 | "cfonts": "^2.4.2", 104 | "chalk": "^2.4.2", 105 | "copy-webpack-plugin": "^5.0.2", 106 | "cross-env": "^5.2.0", 107 | "css-loader": "^2.1.1", 108 | "del": "^4.1.0", 109 | "devtron": "^1.4.0", 110 | "electron": "^9.4.0", 111 | "electron-builder": "^22.4.0", 112 | "electron-debug": "^2.2.0", 113 | "electron-devtools-installer": "^2.2.4", 114 | "file-loader": "^3.0.1", 115 | "html-webpack-plugin": "^3.2.0", 116 | "mini-css-extract-plugin": "^0.6.0", 117 | "multispinner": "^0.2.1", 118 | "node-loader": "^0.6.0", 119 | "sass": "^1.47.0", 120 | "sass-loader": "^7.1.0", 121 | "style-loader": "^0.23.1", 122 | "url-loader": "^1.1.2", 123 | "vue-html-loader": "^1.2.4", 124 | "vue-loader": "^15.7.0", 125 | "vue-style-loader": "^4.1.2", 126 | "vue-template-compiler": "^2.6.10", 127 | "webpack": "^4.29.6", 128 | "webpack-cli": "^3.3.0", 129 | "webpack-dev-server": "^3.3.1", 130 | "webpack-hot-middleware": "^2.24.3", 131 | "webpack-merge": "^4.2.1" 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /screenshot-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/screenshot-app.png -------------------------------------------------------------------------------- /screenshot-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/screenshot-desktop.png -------------------------------------------------------------------------------- /screenshot-frontend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/screenshot-frontend.png -------------------------------------------------------------------------------- /screenshot-home-online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/screenshot-home-online.png -------------------------------------------------------------------------------- /screenshot-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/screenshot-web.png -------------------------------------------------------------------------------- /sentry-symbols.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | let SentryCli; 4 | let download; 5 | 6 | try { 7 | SentryCli = require('@sentry/cli'); 8 | download = require('electron-download'); 9 | } catch (e) { 10 | console.error('ERROR: Missing required packages, please run:'); 11 | console.error('npm install --save-dev @sentry/cli electron-download'); 12 | process.exit(1); 13 | } 14 | 15 | const VERSION = /\bv?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/i; 16 | const SYMBOL_CACHE_FOLDER = '.electron-symbols'; 17 | const package = require('./package.json'); 18 | const sentryCli = new SentryCli('./sentry.properties'); 19 | 20 | async function main() { 21 | let version = getElectronVersion(); 22 | if (!version) { 23 | console.error('Cannot detect electron version, check package.json'); 24 | return; 25 | } 26 | 27 | console.log('We are starting to download all possible electron symbols'); 28 | console.log('We need it in order to symbolicate native crashes'); 29 | console.log( 30 | 'This step is only needed once whenever you update your electron version', 31 | ); 32 | console.log('Just call this script again it should do everything for you.'); 33 | 34 | let zipPath = await downloadSymbols({ 35 | version, 36 | platform: 'darwin', 37 | arch: 'x64', 38 | dsym: true, 39 | }); 40 | await sentryCli.execute(['upload-dif', '-t', 'dsym', zipPath], true); 41 | 42 | zipPath = await downloadSymbols({ 43 | version, 44 | platform: 'win32', 45 | arch: 'ia32', 46 | symbols: true, 47 | }); 48 | await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true); 49 | 50 | zipPath = await downloadSymbols({ 51 | version, 52 | platform: 'win32', 53 | arch: 'x64', 54 | symbols: true, 55 | }); 56 | await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true); 57 | 58 | zipPath = await downloadSymbols({ 59 | version, 60 | platform: 'linux', 61 | arch: 'x64', 62 | symbols: true, 63 | }); 64 | await sentryCli.execute(['upload-dif', '-t', 'breakpad', zipPath], true); 65 | 66 | console.log('Finished downloading and uploading to Sentry'); 67 | console.log(`Feel free to delete the ${SYMBOL_CACHE_FOLDER}`); 68 | } 69 | 70 | function getElectronVersion() { 71 | if (!package) { 72 | return false; 73 | } 74 | 75 | let electronVersion = 76 | (package.dependencies && package.dependencies.electron) || 77 | (package.devDependencies && package.devDependencies.electron); 78 | 79 | if (!electronVersion) { 80 | return false; 81 | } 82 | 83 | const matches = VERSION.exec(electronVersion); 84 | return matches ? matches[0] : false; 85 | } 86 | 87 | async function downloadSymbols(options) { 88 | return new Promise((resolve, reject) => { 89 | download( 90 | { 91 | ...options, 92 | cache: SYMBOL_CACHE_FOLDER, 93 | }, 94 | (err, zipPath) => { 95 | if (err) { 96 | reject(err); 97 | } else { 98 | resolve(zipPath); 99 | } 100 | }, 101 | ); 102 | }); 103 | } 104 | 105 | main().catch(e => console.error(e)); 106 | -------------------------------------------------------------------------------- /sentry.properties: -------------------------------------------------------------------------------- 1 | defaults.url=https://sentry.io/ 2 | defaults.org=lanmiao 3 | defaults.project=lanmiaodesktop 4 | auth.token=d57bc5f368ae4c05bd6c59cac9631a6ed12b4811b2c44081a73a765b125ec9c2 5 | cli.executable=C:\\Users\\sheny\\AppData\\Roaming\\npm\\node_modules\\@sentry\\wizard\\node_modules\\@sentry\\cli\\bin\\sentry-cli 6 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: lanmiaodesktop 3 | version: '1.0.0' # just for humans, typically '1.2+git' or '1.3.2' 4 | summary: Desktop bookkeeping program(Electron demo) # 79 char long summary 5 | description: | 6 | this is an Electron demo. 7 | https://github.com/hilanmiao/LanMiaoDesktop 8 | 9 | #grade: devel # must be 'stable' to release into candidate/stable channels 10 | #confinement: devmode # use 'strict' once you have the right plugs and slots 11 | grade: stable # must be 'stable' to release into candidate/stable channels 12 | confinement: strict # use 'strict' once you have the right plugs and slots 13 | 14 | parts: 15 | my-part: 16 | # See 'snapcraft plugins' 17 | plugin: nil 18 | 19 | # 20 | # 21 | #sudo: false 22 | # 23 | #language: node_js 24 | #node_js: "9.11.1" 25 | # 26 | #before_install: 27 | # - cd app 28 | # 29 | #cache: 30 | # directories: 31 | # - node_modules 32 | # - app/node_modules 33 | # - $HOME/.cache/electron 34 | # - $HOME/.cache/electron-builder 35 | # - $HOME/.npm/_prebuilds 36 | # 37 | #env: 38 | # global: 39 | # - ELECTRON_CACHE=$HOME/.cache/electron 40 | # - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 41 | # 42 | #jobs: 43 | # include: 44 | # - stage: Test 45 | # script: 46 | # - npm test 47 | # - stage: Deploy Windows & Mac 48 | # if: tag IS present 49 | # os: osx 50 | # osx_image: xcode10.1 51 | # script: 52 | # - npm run release -- --mac --win 53 | # before_cache: 54 | # - rm -rf $HOME/.cache/electron-builder/wine 55 | # - stage: Deploy linux 56 | # if: tag IS present 57 | # os: linux 58 | # dist: trusty 59 | # script: 60 | # - npm run release 61 | -------------------------------------------------------------------------------- /src/api/assets.js: -------------------------------------------------------------------------------- 1 | import db from '../datastore' 2 | import _ from 'lodash' 3 | 4 | const Table = 'assets' 5 | 6 | export function getModelById(id) { 7 | return new Promise((resolve, reject) => { 8 | try { 9 | const collection = db.get(Table) 10 | const model = collection.getById(id).value() 11 | resolve({ 12 | code: 200, 13 | data: _.cloneDeep(model) 14 | }) 15 | } catch (err) { 16 | return reject({ 17 | code: 400, 18 | message: err.message 19 | }) 20 | } 21 | }) 22 | } 23 | 24 | export function getModelWhere(attrs) { 25 | return new Promise((resolve, reject) => { 26 | try { 27 | const collection = db.get(Table) 28 | const list = collection.filter(attrs).value() 29 | resolve({ 30 | code: 200, 31 | data: _.cloneDeep(list) 32 | }) 33 | } catch (err) { 34 | return reject({ 35 | code: 400, 36 | message: err.message 37 | }) 38 | } 39 | }) 40 | } 41 | 42 | export function getModelAll() { 43 | return new Promise((resolve, reject) => { 44 | try { 45 | const collection = db.get(Table) 46 | const list = collection.value() 47 | resolve({ 48 | code: 200, 49 | data: _.cloneDeep(list) 50 | }) 51 | } catch (err) { 52 | return reject({ 53 | code: 400, 54 | message: err.message 55 | }) 56 | } 57 | }) 58 | } 59 | 60 | export function getModelPagination(pagination, whereAttrs, filterFun) { 61 | return new Promise((resolve, reject) => { 62 | try { 63 | const collection = db.get(Table) 64 | const total = collection.size().value() 65 | const list = collection 66 | .filter(filterFun) 67 | .orderBy(pagination.sortBy, pagination.descending ? 'desc' : 'asc') 68 | .chunk(pagination.rowsPerPage === -1 ? total : pagination.rowsPerPage) 69 | .take(pagination.page) 70 | .last() // 因为上面用了chunk,是个二维数组,所以这里取最后一个 71 | .value() 72 | resolve({ 73 | code: 200, 74 | data: _.cloneDeep({total: total, list: list}) 75 | }) 76 | } catch (err) { 77 | return reject({ 78 | code: 400, 79 | message: err.message 80 | }) 81 | } 82 | }) 83 | } 84 | 85 | export function postModel(document) { 86 | return new Promise((resolve, reject) => { 87 | try { 88 | const collection = db.get(Table) 89 | const model = collection.insert(document).write() 90 | resolve({ 91 | code: 200, 92 | data: _.cloneDeep(model) 93 | }) 94 | } catch (err) { 95 | return reject({ 96 | code: 400, 97 | message: err.message 98 | }) 99 | } 100 | }) 101 | } 102 | 103 | export function postOrPutModel(document) { 104 | return new Promise((resolve, reject) => { 105 | try { 106 | const collection = db.get(Table) 107 | const model = collection.upsert(document).write() 108 | resolve({ 109 | code: 200, 110 | data: _.cloneDeep(model) 111 | }) 112 | } catch (err) { 113 | return reject({ 114 | code: 400, 115 | message: err.message 116 | }) 117 | } 118 | }) 119 | } 120 | 121 | export function putModelById(id, attrs) { 122 | return new Promise((resolve, reject) => { 123 | try { 124 | const collection = db.get(Table) 125 | const model = collection.updateById(id, attrs).write() 126 | resolve({ 127 | code: 200, 128 | data: _.cloneDeep(model) 129 | }) 130 | } catch (err) { 131 | return reject({ 132 | code: 400, 133 | message: err.message 134 | }) 135 | } 136 | }) 137 | } 138 | 139 | export function putModelWhere(whereAttrs, attrs) { 140 | return new Promise((resolve, reject) => { 141 | try { 142 | const collection = db.get(Table) 143 | const model = collection.updateWhere(whereAttrs, attrs).write() 144 | resolve({ 145 | code: 200, 146 | data: _.cloneDeep(model) 147 | }) 148 | } catch (err) { 149 | return reject({ 150 | code: 400, 151 | message: err.message 152 | }) 153 | } 154 | }) 155 | } 156 | 157 | export function replaceModelById(id, attrs) { 158 | return new Promise((resolve, reject) => { 159 | try { 160 | const collection = db.get(Table) 161 | const model = collection.replaceById(id, attrs).write() 162 | resolve({ 163 | code: 200, 164 | data: _.cloneDeep(model) 165 | }) 166 | } catch (err) { 167 | return reject({ 168 | code: 400, 169 | message: err.message 170 | }) 171 | } 172 | }) 173 | } 174 | 175 | export function deleteModelById(id) { 176 | return new Promise((resolve, reject) => { 177 | try { 178 | const collection = db.get(Table) 179 | collection.removeById(id).write() 180 | resolve({ 181 | code: 200 182 | }) 183 | } catch (err) { 184 | return reject({ 185 | code: 400, 186 | message: err.message 187 | }) 188 | } 189 | }) 190 | } 191 | 192 | export function deleteModelByIds(ids) { 193 | return new Promise((resolve, reject) => { 194 | try { 195 | const collection = db.get(Table) 196 | ids.forEach(id => { 197 | collection.removeById(id).write() 198 | }) 199 | resolve({ 200 | code: 200 201 | }) 202 | } catch (err) { 203 | return reject({ 204 | code: 400, 205 | message: err.message 206 | }) 207 | } 208 | }) 209 | } 210 | 211 | export function deleteModelWhere(whereAttrs) { 212 | return new Promise((resolve, reject) => { 213 | try { 214 | const collection = db.get(Table) 215 | const list = collection.removeWhere(whereAttrs).write() 216 | resolve({ 217 | code: 200, 218 | data: _.cloneDeep(list) 219 | }) 220 | } catch (err) { 221 | return reject({ 222 | code: 400, 223 | message: err.message 224 | }) 225 | } 226 | }) 227 | } 228 | -------------------------------------------------------------------------------- /src/api/category.js: -------------------------------------------------------------------------------- 1 | import db from '../datastore' 2 | import _ from 'lodash' 3 | 4 | const Table = 'category' 5 | 6 | export function getModelById(id) { 7 | return new Promise((resolve, reject) => { 8 | try { 9 | const collection = db.get(Table) 10 | const model = collection.getById(id).value() 11 | resolve({ 12 | code: 200, 13 | data: _.cloneDeep(model) 14 | }) 15 | } catch (err) { 16 | return reject({ 17 | code: 400, 18 | message: err.message 19 | }) 20 | } 21 | }) 22 | } 23 | 24 | export function getModelWhere(attrs) { 25 | return new Promise((resolve, reject) => { 26 | try { 27 | const collection = db.get(Table) 28 | const list = collection.filter(attrs).value() 29 | resolve({ 30 | code: 200, 31 | data: _.cloneDeep(list) 32 | }) 33 | } catch (err) { 34 | return reject({ 35 | code: 400, 36 | message: err.message 37 | }) 38 | } 39 | }) 40 | } 41 | 42 | export function getModelAll() { 43 | return new Promise((resolve, reject) => { 44 | try { 45 | const collection = db.get(Table) 46 | const list = collection.value() 47 | resolve({ 48 | code: 200, 49 | data: _.cloneDeep(list) 50 | }) 51 | } catch (err) { 52 | return reject({ 53 | code: 400, 54 | message: err.message 55 | }) 56 | } 57 | }) 58 | } 59 | 60 | export function getModelPagination(pagination, whereAttrs, filterFun) { 61 | return new Promise((resolve, reject) => { 62 | try { 63 | const collection = db.get(Table) 64 | const total = collection.size().value() 65 | const list = collection 66 | .filter(filterFun) 67 | .orderBy(pagination.sortBy, pagination.descending ? 'desc' : 'asc') 68 | .chunk(pagination.rowsPerPage === -1 ? total : pagination.rowsPerPage) 69 | .take(pagination.page) 70 | .last() // 因为上面用了chunk,是个二维数组,所以这里取最后一个 71 | .value() 72 | resolve({ 73 | code: 200, 74 | data: _.cloneDeep({total: total, list: list}) 75 | }) 76 | } catch (err) { 77 | return reject({ 78 | code: 400, 79 | message: err.message 80 | }) 81 | } 82 | }) 83 | } 84 | 85 | export function postModel(document) { 86 | return new Promise((resolve, reject) => { 87 | try { 88 | const collection = db.get(Table) 89 | const model = collection.insert(document).write() 90 | resolve({ 91 | code: 200, 92 | data: _.cloneDeep(model) 93 | }) 94 | } catch (err) { 95 | return reject({ 96 | code: 400, 97 | message: err.message 98 | }) 99 | } 100 | }) 101 | } 102 | 103 | export function postOrPutModel(document) { 104 | return new Promise((resolve, reject) => { 105 | try { 106 | const collection = db.get(Table) 107 | const model = collection.upsert(document).write() 108 | resolve({ 109 | code: 200, 110 | data: _.cloneDeep(model) 111 | }) 112 | } catch (err) { 113 | return reject({ 114 | code: 400, 115 | message: err.message 116 | }) 117 | } 118 | }) 119 | } 120 | 121 | export function putModelById(id, attrs) { 122 | return new Promise((resolve, reject) => { 123 | try { 124 | const collection = db.get(Table) 125 | const model = collection.updateById(id, attrs).write() 126 | resolve({ 127 | code: 200, 128 | data: _.cloneDeep(model) 129 | }) 130 | } catch (err) { 131 | return reject({ 132 | code: 400, 133 | message: err.message 134 | }) 135 | } 136 | }) 137 | } 138 | 139 | export function putModelWhere(whereAttrs, attrs) { 140 | return new Promise((resolve, reject) => { 141 | try { 142 | const collection = db.get(Table) 143 | const model = collection.updateWhere(whereAttrs, attrs).write() 144 | resolve({ 145 | code: 200, 146 | data: _.cloneDeep(model) 147 | }) 148 | } catch (err) { 149 | return reject({ 150 | code: 400, 151 | message: err.message 152 | }) 153 | } 154 | }) 155 | } 156 | 157 | export function replaceModelById(id, attrs) { 158 | return new Promise((resolve, reject) => { 159 | try { 160 | const collection = db.get(Table) 161 | const model = collection.replaceById(id, attrs).write() 162 | resolve({ 163 | code: 200, 164 | data: _.cloneDeep(model) 165 | }) 166 | } catch (err) { 167 | return reject({ 168 | code: 400, 169 | message: err.message 170 | }) 171 | } 172 | }) 173 | } 174 | 175 | export function deleteModelById(id) { 176 | return new Promise((resolve, reject) => { 177 | try { 178 | const collection = db.get(Table) 179 | collection.removeById(id).write() 180 | resolve({ 181 | code: 200 182 | }) 183 | } catch (err) { 184 | return reject({ 185 | code: 400, 186 | message: err.message 187 | }) 188 | } 189 | }) 190 | } 191 | 192 | export function deleteModelByIds(ids) { 193 | return new Promise((resolve, reject) => { 194 | try { 195 | const collection = db.get(Table) 196 | ids.forEach(id => { 197 | collection.removeById(id).write() 198 | }) 199 | resolve({ 200 | code: 200 201 | }) 202 | } catch (err) { 203 | return reject({ 204 | code: 400, 205 | message: err.message 206 | }) 207 | } 208 | }) 209 | } 210 | 211 | export function deleteModelWhere(whereAttrs) { 212 | return new Promise((resolve, reject) => { 213 | try { 214 | const collection = db.get(Table) 215 | const list = collection.removeWhere(whereAttrs).write() 216 | resolve({ 217 | code: 200, 218 | data: _.cloneDeep(list) 219 | }) 220 | } catch (err) { 221 | return reject({ 222 | code: 400, 223 | message: err.message 224 | }) 225 | } 226 | }) 227 | } 228 | -------------------------------------------------------------------------------- /src/api/incomeAndExpenditure.js: -------------------------------------------------------------------------------- 1 | import db from '../datastore' 2 | import _ from 'lodash' 3 | 4 | const Table = 'incomeAndExpenditure' 5 | const TableAssets = 'assets' 6 | 7 | export function getModelById(id) { 8 | return new Promise((resolve, reject) => { 9 | try { 10 | const collection = db.get(Table) 11 | const model = collection.getById(id).value() 12 | resolve({ 13 | code: 200, 14 | data: _.cloneDeep(model) 15 | }) 16 | } catch (err) { 17 | return reject({ 18 | code: 400, 19 | message: err.message 20 | }) 21 | } 22 | }) 23 | } 24 | 25 | export function getModelWhere(attrs) { 26 | return new Promise((resolve, reject) => { 27 | try { 28 | const collection = db.get(Table) 29 | const list = collection.find(attrs).value() 30 | resolve({ 31 | code: 200, 32 | data: _.cloneDeep(list) 33 | }) 34 | } catch (err) { 35 | return reject({ 36 | code: 400, 37 | message: err.message 38 | }) 39 | } 40 | }) 41 | } 42 | 43 | export function getModelAll() { 44 | return new Promise((resolve, reject) => { 45 | try { 46 | const collection = db.get(Table) 47 | const list = collection.value() 48 | resolve({ 49 | code: 200, 50 | data: _.cloneDeep(list) 51 | }) 52 | } catch (err) { 53 | return reject({ 54 | code: 400, 55 | message: err.message 56 | }) 57 | } 58 | }) 59 | } 60 | 61 | export function getModelExport(filterFun) { 62 | return new Promise((resolve, reject) => { 63 | try { 64 | const collection = db.get(Table) 65 | const list = collection.filter(filterFun).value() 66 | resolve({ 67 | code: 200, 68 | data: _.cloneDeep(list) 69 | }) 70 | } catch (err) { 71 | return reject({ 72 | code: 400, 73 | message: err.message 74 | }) 75 | } 76 | }) 77 | } 78 | 79 | export function getModelPagination(pagination, whereAttrs, filterFun) { 80 | return new Promise((resolve, reject) => { 81 | try { 82 | const collection = db.get(Table) 83 | const total = collection.size().value() 84 | const list = collection 85 | .filter(filterFun) 86 | .orderBy(pagination.sortBy, pagination.descending ? 'desc' : 'asc') 87 | .chunk(pagination.rowsPerPage === -1 ? total : pagination.rowsPerPage) 88 | .take(pagination.page) 89 | .last() // 因为上面用了chunk,是个二维数组,所以这里取最后一个 90 | .value() 91 | resolve({ 92 | code: 200, 93 | data: _.cloneDeep({total: total, list: list}) 94 | }) 95 | } catch (err) { 96 | return reject({ 97 | code: 400, 98 | message: err.message 99 | }) 100 | } 101 | }) 102 | } 103 | 104 | export function postModel(document) { 105 | return new Promise((resolve, reject) => { 106 | try { 107 | const collection = db.get(Table) 108 | const model = collection.insert(document).write() 109 | const collectionAssets = db.get(TableAssets) 110 | let assetsAmountOfMoney = 0 111 | collection.filter({assetsId: model.assetsId}).value().forEach(item => { 112 | assetsAmountOfMoney += item.type ==='e' ? -item.amountOfMoney : item.amountOfMoney 113 | }) 114 | // 更新资产表 115 | collectionAssets.updateById(model.assetsId, {assetsAmountOfMoney: assetsAmountOfMoney}).write() 116 | resolve({ 117 | code: 200, 118 | data: _.cloneDeep(model) 119 | }) 120 | } catch (err) { 121 | return reject({ 122 | code: 400, 123 | message: err.message 124 | }) 125 | } 126 | }) 127 | } 128 | 129 | export function postOrPutModel(document) { 130 | return new Promise((resolve, reject) => { 131 | try { 132 | const collection = db.get(Table) 133 | const model = collection.upsert(document).write() 134 | resolve({ 135 | code: 200, 136 | data: _.cloneDeep(model) 137 | }) 138 | } catch (err) { 139 | return reject({ 140 | code: 400, 141 | message: err.message 142 | }) 143 | } 144 | }) 145 | } 146 | 147 | export function putModelById(id, attrs) { 148 | return new Promise((resolve, reject) => { 149 | try { 150 | const collection = db.get(Table) 151 | const model = collection.updateById(id, attrs).write() 152 | const collectionAssets = db.get(TableAssets) 153 | let assetsAmountOfMoney = 0 154 | collection.filter({assetsId: model.assetsId}).value().forEach(item => { 155 | assetsAmountOfMoney += item.type ==='e' ? -item.amountOfMoney : item.amountOfMoney 156 | }) 157 | // 更新资产表 158 | collectionAssets.updateById(model.assetsId, {assetsAmountOfMoney: assetsAmountOfMoney}).write() 159 | resolve({ 160 | code: 200, 161 | data: _.cloneDeep(model) 162 | }) 163 | } catch (err) { 164 | return reject({ 165 | code: 400, 166 | message: err.message 167 | }) 168 | } 169 | }) 170 | } 171 | 172 | export function putModelWhere(whereAttrs, attrs) { 173 | return new Promise((resolve, reject) => { 174 | try { 175 | const collection = db.get(Table) 176 | const model = collection.updateWhere(whereAttrs, attrs).write() 177 | resolve({ 178 | code: 200, 179 | data: _.cloneDeep(model) 180 | }) 181 | } catch (err) { 182 | return reject({ 183 | code: 400, 184 | message: err.message 185 | }) 186 | } 187 | }) 188 | } 189 | 190 | export function replaceModelById(id, attrs) { 191 | return new Promise((resolve, reject) => { 192 | try { 193 | const collection = db.get(Table) 194 | const model = collection.replaceById(id, attrs).write() 195 | resolve({ 196 | code: 200, 197 | data: _.cloneDeep(model) 198 | }) 199 | } catch (err) { 200 | return reject({ 201 | code: 400, 202 | message: err.message 203 | }) 204 | } 205 | }) 206 | } 207 | 208 | export function deleteModelById(id) { 209 | return new Promise((resolve, reject) => { 210 | try { 211 | const collection = db.get(Table) 212 | collection.removeById(id).write() 213 | resolve({ 214 | code: 200 215 | }) 216 | } catch (err) { 217 | return reject({ 218 | code: 400, 219 | message: err.message 220 | }) 221 | } 222 | }) 223 | } 224 | 225 | export function deleteModelByIds(ids) { 226 | return new Promise((resolve, reject) => { 227 | try { 228 | const collection = db.get(Table) 229 | ids.forEach(id => { 230 | collection.removeById(id).write() 231 | }) 232 | resolve({ 233 | code: 200 234 | }) 235 | } catch (err) { 236 | return reject({ 237 | code: 400, 238 | message: err.message 239 | }) 240 | } 241 | }) 242 | } 243 | 244 | export function deleteModelWhere(whereAttrs) { 245 | return new Promise((resolve, reject) => { 246 | try { 247 | const collection = db.get(Table) 248 | const list = collection.removeWhere(whereAttrs).write() 249 | resolve({ 250 | code: 200, 251 | data: _.cloneDeep(list) 252 | }) 253 | } catch (err) { 254 | return reject({ 255 | code: 400, 256 | message: err.message 257 | }) 258 | } 259 | }) 260 | } 261 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import db from '../datastore' 2 | import _ from 'lodash' 3 | 4 | const Table = 'user' 5 | 6 | export function login(data) { 7 | return new Promise((resolve, reject) => { 8 | try { 9 | const user = db.read().get(Table).find({userId: data.name, password: data.password}).value() 10 | resolve(_.cloneDeep(user)) 11 | } catch (err) { 12 | return reject(err) 13 | } 14 | }) 15 | } 16 | 17 | export function getModelWhere(attrs) { 18 | return new Promise((resolve, reject) => { 19 | try { 20 | const collection = db.get(Table) 21 | const list = collection.filter(attrs).value() 22 | resolve({ 23 | code: 200, 24 | data: _.cloneDeep(list) 25 | }) 26 | } catch (err) { 27 | return reject({ 28 | code: 400, 29 | message: err.message 30 | }) 31 | } 32 | }) 33 | } 34 | 35 | export function putModelById(id, attrs) { 36 | return new Promise((resolve, reject) => { 37 | try { 38 | const collection = db.get(Table) 39 | const model = collection.updateById(id, attrs).write() 40 | resolve({ 41 | code: 200, 42 | data: _.cloneDeep(model) 43 | }) 44 | } catch (err) { 45 | return reject({ 46 | code: 400, 47 | message: err.message 48 | }) 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /src/datastore/README.md: -------------------------------------------------------------------------------- 1 | # 表设计 2 | 3 | ## User(用户表不是必须的,我只是用来模拟登录) 4 | 5 | | 字段名 | 数据类型 | 是否主键 | 描述 | 6 | |--------|--------|--------|--------| 7 | | id | string | Y | id | 8 | | userId | string | Y | 用户名 | 9 | | password | string | | 用户名 | 10 | 11 | ## IncomeAndExpenditure(收入/支出表) 12 | 13 | | 字段名 | 数据类型 | 是否主键 | 描述 | 14 | |--------|--------|--------|--------| 15 | | id | string | Y | id | 16 | | type | string | | 收入i\支出e | 17 | | amountOfMoney | int | | 金额 | 18 | | remark | string | | 备注 | 19 | | categoryId | string | | 类别id | 20 | | assetsId | string | | 资产id | 21 | | createdAt | string | | 创建时间 | 22 | | updatedAt | string | | 更新时间 | 23 | 24 | ## Assets(资产管理表) 25 | 26 | | 字段名 | 数据类型 | 是否主键 | 描述 | 27 | |--------|--------|--------|--------| 28 | | id | string | Y | id | 29 | | assetsName | string | Y | 资产名 | 30 | | assetsDetailed | string | | 资产详细 | 31 | | assetsAmountOfMoney | int | | 金额 | 32 | 33 | ## Category(收入支出类别表) 34 | 35 | | 字段名 | 数据类型 | 是否主键 | 描述 | 36 | |--------|--------|--------|--------| 37 | | id | string | Y | id | 38 | | category | string | Y | 类别名称 | 39 | | remark | string | | 备注 | -------------------------------------------------------------------------------- /src/datastore/index.js: -------------------------------------------------------------------------------- 1 | import Datastore from 'lowdb' 2 | import LodashId from 'lodash-id' 3 | import FileSync from 'lowdb/adapters/FileSync' 4 | import path from 'path' 5 | import fs from 'fs-extra' 6 | // 引入remote模块 7 | import { app, remote } from 'electron' 8 | // package.json 9 | import pkg from '../../package.json' 10 | 11 | // 根据process.type来分辨在哪种模式使用哪种模块 12 | const APP = process.type === 'renderer' ? remote.app : app 13 | 14 | // 获取electron应用的用户目录 15 | const STORE_PATH = APP.getPath('userData') 16 | console.log(STORE_PATH) 17 | // dev C:\Users\JD\AppData\Roaming\Electron 18 | // prod C:\Users\JD\AppData\Roaming\PocketBook 19 | 20 | if (process.type !== 'renderer') { 21 | // 如果不存在路径 22 | if (!fs.pathExistsSync(STORE_PATH)) { 23 | // 就创建 24 | fs.mkdirpSync(STORE_PATH) 25 | } 26 | } 27 | 28 | // 以同步的方式初始化lowdb读写的json文件名以及存储路径 29 | const adapter = new FileSync(path.join(STORE_PATH, `/${pkg.name}_lowdb.json`)) 30 | 31 | // lowdb接管该文件 32 | const db = Datastore(adapter) 33 | // 通过._mixin()引入lodash_id唯一id插件 34 | db._.mixin(LodashId) 35 | 36 | // 初始化数据 37 | if(!db.has('user').value()) { 38 | db.set('user',[]).write() 39 | db.get('user').insert({userId: 'admin', password: '123456'}).write() 40 | } 41 | 42 | if(!db.has('category').value()) { 43 | const tempData = [ 44 | { 45 | "category": "breakfast", 46 | "remark": "daily breakfast expenses", 47 | }, 48 | { 49 | "category": "dinner", 50 | "remark": "daily dinner expenses", 51 | }, 52 | { 53 | "category": "lunch", 54 | "remark": "Daily lunch expenses", 55 | } 56 | ] 57 | db.set('category', []).write() 58 | tempData.forEach(item => { 59 | db.get('category').insert(item).write() 60 | }) 61 | } 62 | 63 | if(!db.has('assets').value()) { 64 | const tempData = [ 65 | {assetsName: 'me', assetsDetailed: 'My assets', assetsAmountOfMoney: 0}, 66 | {assetsName: 'wife', assetsDetailed: 'My wife\'s assets', assetsAmountOfMoney: 0} 67 | ] 68 | db.set('assets', []).write() 69 | tempData.forEach(item => { 70 | db.get('assets').insert(item).write() 71 | }) 72 | } 73 | 74 | if(!db.has('incomeAndExpenditure').value()) { 75 | db.set('incomeAndExpenditure', []).write() 76 | } 77 | 78 | export default db // 暴露出去 79 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PocketBook 6 | 7 | 8 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 9 | 10 | 13 | <% } %> 14 | 15 | 16 |
17 | 18 | <% if (!process.browser) { %> 19 | 22 | <% } %> 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /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 | // Install `electron-debug` with `devtron` 11 | require('electron-debug')({ showDevTools: true }) 12 | 13 | // Install `vue-devtools` 14 | require('electron').app.on('ready', () => { 15 | let installExtension = require('electron-devtools-installer') 16 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 17 | .then(() => {}) 18 | .catch(err => { 19 | console.log('Unable to install `vue-devtools`: \n', err) 20 | }) 21 | }) 22 | 23 | // Require `main` process to boot app 24 | require('./index') -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import {app, BrowserWindow, ipcMain, Menu, shell, Tray, Notification, dialog, crashReporter} from 'electron' 2 | 3 | // 自动更新相关 4 | import {autoUpdater} from 'electron-updater' 5 | 6 | // 引入自动启动模块 7 | const startOnBoot = require('./startOnBoot.js') 8 | 9 | // 崩溃报告 10 | import * as Sentry from '@sentry/electron' 11 | 12 | // package.json 13 | import pkg from '../../package.json' 14 | 15 | /** 16 | * Set `__static` path to static files in production 17 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 18 | */ 19 | if (process.env.NODE_ENV !== 'development') { 20 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') 21 | } 22 | 23 | let mainWindow, loginWindow 24 | const winURL = process.env.NODE_ENV === 'development' 25 | ? `http://localhost:9080` 26 | : `file://${__dirname}/index.html` 27 | 28 | const loginURL = process.env.NODE_ENV === 'development' 29 | ? `http://localhost:9080/#login` 30 | : `file://${__dirname}/index.html#login` 31 | 32 | const path = require('path') 33 | const ApplicationName = pkg.name 34 | // 托盘对象 35 | let appTray = null 36 | // 是否可以退出 37 | let trayClose = false 38 | // 系统托盘右键菜单 39 | let trayMenuTemplate 40 | // 系统托盘图标 41 | let iconPath 42 | // 图标的上上下文 43 | let contextMenu 44 | // 图标闪烁定时器 45 | let flashTrayTimer 46 | // 单一实例 47 | const gotTheLock = app.requestSingleInstanceLock() 48 | 49 | if (process.platform === 'win32') { 50 | app.setAppUserModelId(ApplicationName) 51 | } 52 | 53 | /** 54 | * 创建主窗口 55 | */ 56 | function createLoginWindow() { 57 | if (loginWindow) { 58 | return 59 | } 60 | 61 | /** 62 | * Initial window options 63 | */ 64 | loginWindow = new BrowserWindow({ 65 | show: true, 66 | height: 360, 67 | width: 300, 68 | maxHeight: 360, 69 | maxWidth: 300, 70 | useContentSize: true, 71 | frame: false, // 无边框 72 | transparent: true, // 透明 73 | // fullscreen: true, // 全屏, 74 | resizable: false, 75 | maximizable: false, 76 | minimizable: false, 77 | webPreferences: { 78 | nodeIntegration: true 79 | }, 80 | }) 81 | 82 | loginWindow.loadURL(loginURL) 83 | 84 | // 为了防止闪烁,让画面准备好了再显示 85 | // 对于一个复杂的应用,ready-to-show 可能发出的太晚,会让应用感觉缓慢。 在这种情况下,建议立刻显示窗口,并使用接近应用程序背景的 backgroundColor 86 | // 请注意,即使是使用 ready-to-show 事件的应用程序,仍建议使用设置 backgroundColor 使应用程序感觉更原生。 87 | loginWindow.once('ready-to-show', () => { 88 | loginWindow.show() 89 | }) 90 | 91 | loginWindow.on('close', (event) => { 92 | 93 | }) 94 | 95 | loginWindow.on('closed', () => { 96 | loginWindow = null 97 | }) 98 | 99 | ipcMain.on('openMainWindow', () => { 100 | if (!mainWindow) { 101 | createMainWindow() 102 | } 103 | 104 | // loginWindow.hide() 105 | loginWindow.destroy() 106 | mainWindow.show() 107 | mainWindow.focus() 108 | }) 109 | 110 | ipcMain.on('openLoginWindow', () => { 111 | if (!loginWindow) { 112 | createLoginWindow() 113 | } 114 | 115 | // loginWindow.hide() 116 | mainWindow.destroy() 117 | loginWindow.show() 118 | loginWindow.focus() 119 | }) 120 | } 121 | 122 | /** 123 | * 创建主窗口 124 | */ 125 | function createMainWindow() { 126 | if (mainWindow) { 127 | return 128 | } 129 | 130 | /** 131 | * Initial window options 132 | */ 133 | mainWindow = new BrowserWindow({ 134 | show: false, 135 | height: 1000, 136 | width: 1600, 137 | minWidth: 900, 138 | minHeight: 600, 139 | useContentSize: true, 140 | frame: false, // 无边框 141 | transparent: true, // 透明 142 | // fullscreen: true, // 全屏 143 | webPreferences: { 144 | nodeIntegration: true 145 | }, 146 | }) 147 | 148 | mainWindow.loadURL(winURL) 149 | 150 | /** 151 | * 监听 152 | */ 153 | 154 | mainWindow.on('close', (event) => { 155 | if(process.platform === 'win32') { 156 | if (!trayClose) { 157 | // 最小化 158 | mainWindow.hide() 159 | event.preventDefault() 160 | } 161 | } 162 | }) 163 | 164 | mainWindow.on('closed', () => { 165 | mainWindow = null 166 | }) 167 | 168 | mainWindow.on('maximize', () => { 169 | 170 | }) 171 | } 172 | 173 | /** 174 | * 设置系统托盘 175 | */ 176 | function createTray() { 177 | // 是否可以退出 178 | trayClose = false 179 | 180 | // 系统托盘图标 181 | iconPath = `${__static}/logoNotWin.png` 182 | let iconMessagePath = `${__static}/iconMessageNotWin.png` 183 | let iconTransparentPath = `${__static}/iconTransparentNotWin.png` 184 | // 通知图标 185 | const iconNoticePath = `${__static}/logo.png` 186 | 187 | if (process.platform === 'win32') { 188 | iconPath = `${__static}/logo.ico` 189 | iconMessagePath = `${__static}/iconMessage.ico` 190 | iconTransparentPath = `${__static}/iconTransparent.ico` 191 | } 192 | 193 | 194 | // 系统托盘右键菜单 195 | trayMenuTemplate = [ 196 | { 197 | label: '崩溃报告测试 process.crash()', 198 | click: function () { 199 | console.log('process.crash()') 200 | process.crash() 201 | } 202 | }, 203 | { 204 | label: '崩溃报告测试throw new Error', 205 | click: function () { 206 | console.log('Error test in main progress') 207 | throw new Error('Error test in main progress') 208 | } 209 | }, 210 | { 211 | label: '托盘闪烁', 212 | click: function () { 213 | // 判断如果上一个定时器是否执行完 214 | if (flashTrayTimer) { 215 | return 216 | } 217 | 218 | // 任务栏闪烁 219 | // if (!mainWindow.isFocused()) { 220 | // mainWindow.showInactive(); 221 | // mainWindow.flashFrame(true); 222 | // } 223 | 224 | //系统托盘图标闪烁 225 | appTray.setImage(iconMessagePath) 226 | let count = 0; 227 | flashTrayTimer = setInterval(function () { 228 | count++; 229 | if (count % 2 == 0) { 230 | appTray.setImage(iconTransparentPath) 231 | } else { 232 | appTray.setImage(iconMessagePath) 233 | } 234 | }, 600); 235 | } 236 | }, 237 | { 238 | label: '弹出通知', 239 | click: function () { 240 | console.log(Notification.isSupported()) 241 | let notification = new Notification({ 242 | title: '通知的标题', // 通知的标题, 将在通知窗口的顶部显示 243 | body: '通知的正文文本', // 通知的正文文本, 将显示在标题或副标题下面 244 | icon: iconNoticePath, // 用于在该通知上显示的图标 245 | silent: true, // 在显示通知时是否发出系统提示音 246 | }) 247 | 248 | notification.show() 249 | notification.on('click', () => { 250 | notification.close() 251 | console.log('click notification') 252 | }) 253 | } 254 | }, 255 | { 256 | label: '关于项目', 257 | click: function () { 258 | // 打开外部链接 259 | shell.openExternal('https://github.com/hilanmiao/LanMiaoDesktop') 260 | } 261 | }, 262 | { 263 | label: '退出', 264 | click: function () { 265 | // 退出 266 | trayClose = true 267 | app.quit() 268 | } 269 | } 270 | ] 271 | 272 | appTray = new Tray(iconPath) 273 | // 图标的上上下文 274 | contextMenu = Menu.buildFromTemplate(trayMenuTemplate) 275 | // 设置此托盘图标的悬停提示内容 276 | appTray.setToolTip(ApplicationName) 277 | // 设置此图标的上下文菜单 278 | appTray.setContextMenu(contextMenu) 279 | // 主窗口显示隐藏切换 280 | appTray.on('click', () => { 281 | // 清楚图标闪烁定时器 282 | clearInterval(flashTrayTimer) 283 | flashTrayTimer = null 284 | // 还原图标 285 | appTray.setImage(iconPath) 286 | mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show() 287 | }) 288 | } 289 | 290 | /** 291 | * 开机启动 292 | */ 293 | function ipcStartOnBoot() { 294 | // 检查是否自动启动 295 | ipcMain.on('getAutoStartValue', () => { 296 | startOnBoot.getAutoStartValue(ApplicationName, (error, result) => { 297 | if (error) { 298 | mainWindow.webContents.send('getAutoStartValue', false) 299 | } else { 300 | mainWindow.webContents.send('getAutoStartValue', true) 301 | } 302 | }) 303 | }) 304 | 305 | // 设置开机自动启动 306 | ipcMain.on('enableAutoStart', () => { 307 | startOnBoot.enableAutoStart(ApplicationName, process.execPath) 308 | }) 309 | 310 | // 取消开机自动启动 311 | ipcMain.on('disableAutoStart', () => { 312 | startOnBoot.disableAutoStart(ApplicationName) 313 | }) 314 | } 315 | 316 | /** 317 | * 自动更新 318 | */ 319 | function autoUpdate() { 320 | // 通过main进程发送事件给renderer进程,提示更新信息 321 | function sendUpdateMessage(obj) { 322 | mainWindow.webContents.send('updateMessage', obj) 323 | } 324 | 325 | // 监测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写 326 | const message = { 327 | error: '检查更新出错', 328 | checking: '正在检查更新......', 329 | updateAva: '监测到新版本,正在下载......', 330 | updateNotAva: '现在使用的就是最新版本,不用下载' 331 | } 332 | 333 | // 当更新出现错误时触发 334 | autoUpdater.on('error', (err) => { 335 | // sendUpdateMessage('error') 336 | sendUpdateMessage({action: 'error', errorInfo: err}) 337 | }) 338 | 339 | // 当开始检查更新的时候触发 340 | autoUpdater.on('checking-for-update', () => { 341 | // sendUpdateMessage('checking') 342 | sendUpdateMessage({action: 'checking'}) 343 | }) 344 | 345 | // 当发现一个可用更新的时候触发,更新下载包会自动开始 346 | autoUpdater.autoDownload = false 347 | autoUpdater.on('update-available', (info) => { 348 | // sendUpdateMessage('updateAva') 349 | sendUpdateMessage({action: 'updateAva', updateInfo: info}) 350 | }) 351 | 352 | // 当没有可用更新的时候触发 353 | autoUpdater.on('update-not-available', (info) => { 354 | // sendUpdateMessage('updateNotAva') 355 | sendUpdateMessage({action: 'updateNotAva', updateInfo: info}) 356 | }) 357 | 358 | // 更新下载进度事件 359 | autoUpdater.on('download-progress', (progressObj) => { 360 | mainWindow.webContents.send('downloadProgress', progressObj) 361 | }) 362 | 363 | /** 364 | * event Event 365 | * releaseNotes String - 新版本更新公告 366 | * releaseName String - 新的版本号 367 | * releaseDate Date - 新版本发布的日期 368 | * updateUrl String - 更新地址 369 | */ 370 | autoUpdater.on('update-downloaded', (info) => { 371 | // 下载太快可能无法触发downloadProgress事件,所以手动通知一下 372 | mainWindow.webContents.send('downloadProgress', {percent: 100}) 373 | // 可以手动选择是否立即退出并更新 374 | ipcMain.on('isUpdateNow', (e, arg) => { 375 | // some code here to handle event 376 | autoUpdater.quitAndInstall() 377 | }) 378 | }) 379 | 380 | ipcMain.on('checkForUpdate', () => { 381 | // 执行自动更新检查 382 | autoUpdater.checkForUpdates() 383 | }) 384 | 385 | ipcMain.on('downloadUpdate', () => { 386 | // 下载 387 | autoUpdater.downloadUpdate() 388 | }) 389 | } 390 | 391 | 392 | /** 393 | * 崩溃报告 394 | */ 395 | function crashReport() { 396 | // 报告常规错误 397 | Sentry.init({ 398 | dsn: 'https://8e0258fcf49d43d09d9fe7c6a0c8ea80@sentry.io/1455801', 399 | }) 400 | 401 | // 报告系统错误 402 | crashReporter.start({ 403 | companyName: 'lanmiao', 404 | productName: 'LanMiaoDesktop', 405 | ignoreSystemCrashHandler: true, 406 | submitURL: 'https://sentry.io/api/1455801/minidump/?sentry_key=8e0258fcf49d43d09d9fe7c6a0c8ea80' 407 | }) 408 | 409 | // 渲染进程崩溃事件 410 | mainWindow.webContents.on('crashed', () => { 411 | const options = { 412 | type: 'error', 413 | title: '进程崩溃了', 414 | message: '这个进程已经崩溃.', 415 | buttons: ['重载', '退出'], 416 | }; 417 | recordCrash().then(() => { 418 | dialog.showMessageBox(options, (index) => { 419 | if (index === 0) { 420 | reloadWindow(mainWindow) 421 | } else { 422 | app.quit() 423 | } 424 | }); 425 | }).catch((e) => { 426 | console.log('err', e) 427 | }); 428 | }) 429 | 430 | function recordCrash() { 431 | return new Promise(resolve => { 432 | // 崩溃日志请求成功.... 433 | resolve(); 434 | }) 435 | } 436 | 437 | function reloadWindow(mainWin) { 438 | if (mainWin.isDestroyed()) { 439 | app.relaunch(); 440 | app.exit(0); 441 | } else { 442 | BrowserWindow.getAllWindows().forEach((w) => { 443 | if (w.id !== mainWin.id) { 444 | w.destroy() 445 | } 446 | }); 447 | mainWin.reload(); 448 | } 449 | } 450 | } 451 | 452 | /** 453 | * 协议处理 454 | */ 455 | function protocalHandler() { 456 | const args = []; 457 | if (!app.isPackaged) { 458 | // 如果是开发阶段,需要把我们的脚本的绝对路径加入参数中 459 | args.push(path.resolve(process.argv[1])) 460 | } 461 | // 加一个 `--` 以确保后面的参数不被 Electron 处理 462 | args.push('--') 463 | 464 | // 注册协议 465 | const PROTOCOL = pkg.name 466 | app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, args) 467 | 468 | // 如果打开协议时,没有其他实例,则当前实例当做主实例,处理参数 469 | handleArgv(process.argv) 470 | 471 | // 其他实例启动时,主实例会通过 second-instance 事件接收其他实例的启动参数 `argv` 472 | app.on('second-instance', (event, argv) => { 473 | // Windows 下通过协议URL启动时,URL会作为参数,所以需要在这个事件里处理 474 | if (process.platform === 'win32') { 475 | handleArgv(argv) 476 | } 477 | }) 478 | 479 | // macOS 下通过协议URL启动时,主实例会通过 open-url 事件接收这个 URL 480 | app.on('open-url', (event, urlStr) => { 481 | handleUrl(urlStr) 482 | }) 483 | 484 | // 处理参数 485 | function handleArgv(argv) { 486 | const prefix = `${PROTOCOL}:`; 487 | // 开发阶段,跳过前两个参数(`electron.exe .`) 488 | // 打包后,跳过第一个参数(`myapp.exe`) 489 | const offset = app.isPackaged ? 1 : 2 490 | const url = argv.find((arg, i) => i >= offset && arg.startsWith(prefix)) 491 | if (url) handleUrl(url) 492 | } 493 | 494 | // 解析Url 495 | function handleUrl(urlStr) { 496 | // myapp:?a=1&b=2 497 | const urlObj = new URL(urlStr); 498 | const {searchParams} = urlObj; 499 | console.log(urlObj.query); // -> a=1&b=2 500 | console.log(searchParams.get('a')); // -> 1 501 | console.log(searchParams.get('b')); // -> 2 502 | // 根据需要做其他事情 503 | } 504 | 505 | } 506 | 507 | /** 508 | * 单一实例 509 | */ 510 | if (!gotTheLock) { 511 | app.quit() 512 | } else { 513 | app.on('second-instance', (event, commandLine, workingDirectory) => { 514 | // 当运行第二个实例时,将会聚焦到myWindow这个窗口 515 | // if (mainWindow) { 516 | // if (mainWindow.isMinimized()) mainWindow.restore() 517 | // mainWindow.focus() 518 | // } 519 | if (loginWindow) { 520 | loginWindow.focus() 521 | } 522 | }) 523 | 524 | // 创建 mainWindow, 加载应用的其余部分, etc... 525 | app.on('ready', () => { 526 | createLoginWindow() 527 | createMainWindow() 528 | createTray() 529 | ipcStartOnBoot() 530 | autoUpdate() 531 | crashReport() 532 | protocalHandler() 533 | }) 534 | } 535 | 536 | app.on('window-all-closed', () => { 537 | if (process.platform !== 'darwin') { 538 | app.quit() 539 | } 540 | }) 541 | 542 | app.on('activate', () => { 543 | if (mainWindow === null) { 544 | createMainWindow() 545 | } 546 | 547 | if (loginWindow === null) { 548 | createLoginWindow() 549 | } 550 | }) 551 | -------------------------------------------------------------------------------- /src/main/startOnBoot.js: -------------------------------------------------------------------------------- 1 | // 引用winreg模块 2 | var WinReg = require('winreg') 3 | var startOnBoot = { 4 | 5 | // 设置自动启动 6 | enableAutoStart: function (name, file, callback) { 7 | var key = getKey() 8 | key.set(name, WinReg.REG_SZ, file, callback || noop) 9 | }, 10 | 11 | // 取消自动启动 12 | disableAutoStart: function (name, callback) { 13 | var key = getKey() 14 | key.remove(name, callback || noop) 15 | }, 16 | 17 | // 获取是否自动启动 18 | getAutoStartValue: function (name, callback) { 19 | var key = getKey() 20 | key.get(name, function (error, result) { 21 | if (result) { 22 | callback(null, result.value) 23 | } else { 24 | callback(error) 25 | } 26 | }) 27 | } 28 | } 29 | 30 | var RUN_LOCATION = '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' 31 | 32 | // 获取注册表key 33 | function getKey() { 34 | return new WinReg({ 35 | // hive: WinReg.HKCU, // CurrentUser, 36 | hive: WinReg.HKLM, // LocalMachine, 37 | key: RUN_LOCATION 38 | }) 39 | } 40 | 41 | // callback自定义方法,你可以在这里写代码 42 | function noop() { 43 | } 44 | 45 | // 导出 46 | module.exports = startOnBoot 47 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | 38 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/components/Layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 147 | 148 | 242 | 243 | 256 | -------------------------------------------------------------------------------- /src/renderer/components/add/add.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 295 | 296 | 299 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | // TODO:如果不用,记得注释掉,否则travis 和 appveyor 构建会失败 2 | // import devtools from '@vue/devtools' 3 | import Vue from 'vue' 4 | import axios from 'axios' 5 | 6 | import App from './App' 7 | import router from './router' 8 | import store from './store' 9 | import Vuetify from 'vuetify' // 引入Vuetify 10 | import 'vuetify/dist/vuetify.min.css' // 引入Vuetify的css文件 11 | import 'material-design-icons-iconfont/dist/material-design-icons.css' // 引入Material Desgin Icon的css文件 12 | import {Upload, Button} from 'element-ui' 13 | 14 | Vue.use(Vuetify) 15 | Vue.use(Button) 16 | Vue.use(Upload) 17 | 18 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 19 | Vue.http = Vue.prototype.$http = axios 20 | Vue.config.productionTip = false 21 | 22 | /* eslint-disable no-new */ 23 | new Vue({ 24 | components: { App }, 25 | router, 26 | store, 27 | template: '' 28 | }).$mount('#app') 29 | 30 | // if (process.env.NODE_ENV === 'development') { 31 | // devtools.connect('localhost', '9080') 32 | // } 33 | -------------------------------------------------------------------------------- /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: '/login', 10 | name: 'login', 11 | component: require('@/views/Login/Login').default 12 | }, 13 | { 14 | path: '/', 15 | name: 'home', 16 | redirect: '/home', 17 | component: require('@/components/Layout/Layout').default, 18 | children: [{ 19 | path: 'home', 20 | component: require('@/views/Home/Home').default 21 | }] 22 | }, 23 | { 24 | path: '/incomeAndExpenditure', 25 | name: 'incomeAndExpenditure', 26 | redirect: '/incomeAndExpenditure/incomeAndExpenditure', 27 | component: require('@/components/Layout/Layout').default, 28 | children: [{ 29 | path: 'incomeAndExpenditure', 30 | component: require('@/views/IncomeAndExpenditure/IncomeAndExpenditure').default 31 | }] 32 | }, 33 | { 34 | path: '/category', 35 | name: 'category', 36 | redirect: '/category/category', 37 | component: require('@/components/Layout/Layout').default, 38 | children: [{ 39 | path: 'category', 40 | component: require('@/views/Category/Category').default 41 | }] 42 | }, 43 | { 44 | path: '/assets', 45 | name: 'assets', 46 | redirect: '/assets/assets', 47 | component: require('@/components/Layout/Layout').default, 48 | children: [{ 49 | path: 'assets', 50 | component: require('@/views/Assets/Assets').default 51 | }] 52 | }, 53 | { 54 | path: '/settings', 55 | name: 'settings', 56 | redirect: '/settings/settings', 57 | component: require('@/components/Layout/Layout').default, 58 | children: [{ 59 | path: 'settings', 60 | component: require('@/views/Settings/Settings').default 61 | }] 62 | }, 63 | { 64 | path: '/notes', 65 | name: 'notes', 66 | redirect: '/notes/notes', 67 | component: require('@/components/Layout/Layout').default, 68 | children: [{ 69 | path: 'notes', 70 | component: require('@/views/Notes/Notes').default 71 | }] 72 | }, 73 | { 74 | path: '/help', 75 | name: 'help', 76 | redirect: '/help/help', 77 | component: require('@/components/Layout/Layout').default, 78 | children: [{ 79 | path: 'help', 80 | component: require('@/views/Help/Help').default 81 | }] 82 | }, 83 | { 84 | path: '/password', 85 | name: 'password', 86 | redirect: '/password/password', 87 | component: require('@/components/Layout/Layout').default, 88 | children: [{ 89 | path: 'password', 90 | component: require('@/views/Password/Password').default 91 | }] 92 | }, 93 | { 94 | path: '*', 95 | redirect: '/' 96 | } 97 | ] 98 | }) 99 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import {createPersistedState, createSharedMutations} from 'vuex-electron' 5 | import todoPlugin from './plugins/todo' 6 | 7 | import modules from './modules' 8 | 9 | Vue.use(Vuex) 10 | 11 | export default new Vuex.Store({ 12 | modules, 13 | plugins: [ 14 | todoPlugin, 15 | // createPersistedState(), // 需要统一,上面的todoPlugin没有括号,所以用变量 16 | createPersistedState, 17 | createSharedMutations 18 | ], 19 | strict: process.env.NODE_ENV !== 'production' 20 | }) 21 | -------------------------------------------------------------------------------- /src/renderer/store/modules/Counter.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | main: 0 3 | } 4 | 5 | const mutations = { 6 | DECREMENT_MAIN_COUNTER (state) { 7 | state.main-- 8 | }, 9 | INCREMENT_MAIN_COUNTER (state) { 10 | state.main++ 11 | } 12 | } 13 | 14 | const actions = { 15 | someAsyncTask ({ commit }) { 16 | // do something async 17 | commit('INCREMENT_MAIN_COUNTER') 18 | } 19 | } 20 | 21 | export default { 22 | state, 23 | mutations, 24 | actions 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/store/modules/dialogAdd.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | dialogAddShow: false 3 | } 4 | 5 | const mutations = { 6 | show(state, show) { 7 | state.dialogAddShow = show 8 | } 9 | } 10 | 11 | const actions = { 12 | updateDialog({commit}, show) { 13 | commit('show', show) 14 | } 15 | } 16 | 17 | export default { 18 | state, 19 | mutations, 20 | actions 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The file enables `@/store/index.js` to import all vuex modules 3 | * in a one-shot manner. There should not be any reason to edit this file. 4 | */ 5 | 6 | const files = require.context('.', false, /\.js$/) 7 | const modules = {} 8 | 9 | files.keys().forEach(key => { 10 | if (key === './index.js') return 11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /src/renderer/store/modules/todo.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'vuetify-todos' 2 | 3 | const state = { 4 | todos: JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]') 5 | } 6 | 7 | const mutations = { 8 | addTodo (state, todo) { 9 | state.todos.push(todo) 10 | }, 11 | removeTodo (state, todo) { 12 | state.todos.splice(state.todos.indexOf(todo), 1) 13 | }, 14 | editTodo (state, { todo, text = todo.text, done = todo.done }) { 15 | todo.text = text 16 | todo.done = done 17 | } 18 | } 19 | 20 | const actions = { 21 | addTodo ({ commit }, text) { 22 | commit('addTodo', { 23 | uid: Date.now(), 24 | text, 25 | done: false 26 | }) 27 | }, 28 | removeTodo ({ commit }, todo) { 29 | commit('removeTodo', todo) 30 | }, 31 | toggleTodo ({ commit }, todo) { 32 | commit('editTodo', { todo, done: !todo.done }) 33 | }, 34 | editTodo ({ commit }, { todo, value }) { 35 | commit('editTodo', { todo, text: value }) 36 | }, 37 | toggleAll ({ state, commit }, done) { 38 | state.todos.forEach((todo) => { 39 | commit('editTodo', { todo, done }) 40 | }) 41 | }, 42 | clearCompleted ({ state, commit }) { 43 | state.todos.filter(todo => todo.done) 44 | .forEach(todo => { 45 | commit('removeTodo', todo) 46 | }) 47 | } 48 | } 49 | 50 | export default { 51 | state, 52 | mutations, 53 | actions 54 | } 55 | -------------------------------------------------------------------------------- /src/renderer/store/plugins/todo.js: -------------------------------------------------------------------------------- 1 | const STORAGE_KEY = 'vuetify-todos' 2 | 3 | export default store => { 4 | store.subscribe((mutation, state) => { 5 | console.log(mutation, state) 6 | window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state.todo.todos)) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/views/Help/Help.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 73 | 74 | 99 | -------------------------------------------------------------------------------- /src/renderer/views/Home/Home.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 68 | 69 | 72 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/add/add.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/history/history.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 151 | 152 | 158 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/statsCurve/statsCurve.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/statsIcon/statsIcon.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 57 | 58 | 71 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/todo/todo.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 145 | 146 | 149 | -------------------------------------------------------------------------------- /src/renderer/views/Home/components/todo/todoItem.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 95 | 96 | 106 | -------------------------------------------------------------------------------- /src/renderer/views/Login/Login.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 131 | 132 | 150 | -------------------------------------------------------------------------------- /src/renderer/views/Notes/Notes.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 174 | 175 | 178 | -------------------------------------------------------------------------------- /src/renderer/views/Password/Password.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 176 | 177 | 191 | -------------------------------------------------------------------------------- /src/renderer/views/Settings/Settings.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 293 | 294 | 297 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/.gitkeep -------------------------------------------------------------------------------- /static/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/icon.ico -------------------------------------------------------------------------------- /static/iconMessage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconMessage.ico -------------------------------------------------------------------------------- /static/iconMessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconMessage.png -------------------------------------------------------------------------------- /static/iconMessageNotWin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconMessageNotWin.png -------------------------------------------------------------------------------- /static/iconMessageNotWin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconMessageNotWin@2x.png -------------------------------------------------------------------------------- /static/iconMessageNotWin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconMessageNotWin@3x.png -------------------------------------------------------------------------------- /static/iconTransparent.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconTransparent.ico -------------------------------------------------------------------------------- /static/iconTransparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconTransparent.png -------------------------------------------------------------------------------- /static/iconTransparentNotWin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconTransparentNotWin.png -------------------------------------------------------------------------------- /static/iconTransparentNotWin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconTransparentNotWin@2x.png -------------------------------------------------------------------------------- /static/iconTransparentNotWin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/iconTransparentNotWin@3x.png -------------------------------------------------------------------------------- /static/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/logo.ico -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/logo.png -------------------------------------------------------------------------------- /static/logoNotWin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/logoNotWin.png -------------------------------------------------------------------------------- /static/logoNotWin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/logoNotWin@2x.png -------------------------------------------------------------------------------- /static/logoNotWin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilanmiao/LanMiaoDesktop/e7321d06a5b933b04004b2f9ef3814e76a4d9acd/static/logoNotWin@3x.png --------------------------------------------------------------------------------