commit d33ffa216d41db90cec4304e6ef17b6dca014727 Author: leonZ Date: Sat Aug 3 20:01:18 2024 +0800 init diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..91e5d64 --- /dev/null +++ b/.babelrc @@ -0,0 +1,95 @@ +{ + "comments": false, + "env": { + "renderer": { + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "useBuiltIns": "entry", + "corejs": 3 + } + ], + [ + "@vue/babel-preset-jsx" + ] + ], + "plugins": [ + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-syntax-import-meta", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-json-strings", + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + "@babel/plugin-proposal-function-sent", + "@babel/plugin-proposal-export-namespace-from", + "@babel/plugin-proposal-numeric-separator", + "@babel/plugin-proposal-throw-expressions", + "@babel/plugin-proposal-export-default-from", + "@babel/plugin-proposal-logical-assignment-operators", + "@babel/plugin-proposal-optional-chaining", + [ + "@babel/plugin-proposal-pipeline-operator", + { + "proposal": "minimal" + } + ], + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-do-expressions", + "@babel/plugin-proposal-function-bind" + ] + }, + "web": { + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "useBuiltIns": "entry", + "corejs": 3 + } + ], + [ + "@vue/babel-preset-jsx" + ] + ], + "plugins": [ + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-syntax-import-meta", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-json-strings", + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + "@babel/plugin-proposal-function-sent", + "@babel/plugin-proposal-export-namespace-from", + "@babel/plugin-proposal-numeric-separator", + "@babel/plugin-proposal-throw-expressions", + "@babel/plugin-proposal-export-default-from", + "@babel/plugin-proposal-logical-assignment-operators", + "@babel/plugin-proposal-optional-chaining", + [ + "@babel/plugin-proposal-pipeline-operator", + { + "proposal": "minimal" + } + ], + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-do-expressions", + "@babel/plugin-proposal-function-bind" + ] + } + }, + "plugins": [ + "@babel/transform-runtime", + "@babel/plugin-syntax-dynamic-import" + ] +} \ No newline at end of file diff --git a/.electron-vue/build.js b/.electron-vue/build.js new file mode 100644 index 0000000..b62bcc1 --- /dev/null +++ b/.electron-vue/build.js @@ -0,0 +1,122 @@ +'use strict' +process.env.NODE_ENV = 'production' +const { say } = require('cfonts') +const chalk = require('chalk') +const del = require('del') +const webpack = require('webpack') +const { Listr } = require('listr2') + + +const mainConfig = require('./webpack.main.config') +const rendererConfig = require('./webpack.renderer.config') + +const doneLog = chalk.bgGreen.white(' DONE ') + ' ' +const errorLog = chalk.bgRed.white(' ERROR ') + ' ' +const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' +const isCI = process.env.CI || false + +if (process.env.BUILD_TARGET === 'web') web() +else build() + +function clean() { + del.sync(['dist/electron/*', 'build/*', '!build/icons', '!build/lib', '!build/lib/electron-build.*', '!build/icons/icon.*']) + console.log(`\n${doneLog}clear done`) + if (process.env.BUILD_TARGET === 'onlyClean') process.exit() +} + +function build() { + greeting() + if (process.env.BUILD_TARGET === 'clean' || process.env.BUILD_TARGET === 'onlyClean') clean() + const tasksLister = new Listr([ + { + title: 'building main process', + task: async (_, tasks) => { + try { + await pack(mainConfig) + } catch (error) { + console.error(`\n${error}\n`) + console.log(`\n ${errorLog}failed to build main process`) + process.exit(1) + } + } + }, + { + title: "building renderer process", + task: async (_, tasks) => { + try { + await pack(rendererConfig) + tasks.output = `${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n` + } catch (error) { + console.error(`\n${error}\n`) + console.log(`\n ${errorLog}failed to build renderer process`) + process.exit(1) + } + }, + options: { persistentOutput: true } + } + ], { + exitOnError: true + }) + tasksLister.run() +} + +function pack(config) { + return new Promise((resolve, reject) => { + config.mode = 'production' + webpack(config, (err, stats) => { + if (err) reject(err.stack || err) + else if (stats.hasErrors()) { + let err = '' + + stats.toString({ + chunks: false, + colors: true + }) + .split(/\r?\n/) + .forEach(line => { + err += ` ${line}\n` + }) + + reject(err) + } else { + resolve(stats.toString({ + chunks: false, + colors: true + })) + } + }) + }) +} + +function web() { + del.sync(['dist/web/*', '!.gitkeep']) + rendererConfig.mode = 'production' + webpack(rendererConfig, (err, stats) => { + if (err || stats.hasErrors()) console.log(err) + + console.log(stats.toString({ + chunks: false, + colors: true + })) + + process.exit() + }) +} + +function greeting() { + const cols = process.stdout.columns + let text = '' + + if (cols > 85) text = `let's-build` + else if (cols > 60) text = `let's-|build` + else text = false + + if (text && !isCI) { + say(text, { + colors: ['yellow'], + font: 'simple3d', + space: false + }) + } else console.log(chalk.yellow.bold(`\n let's-build`)) + console.log() +} \ No newline at end of file diff --git a/.electron-vue/dev-runner.js b/.electron-vue/dev-runner.js new file mode 100644 index 0000000..34c0273 --- /dev/null +++ b/.electron-vue/dev-runner.js @@ -0,0 +1,210 @@ +'use strict' + +process.env.NODE_ENV = 'development' + +const chalk = require('chalk') +const electron = require('electron') +const path = require('path') +const { say } = require('cfonts') +const { spawn } = require('child_process') +const config = require('../config') +const webpack = require('webpack') +const WebpackDevServer = require('webpack-dev-server') +const Portfinder = require("portfinder") + +const mainConfig = require('./webpack.main.config') +const rendererConfig = require('./webpack.renderer.config') + +let electronProcess = null +let manualRestart = false + +function logStats(proc, data) { + let log = '' + + log += chalk.yellow.bold(`┏ ${proc} ${config.dev.chineseLog ? '编译过程' : 'Process'} ${new Array((19 - proc.length) + 1).join('-')}`) + log += '\n\n' + + if (typeof data === 'object') { + data.toString({ + colors: true, + chunks: false + }).split(/\r?\n/).forEach(line => { + log += ' ' + line + '\n' + }) + } else { + log += ` ${data}\n` + } + + log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' + console.log(log) +} + +function removeJunk(chunk) { + if (config.dev.removeElectronJunk) { + // Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window + if (/\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(chunk)) { + return false; + } + + // Example: [90789:0810/225804.894349:ERROR:CONSOLE(105)] "Uncaught (in promise) Error: Could not instantiate: ProductRegistryImpl.Registry", source: chrome-devtools://devtools/bundled/inspector.js (105) + if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(chunk)) { + return false; + } + + // Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0' + if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(chunk)) { + return false; + } + } + + + return chunk; +} + +function startRenderer() { + return new Promise((resolve, reject) => { + rendererConfig.mode = 'development' + Portfinder.basePort = config.dev.port || 9080 + Portfinder.getPort((err, port) => { + if (err) { + reject("PortError:" + err) + } else { + const compiler = webpack(rendererConfig) + + compiler.hooks.done.tap('done', stats => { + logStats('Renderer', stats) + }) + + const server = new WebpackDevServer( + { + port, + static: { + directory: path.join(__dirname, '..', 'static'), + publicPath: '/static/', + } + }, + compiler + ) + + process.env.PORT = port + server.start().then(() => { + resolve() + }) + + } + }) + + }) +} + +function startMain() { + return new Promise((resolve) => { + mainConfig.mode = 'development' + const compiler = webpack(mainConfig) + + compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { + logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, chalk.white.bold(`${config.dev.chineseLog ? '正在处理资源文件...' : 'compiling...'}`)) + done() + }) + + compiler.watch({}, (err, stats) => { + if (err) { + console.log(err) + return + } + + logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, stats) + + if (electronProcess && electronProcess.kill) { + manualRestart = true + process.kill(electronProcess.pid) + electronProcess = null + startElectron() + + setTimeout(() => { + manualRestart = false + }, 5000) + } + + resolve() + }) + }) +} + +function startElectron() { + var args = [ + '--inspect=5858', + path.join(__dirname, '../dist/electron/main.js') + ] + + // detect yarn or npm and process commandline args accordingly + if (process.env.npm_execpath.endsWith('yarn.js')) { + args = args.concat(process.argv.slice(3)) + } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { + args = args.concat(process.argv.slice(2)) + } + + electronProcess = spawn(electron, args) + + electronProcess.stdout.on('data', data => { + electronLog(removeJunk(data), 'blue') + }) + electronProcess.stderr.on('data', data => { + electronLog(removeJunk(data), 'red') + }) + + electronProcess.on('close', () => { + if (!manualRestart) process.exit() + }) +} + +function electronLog(data, color) { + if (data) { + let log = '' + data = data.toString().split(/\r?\n/) + data.forEach(line => { + log += ` ${line}\n` + }) + console.log( + chalk[color].bold(`┏ ${config.dev.chineseLog ? '主程序日志' : 'Electron'} -------------------`) + + '\n\n' + + log + + chalk[color].bold('┗ ----------------------------') + + '\n' + ) + } + +} + +function greeting() { + const cols = process.stdout.columns + let text = '' + + if (cols > 104) text = 'electron-vue' + else if (cols > 76) text = 'electron-|vue' + else text = false + + if (text) { + say(text, { + colors: ['yellow'], + font: 'simple3d', + space: false + }) + } else console.log(chalk.yellow.bold('\n electron-vue')) + console.log(chalk.blue(`${config.dev.chineseLog ? ' 准备启动...' : ' getting ready...'}`) + '\n') +} + +async function init() { + greeting() + + try { + await startRenderer() + await startMain() + await startElectron() + } catch (error) { + console.error(error) + } + +} + +init() \ No newline at end of file diff --git a/.electron-vue/hot-updater.js b/.electron-vue/hot-updater.js new file mode 100644 index 0000000..910f502 --- /dev/null +++ b/.electron-vue/hot-updater.js @@ -0,0 +1,122 @@ +/** + * power by biuuu + */ + +const chalk = require("chalk"); +const { join } = require('path') +const crypto = require('crypto') +const AdmZip = require('adm-zip') +const packageFile = require('../package.json') +const { build } = require("../config/index") +const { platform } = require("os") +const { ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile } = require("fs-extra"); + +const platformName = platform().includes('win32') ? 'win' : platform().includes('darwin') ? 'mac' : 'linux' +const buildPath = join('.', 'build', `${platformName === 'mac' ? 'mac' : platformName + '-unpacked'}`) + +const hash = (data, type = 'sha256') => { + const hmac = crypto.createHmac(type, 'Sky') + hmac.update(data) + return hmac.digest('hex') +} + +const createZip = (filePath, dest) => { + const zip = new AdmZip() + zip.addLocalFolder(filePath) + zip.toBuffer() + zip.writeZip(dest) +} + +const start = async () => { + console.log(chalk.green.bold(`\n Start packing`)) + + if (packageFile.build.asar) { + console.log( + "\n" + + chalk.bgRed.white(" ERROR ") + + " " + + chalk.red("Please make sure the build.asar option in the Package.json file is set to false") + + "\n" + ); + return; + } + + if (build.hotPublishConfigName === '') { + console.log( + "\n" + + chalk.bgRed.white(" ERROR ") + + " " + + chalk.red("HotPublishConfigName is not set, which will cause the update to fail, please set it in the config/index.js \n") + + chalk.red.bold(`\n Packing failed \n`) + ); + process.exit(1) + } + + stat(join(buildPath, 'resources', 'app'), async (err, stats) => { + if (err) { + console.log( + "\n" + + chalk.bgRed.white(" ERROR ") + + " " + + chalk.red("No resource files were found, please execute this command after the build command") + + "\n" + ); + return; + } + + try { + const packResourcesPath = join('.', 'build', 'resources', 'dist'); + const packPackagePath = join('.', 'build', 'resources'); + const resourcesPath = join('.', 'dist'); + const appPath = join('.', 'build', 'resources'); + const name = "app.zip"; + const outputPath = join('.', 'build', 'update'); + const zipPath = join(outputPath, name); + + await ensureDir(packResourcesPath); + await emptyDir(packResourcesPath); + await copy(resourcesPath, packResourcesPath); + await outputJSON(join(packPackagePath, "package.json"), { + name: packageFile.name, + productName: packageFile.productName, + version: packageFile.version, + private: packageFile.private, + description: packageFile.description, + main: packageFile.main, + author: packageFile.author, + dependencies: packageFile.dependencies + }); + await ensureDir(outputPath); + await emptyDir(outputPath); + createZip(appPath, zipPath); + const buffer = await readFile(zipPath); + const sha256 = hash(buffer); + const hashName = sha256.slice(7, 12); + await copy(zipPath, join(outputPath, `${hashName}.zip`)); + await remove(zipPath); + await remove(appPath) + await outputJSON(join(outputPath, `${build.hotPublishConfigName}.json`), + { + version: packageFile.version, + name: `${hashName}.zip`, + hash: sha256 + } + ); + console.log( + "\n" + chalk.bgGreen.white(" DONE ") + " " + "The resource file is packaged!\n" + ); + console.log("File location: " + chalk.green(outputPath) + "\n"); + } catch (error) { + console.log( + "\n" + + chalk.bgRed.white(" ERROR ") + + " " + + chalk.red(error.message || error) + + "\n" + ); + process.exit(1) + } + }); +} + +start() \ No newline at end of file diff --git a/.electron-vue/utils.js b/.electron-vue/utils.js new file mode 100644 index 0000000..480fdb0 --- /dev/null +++ b/.electron-vue/utils.js @@ -0,0 +1,101 @@ +'use strict' +const MiniCssPlugin = require('mini-css-extract-plugin'); +const dotenv = require('dotenv') +const { join } = require("path") +const argv = require('minimist')(process.argv.slice(2)); +const rootResolve = (...pathSegments) => join(__dirname, '..', ...pathSegments) + +function getEnv() { + return argv['m'] +} +function getEnvPath() { + if (String(typeof getEnv()) === 'boolean' || String(typeof getEnv()) === 'undefined') { + return rootResolve('env/.env') + } + return rootResolve(`env/${getEnv()}.env`) +} +function getConfig() { + return dotenv.config({ path: getEnvPath() }).parsed +} + +// 获取环境 +exports.getEnv = getEnv() +// 获取配置 +exports.getConfig = getConfig() + +exports.cssLoaders = function (options) { + options = options || {} + const esbuildCss = { + loader: 'esbuild-loader', + options: { + loader: 'css', + minify: options.minifyCss + } + } + + const cssLoader = { + loader: 'css-loader', + options: { + sourceMap: options.sourceMap, + esModule: false + } + } + + const postcssLoader = { + loader: 'postcss-loader', + options: { + sourceMap: options.sourceMap + } + } + + // 这里就是生成loader和其对应的配置 + function generateLoaders(loader, loaderOptions) { + const loaders = [cssLoader, postcssLoader, esbuildCss] + + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // 当配置信息中开启此项时,启用css分离压缩 + // 这一项在生产环境时,是默认开启的 + if (options.extract) { + return [MiniCssPlugin.loader].concat(loaders) + } else { + // 如果不开启则让vue-style-loader来处理 + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + less: generateLoaders('less'), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装 +exports.styleLoaders = function (options) { + const output = [] + const loaders = exports.cssLoaders(options) + + + for (const extension in loaders) { + const loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + + return output +} diff --git a/.electron-vue/webpack.main.config.js b/.electron-vue/webpack.main.config.js new file mode 100644 index 0000000..9f6d9f0 --- /dev/null +++ b/.electron-vue/webpack.main.config.js @@ -0,0 +1,99 @@ +'use strict' + +process.env.BABEL_ENV = 'main' + +const path = require('path') +const { dependencies } = require('../package.json') +const webpack = require('webpack') +const TerserPlugin = require('terser-webpack-plugin') +const config = require('../config') +const { getConfig } = require("./utils") + +function resolve(dir) { + return path.join(__dirname, '..', dir) +} + +let mainConfig = { + infrastructureLogging: { + level: 'warn' + }, + entry: { + main: path.join(__dirname, '../src/main/index.js') + }, + externals: [ + ...Object.keys(dependencies || {}) + ], + module: { + rules: [ + { + test: /\.js$/, + loader: 'esbuild-loader' + }, + { + test: /\.node$/, + use: 'node-loader' + } + ] + }, + node: { + __dirname: process.env.NODE_ENV !== 'production', + __filename: process.env.NODE_ENV !== 'production' + }, + output: { + filename: '[name].js', + libraryTarget: 'commonjs2', + path: path.join(__dirname, '../dist/electron') + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.userConfig':JSON.stringify(getConfig) + }) + ], + resolve: { + alias: { + '@config': resolve('config'), + }, + extensions: ['.js', '.json', '.node'] + }, + target: 'electron-main', +} + +/** + * Adjust mainConfig for development settings + */ +if (process.env.NODE_ENV !== 'production') { + mainConfig.plugins.push( + new webpack.DefinePlugin({ + '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`, + 'process.env.libPath': `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"` + }) + ) +} + +/** + * Adjust mainConfig for production settings + */ +if (process.env.NODE_ENV === 'production' && config.build.cleanConsole) { + mainConfig.optimization = { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + pure_funcs: ["console.log", "console.warn"] + } + } + + }) + ] + } + mainConfig.plugins.push( + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"production"' + }) + ) +} + +module.exports = mainConfig diff --git a/.electron-vue/webpack.renderer.config.js b/.electron-vue/webpack.renderer.config.js new file mode 100644 index 0000000..a211412 --- /dev/null +++ b/.electron-vue/webpack.renderer.config.js @@ -0,0 +1,219 @@ +'use strict' + +const IsWeb = process.env.BUILD_TARGET === 'web' +process.env.BABEL_ENV = IsWeb ? 'web' : 'renderer' + +const path = require('path') +const { dependencies } = require('../package.json') +const webpack = require('webpack') +const config = require('../config') +const { styleLoaders } = require('./utils') + +const CopyWebpackPlugin = require('copy-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const TerserPlugin = require('terser-webpack-plugin') +// const ESLintPlugin = require('eslint-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader') +const { getConfig } = require("./utils") + +function resolve(dir) { + return path.join(__dirname, '..', dir) +} +/** + * List of node_modules to include in webpack bundle + * + * Required for specific packages like Vue UI libraries + * that provide pure *.vue files that need compiling + * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals + */ + +let rendererConfig = { + entry: IsWeb ? { web: path.join(__dirname, '../src/renderer/main.js') } : { renderer: resolve('src/renderer/main.js') }, + infrastructureLogging: { level: 'warn' }, + stats: 'none', + module: { + rules: [ + { + test: /\.vue$/, + loader: "vue-loader", + options: { + babelParserPlugins: [ + 'jsx', + 'classProperties', + 'decorators-legacy' + ] + } + }, + { + test: /\.jsx$/, + loader: 'babel-loader', + }, + { + test: /\.html$/, + use: 'vue-html-loader' + }, + { + test: /\.svg$/, + loader: 'svg-sprite-loader', + include: [resolve('src/renderer/icons')], + options: { + symbolId: 'icon-[name]' + } + }, + { + test: /\.(png|jpe?g|gif)(\?.*)?$/, + type: "asset/resource", + generator: { + filename: 'imgs/[name]--[hash].[ext]' + } + }, + { + test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, + type: "asset/resource", + generator: { + filename: 'media/[name]--[hash].[ext]' + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + type: "asset/resource", + generator: { + filename: 'fonts/[name]--[hash].[ext]' + } + } + ] + }, + node: { + __dirname: process.env.NODE_ENV !== 'production', + __filename: process.env.NODE_ENV !== 'production' + }, + plugins: [ + new VueLoaderPlugin(), + new MiniCssExtractPlugin(), + new webpack.DefinePlugin({ + 'process.env.userConfig': JSON.stringify(getConfig), + 'process.env.IS_WEB': IsWeb + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: resolve('src/index.ejs'), + minify: { + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + minifyJS: true, + minifyCSS: true + }, + templateParameters(compilation, assets, options) { + return { + compilation: compilation, + webpack: compilation.getStats().toJson(), + webpackConfig: compilation.options, + htmlWebpackPlugin: { + files: assets, + options: options + }, + process, + }; + }, + nodeModules: false + }), + ], + output: { + filename: '[name].js', + path: IsWeb ? path.join(__dirname, '../dist/web') : path.join(__dirname, '../dist/electron') + }, + resolve: { + alias: { + '@': resolve('src/renderer'), + 'vue$': 'vue/dist/vue.esm.js' + }, + extensions: ['.js', '.vue', '.json', '.css', '.node'] + }, + target: IsWeb ? 'web' : 'electron-renderer' +} +// 将css相关得loader抽取出来 +rendererConfig.module.rules = rendererConfig.module.rules.concat(styleLoaders({ sourceMap: process.env.NODE_ENV !== 'production' ? config.dev.cssSourceMap : false, extract: IsWeb, minifyCss: process.env.NODE_ENV === 'production' })); +(IsWeb || config.UseJsx) ? rendererConfig.module.rules.push({ test: /\.m?[jt]sx$/, use: [{ loader: 'babel-loader', options: { cacheDirectory: true } }] }) : rendererConfig.module.rules.push({ test: /\.m?[jt]s$/, loader: 'esbuild-loader', options: { loader: 'ts', } }) + +/** + * Adjust rendererConfig for development settings + */ +if (process.env.NODE_ENV !== 'production' && !IsWeb) { + rendererConfig.plugins.push( + new webpack.DefinePlugin({ + __lib: `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"` + }) + ) +} + +/** + * Adjust rendererConfig for production settings + */ +if (process.env.NODE_ENV === 'production') { + + rendererConfig.plugins.push( + new CopyWebpackPlugin({ + patterns: [ + { + from: path.join(__dirname, '../static'), + to: path.join(__dirname, '../dist/electron/static'), + globOptions: { + ignore: ['.*'] + } + } + ] + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': '"production"', + }), + new webpack.LoaderOptionsPlugin({ + minimize: true + }) + ) + rendererConfig.optimization = { + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + pure_funcs: ["console.log", "console.warn"] + } + } + + }) + ] + } + rendererConfig.optimization.splitChunks = { + chunks: "async", + cacheGroups: { + vendor: { // 将第三方模块提取出来 + minSize: 30000, + minChunks: 1, + test: /node_modules/, + chunks: 'initial', + name: 'vendor', + priority: 1 + }, + commons: { + test: /[\\/]src[\\/]common[\\/]/, + name: 'commons', + minSize: 30000, + minChunks: 3, + chunks: 'initial', + priority: -1, + reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块 + } + } + } + rendererConfig.optimization.runtimeChunk = { name: 'runtime' } +} else { + rendererConfig.devtool = 'eval-source-map' + // eslint + // rendererConfig.plugins.push(new ESLintPlugin(config.dev.ESLintoptions)) +} + +module.exports = rendererConfig diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..d8b0d0a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +test/unit/coverage/** +test/unit/*.js +test/e2e/*.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..7877f96 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + root: true, + parser: 'babel-eslint', + parserOptions: { + sourceType: 'module' + }, + env: { + browser: true, + node: true + }, + extends: 'standard', + globals: { + __static: true, + __lib: true + }, + plugins: [ + 'html' + ], + 'rules': { + // allow paren-less arrow functions + 'arrow-parens': 0, + // allow async-await + 'generator-star-spacing': 0, + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c23593 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.DS_Store +node_modules/ +/dist/ +/build/ +build/win-unpacked/ +build/win-ia32-unpacked/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a673ac8 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry = https://registry.npmmirror.com/ +sqlite3_binary_host_mirror = https://foxgis.oss-cn-shanghai.aliyuncs.com/ +electron_mirror = https://npmmirror.com/mirrors/electron/ \ No newline at end of file diff --git a/.postcssrc.js b/.postcssrc.js new file mode 100644 index 0000000..9252a40 --- /dev/null +++ b/.postcssrc.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {}, + } +} \ No newline at end of file diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..4656aab --- /dev/null +++ b/config/index.js @@ -0,0 +1,12 @@ +module.exports = { + build: { + cleanConsole: true, + }, + dev: { + removeElectronJunk: true, + chineseLog: false, + port: 8088 + }, + DllFolder: '', + UseJsx: true +} diff --git a/env/.env b/env/.env new file mode 100644 index 0000000..ef4c2b8 --- /dev/null +++ b/env/.env @@ -0,0 +1,4 @@ +API_HOST = 'https://mc.keyelement.cn/api/' +# API_HOST = 'http://127.0.0.1:8080/' +NODE_ENV = 'development' +SYSTEM_NAME = '会员收银系统' diff --git a/env/sit.env b/env/sit.env new file mode 100644 index 0000000..c56dd35 --- /dev/null +++ b/env/sit.env @@ -0,0 +1,2 @@ +API_HOST = 'http://127.0.0.1:25565' +NODE_ENV = 'sit' diff --git a/package.json b/package.json new file mode 100644 index 0000000..243ca4f --- /dev/null +++ b/package.json @@ -0,0 +1,157 @@ +{ + "name": "membercashier", + "version": "1.2.0", + "author": "成都关键元素科技有限公司", + "description": "成都关键元素科技有限公司 - 会员管理收银系统", + "license": "MIT", + "main": "./dist/electron/main.js", + "scripts": { + "dev": "cross-env TERGET_ENV=development node .electron-vue/dev-runner.js", + "build": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder", + "build:win32": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --win --ia32", + "build:win64": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --win --x64", + "build:mac": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --mac", + "build:dir": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --dir", + "build:clean": "cross-env BUILD_TARGET=onlyClean node .electron-vue/build.js", + "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", + "pack:resources": "node .electron-vue/hot-updater.js", + "update:serve": "node server/index.js", + "dep:upgrade": "yarn upgrade-interactive --latest", + "postinstall": "electron-builder install-app-deps" + }, + "build": { + "asar": false, + "extraFiles": [], + "publish": [ + { + "provider": "generic", + "url": "http://mc.keyelement.cn" + } + ], + "productName": "会员收银系统", + "appId": "com.keyelement.membercashier", + "directories": { + "output": "build" + }, + "files": [ + "dist/electron/**/*" + ], + "dmg": { + "contents": [ + { + "x": 410, + "y": 150, + "type": "link", + "path": "/Applications" + }, + { + "x": 130, + "y": 150, + "type": "file" + } + ] + }, + "mac": { + "icon": "public/icons/icon.icns" + }, + "win": { + "icon": "public/icons/icon.ico", + "target": "nsis" + }, + "linux": { + "target": "deb", + "icon": "public/icons" + } + }, + "dependencies": { + "axios": "^1.4.0", + "clipboard": "2.0.8", + "electron-updater": "^5.3.0", + "express": "4.18.2", + "fs-extra": "^11.1.0", + "vue-print-nb": "^1.7.5" + }, + "devDependencies": { + "@babel/core": "^7.22.5", + "@babel/eslint-parser": "^7.22.5", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-decorators": "^7.22.5", + "@babel/plugin-proposal-do-expressions": "^7.22.5", + "@babel/plugin-proposal-export-default-from": "^7.22.5", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-function-bind": "^7.22.5", + "@babel/plugin-proposal-function-sent": "^7.22.5", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-pipeline-operator": "^7.22.5", + "@babel/plugin-proposal-throw-expressions": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-runtime": "^7.22.5", + "@babel/preset-env": "^7.22.5", + "@babel/register": "^7.22.5", + "@babel/runtime": "^7.22.5", + "@types/fs-extra": "^11.0.1", + "@types/node": "^18.14.5", + "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", + "@vue/babel-preset-jsx": "^1.4.0", + "adm-zip": "^0.5.10", + "autoprefixer": "^10.4.14", + "babel-loader": "^9.1.2", + "cfonts": "^2.10.0", + "chalk": "^4.1.2", + "copy-webpack-plugin": "^11.0.0", + "core-js": "^3.31.0", + "cross-env": "^7.0.3", + "css-loader": "^6.8.1", + "date-fns": "^2.30.0", + "del": "^6.1.1", + "dotenv": "^16.1.4", + "electron": "^24.8.1", + "electron-builder": "^24.4.0", + "electron-devtools-installer": "^3.2.0", + "element-ui": "^2.15.13", + "esbuild-loader": "^3.0.1", + "eslint": "^7.32.0", + "eslint-config-standard": "^14.1.1", + "eslint-friendly-formatter": "^4.0.1", + "eslint-plugin-html": "^6.2.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.3.1", + "eslint-plugin-standard": "^5.0.0", + "eslint-webpack-plugin": "^3.2.0", + "extract-zip": "^2.0.1", + "html-webpack-plugin": "^5.5.3", + "listr2": "^5.0.7", + "mini-css-extract-plugin": "2.7.6", + "minimist": "^1.2.8", + "node-loader": "^2.0.0", + "nprogress": "^0.2.0", + "pinia": "^2.0.33", + "portfinder": "^1.0.32", + "postcss": "^8.4.24", + "postcss-loader": "^7.3.3", + "sass": "^1.63.4", + "sass-loader": "^13.3.2", + "style-loader": "^3.3.3", + "svg-sprite-loader": "^6.0.11", + "terser-webpack-plugin": "^5.3.9", + "vue": "^2.7.14", + "vue-devtools": "^5.1.4", + "vue-html-loader": "^1.2.4", + "vue-i18n": "^8.27.1", + "vue-loader": "15.10.1", + "vue-router": "^3.6.5", + "vue-style-loader": "^4.1.3", + "vue-template-compiler": "^2.7.14", + "webpack": "^5.87.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "webpack-hot-middleware": "^2.25.3", + "webpack-merge": "^5.9.0" + } +} diff --git a/public/icons/256x256.png b/public/icons/256x256.png new file mode 100644 index 0000000..5ba8c4d Binary files /dev/null and b/public/icons/256x256.png differ diff --git a/public/icons/icon.icns b/public/icons/icon.icns new file mode 100644 index 0000000..60c8d73 Binary files /dev/null and b/public/icons/icon.icns differ diff --git a/public/icons/icon.ico b/public/icons/icon.ico new file mode 100644 index 0000000..1b1ab74 Binary files /dev/null and b/public/icons/icon.ico differ diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..87e1374 --- /dev/null +++ b/server/index.js @@ -0,0 +1,12 @@ +const express = require('express') +const path = require('path') +const app = express() + +app.use(express.static(path.join(__dirname, './client'))) + +const server = app.listen(25565, function () { + const host = server.address().address + const port = server.address().port + + console.log('服务启动', host, port) +}) diff --git a/src/index.ejs b/src/index.ejs new file mode 100644 index 0000000..594ad0a --- /dev/null +++ b/src/index.ejs @@ -0,0 +1,32 @@ + + + + + + 会员收银系统 + <% if (htmlWebpackPlugin.options.nodeModules) { %> + + + <% } %> + + + +
+ + <% if (!process.browser) { %> + + <% } %> + + + + + diff --git a/src/main/config/DisableButton.js b/src/main/config/DisableButton.js new file mode 100644 index 0000000..669706f --- /dev/null +++ b/src/main/config/DisableButton.js @@ -0,0 +1,12 @@ +import { globalShortcut } from 'electron' +import { DisableF12 } from "./const" + +export default { + Disablef12() { + if (process.env.NODE_ENV === 'production' && DisableF12) { + globalShortcut.register('f12', () => { + console.log('用户试图启动控制台') + }) + } + } +} diff --git a/src/main/config/StaticPath.js b/src/main/config/StaticPath.js new file mode 100644 index 0000000..a82da8f --- /dev/null +++ b/src/main/config/StaticPath.js @@ -0,0 +1,17 @@ +// 这里定义了静态文件路径的位置 +import path from 'path' +import { DllFolder } from '@config/index' + +/** + * Set `__static` path to static files in production + * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html + */ +// 这个瓜皮全局变量只能在单个js中生效,而并不是整个主进程中 +if (process.env.NODE_ENV !== 'development') { + global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\') + process.env.libPath = path.join(__dirname, '..', '..', '..', '..', `${DllFolder}`).replace(/\\/g, '\\\\') +} + +export const winURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}` : `file://${__dirname}/index.html` +export const loadingURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}/static/loader.html` : `file://${__static}/loader.html` +export const libPath = process.env.libPath diff --git a/src/main/config/const.js b/src/main/config/const.js new file mode 100644 index 0000000..a6790e8 --- /dev/null +++ b/src/main/config/const.js @@ -0,0 +1,7 @@ +export const UseStartupChart = true +export const IsUseSysTitle = false +export const BuiltInServerPort = 25565 +export const hotPublishUrl = "" +export const hotPublishConfigName = "update-config" +export const openDevTools = false +export const DisableF12 = true diff --git a/src/main/config/hotPublish.js b/src/main/config/hotPublish.js new file mode 100644 index 0000000..1007d67 --- /dev/null +++ b/src/main/config/hotPublish.js @@ -0,0 +1,6 @@ +import { hotPublishUrl, hotPublishConfigName } from './const' + +export const hotPublishConfig = { + url: hotPublishUrl, + configName: hotPublishConfigName +} \ No newline at end of file diff --git a/src/main/config/menu.js b/src/main/config/menu.js new file mode 100644 index 0000000..44572ed --- /dev/null +++ b/src/main/config/menu.js @@ -0,0 +1,68 @@ +// 这里是定义菜单的地方,详情请查看 https://electronjs.org/docs/api/menu +const { dialog } = require('electron') +const os = require('os') +const version = require('../../../package.json').version +const menu = [ + { + label: '开始', + submenu: [{ + label: '快速重启', + accelerator: 'F5', + role: 'reload' + }, { + label: '退出', + accelerator: 'CmdOrCtrl+F4', + role: 'close' + }] + }, + { + label: '编辑', + submenu: [{ + label: '撤销', + accelerator: 'CmdOrCtrl+Z', + role: 'undo' + }, + { + label: '重做', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo' + }, + { + label: '剪切', + accelerator: 'CmdOrCtrl+X', + role: 'cut' + }, + { + label: '复制', + accelerator: 'CmdOrCtrl+C', + role: 'copy' + }, + { + label: '粘贴', + accelerator: 'CmdOrCtrl+V', + role: 'paste' + } + ] + }, + + { + label: '帮助', + submenu: [{ + label: '关于', + role: 'about', + click: function () { + info() + } + }] + }] +function info() { + dialog.showMessageBox({ + title: '关于', + type: 'info', + message: '会员收银系统', + detail: `版本信息:${version}\n引擎版本:${process.versions.v8}\n当前系统:${os.type()} ${os.arch()} ${os.release()}`, + noLink: true, + buttons: ['查看官网', '确定'] + }) +} +export default menu diff --git a/src/main/index.js b/src/main/index.js new file mode 100644 index 0000000..be9c008 --- /dev/null +++ b/src/main/index.js @@ -0,0 +1,41 @@ +'use strict' + +import { app } from 'electron' +import initWindow from './services/windowManager' +import DisableButton from './config/DisableButton' +import electronDevtoolsInstaller, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' + +function onAppReady () { + initWindow() + DisableButton.Disablef12() + if (process.env.NODE_ENV === 'development') { + electronDevtoolsInstaller(VUEJS_DEVTOOLS) + .then((name) => console.log(`installed: ${name}`)) + .catch(err => console.log('Unable to install `vue-devtools`: \n', err)) + } +} +// 禁止程序多开,此处需要单例锁的同学打开注释即可 +const gotTheLock = app.requestSingleInstanceLock() +if(!gotTheLock){ + app.quit() +} +app.isReady() ? onAppReady() : app.on('ready', onAppReady) +// 解决9.x跨域异常问题 +app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') + +app.on('window-all-closed', () => { + // 所有平台均为所有窗口关闭就退出软件 + app.quit() +}) +app.on('browser-window-created', () => { + console.log('window-created') +}) + +if (process.defaultApp) { + if (process.argv.length >= 2) { + app.removeAsDefaultProtocolClient('electron-vue-template') + console.log('框架特殊性开发环境下无法使用') + } +} else { + app.setAsDefaultProtocolClient('electron-vue-template') +} diff --git a/src/main/server/index.js b/src/main/server/index.js new file mode 100644 index 0000000..072f414 --- /dev/null +++ b/src/main/server/index.js @@ -0,0 +1,44 @@ +/* eslint-disable prefer-promise-reject-errors */ +import app from './server' +import http from 'http' +import config from '@config' +const port = config.BuiltInServerPort +var server = null +app.set('port', port) + +export default { + StatrServer () { + return new Promise((resolve, reject) => { + server = http.createServer(app) + server.listen(port) + server.on('error', (error) => { + switch (error.code) { + case 'EACCES': + reject('权限不足内置服务器启动失败,请使用管理员权限运行。') + break + case 'EADDRINUSE': + reject('内置服务器端口已被占用,请检查。') + break + default: + reject(error) + } + }) + server.on('listening', () => { + resolve('服务端运行中') + }) + }) + }, + StopServer () { + return new Promise((resolve, reject) => { + if (server) { + server.close() + server.on('close', () => { + server = null + resolve(1) + }) + } else { + reject('服务端尚未开启') + } + }) + } +} diff --git a/src/main/server/server.js b/src/main/server/server.js new file mode 100644 index 0000000..96fa658 --- /dev/null +++ b/src/main/server/server.js @@ -0,0 +1,14 @@ +import express from 'express' +const app = express() + +app.get('/message', (req, res) => { + res.send('这是来自node服务端的信息') +}) + +app.post('/message', (req, res) => { + if (req) { + res.send(req + '--来自node') + } +}) + +export default app diff --git a/src/main/services/HotUpdater.js b/src/main/services/HotUpdater.js new file mode 100644 index 0000000..8c19d51 --- /dev/null +++ b/src/main/services/HotUpdater.js @@ -0,0 +1,91 @@ +/** + * power by biuuu + */ + +import { emptyDir, createWriteStream, readFile, copy, remove } from 'fs-extra' +import { join, resolve } from 'path' +import { promisify } from 'util' +import { pipeline } from 'stream' +import { app } from 'electron' +import { gt } from 'semver' +import { createHmac } from 'crypto' +import extract from 'extract-zip' +import { version } from '../../../package.json' +import { hotPublishConfig } from '../config/hotPublish' +import axios from 'axios' + +const streamPipeline = promisify(pipeline) +const appPath = app.getAppPath() +const updatePath = resolve(appPath, '..', '..', 'update') +const request = axios.create() + +/** + * @param data 文件流 + * @param type 类型,默认sha256 + * @param key 密钥,用于匹配计算结果 + * @returns {string} 计算结果 + * @author umbrella22 + * @date 2021-03-05 + */ +function hash(data, type = 'sha256', key = 'Sky') { + const hmac = createHmac(type, key) + hmac.update(data) + return hmac.digest('hex') +} + + +/** + * @param url 下载地址 + * @param filePath 文件存放地址 + * @returns {void} + * @author umbrella22 + * @date 2021-03-05 + */ +async function download(url, filePath) { + const res = await request({ url, responseType: "stream" }) + await streamPipeline(res.data, createWriteStream(filePath)) +} + +const updateInfo = { + status: 'init', + message: '' +} + +/** + * @param windows 指主窗口 + * @returns {void} + * @author umbrella22 + * @date 2021-03-05 + */ +export const updater = async (windows) => { + try { + const res = await request({ url: `${hotPublishConfig.url}/${hotPublishConfig.configName}.json?time=${new Date().getTime()}`, }) + if (gt(res.data.version, version)) { + await emptyDir(updatePath) + const filePath = join(updatePath, res.data.name) + updateInfo.status = 'downloading' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + await download(`${hotPublishConfig.url}/${res.data.name}`, filePath); + const buffer = await readFile(filePath) + const sha256 = hash(buffer) + if (sha256 !== res.data.hash) throw new Error('sha256 error') + const appPathTemp = join(updatePath, 'temp') + await extract(filePath, { dir: appPathTemp }) + updateInfo.status = 'moving' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + await remove(join(`${appPath}`, 'dist')); + await remove(join(`${appPath}`, 'package.json')); + await copy(appPathTemp, appPath) + updateInfo.status = 'finished' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + } + + + } catch (error) { + updateInfo.status = 'failed' + updateInfo.message = error + if (windows) windows.webContents.send('hot-update-status', updateInfo) + } +} + +export const getUpdateInfo = () => updateInfo \ No newline at end of file diff --git a/src/main/services/checkupdate.js b/src/main/services/checkupdate.js new file mode 100644 index 0000000..af7e059 --- /dev/null +++ b/src/main/services/checkupdate.js @@ -0,0 +1,76 @@ +import { autoUpdater } from 'electron-updater' +/** + * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成 + **/ +class Update { + mainWindow + constructor() { + autoUpdater.setFeedURL('https://mc.keyelement.cn/downloads/') + + // 当更新发生错误的时候触发。 + autoUpdater.on('error', (err) => { + console.log('更新出现错误', err.message) + if (err.message.includes('sha512 checksum mismatch')) { + this.Message(this.mainWindow, -1, 'sha512校验失败') + } else { + this.Message(this.mainWindow, -1, '错误信息请看主进程控制台') + + } + }) + + // 当开始检查更新的时候触发 + autoUpdater.on('checking-for-update', (event, arg) => { + console.log('开始检查更新') + this.Message(this.mainWindow, 0) + }) + + // 发现可更新数据时 + autoUpdater.on('update-available', (event, arg) => { + console.log('有更新') + this.Message(this.mainWindow, 1) + }) + + // 没有可更新数据时 + autoUpdater.on('update-not-available', (event, arg) => { + console.log('没有更新') + this.Message(this.mainWindow, 2) + }) + + // 下载监听 + autoUpdater.on('download-progress', (progressObj) => { + this.Message(this.mainWindow, 3, progressObj) + }) + + // 下载完成 + autoUpdater.on('update-downloaded', () => { + console.log('done') + this.Message(this.mainWindow, 4) + }) + } + // 负责向渲染进程发送信息 + Message(mainWindow, type, data) { + console.log('发送消息') + const senddata = { + state: type, + msg: data || '' + } + mainWindow.webContents.send('update-msg', senddata) + } + + + + // 执行自动更新检查 + checkUpdate(mainWindow) { + this.mainWindow = mainWindow + autoUpdater.checkForUpdates().catch(err => { + console.log('网络连接问题', err) + }) + } + + // 退出并安装 + quitInstall() { + autoUpdater.quitAndInstall() + } +} + +export default Update diff --git a/src/main/services/downloadFile.js b/src/main/services/downloadFile.js new file mode 100644 index 0000000..269773f --- /dev/null +++ b/src/main/services/downloadFile.js @@ -0,0 +1,63 @@ +/* eslint-disable no-case-declarations */ +import { app, dialog } from 'electron' +import path from 'path' +import os from 'os' +// 版本以package.json为基准。 +const version = require('../../../package.json').version +// 您的下载地址 +const baseUrl = 'http://127.0.0.1:25565/' +var Sysarch = null +var defaultDownloadUrL = null +// 识别操作系统位数D +os.arch().includes('64') ? Sysarch = 'win64' : Sysarch = 'win32' +// 识别操作系统 +// linux自己修改后缀名哦,我没有linux就没有测试了 +if (os.platform().includes('win32')) { + defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}.exe?${new Date().getTime()}` +} else if (os.platform().includes('linux')) { + defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}?${new Date().getTime()}` +} else { + defaultDownloadUrL = baseUrl + `electron_${version}_mac.dmg?${new Date().getTime()}` +} +export default { + download(mainWindow, downloadUrL) { + mainWindow.webContents.downloadURL(downloadUrL || defaultDownloadUrL) + mainWindow.webContents.session.on('will-download', (event, item, webContents) => { + // 将文件保存在系统的下载目录 + const filePath = path.join(app.getPath('downloads'), item.getFilename()) + // 自动保存 + item.setSavePath(filePath) + // 下载进度 + item.on('updated', (event, state) => { + switch (state) { + case 'progressing': + mainWindow.webContents.send('download-progress', (item.getReceivedBytes() / item.getTotalBytes() * 100).toFixed(0)) + break + case 'interrupted ': + mainWindow.webContents.send('download-paused', true) + break + default: + + break + } + }) + // 下载完成或失败 + item.once('done', (event, state) => { + switch (state) { + case 'completed': + const data = { + filePath + } + mainWindow.webContents.send('download-done', data) + break + case 'interrupted': + mainWindow.webContents.send('download-error', true) + dialog.showErrorBox('下载出错', '由于网络或其他未知原因导致下载出错.') + break + default: + break + } + }) + }) + } +} diff --git a/src/main/services/ipcMain.js b/src/main/services/ipcMain.js new file mode 100644 index 0000000..a3eb129 --- /dev/null +++ b/src/main/services/ipcMain.js @@ -0,0 +1,186 @@ +import { ipcMain, dialog, BrowserWindow } from 'electron' +import Server from '../server/index' +import { winURL } from '../config/StaticPath' +import downloadFile from './downloadFile' +import Update from './checkupdate' +import { updater } from './HotUpdater' + +export default { + Mainfunc(IsUseSysTitle) { + const allUpdater = new Update(); + ipcMain.handle('IsUseSysTitle', async () => { + return IsUseSysTitle + }) + ipcMain.handle('windows-mini', (event, args) => { + BrowserWindow.fromWebContents(event.sender)?.minimize() + }) + ipcMain.handle('window-max', async (event, args) => { + if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) { + BrowserWindow.fromWebContents(event.sender)?.unmaximize() + return { status: false } + } else { + BrowserWindow.fromWebContents(event.sender)?.maximize() + return { status: true } + } + }) + ipcMain.handle('window-close', (event, args) => { + BrowserWindow.fromWebContents(event.sender)?.close() + }) + ipcMain.handle('start-download', (event, msg) => { + downloadFile.download(BrowserWindow.fromWebContents(event.sender), msg.downloadUrL) + }) + ipcMain.handle('check-update', (event, args) => { + allUpdater.checkUpdate(BrowserWindow.fromWebContents(event.sender)) + }) + ipcMain.handle('confirm-update', () => { + allUpdater.quitInstall() + }) + ipcMain.handle('hot-update', (event, arg) => { + updater(BrowserWindow.fromWebContents(event.sender)) + }) + ipcMain.handle('open-messagebox', async (event, arg) => { + const res = await dialog.showMessageBox(BrowserWindow.fromWebContents(event.sender), { + type: arg.type || 'info', + title: arg.title || '', + buttons: arg.buttons || [], + message: arg.message || '', + noLink: arg.noLink || true + }) + return res + }) + ipcMain.handle('open-errorbox', (event, arg) => { + dialog.showErrorBox( + arg.title, + arg.message + ) + }) + ipcMain.handle('statr-server', async () => { + try { + const serveStatus = await Server.StatrServer() + return serveStatus + } catch (error) { + dialog.showErrorBox( + '错误', + error + ) + } + }) + ipcMain.handle('stop-server', async (event, arg) => { + try { + const serveStatus = await Server.StopServer() + return serveStatus + } catch (error) { + dialog.showErrorBox( + '错误', + error + ) + } + }) + let childWin = null; + let cidArray = []; + ipcMain.handle('open-win', (event, arg) => { + let cidJson = { id: null, url: '' } + let data = cidArray.filter((currentValue) => { + if (currentValue.url === arg.url) { + return currentValue + } + }) + if (data.length > 0) { + //获取当前窗口 + let currentWindow = BrowserWindow.fromId(data[0].id) + //聚焦窗口 + currentWindow.focus(); + } else { + //获取主窗口ID + let parentID = event.sender.id + //创建窗口 + childWin = new BrowserWindow({ + width: arg?.width || 842, + height: arg?.height || 595, + //width 和 height 将设置为 web 页面的尺寸(译注: 不包含边框), 这意味着窗口的实际尺寸将包括窗口边框的大小,稍微会大一点。 + useContentSize: true, + //自动隐藏菜单栏,除非按了Alt键。 + autoHideMenuBar: true, + //窗口大小是否可调整 + resizable: arg?.resizable ?? false, + //窗口的最小高度 + minWidth: arg?.minWidth || 842, + show: arg?.show ?? false, + //窗口透明度 + opacity: arg?.opacity || 1.0, + //当前窗口的父窗口ID + parent: parentID, + frame: IsUseSysTitle, + icon: '', + webPreferences: { + nodeIntegration: true, + webSecurity: true, + //使用webview标签 必须开启 + webviewTag: arg?.webview ?? false, + // 如果是开发模式可以使用devTools + devTools: process.env.NODE_ENV === 'development', + // 在macos中启用橡皮动画 + scrollBounce: process.platform === 'darwin', + // 临时修复打开新窗口报错 + contextIsolation: false + } + }) + childWin.loadURL(winURL + `#${arg.url}`) + cidJson.id = childWin?.id + cidJson.url = arg.url + cidArray.push(cidJson) + childWin.webContents.once('dom-ready', () => { + childWin.show() + childWin.webContents.send('send-data', arg.sendData) + if (arg.IsPay) { + // 检查支付时候自动关闭小窗口 + const testUrl = setInterval(() => { + const Url = childWin.webContents.getURL() + if (Url.includes(arg.PayUrl)) { + childWin.close() + } + }, 1200) + childWin.on('close', () => { + clearInterval(testUrl) + }) + } + }) + childWin.on('closed', () => { + childWin = null + let index = cidArray.indexOf(cidJson) + if (index > -1) { + cidArray.splice(index, 1); + } + }) + } + childWin.on('maximize', () => { + if (cidJson.id != null) { + BrowserWindow.fromId(cidJson.id).webContents.send("w-max", true) + } + }) + childWin.on('unmaximize', () => { + if (cidJson.id != null) { + BrowserWindow.fromId(cidJson.id).webContents.send("w-max", false) + } + }) + }) + + ipcMain.handle('getPrintList', async () => { + try { + const currentWindow = BrowserWindow.getFocusedWindow(); + const list = await currentWindow.webContents.getPrintersAsync() + return list + } catch (error) { + console.log('error', error) + } + }) + + ipcMain.on('handle_print', (e, { htmlText, deviceName }) => { + let printWindow = new BrowserWindow({ show: false, width: 1920, height: 1080, contextIsolation: false, enableRemoteModule: true, nodeIntegration: true, webSecurity: false }) + printWindow.loadURL('data:text/html,' + encodeURIComponent(htmlText)) + printWindow.webContents.print({ deviceName, silent: true }) + }) + + + } +} diff --git a/src/main/services/windowManager.js b/src/main/services/windowManager.js new file mode 100644 index 0000000..bbc5589 --- /dev/null +++ b/src/main/services/windowManager.js @@ -0,0 +1,103 @@ +import { BrowserWindow, Menu, app, globalShortcut } from 'electron' +import { platform } from "os" +import menuconfig from '../config/menu' +import { openDevTools, IsUseSysTitle, UseStartupChart } from '../config/const' +import setIpc from './ipcMain' +import { winURL, loadingURL } from '../config/StaticPath' + +var loadWindow = null +var mainWindow = null +setIpc.Mainfunc(IsUseSysTitle) + +function createMainWindow() { + /** + * Initial window options + */ + mainWindow = new BrowserWindow({ + height: 800, + useContentSize: true, + width: 1700, + color: "#ffffff", + backgroundColor: "#ffffff", + minWidth: 1000, + show: false, + frame: IsUseSysTitle, + icon: '', + titleBarStyle: platform().includes('win32') ? 'default' : 'hidden', + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + webSecurity: true, + // 如果是开发模式可以使用devTools + devTools: process.env.NODE_ENV === 'development' || openDevTools, + // devTools: true, + // 在macos中启用橡皮动画 + scrollBounce: process.platform === 'darwin' + } + }) + + // 开发工具快捷键 + globalShortcut.register('ctrl+shift+i', () => { + const foucsedWin = BrowserWindow.getFocusedWindow() + if (foucsedWin) { + foucsedWin.webContents.openDevTools() + } + }) + + // 载入菜单 + const menu = Menu.buildFromTemplate(menuconfig) + Menu.setApplicationMenu(menu) + mainWindow.loadURL(winURL) + + mainWindow.webContents.once('dom-ready', () => { + mainWindow.show() + if (process.env.NODE_ENV === 'development' || openDevTools) mainWindow.webContents.openDevTools(true) + if (UseStartupChart) loadWindow.destroy() + }) + mainWindow.on('maximize', () => { + mainWindow.webContents.send("w-max", true) + }) + mainWindow.on('unmaximize', () => { + mainWindow.webContents.send("w-max", false) + }) + mainWindow.on('closed', () => { + mainWindow = null + app.quit(); + }) +} + +function loadingWindow() { + loadWindow = new BrowserWindow({ + width: 400, + height: 600, + frame: false, + backgroundColor: '#fff', + color: '#fff', + skipTaskbar: true, + transparent: true, + resizable: false, + icon: '', + webPreferences: { experimentalFeatures: true } + }) + + loadWindow.loadURL(loadingURL) + + loadWindow.show() + + setTimeout(() => { + createMainWindow() + }, 2000) + + loadWindow.on('closed', () => { + loadWindow = null + }) +} + +function initWindow() { + if (UseStartupChart) { + return loadingWindow() + } else { + return createMainWindow() + } +} +export default initWindow diff --git a/src/renderer/App.vue b/src/renderer/App.vue new file mode 100644 index 0000000..bf81a4f --- /dev/null +++ b/src/renderer/App.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/src/renderer/api/balance.js b/src/renderer/api/balance.js new file mode 100644 index 0000000..e129508 --- /dev/null +++ b/src/renderer/api/balance.js @@ -0,0 +1,58 @@ +import request from '@/utils/request' + +// 分页查询余额明细列表 +export function getBalanceList(query) { + return request({ + url: 'backendApi/balance/list', + method: 'get', + params: query + }) +} + +// 查询明细详情 +export function getBalanceInfo(memberId) { + return request({ + url: 'backendApi/balance/info/' + memberId, + method: 'get' + }) +} + +// 更新状态 +export function updateBalanceStatus(id, status) { + const data = { + id, + status + } + return request({ + url: 'backendApi/balance/updateStatus', + method: 'post', + data: data + }) +} + +// 获取配置信息 +export function getSettingInfo() { + return request({ + url: 'backendApi/balance/setting', + method: 'get' + }) +} + +// 保存配置 +export function saveSetting(data) { + return request({ + url: 'backendApi/balance/saveSetting', + method: 'post', + data: data + }) +} + +// 确定充值 +export function doRecharge(data) { + return request({ + url: 'backendApi/balance/doRecharge', + method: 'post', + data: data + }) +} + diff --git a/src/renderer/api/cashier.js b/src/renderer/api/cashier.js new file mode 100644 index 0000000..0f4cea2 --- /dev/null +++ b/src/renderer/api/cashier.js @@ -0,0 +1,133 @@ +import request from '@/utils/request' +import exp from 'constants' + +// 初始化数据 +export function init(userId, cateId, page, pageSize) { + return request({ + url: 'backendApi/cashier/init/' + userId, + method: 'get', + params: { cateId: cateId, page: page, pageSize: pageSize } + }) +} + +// 查询商品详情 +export function getGoodsInfo(goodsId) { + return request({ + url: 'backendApi/cashier/getGoodsInfo/' + goodsId, + method: 'get' + }) +} + +// 查询商品 +export function searchGoods(data) { + return request({ + url: 'backendApi/cashier/searchGoods', + method: 'post', + data: data + }) +} + +// 查询会员信息 +export function getMemberInfo(data) { + return request({ + url: 'backendApi/cashier/getMemberInfo', + method: 'post', + data: data + }) +} + +// 查询会员信息 +export function getMemberInfoById(userId) { + return request({ + url: 'backendApi/cashier/getMemberInfoById/' + userId, + method: 'get' + }) +} + +// 获取购物车列表 +export function getCartList(data) { + return request({ + url: 'clientApi/cart/list', + method: 'post', + data: data + }) +} + +// 保存购物车 +export function saveCart(data) { + return request({ + url: 'clientApi/cart/save', + method: 'post', + data: data + }) +} + +// 删除购物车 +export function removeFromCart(data) { + return request({ + url: 'clientApi/cart/clear', + method: 'post', + data: data + }) +} + +// 提交结算 +export function submitSettlement(data) { + return request({ + url: 'clientApi/settlement/submit', + method: 'post', + data: data + }) +} + +// 发起支付 +export function doPay(params) { + return request({ + url: 'clientApi/pay/doPay', + method: 'get', + params: params + }) +} + +// 获取订单列表 +export function getOrderList(data) { + return request({ + url: 'backendApi/order/latest', + method: 'post', + data: data + }) +} + +// 执行挂单 +export function doHangUp(data) { + return request({ + url: 'backendApi/cashier/doHangUp', + method: 'post', + data: data + }) +} + +// 获取挂单 +export function getHangUpList() { + return request({ + url: 'backendApi/cashier/getHangUpList', + method: 'get' + }) +} + +// 删除挂单 +export function removeHangUp(data) { + return request({ + url: 'clientApi/cart/clear', + method: 'post', + data: data + }) +} + +// 获取用户资源信息 +export function asset(userId){ + return request({ + url: 'clientApi/user/asset/'+userId, + method: 'get' + }) +} diff --git a/src/renderer/api/coupon.js b/src/renderer/api/coupon.js new file mode 100644 index 0000000..6c6e9d4 --- /dev/null +++ b/src/renderer/api/coupon.js @@ -0,0 +1,75 @@ +import request from '@/utils/request' + +// 分页查询卡券列表 +export function getCouponList(query) { + return request({ + url: 'backendApi/coupon/list', + method: 'get', + params: query + }) +} + +// 查询卡券信息 +export function getCouponInfo(id) { + return request({ + url: 'backendApi/coupon/info/' + id, + method: 'get' + }) +} + +// 更新状态 +export function updateCouponStatus(id, status) { + const data = { + id, + status + } + return request({ + url: 'backendApi/coupon/updateStatus', + method: 'post', + data: data + }) +} + +// 删除卡券 +export function deleteCoupon(id) { + return request({ + url: 'backendApi/coupon/delete/' + id, + method: 'get' + }) +} + +// 保存卡券 +export function saveCoupon(data) { + return request({ + url: 'backendApi/coupon/save', + method: 'post', + data: data + }) +} + +// 查询卡券核销信息 +export function getConfirmInfo(data) { + return request({ + url: 'backendApi/doConfirm/info', + method: 'post', + data: data + }) +} + +// 执行核销 +export function doConfirm(data) { + return request({ + url: 'backendApi/doConfirm/doConfirm', + method: 'post', + data: data + }) +} + +// 发放卡券 +export function sendCoupon(params) { + return request({ + url: 'backendApi/coupon/sendCoupon', + method: 'get', + params: params + }) +} diff --git a/src/renderer/api/goods.js b/src/renderer/api/goods.js new file mode 100644 index 0000000..3b7836c --- /dev/null +++ b/src/renderer/api/goods.js @@ -0,0 +1,76 @@ +import request from '@/utils/request' + +// 分页查询商品列表 +export function getGoodsList(query) { + return request({ + url: 'backendApi/goods/goods/list', + method: 'get', + params: query + }) +} + +// 查询商品详情 +export function getGoodsInfo(goodsId) { + return request({ + url: 'backendApi/goods/goods/info/' + goodsId, + method: 'get' + }) +} + +// 更新状态 +export function updateGoodsStatus(id, status) { + const data = { + id, + status + } + return request({ + url: 'backendApi/goods/goods/updateStatus', + method: 'post', + data: data + }) +} + +// 保存分类数据 +export function saveGoods(data) { + return request({ + url: 'backendApi/goods/goods/save', + method: 'post', + data: data + }) +} + +// 保存商品规格名称 +export function saveSpecName(data) { + return request({ + url: 'backendApi/goods/goods/saveSpecName', + method: 'post', + data: data + }) +} + +// 保存商品规格值 +export function saveSpecValue(data) { + return request({ + url: 'backendApi/goods/goods/saveSpecValue', + method: 'post', + data: data + }) +} + +// 删除商品规格 +export function deleteSpec(query) { + return request({ + url: 'backendApi/goods/goods/deleteSpec', + method: 'get', + params: query + }) +} + +// 删除商品规格值 +export function deleteSpecValue(query) { + return request({ + url: 'backendApi/goods/goods/deleteSpecValue', + method: 'get', + params: query + }) +} diff --git a/src/renderer/api/location.js b/src/renderer/api/location.js new file mode 100644 index 0000000..cc09a16 --- /dev/null +++ b/src/renderer/api/location.js @@ -0,0 +1,14 @@ +import request from '@/utils/request' + +// 分页查询 +export function getStoreLocationList() { + return request({ + url: 'backendApi/storeLocation/list', + method: 'get', + params: { + page: 1, + pageSize: 100, + status: 'A' + } + }) +} \ No newline at end of file diff --git a/src/renderer/api/login.js b/src/renderer/api/login.js new file mode 100644 index 0000000..2555a07 --- /dev/null +++ b/src/renderer/api/login.js @@ -0,0 +1,51 @@ +import request from '@/utils/request' + +// 登录方法 +export function login(username, password, captchaCode, uuid) { + const data = { + username, + password, + captchaCode, + uuid + } + return request({ + url: 'backendApi/login/doClientLogin', + headers: { + isToken: false + }, + method: 'post', + data: data + }) +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: 'backendApi/login/getInfo', + method: 'get' + }) +} + +// 退出方法 +export function logout() { + return request({ + url: 'backendApi/login/logout', + method: 'post' + }) +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: 'clientApi/captcha/getCode', + method: 'get' + }) +} + +// 系统消息 +export function message () { + return request({ + url: 'clientApi/captcha/message', + method: 'get' + }) +} diff --git a/src/renderer/api/member.js b/src/renderer/api/member.js new file mode 100644 index 0000000..fb5e6cf --- /dev/null +++ b/src/renderer/api/member.js @@ -0,0 +1,75 @@ +import request from '@/utils/request' + +// 分页查询会员列表 +export function getMemberList(query) { + return request({ + url: 'backendApi/member/list', + method: 'get', + params: query + }) +} + +// 查询会员信息 +export function getMemberInfo(memberId) { + return request({ + url: 'backendApi/member/info/' + memberId, + method: 'get' + }) +} + +// 查询会员设置 +export function getMemberSetting() { + return request({ + url: 'backendApi/member/setting', + method: 'get' + }) +} + +// 保存会员设置 +export function saveSetting(data) { + return request({ + url: 'backendApi/member/saveSetting', + method: 'post', + data: data + }) +} + +// 更新会员状态 +export function updateMemberStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: 'backendApi/member/updateStatus', + method: 'post', + data: data + }) +} + +// 删除会员信息 +export function deleteMember(memberId) { + return request({ + url: 'backendApi/member/delete/' + memberId, + method: 'get' + }) +} + +// 保存数据 +export function saveMember(data) { + return request({ + url: 'backendApi/member/save', + method: 'post', + data: data + }) +} + +// 验证用户密码 +export function checkPwd(userId, password){ + return request({ + url: 'backendApi/member/checkPwd', + method: 'post', + data: {userId, password} + }) +} + diff --git a/src/renderer/api/order.js b/src/renderer/api/order.js new file mode 100644 index 0000000..4ab73ab --- /dev/null +++ b/src/renderer/api/order.js @@ -0,0 +1,83 @@ +import request from '@/utils/request' + +// 分页查询订单列表 +export function getOrderList(data) { + return request({ + url: 'backendApi/order/list', + method: 'post', + data: data + }) +} + +// 查询订单信息 +export function getOrderInfo(orderId) { + return request({ + url: 'backendApi/order/info/' + orderId, + method: 'get' + }) +} + +// 更新订单状态 +export function updateOrderStatus(orderId, status) { + const data = { + orderId, + status + } + return request({ + url: 'backendApi/order/updateStatus', + method: 'post', + data: data + }) +} + +// 删除订单 +export function deleteOrder(orderId) { + return request({ + url: 'backendApi/order/delete/' + orderId, + method: 'get' + }) +} + +// 保存订单数据 +export function saveOrder(data) { + return request({ + url: 'backendApi/order/save', + method: 'post', + data: data + }) +} + +// 验证核销订单 +export function verifyOrder(data) { + return request({ + url: 'backendApi/order/verify', + method: 'post', + data: data + }) +} + +// 提交发货信息 +export function delivered(data) { + return request({ + url: 'backendApi/order/delivered', + method: 'post', + data: data + }) +} + +// 获取配置信息 +export function getSettingInfo() { + return request({ + url: 'backendApi/order/setting', + method: 'get' + }) +} + +// 保存配置 +export function saveSetting(data) { + return request({ + url: 'backendApi/order/saveSetting', + method: 'post', + data: data + }) +} diff --git a/src/renderer/api/point.js b/src/renderer/api/point.js new file mode 100644 index 0000000..9427b6a --- /dev/null +++ b/src/renderer/api/point.js @@ -0,0 +1,58 @@ +import request from '@/utils/request' + +// 分页查询积分明细列表 +export function getPointList(query) { + return request({ + url: 'backendApi/point/list', + method: 'get', + params: query + }) +} + +// 查询明细详情 +export function getPointInfo(memberId) { + return request({ + url: 'backendApi/point/info/' + memberId, + method: 'get' + }) +} + +// 更新状态 +export function updatePointStatus(id, status) { + const data = { + id, + status + } + return request({ + url: 'backendApi/point/updateStatus', + method: 'post', + data: data + }) +} + +// 获取配置信息 +export function getSettingInfo() { + return request({ + url: 'backendApi/point/setting', + method: 'get' + }) +} + +// 保存配置 +export function saveSetting(data) { + return request({ + url: 'backendApi/point/saveSetting', + method: 'post', + data: data + }) +} + +// 确定充值 +export function doRecharge(data) { + return request({ + url: 'backendApi/point/doRecharge', + method: 'post', + data: data + }) +} + diff --git a/src/renderer/api/refund.js b/src/renderer/api/refund.js new file mode 100644 index 0000000..7206213 --- /dev/null +++ b/src/renderer/api/refund.js @@ -0,0 +1,53 @@ +import request from '@/utils/request' + +// 分页查询退款订单列表 +export function getRefundList(query) { + return request({ + url: 'backendApi/refund/list', + method: 'get', + params: query + }) +} + +// 查询订单信息 +export function getRefundInfo(refundId) { + return request({ + url: 'backendApi/refund/info/' + refundId, + method: 'get' + }) +} + +// 订单退款 +export function doRefund(data) { + return request({ + url: 'backendApi/refund/doRefund', + method: 'post', + data: data + }) +} + +// 删除退款订单 +export function deleteRefund(refundId) { + return request({ + url: 'backendApi/refund/delete/' + refundId, + method: 'get' + }) +} + +// 保存退款订单 +export function saveRefund(data) { + return request({ + url: 'backendApi/refund/save', + method: 'post', + data: data + }) +} + +// 订单预付金中补扣 +export function doDeduction(data) { + return request({ + url: 'backendApi/refund/doDeduction', + method: 'post', + data: data + }) +} \ No newline at end of file diff --git a/src/renderer/api/sms.js b/src/renderer/api/sms.js new file mode 100644 index 0000000..c1e4e97 --- /dev/null +++ b/src/renderer/api/sms.js @@ -0,0 +1,26 @@ +import request from '@/utils/request' + +// 发送用户结算验证码 +export function sendSettlementCode(merchantId,mobile, amount) { + return request({ + url: 'clientApi/sms/sendSCode', + method: 'post', + data:{ + merchantId, + mobile, + amount + } + }) + } + + // 验证用户结算验证码 + export function checkSettlementCode(mobile,code) { + return request({ + url: 'clientApi/sms/checkSCode', + method: 'post', + data:{ + mobile, + code + } + }) + } \ No newline at end of file diff --git a/src/renderer/assets/.gitkeep b/src/renderer/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/assets/404_images/404.png b/src/renderer/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/renderer/assets/404_images/404.png differ diff --git a/src/renderer/assets/404_images/404_cloud.png b/src/renderer/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/renderer/assets/404_images/404_cloud.png differ diff --git a/src/renderer/assets/images/avatar.png b/src/renderer/assets/images/avatar.png new file mode 100644 index 0000000..cc951fb Binary files /dev/null and b/src/renderer/assets/images/avatar.png differ diff --git a/src/renderer/assets/images/bg.png b/src/renderer/assets/images/bg.png new file mode 100644 index 0000000..9f1e494 Binary files /dev/null and b/src/renderer/assets/images/bg.png differ diff --git a/src/renderer/assets/images/cashier.png b/src/renderer/assets/images/cashier.png new file mode 100644 index 0000000..2f45e4a Binary files /dev/null and b/src/renderer/assets/images/cashier.png differ diff --git a/src/renderer/assets/images/goods.png b/src/renderer/assets/images/goods.png new file mode 100644 index 0000000..e226ba7 Binary files /dev/null and b/src/renderer/assets/images/goods.png differ diff --git a/src/renderer/assets/images/hot.png b/src/renderer/assets/images/hot.png new file mode 100644 index 0000000..164072f Binary files /dev/null and b/src/renderer/assets/images/hot.png differ diff --git a/src/renderer/assets/images/life.png b/src/renderer/assets/images/life.png new file mode 100644 index 0000000..1d04c05 Binary files /dev/null and b/src/renderer/assets/images/life.png differ diff --git a/src/renderer/assets/images/love.png b/src/renderer/assets/images/love.png new file mode 100644 index 0000000..6de11fe Binary files /dev/null and b/src/renderer/assets/images/love.png differ diff --git a/src/renderer/assets/images/noGoods.png b/src/renderer/assets/images/noGoods.png new file mode 100644 index 0000000..59ce921 Binary files /dev/null and b/src/renderer/assets/images/noGoods.png differ diff --git a/src/renderer/assets/images/office.png b/src/renderer/assets/images/office.png new file mode 100644 index 0000000..d569f0d Binary files /dev/null and b/src/renderer/assets/images/office.png differ diff --git a/src/renderer/assets/images/order.png b/src/renderer/assets/images/order.png new file mode 100644 index 0000000..9de8744 Binary files /dev/null and b/src/renderer/assets/images/order.png differ diff --git a/src/renderer/assets/logo.png b/src/renderer/assets/logo.png new file mode 100644 index 0000000..5ba8c4d Binary files /dev/null and b/src/renderer/assets/logo.png differ diff --git a/src/renderer/assets/user.png b/src/renderer/assets/user.png new file mode 100644 index 0000000..ecd7a83 Binary files /dev/null and b/src/renderer/assets/user.png differ diff --git a/src/renderer/components/Breadcrumb/index.vue b/src/renderer/components/Breadcrumb/index.vue new file mode 100644 index 0000000..264ad9a --- /dev/null +++ b/src/renderer/components/Breadcrumb/index.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/renderer/components/Hamburger/index.vue b/src/renderer/components/Hamburger/index.vue new file mode 100644 index 0000000..011815d --- /dev/null +++ b/src/renderer/components/Hamburger/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/renderer/components/LandingPage.vue b/src/renderer/components/LandingPage.vue new file mode 100644 index 0000000..3beffaf --- /dev/null +++ b/src/renderer/components/LandingPage.vue @@ -0,0 +1,340 @@ + + + + + diff --git a/src/renderer/components/LandingPage/SystemInformation.vue b/src/renderer/components/LandingPage/SystemInformation.vue new file mode 100644 index 0000000..5945186 --- /dev/null +++ b/src/renderer/components/LandingPage/SystemInformation.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/renderer/components/Pagination/index.vue b/src/renderer/components/Pagination/index.vue new file mode 100644 index 0000000..56f5a6b --- /dev/null +++ b/src/renderer/components/Pagination/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/renderer/components/ScrollBar/index.vue b/src/renderer/components/ScrollBar/index.vue new file mode 100644 index 0000000..dc32f5c --- /dev/null +++ b/src/renderer/components/ScrollBar/index.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/renderer/components/SvgIcon/index.vue b/src/renderer/components/SvgIcon/index.vue new file mode 100644 index 0000000..e331a27 --- /dev/null +++ b/src/renderer/components/SvgIcon/index.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/renderer/components/Tinymce/components/EditorImage.vue b/src/renderer/components/Tinymce/components/EditorImage.vue new file mode 100644 index 0000000..1eff157 --- /dev/null +++ b/src/renderer/components/Tinymce/components/EditorImage.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/renderer/components/Tinymce/dynamicLoadScript.js b/src/renderer/components/Tinymce/dynamicLoadScript.js new file mode 100644 index 0000000..cb15aa6 --- /dev/null +++ b/src/renderer/components/Tinymce/dynamicLoadScript.js @@ -0,0 +1,60 @@ +let callbacks = [] + +function loadedTinymce () { + // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144 + // check is successfully downloaded script + return window.tinymce +} + +const dynamicLoadScript = (src, callback) => { + const existingScript = document.getElementById(src) + const cb = callback || function () {} + + if (!existingScript) { + const script = document.createElement('script') + console.log(src) + script.src = src // src url for the third-party library being loaded. + script.id = src + document.body.appendChild(script) + callbacks.push(cb) + const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd + onEnd(script) + } + + if (existingScript && cb) { + if (loadedTinymce()) { + cb(null, existingScript) + } else { + callbacks.push(cb) + } + } + + function stdOnEnd (script) { + script.onload = function () { + // this.onload = null here is necessary + // because even IE9 works not like others + this.onerror = this.onload = null + for (const cb of callbacks) { + cb(null, script) + } + callbacks = null + } + script.onerror = function () { + this.onerror = this.onload = null + cb(new Error('无法加载 ' + src), script) + } + } + + function ieOnEnd (script) { + script.onreadystatechange = function () { + if (this.readyState !== 'complete' && this.readyState !== 'loaded') return + this.onreadystatechange = null + for (const cb of callbacks) { + cb(null, script) // there is no way to catch loading errors in IE8 + } + callbacks = null + } + } +} + +export default dynamicLoadScript diff --git a/src/renderer/components/Tinymce/index.vue b/src/renderer/components/Tinymce/index.vue new file mode 100644 index 0000000..8841d2e --- /dev/null +++ b/src/renderer/components/Tinymce/index.vue @@ -0,0 +1,223 @@ +