This commit is contained in:
砂糖
2026-02-07 18:01:13 +08:00
commit 8015759c65
2110 changed files with 269866 additions and 0 deletions

95
fuintCashier/.babelrc Normal file
View File

@@ -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"
]
}

View File

@@ -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()
}

View File

@@ -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 <AtomNSWindow: 0x7fb75f68a770>
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()

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
test/unit/coverage/**
test/unit/*.js
test/e2e/*.js

27
fuintCashier/.eslintrc.js Normal file
View File

@@ -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
}
}

View File

@@ -0,0 +1,29 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Build TEST
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
node-version: [ 16.x, 18.x ]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn --frozen-lockfile
- run: yarn build

18
fuintCashier/.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
.DS_Store
node_modules/
/dist/
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
server/client/fuintCashier Setup 0.0.1.exe
build/builder-effective-config.yaml

View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {},
}
}

247
fuintCashier/CHANGELOG.md Normal file
View File

@@ -0,0 +1,247 @@
# 更新日志
# 2023 年 03 月 07 日
- 修复 pinia加载问题
# 2022 年 12 月 29 日
- 清理无用的 config 注入,现在环境变量将不再依赖于 `config` 文件,转而使用 env 文件夹中的 `.env` 在改文件内的所有内容均会注入到 `process.env.userConfig` 主进程和渲染进程同名
# 2022 年 06 月 10 日
- 当需要使用 jsx 时,关闭 esbuild
## 2021 年 08 月 26 日
- 添加对 jsx 的支持
## 2021 年 7 月 31 日
- 将 webpack 更新为 5安装对应的 babel在打包成 web 时,使用 babel 进行代码垫片设置。
## 2021 年 06 月 24 日
- `i18n` 新增对 `element-ui` 的支持
> 为保证 `element-ui` 正确国际化,请确保 `src/renderer/i18n/languages` 文件夹下的语言包名与 `element-ui` 的语言包名一致
## 2021 年 06 月 23 日
- 新增 [`i18n`](https://kazupon.github.io/vue-i18n/zh/) 支持
### 如何使用?
语言包存放在 `src/renderer/i18n/languages` 文件夹下,每种语言一个文件,新增语言包也无需手动引入
## 2021 年 06 月 22 日
- 尝试修复 mac 环境下窗口无法拖动问题
## 2021 年 09 月 01 日 9 月?)
- 双分支添加 TERGET_ENV 标识,用于区分区分当前工作环境,如 test or alphabetagamma通过在`node .electron-vue/build.js`前添加`cross-env TERGET_ENV=test`,即可启用,相应的您应当在`config文件夹以及其index.js`文件中按照`development`对象写好相同的代码方可在主/渲染进程中使用`process.env.TERGET_ENV`来取得您的设置项。
## 2021 年 04 月 13 日
- **master 剔除了 babel 转使用 esbuild 对 js 和 css 进行压缩操作。**
## 2021 年 03 月 03 日
- 更新 electron 版本到 12noLayOut 分支)
## 2021 年 02 月 26 日
- 添加 vite 版本,但并不合并到当前库中,请[前往对应库查看](https://gitee.com/Zh-Sky/electron-vite-template)
## 2021 年 02 月 08 日
- noLayOut 分支更新 webpack 版本到 5并分别对所属依赖进行更新在稍后的日子里noLayOut 分支将跟进 vue3感兴趣的小伙伴可以切换分支
## 2020 年 10 月 12 日
- 例行更新基础依赖,感谢 @jiumengs 贡献代码,修正删除数据库文件时,会发生多次监听。
## 2020 年 09 月 10 日
- 例行更新依赖,感谢 @BelinChung 贡献代码,修正 bug。
## 2020 年 04 月 30 日
- 添加内置服务端关闭方法,进一步简化登录流程;多窗口文档已就绪,服务端说明已补充。
## 2020 年 04 月 29 日
- 添加了路由多窗口示例,修复 web 打包,提升依赖;文档还未就绪
## 2020 年 02 月 09 日
- 添加[中文在线文档](https://umbrella22.github.io/electron-vue-template-doc/)[国内访问地址](https://zh-sky.gitee.io/electron-vue-template-doc/)
- 剔除 win 打包依赖,因为太大了,将它放到码云的额外仓库中,[地址](https://gitee.com/Zh-Sky/HardToDownloadLib)
## 2020 年 02 月 06 日
- 激进分支更新至 8.0.0.
## 2020 年 01 月 09 日
- 例行更新依赖,在 dev 中加入了端口监听检测,如果 9080 端口被占用就会自动向后开启一个端口号并使用,同时在 config/index.js 的 dev 中加入了端口设置,可以快捷设置端口号而不用去更改 webpack 的配置了。
## 2019 年 12 月 18 日
- 我在 build 文件夹内添加了 windows 的打包依赖,在打包爆错的话,可以尝试使用/build/lib 内的压缩包,记得看使用说明哦~
## 2019 年 11 月 22 日
- 得益于群里老哥的提醒,通过修改系统环境变量得到了通过 yarn 下载 electron 失败的问题,具体操作如下:
- 用户环境变量中新增两个个变量,一个是变量名为`ELECTRON_MIRROR`,变量值为`https://npm.taobao.org/mirrors/electron/`,另一个是变量名为`registry`,变量值为`https://registry.npm.taobao.org/`,然后系统变量中同样也加上这两个值,完成之后,删除 node_module 文件夹。然后执行 yarn install如果还是提示未安装那就去 electron 文件夹内执行一次 yarn install就好了。这样的话不仅仅只是 yarn 更快了electron 的 rebuild 也会加速很多。所以推荐使用 yarn。
- (优先尝试)使用 npm config edit 打开 npm 配置文件,添加上 electron_mirror=https://cdn.npm.taobao.org/dist/electron/ ,然后重启窗口删除 node_module 文件夹,重新安装依赖即可。
## 2019 年 11 月 19 日
- 更新了不使用 updater 进行全量更新的方法,但是该方法不会校验安装包 md5 值,也就是说,包如果被拦截了。。可能就会出问题,这一点我正在想办法处理。
## 2019 年 10 月 31 日
- 升级 electron 版本至 7但是需要做一些修改由于淘宝的问题导致 electron 新的下载器出现故障,故我们需要对 electron 的下载器做一些更改,这非常容易,不用担心:
- 首先我们在淘宝代理设置下,安装完成依赖,此时是报错的,现在进入项目的 node_modules 文件夹内找到 electron,点击进入,然后修改其中的 package.json 文件,修改 dependencies 对象中的依赖为:
```json
"dependencies": {
"@types/node": "^12.0.12",
"extract-zip": "^1.0.3",
"electron-download": "^4.1.0"
},
```
- - 然后我们需要再修改 install.js 中的代码(实际就是 6 中的 install 代码)
```js
#!/usr/bin/env node
var version = require("./package").version;
var fs = require("fs");
var os = require("os");
var path = require("path");
var extract = require("extract-zip");
var download = require("electron-download");
var installedVersion = null;
try {
installedVersion = fs
.readFileSync(path.join(__dirname, "dist", "version"), "utf-8")
.replace(/^v/, "");
} catch (ignored) {
// do nothing
}
if (process.env.ELECTRON_SKIP_BINARY_DOWNLOAD) {
process.exit(0);
}
var platformPath = getPlatformPath();
var electronPath =
process.env.ELECTRON_OVERRIDE_DIST_PATH ||
path.join(__dirname, "dist", platformPath);
if (installedVersion === version && fs.existsSync(electronPath)) {
process.exit(0);
}
// downloads if not cached
download(
{
cache: process.env.electron_config_cache,
version: version,
platform: process.env.npm_config_platform,
arch: process.env.npm_config_arch,
strictSSL: process.env.npm_config_strict_ssl === "true",
force: process.env.force_no_cache === "true",
quiet: process.env.npm_config_loglevel === "silent" || process.env.CI,
},
extractFile
);
// unzips and makes path.txt point at the correct executable
function extractFile(err, zipPath) {
if (err) return onerror(err);
extract(zipPath, { dir: path.join(__dirname, "dist") }, function (err) {
if (err) return onerror(err);
fs.writeFile(
path.join(__dirname, "path.txt"),
platformPath,
function (err) {
if (err) return onerror(err);
}
);
});
}
function onerror(err) {
throw err;
}
function getPlatformPath() {
var platform = process.env.npm_config_platform || os.platform();
switch (platform) {
case "darwin":
return "Electron.app/Contents/MacOS/Electron";
case "freebsd":
case "linux":
return "electron";
case "win32":
return "electron.exe";
default:
throw new Error(
"Electron builds are not available on platform: " + platform
);
}
}
```
- - 然后执行 npm i 即可完成安装,至于打包的话,您可能需要去淘宝镜像手动下载并且放好位置,才能完成打包操作,不然依旧还是报下载错误的信息。
## 2019 年 10 月 18 日
- 不知不觉中倒也过去了一个月,啊哈哈这次更新给大家带来的是 updater 的示例,这依旧是个实验特性,所以在新分支中才可以使用,使用方式则是,安装依赖,
- 运行 `npm run update:serve` 来启动这个 node 服务器,然后您如果想在 dev 的时候就看到效果需要先运行 build 拿到 `latest.yml`文件,然后将其更名为 `dev-app-update.yml` 放入`dist/electron`中,和`main.js`同级,然后你需要关闭或者排除 webpack 的自动清除插件(我已经屏蔽了,所以无需大家自己动手),然后点击软件中的检查更新即可,记住当软件正在运行的时候,是无法应用安装的,所以您需要关闭之后方可安装。这并不是一个错误!
## 2019 年 09 月 18 日
- 修正生产环境时,没有正确去除控制台输出的问题,双分支例行更新依赖,修正 ui 部分颜色问题,日后准备使用 element 主题功能
## 2019 年 09 月 16 日
- 去除 easymock直接粗暴更改登陆验证如有需要请自行修改例行更新新分支依赖修正当自定义头部和系统头部互换时布局不会做出相应变化的问题。
## 2019 年 09 月 03 日
- 修正了当 nodejs >= 12 时,出现 process 未定义的问题,新分支加入自定义头部,现在我们可以做出更 cooool~~的效果了。
## 2019 年 08 月 20 日
- 添加登录拦击,实现登录功能,在 dev 中加入关闭 ELECTRON 无用控制台输出,新分支例行更新依赖,加入生产环境屏蔽 f12 按键。
## 2019 年 08 月 13 日
- 将新分支的所有依赖均更新至最新但是我觉得babel 似乎有些东西不需要,还是保留着吧,日后测试后移除)依赖更新之后通过打包和 dev 测试
## 2019 年 08 月 12 日
- 添加一个新分支,该新分支后续将会持续保持 ELECTRON包括其对应的辅助组件的版本处于最新状态去除了单元测试和一些无用的文件。master 分支中则是为路由添加新参数具体
- 用途,详看路由中的注释
## 2019 年 08 月 10 日
- 添加各个平台的 build 脚本,当您直接使用 build 时则会打包您当前操作系统对应的安装包mac 需要在 macos 上才能进行打包,而 linux 打包 win 的话,需要 wine 的支持,否则会失败
## 2019 年 08 月 04 日
- 修正原 webpack 配置中没有将 config 注入的小问题,添加了拦截实例,修改了侧栏,侧栏需要底色的请勿更新,此更新可能会导致侧栏底色无法完全覆盖(待修正),添加 axios 接口示例,待测。
## 2019 年 08 月 01 日
- 将 node-sass 版本更新至最新版本,尝试修正由于 nodejs 环境是 12 版导致失败(注意!此次更新可能会导致 32 位系统或者 nodejs 版本低于 10 的用户安装依赖报错)去除路由表中重复路由,解决控制台无端报错问题。

21
fuintCashier/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Modified work Copyright (c) 2019-present umbrella22
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

80
fuintCashier/README.md Normal file
View File

@@ -0,0 +1,80 @@
# Electron-Vue-template
> This is a project based on electron-vue, using the elementUI, vuex, vue-router, axios technology stack. This project is divided into two branches. The master maintains the original project structure and features for dependency updates, and the SynchronizedUpdates branch. It keeps the original file structure and the code part is completely customized by me personally; it includes: a complete background management interface, nedb database package, and two complete update download methods. If you feel redundant, you can delete it yourself.
[中文在线文档](https://umbrella22.github.io/electron-vue-template-doc/)
[国内访问地址](https://gitee.com/Zh-Sky/electron-vue-template)
[国内文档访问地址](https://zh-sky.gitee.io/electron-vue-template-doc/)
[vite 版本](https://github.com/umbrella22/electron-vite-template)
[vite 版本(码云)](https://gitee.com/Zh-Sky/electron-vite-template)
[Open in Visual Studio Code](https://open.vscode.dev/umbrella22/electron-vue-template)
![GitHub Repo stars](https://img.shields.io/github/stars/umbrella22/electron-vue-template)
[![vue](https://img.shields.io/badge/vue-2.7.10-brightgreen.svg)](https://github.com/vuejs/vue)
[![element-ui](https://img.shields.io/badge/element--ui-2.15.9-brightgreen.svg)](https://github.com/ElemeFE/element)
[![electron](https://img.shields.io/badge/electron-19.0.17-brightgreen.svg)](https://github.com/electron/electron)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/umbrella22/electron-vue-template/blob/master/LICENSE)
- electron of react [Electron-react-template](https://github.com/umbrella22/electron-react-template)
### To run a project, you need to have **node version 16** or higher and **use yarn as your dependency management tool**
<p align="center">
<a href="https://github.com/umbrella22/electron-vue-template">
<img src="https://github.com/umbrella22/electron-vue-template/actions/workflows/build-test.yml/badge.svg">
</a>
</p>
<h3 align="center">Thanks for support.</h3>
<p align="center">
<a href="https://www.jetbrains.com/?from=electron-vue-template" target="_blank">
<img width="160px" src="https://github.com/umbrella22/MCsever/blob/master/jetbrains.png">
</a>
</p>
#### Build Setup
```bash
# install dependencies
yarn or yarn install
# serve with hot reload at localhost:9080
yarn dev
# build electron application for production
yarn build
```
---
# Function list
- Auto update
- Incremental update
- Loading animation before startup
- i18n
- Incremental update (wait for test)
# Built-in
- [vue-router](https://router.vuejs.org)
- [vuex](https://vuex.vuejs.org)
- [electron](http://www.electronjs.org/docs)
- electron-updater
- typescript
- [element-plus](https://element.eleme.cn/#/en-US)
# Note
- [gitee](https://gitee.com/Zh-Sky/electron-vue-template) is only for domestic users to pull codefrom github to synchronizeplease visit github for PR
- **Welcome to Issues and PR**
---
This project was generated with [electron-vue](https://github.com/SimulatedGREG/electron-vue)@[8fae476](https://github.com/SimulatedGREG/electron-vue/tree/8fae4763e9d225d3691b627e83b9e09b56f6c935) using [vue-cli](https://github.com/vuejs/vue-cli). Documentation about the original structure can be found [here](https://simulatedgreg.gitbooks.io/electron-vue/content/index.html).
Manage interface code address [here](https://github.com/PanJiaChen/electron-vue-admin)
# [CHANGELOG](CHANGELOG.md)

36
fuintCashier/README_ZH.md Normal file
View File

@@ -0,0 +1,36 @@
# Electron-Vue-template
## 介绍
### 这是什么?
- 这是一个基于 webpack5 和 vue2/3 的 electron 快速上手框架,你不需要关注它们是怎么联动的,你只需要关注你当前的界面组合就好
剩下的交给我就好
- 上手极快,几乎没有任何心智负担对于前端而言,特别是当你熟悉 vue 和 js 时,就好像回家了一样
### 有什么功能?
1. 文件下载
2. 全量更新
> ### **请确保您的 node 环境是大于或等于 16**
#### 如何安装
```bash
npm config edit
# 该命令会打开npm的配置文件请在空白处添加此操作是配置淘宝镜像。
# registry=https://registry.npmmirror.com
# electron_mirror=https://cdn.npmmirror.com/binaries/electron/
# electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
# 然后关闭该窗口,重启命令行.
# 使用yarn安装
yarn or yarn install
# 启动之后会在9080端口监听
yarn dev
# build命令在不同系统环境中需要的的不一样需要自己根据自身环境进行配置
yarn build
```
# [更新日志](CHANGELOG.md)

View File

@@ -0,0 +1,220 @@
x64:
firstOrDefaultFilePatterns:
- '!**/node_modules'
- '!build{,/**/*}'
- '!build{,/**/*}'
- dist/electron/**/*
- package.json
- '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,mk,a,o,forge-meta,pdb}'
- '!**/._*'
- '!**/electron-builder.{yaml,yml,json,json5,toml,ts}'
- '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env}'
- '!.yarn{,/**/*}'
- '!.editorconfig'
- '!.yarnrc.yml'
nodeModuleFilePatterns:
- '**/*'
- dist/electron/**/*
nsis:
script: |-
!include "C:\Code\fuint\fuint-cashier\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
!addincludedir "C:\Code\fuint\fuint-cashier\node_modules\app-builder-lib\templates\nsis\include"
!macro _isUpdated _a _b _t _f
${StdUtils.TestParameter} $R9 "updated"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isUpdated `"" isUpdated ""`
!macro _isForceRun _a _b _t _f
${StdUtils.TestParameter} $R9 "force-run"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForceRun `"" isForceRun ""`
!macro _isKeepShortcuts _a _b _t _f
${StdUtils.TestParameter} $R9 "keep-shortcuts"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isKeepShortcuts `"" isKeepShortcuts ""`
!macro _isNoDesktopShortcut _a _b _t _f
${StdUtils.TestParameter} $R9 "no-desktop-shortcut"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isNoDesktopShortcut `"" isNoDesktopShortcut ""`
!macro _isDeleteAppData _a _b _t _f
${StdUtils.TestParameter} $R9 "delete-app-data"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isDeleteAppData `"" isDeleteAppData ""`
!macro _isForAllUsers _a _b _t _f
${StdUtils.TestParameter} $R9 "allusers"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForAllUsers `"" isForAllUsers ""`
!macro _isForCurrentUser _a _b _t _f
${StdUtils.TestParameter} $R9 "currentuser"
StrCmp "$R9" "true" `${_t}` `${_f}`
!macroend
!define isForCurrentUser `"" isForCurrentUser ""`
!macro addLangs
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "German"
!insertmacro MUI_LANGUAGE "French"
!insertmacro MUI_LANGUAGE "SpanishInternational"
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_LANGUAGE "TradChinese"
!insertmacro MUI_LANGUAGE "Japanese"
!insertmacro MUI_LANGUAGE "Korean"
!insertmacro MUI_LANGUAGE "Italian"
!insertmacro MUI_LANGUAGE "Dutch"
!insertmacro MUI_LANGUAGE "Danish"
!insertmacro MUI_LANGUAGE "Swedish"
!insertmacro MUI_LANGUAGE "Norwegian"
!insertmacro MUI_LANGUAGE "Finnish"
!insertmacro MUI_LANGUAGE "Russian"
!insertmacro MUI_LANGUAGE "Portuguese"
!insertmacro MUI_LANGUAGE "PortugueseBR"
!insertmacro MUI_LANGUAGE "Polish"
!insertmacro MUI_LANGUAGE "Ukrainian"
!insertmacro MUI_LANGUAGE "Czech"
!insertmacro MUI_LANGUAGE "Slovak"
!insertmacro MUI_LANGUAGE "Hungarian"
!insertmacro MUI_LANGUAGE "Arabic"
!insertmacro MUI_LANGUAGE "Turkish"
!insertmacro MUI_LANGUAGE "Thai"
!insertmacro MUI_LANGUAGE "Vietnamese"
!macroend
!addplugindir /x86-unicode "C:\Users\zach\AppData\Local\electron-builder\Cache\nsis\nsis-resources-3.4.1\plugins\x86-unicode"
!include "C:\Users\zach\AppData\Local\Temp\t-idwXjr\0-messages.nsh"
Var newStartMenuLink
Var oldStartMenuLink
Var newDesktopLink
Var oldDesktopLink
Var oldShortcutName
Var oldMenuDirectory
!include "common.nsh"
!include "MUI2.nsh"
!include "multiUser.nsh"
!include "allowOnlyOneInstallerInstance.nsh"
!ifdef INSTALL_MODE_PER_ALL_USERS
!ifdef BUILD_UNINSTALLER
RequestExecutionLevel user
!else
RequestExecutionLevel admin
!endif
!else
RequestExecutionLevel user
!endif
!ifdef BUILD_UNINSTALLER
SilentInstall silent
!else
Var appExe
Var launchLink
!endif
!ifdef ONE_CLICK
!include "oneClick.nsh"
!else
!include "assistedInstaller.nsh"
!endif
!insertmacro addLangs
!ifmacrodef customHeader
!insertmacro customHeader
!endif
Function .onInit
Call setInstallSectionSpaceRequired
SetOutPath $INSTDIR
${LogSet} on
!ifmacrodef preInit
!insertmacro preInit
!endif
!ifdef DISPLAY_LANG_SELECTOR
!insertmacro MUI_LANGDLL_DISPLAY
!endif
!ifdef BUILD_UNINSTALLER
WriteUninstaller "${UNINSTALLER_OUT_FILE}"
!insertmacro quitSuccess
!else
!insertmacro check64BitAndSetRegView
!ifdef ONE_CLICK
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
!else
${IfNot} ${UAC_IsInnerInstance}
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
${EndIf}
!endif
!insertmacro initMultiUser
!ifmacrodef customInit
!insertmacro customInit
!endif
!ifmacrodef addLicenseFiles
InitPluginsDir
!insertmacro addLicenseFiles
!endif
!endif
FunctionEnd
!ifndef BUILD_UNINSTALLER
!include "installUtil.nsh"
!endif
Section "install" INSTALL_SECTION_ID
!ifndef BUILD_UNINSTALLER
# If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
# For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
# but that won't be executed when silent.
!ifndef INSTALL_MODE_PER_ALL_USERS
!ifndef ONE_CLICK
${if} $hasPerMachineInstallation == "1" # set in onInit by initMultiUser
${andIf} ${Silent}
${ifNot} ${UAC_IsAdmin}
ShowWindow $HWNDPARENT ${SW_HIDE}
!insertmacro UAC_RunElevated
${Switch} $0
${Case} 0
${Break}
${Case} 1223 ;user aborted
${Break}
${Default}
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0"
${Break}
${EndSwitch}
Quit
${else}
!insertmacro setInstallModePerAllUsers
${endIf}
${endIf}
!endif
!endif
!include "installSection.nsh"
!endif
SectionEnd
Function setInstallSectionSpaceRequired
!insertmacro setSpaceRequired ${INSTALL_SECTION_ID}
FunctionEnd
!ifdef BUILD_UNINSTALLER
!include "uninstaller.nsh"
!endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@@ -0,0 +1,8 @@
version: 1.0.0
files:
- url: fuint收银系统 Setup 1.0.0.exe
sha512: /5Jf84ozx4NWmYJ0ifp4QCXTCXoDCuxT4ECBKBlyysiJgqbb8KZ1z4ShFeQ8W6zYCKzz+IPtj7MzK7fn/bnOAw==
size: 75309070
path: fuint收银系统 Setup 1.0.0.exe
sha512: /5Jf84ozx4NWmYJ0ifp4QCXTCXoDCuxT4ECBKBlyysiJgqbb8KZ1z4ShFeQ8W6zYCKzz+IPtj7MzK7fn/bnOAw==
releaseDate: '2024-08-09T11:04:57.235Z'

View File

@@ -0,0 +1,12 @@
module.exports = {
build: {
cleanConsole: true,
},
dev: {
removeElectronJunk: true,
chineseLog: false,
port: 8088
},
DllFolder: '',
UseJsx: true
}

3
fuintCashier/env/.env vendored Normal file
View File

@@ -0,0 +1,3 @@
API_HOST = 'https://www.fuint.cn/fuint-application/'
NODE_ENV = 'development'
SYSTEM_NAME = 'fuint收银系统'

2
fuintCashier/env/sit.env vendored Normal file
View File

@@ -0,0 +1,2 @@
API_HOST = 'http://127.0.0.1:25565'
NODE_ENV = 'sit'

View File

157
fuintCashier/package.json Normal file
View File

@@ -0,0 +1,157 @@
{
"name": "fuintCashier",
"version": "1.0.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://127.0.0.1"
}
],
"productName": "fuint收银系统",
"appId": "cn.fuint.cashier",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns"
},
"win": {
"icon": "build/icons/icon.ico",
"target": "nsis"
},
"linux": {
"target": "deb",
"icon": "build/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": "22.3.27",
"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"
}
}

View File

@@ -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)
})

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>fuintCashier</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<% if (!process.browser) { %>
<script>
if (process.env.NODE_ENV !== 'development') {
window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
} else {
window.__static = `http://localhost:${process.env.PORT}/static`
}
if (process.env.NODE_ENV !== 'development') window.__lib = process.env.libPath
</script>
<% } %>
<!-- webpack builds are automatically injected -->
</body>
</html>

View File

@@ -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('用户试图启动控制台')
})
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,6 @@
import { hotPublishUrl, hotPublishConfigName } from './const'
export const hotPublishConfig = {
url: hotPublishUrl,
configName: hotPublishConfigName
}

View File

@@ -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: 'fuint收银系统',
detail: `版本信息:${version}\n引擎版本:${process.versions.v8}\n当前系统:${os.type()} ${os.arch()} ${os.release()}`,
noLink: true,
buttons: ['查看官网', '确定']
})
}
export default menu

View File

@@ -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')
}

View File

@@ -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('服务端尚未开启')
}
})
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,76 @@
import { autoUpdater } from 'electron-updater'
/**
* -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成
**/
class Update {
mainWindow
constructor() {
autoUpdater.setFeedURL('http://127.0.0.1:25565/')
// 当更新发生错误的时候触发。
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

View File

@@ -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
}
})
})
}
}

View File

@@ -0,0 +1,169 @@
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'
import {platform} from "os";
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: false,
//使用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)
}
})
})
}
}

View File

@@ -0,0 +1,105 @@
import { BrowserWindow, Menu, app } 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: false,
// 如果是开发模式可以使用devTools
devTools: process.env.NODE_ENV === 'development' || openDevTools,
// devTools: true,
// 在macos中启用橡皮动画
scrollBounce: process.platform === 'darwin'
}
})
// 这里设置只有开发环境才注入显示开发者模式
if (process.env.NODE_ENV === 'development' || openDevTools) {
menuconfig.push({
label: '设置',
submenu: [{
label: '开发者模式',
accelerator: 'CmdOrCtrl+I',
role: 'toggledevtools'
}]
})
}
// 载入菜单
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

View File

@@ -0,0 +1,17 @@
<template>
<div id="app">
<c-header></c-header>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</div>
</template>
<script setup>
import CHeader from "./components/title";
</script>
<style>
/* CSS */
</style>

View File

@@ -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
})
}

View File

@@ -0,0 +1,124 @@
import request from '@/utils/request'
// 初始化数据
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
})
}

View File

@@ -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
})
}

View File

@@ -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
})
}

View File

@@ -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/doLogin',
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?v='+Math.random(),
method: 'get'
})
}
// 系统消息
export function message () {
return request({
url: 'clientApi/captcha/message',
method: 'get'
})
}

View File

@@ -0,0 +1,65 @@
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
})
}

View File

@@ -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
})
}

View File

@@ -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
})
}

View File

@@ -0,0 +1,44 @@
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
})
}

View File

@@ -0,0 +1,10 @@
import request from '@/utils/request'
// 分页查询员工列表
export function getStaffList(query) {
return request({
url: 'backendApi/staff/list',
method: 'get',
params: query
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,51 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title">
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
<router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
created() {
this.getBreadcrumb()
},
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.name)
const first = matched[0]
if (first && first.name !== 'dashboard') {
matched = [{ path: '/dashboard', meta: { title: '总览' }}].concat(matched)
}
this.levelList = matched
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div>
<svg t="1492500959545" @click="toggleClick" class="hamburger" :class="{ 'is-active': isActive }" style=""
viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
p-id="1692"></path>
<path
d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
p-id="1693"></path>
<path
d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
p-id="1694"></path>
</svg>
</div>
</template>
<script setup>
defineProps({
isActive: {
type: Boolean
}
})
const emits = defineEmits(['toggle-click'])
const toggleClick = () => {
emits('toggle-click')
}
</script>
<style scoped>
.hamburger {
display: inline-block;
cursor: pointer;
width: 20px;
height: 20px;
transform: rotate(90deg);
transition: .38s;
transform-origin: 50% 50%;
}
.hamburger.is-active {
transform: rotate(0deg);
}
</style>

View File

@@ -0,0 +1,340 @@
<template>
<div id="wrapper">
<img id="logo" :src="logo" alt="electron-vue" style="display: none"/>
<main style="display: none">
<div class="left-side">
<span class="title">{{ $t("welcome") }}</span>
<system-information></system-information>
<div v-if="textarray.length === 0">
<span>{{ text }}</span>
</div>
<div v-for="(itme, index) in textarray" :key="index" v-else>
<span>{{ itme._id }}</span>
<span>{{ itme.name }}</span>
<span>{{ itme.age }}</span>
</div>
</div>
<div class="right-side">
<div class="doc">
<div class="title alt">{{ $t("buttonTips") }}</div>
<el-button type="primary" round @click="open()">{{
$t("buttons.console")
}}</el-button>
<el-button type="primary" round @click="CheckUpdate('one')">{{
$t("buttons.checkUpdate")
}}</el-button>
</div>
<div class="doc">
<el-button type="primary" round @click="CheckUpdate('two')">{{
$t("buttons.checkUpdate2")
}}</el-button>
<el-button type="primary" round @click="StartServer">{{
$t("buttons.startServer")
}}</el-button>
<el-button type="primary" round @click="StopServer">{{
$t("buttons.stopServer")
}}</el-button>
<el-button type="primary" round @click="getMessage">{{
$t("buttons.viewMessage")
}}</el-button>
</div>
<div class="doc">
<el-button type="primary" round @click="openNewWin">{{
$t("buttons.openNewWindow")
}}</el-button>
<el-button type="primary" round @click="openDocument">{{
$t("buttons.openDocument")
}}</el-button>
<el-button type="primary" round @click="changeLanguage">{{
$t("buttons.changeLanguage")
}}</el-button>
</div>
<div class="doc">
<el-pagination :current-page="1" :page-sizes="[100, 200, 300, 400]" :page-size="100"
layout="total, sizes, prev, pager, next, jumper" :total="400">
</el-pagination>
</div>
</div>
</main>
<el-dialog title="进度" :visible.sync="dialogVisible" :before-close="handleClose" center width="14%" top="45vh">
<div class="conten">
<el-progress type="dashboard" :percentage="percentage" :color="colors" :status="progressStaus"></el-progress>
</div>
</el-dialog>
</div>
</template>
<script>
import SystemInformation from "./LandingPage/SystemInformation";
import { message } from "@/api/login";
import { ipcRenderer, shell } from "electron";
export default {
name: "landing-page",
components: { SystemInformation },
data: () => ({
newdata: {
name: "yyy",
age: "12",
},
logo: require("@/assets/logo.png"),
textarray: [],
percentage: 0,
colors: [
{ color: "#f56c6c", percentage: 20 },
{ color: "#e6a23c", percentage: 40 },
{ color: "#6f7ad3", percentage: 60 },
{ color: "#1989fa", percentage: 80 },
{ color: "#5cb87a", percentage: 100 },
],
dialogVisible: false,
progressStaus: null,
filePath: "",
}),
created() {
console.log("环境打印示例");
console.log("__lib路径", __lib);
console.log("环境变量", process.env.userConfig);
ipcRenderer.on("download-progress", (event, arg) => {
this.percentage = Number(arg);
});
ipcRenderer.on("download-error", (event, arg) => {
if (arg) {
this.progressStaus = "exception";
this.percentage = 40;
this.colors = "#d81e06";
}
});
ipcRenderer.on("download-paused", (event, arg) => {
if (arg) {
this.progressStaus = "warning";
this.$alert("下载由于未知原因被中断!", "提示", {
confirmButtonText: "重试",
callback: (action) => {
ipcRenderer.invoke("satrt-download");
},
});
}
});
ipcRenderer.on("download-done", (event, age) => {
this.filePath = age.filePath;
this.progressStaus = "success";
console.log("下载完成啦");
this.$alert("更新下载完成!", "提示", {
confirmButtonText: "确定",
callback: (action) => {
shell.openPath(this.filePath);
},
});
});
ipcRenderer.on("update-msg", (event, age) => {
console.log("update-msg", age);
switch (age.state) {
case -1:
const msgdata = {
title: "发生错误",
message: age.msg,
};
this.dialogVisible = false;
ipcRenderer.invoke("open-errorbox", msgdata);
break;
case 0:
this.$message("正在检查更新");
break;
case 1:
this.$message({
type: "success",
message: "已检查到新版本,开始下载",
});
this.dialogVisible = true;
break;
case 2:
this.$message({ type: "success", message: "无新版本" });
break;
case 3:
this.percentage = age.msg.percent.toFixed(1);
break;
case 4:
this.progressStaus = "success";
this.$alert("更新下载完成!", "提示", {
confirmButtonText: "确定",
callback: (action) => {
ipcRenderer.invoke("confirm-update");
},
});
break;
default:
break;
}
});
ipcRenderer.on('hot-update-status', (event, arg) => {
console.log(arg);
if (arg.status === 'finished') {
this.$message({
type: 'success',
message: '热更新成功'
});
}
})
},
methods: {
openNewWin() {
let data = {
url: "/form/index",
resizable: true,
};
ipcRenderer.invoke("open-win", data);
},
openDocument() {
shell.openExternal("https://zh-sky.gitee.io/electron-vue-template-doc/Overview/#%E5%8A%9F%E8%83%BD")
},
getMessage() {
message().then((res) => {
this.$alert(res.data, "提示", {
confirmButtonText: "确定",
});
});
},
StopServer() {
ipcRenderer.invoke("stop-server").then((res) => {
this.$message({
type: "success",
message: "已关闭",
});
});
},
StartServer() {
ipcRenderer.invoke("statr-server").then((res) => {
if (res) {
this.$message({
type: "success",
message: res,
});
}
});
},
// 获取electron方法
open() { },
CheckUpdate(data) {
switch (data) {
case "one":
ipcRenderer.invoke("check-update").then((res) => {
console.log("启动检查");
});
break;
case "two":
ipcRenderer.invoke("start-download").then(() => {
this.dialogVisible = true;
});
break;
default:
break;
}
},
handleClose() {
this.dialogVisible = false;
},
changeLanguage() {
let lang = this.$i18n.locale === "zh-CN" ? "en" : "zh-CN";
this.$i18n.locale = lang;
},
},
destroyed() {
console.log("销毁了哦");
ipcRenderer.removeAllListeners("confirm-message");
ipcRenderer.removeAllListeners("download-done");
ipcRenderer.removeAllListeners("download-paused");
ipcRenderer.removeAllListeners("confirm-stop");
ipcRenderer.removeAllListeners("confirm-start");
ipcRenderer.removeAllListeners("confirm-download");
ipcRenderer.removeAllListeners("download-progress");
ipcRenderer.removeAllListeners("download-error");
ipcRenderer.removeAllListeners("update-msg");
},
computed: {
text() {
return this.$i18n.t("waitDataLoading");
},
},
};
</script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Source Sans Pro", sans-serif;
}
#wrapper {
padding: 60px 80px;
}
#logo {
height: auto;
margin-bottom: 20px;
width: 420px;
}
main {
display: flex;
justify-content: space-between;
}
main>div {
flex-basis: 50%;
}
.left-side {
display: flex;
flex-direction: column;
}
.welcome {
color: #555;
font-size: 23px;
margin-bottom: 10px;
}
.title {
color: #2c3e50;
font-size: 20px;
font-weight: bold;
margin-bottom: 6px;
}
.title.alt {
font-size: 18px;
margin-bottom: 10px;
}
.doc {
margin-bottom: 10px;
}
.doc p {
color: black;
margin-bottom: 10px;
}
.doc .el-button {
margin-top: 10px;
margin-right: 10px;
}
.doc .el-button+.el-button {
margin-left: 0;
}
.conten {
text-align: center;
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div>
<div class="title">{{ $t("about.system") }}</div>
<div class="items">
<div class="item" v-for="(item, index) in tips" :key="index">
<div class="name" v-text="item.name" />
<div class="value" v-text="item.value" />
</div>
</div>
</div>
</template>
<script>
import { platform, release, arch } from "os";
export default {
data() {
return {
};
},
computed: {
tips() {
return [
{ name: this.$i18n.t("about.language"), value: this.$i18n.t("about.languageValue") },
{ name: this.$i18n.t("about.currentPagePath"), value: this.$route.path },
{ name: this.$i18n.t("about.currentPageName"), value: this.$route.name },
{ name: this.$i18n.t("about.vueVersion"), value: require("vue/package.json").version },
{
name: this.$i18n.t("about.electronVersion"),
value: process.versions.electron || "浏览器环境",
},
{ name: this.$i18n.t("about.nodeVersion"), value: process.versions.node || "浏览器环境" },
{ name: this.$i18n.t("about.systemPlatform"), value: platform() },
{ name: this.$i18n.t("about.systemVersion"), value: release() },
{ name: this.$i18n.t("about.systemArch"), value: arch() + "位" },
{ name: this.$i18n.t("about.currentEnvironment"), value: process.env?.NODE_ENV }
]
}
},
mounted() {
console.log(this.$route);
}
};
</script>
<style scoped>
.title {
color: #888;
font-size: 18px;
font-weight: initial;
letter-spacing: 0.25px;
margin-top: 10px;
}
.items {
margin-top: 8px;
}
.item {
display: flex;
align-items: center;
margin-bottom: 6px;
line-height: 24px;
}
.item .name {
color: #6a6a6a;
margin-right: 6px;
}
.item .value {
color: #35495e;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
// 移动端页码按钮的数量端默认值5
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
data() {
return {
};
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
if (this.currentPage * val > this.total) {
this.currentPage = 1
}
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll" >
<div class="scroll-wrapper" ref="scrollWrapper" :style="{top: top + 'px'}">
<slot></slot>
</div>
</div>
</template>
<script>
const delta = 15
export default {
name: 'scrollBar',
data() {
return {
top: 0
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 3
const $container = this.$refs.scrollContainer
const $containerHeight = $container.offsetHeight
const $wrapper = this.$refs.scrollWrapper
const $wrapperHeight = $wrapper.offsetHeight
if (eventDelta > 0) {
this.top = Math.min(0, this.top + eventDelta)
} else {
if ($containerHeight - delta < $wrapperHeight) {
if (this.top < -($wrapperHeight - $containerHeight + delta)) {
this.top = this.top
} else {
this.top = Math.max(this.top + eventDelta, $containerHeight - $wrapperHeight - delta)
}
} else {
this.top = 0
}
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import '../../styles/variables.scss';
.scroll-container {
position: relative;
width: 100%;
height: 100%;
background-color: $menuBg;
.scroll-wrapper {
position: absolute;
width: 100%!important;
}
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'svg-icon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<div class="upload-container">
<el-button
:style="{ background: color, borderColor: color }"
icon="el-icon-upload"
size="mini"
type="primary"
@click="dialogVisible = true"
>上传图片</el-button
>
<el-dialog :visible.sync="dialogVisible">
<el-upload
ref="upload"
:multiple="true"
:file-list="fileList"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:on-error="handleError"
:data="picPostData"
class="editor-slide-upload"
action="https://jsonplaceholder.typicode.com/post/"
list-type="picture-card"
:limit="5"
>
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</el-dialog>
</div>
</template>
<script>
export default {
name: "EditorSlideUpload",
props: {
color: {
type: String,
default: "#1890ff",
},
},
data() {
return {
dialogVisible: false,
listObj: {},
fileList: [],
picPostData: {},
};
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(
(item) => this.listObj[item].hasSuccess
);
},
handleSubmit() {
const arr = Object.keys(this.listObj).map((v) => this.listObj[v]);
console.log(arr);
if (!this.checkAllSuccess()) {
this.$message(
"请等待所有图片上传完成,如果存在网络问题请刷新当前页重新上传"
);
return;
}
this.$emit("successCBK", arr);
this.listObj = {};
this.fileList = [];
this.dialogVisible = false;
},
handleSuccess(response, file) {
console.log("file", file);
console.log("handleSuccess", response);
const uid = file.uid;
const objKeyArr = Object.keys(this.listObj);
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = this.config.qiniuHost + response.key;
this.listObj[objKeyArr[i]].hasSuccess = true;
return;
}
}
},
handleRemove(file) {
const uid = file.uid;
const objKeyArr = Object.keys(this.listObj);
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]];
return;
}
}
},
handleError(err) {
console.log(err);
this.$alert(err, "发生错误", {
confirmButtonText: "确定",
callback: (action) => {},
});
},
},
};
</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
::v-deep .el-upload--picture-card {
width: 100%;
}
}
</style>

View File

@@ -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

View File

@@ -0,0 +1,223 @@
<template>
<div
:class="{ fullscreen: fullscreen }"
class="tinymce-container"
:style="{ width: containerWidth }"
>
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage
color="#1890ff"
class="editor-upload-btn"
@successCBK="imageSuccessCBK"
/>
</div>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from "./components/EditorImage";
import plugins from "./plugins";
import toolbar from "./toolbar";
import load from "./dynamicLoadScript";
const tinymceFile = `${
process.browser ? "static" : __static
}/tinymce/tinymce.min.js`;
export default {
name: "Tinymce",
components: { editorImage },
props: {
id: {
type: String,
default: function () {
return (
"vue-tinymce-" +
+new Date() +
((Math.random() * 1000).toFixed(0) + "")
);
},
},
value: {
type: String,
default: "",
},
toolbar: {
type: Array,
required: false,
default() {
return [];
},
},
menubar: {
type: String,
default: "",
},
height: {
type: [Number, String],
required: false,
default: 360,
},
width: {
type: [Number, String],
required: false,
default: "auto",
},
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
zh: "zh_CN",
},
};
},
computed: {
containerWidth() {
const width = this.width;
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
// matches `100`, `'100'`
return `${width}px`;
}
return width;
},
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || "")
);
}
},
},
mounted() {
this.init();
},
activated() {
if (window.tinymce) {
this.initTinymce();
}
},
deactivated() {
this.destroyTinymce();
},
destroyed() {
this.destroyTinymce();
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceFile, (err) => {
if (err) {
this.$message.error(err.message);
return;
}
this.initTinymce();
});
},
initTinymce() {
const _this = this;
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList["zh"],
min_height: this.height,
body_class: "panel-body ",
draggable_modal: true,
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
fontsize_formats: "12px 14px 16px 18px 24px 36px 48px 56px 72px",
font_formats:
"微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;",
end_container_on_empty_block: true,
powerpaste_word_import: "clean",
code_dialog_height: 450,
code_dialog_width: 1000,
custom_undo_redo_levels: 50,
advlist_bullet_styles: "square",
advlist_number_styles: "default",
imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
default_link_target: "_blank",
link_title: false,
branding: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: (editor) => {
if (_this.value) {
editor.setContent(_this.value);
}
_this.hasInit = true;
editor.on("NodeChange Change KeyUp SetContent", () => {
this.hasChange = true;
this.$emit("input", editor.getContent());
});
},
setup(editor) {
editor.on("FullscreenStateChanged", (e) => {
_this.fullscreen = e.state;
});
},
});
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId);
if (this.fullscreen) {
tinymce.execCommand("mceFullScreen");
}
if (tinymce) {
tinymce.destroy();
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value);
},
getContent() {
window.tinymce.get(this.tinymceId).getContent();
},
imageSuccessCBK(arr) {
const _this = this;
arr.forEach((v) => {
window.tinymce
.get(_this.tinymceId)
.insertContent(`<img class="wscnph" src="${v.url}" >`);
});
},
},
};
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container >>> .mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@@ -0,0 +1,7 @@
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools help emoticons autoresize']
export default plugins

View File

@@ -0,0 +1,9 @@
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
// eslint-disable-next-line no-multi-str
const toolbar = ['code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight formatpainter axupimgs']
export default toolbar

View File

@@ -0,0 +1,116 @@
<!-- -->
<template>
<div class="window-title" v-if="!IsUseSysTitle&&!IsWeb">
<!-- 软件logo预留位置 -->
<div style="-webkit-app-region: drag;" class="logo" v-if="isNotMac">
<svg-icon icon-class="electron-logo"></svg-icon>
</div>
<!-- 菜单栏位置 -->
<div></div>
<!-- 中间标题位置 -->
<div style="-webkit-app-region: drag;" class="title"></div>
<div class="controls-container" v-if="isNotMac">
<div class="windows-icon-bg" @click="Mini">
<svg-icon icon-class="mini" class-name="icon-size"></svg-icon>
</div>
<div class="windows-icon-bg" @click="MixOrReduction">
<svg-icon v-if="mix" icon-class="reduction" class-name="icon-size"></svg-icon>
<svg-icon v-else icon-class="mix" class-name="icon-size"></svg-icon>
</div>
<div class="windows-icon-bg close-icon" @click="Close">
<svg-icon icon-class="close" class-name="icon-size"></svg-icon>
</div>
</div>
</div>
</template>
<script>
import { ipcRenderer } from "electron";
export default {
data: () => ({
mix: false,
IsUseSysTitle: false,
isNotMac: process.platform !== "darwin",
IsWeb: process.env.IS_WEB
}),
components: {},
created() {
ipcRenderer.invoke("IsUseSysTitle").then(res => {
this.IsUseSysTitle = res;
});
},
mounted() {
ipcRenderer.on("w-max",(event,state)=>{
this.mix = state
})
},
methods: {
Mini() {
ipcRenderer.invoke("windows-mini");
},
MixOrReduction() {
ipcRenderer.invoke("window-max").then(res=>{
this.mix = res.status
})
},
Close() {
ipcRenderer.invoke("window-close");
}
},
destroyed() {
ipcRenderer.removeAllListeners("w-max");
}
};
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
.window-title {
width: 100%;
height: 30px;
line-height: 30px;
display: flex;
-webkit-app-region: drag;
position: fixed;
top: 0;
z-index: 99999;
.title {
text-align: center;
}
.logo {
margin-left: 20px;
}
.controls-container {
display: flex;
flex-grow: 0;
flex-shrink: 0;
text-align: center;
position: relative;
z-index: 3000;
-webkit-app-region: no-drag;
height: 100%;
width: 138px;
margin-left: auto;
.windows-icon-bg {
display: inline-block;
-webkit-app-region: no-drag;
height: 100%;
width: 33.34%;
color: rgba(129, 129, 129, 0.6);
.icon-size {
width: 12px;
height: 15px;
}
}
.windows-icon-bg:hover {
background-color: rgba(182, 182, 182, 0.2);
color: #333;
}
.close-icon:hover {
background-color: rgba(232, 17, 35, 0.9);
color: #fff;
}
}
}
</style>

View File

@@ -0,0 +1,64 @@
/**
* v-dialogDrag 弹窗拖拽
* Copyright (c) 2022 fuint
*/
export default {
bind(el, binding, vnode, oldVnode) {
const value = binding.value
if (value == false) return
// 获取拖拽内容头部
const dialogHeaderEl = el.querySelector('.el-dialog__header');
const dragDom = el.querySelector('.el-dialog');
dialogHeaderEl.style.cursor = 'move';
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
dragDom.style.position = 'absolute';
dragDom.style.marginTop = 0;
let width = dragDom.style.width;
if (width.includes('%')) {
width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
} else {
width = +width.replace(/\px/g, '');
}
dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
// 鼠标按下事件
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
// 获取到的值带px 正则匹配替换
let styL, styT;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
} else {
styL = +sty.left.replace(/\px/g, '');
styT = +sty.top.replace(/\px/g, '');
};
// 鼠标拖拽事件
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
const l = e.clientX - disX;
const t = e.clientY - disY;
let finallyL = l + styL
let finallyT = t + styT
// 移动当前元素
dragDom.style.left = `${finallyL}px`;
dragDom.style.top = `${finallyT}px`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}
};

View File

@@ -0,0 +1,34 @@
/**
* v-dialogDragWidth 可拖动弹窗高度(右下角)
* Copyright (c) 2022 fuint
*/
export default {
bind(el) {
const dragDom = el.querySelector('.el-dialog');
const lineEl = document.createElement('div');
lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;';
lineEl.addEventListener('mousedown',
function(e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
const disY = e.clientY - el.offsetTop;
// 当前宽度 高度
const curWidth = dragDom.offsetWidth;
const curHeight = dragDom.offsetHeight;
document.onmousemove = function(e) {
e.preventDefault(); // 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const xl = e.clientX - disX;
const yl = e.clientY - disY
dragDom.style.width = `${curWidth + xl}px`;
dragDom.style.height = `${curHeight + yl}px`;
};
document.onmouseup = function(e) {
document.onmousemove = null;
document.onmouseup = null;
};
}, false);
dragDom.appendChild(lineEl);
}
}

View File

@@ -0,0 +1,30 @@
/**
* v-dialogDragWidth 可拖动弹窗宽度(右侧边)
* Copyright (c) 2022 fuint
*/
export default {
bind(el) {
const dragDom = el.querySelector('.el-dialog');
const lineEl = document.createElement('div');
lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';
lineEl.addEventListener('mousedown',
function (e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
// 当前宽度
const curWidth = dragDom.offsetWidth;
document.onmousemove = function (e) {
e.preventDefault(); // 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const l = e.clientX - disX;
dragDom.style.width = `${curWidth + l}px`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}, false);
dragDom.appendChild(lineEl);
}
}

View File

@@ -0,0 +1,23 @@
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag'
import dialogDragWidth from './dialog/dragWidth'
import dialogDragHeight from './dialog/dragHeight'
import clipboard from './module/clipboard'
const install = function(Vue) {
Vue.directive('hasRole', hasRole)
Vue.directive('hasPermi', hasPermi)
Vue.directive('clipboard', clipboard)
Vue.directive('dialogDrag', dialogDrag)
Vue.directive('dialogDragWidth', dialogDragWidth)
Vue.directive('dialogDragHeight', dialogDragHeight)
}
if (window.Vue) {
window['hasRole'] = hasRole
window['hasPermi'] = hasPermi
Vue.use(install); // eslint-disable-line
}
export default install

View File

@@ -0,0 +1,54 @@
/**
* v-clipboard 文字复制剪贴
* Copyright (c) 2022 fuint
*/
import Clipboard from 'clipboard'
export default {
bind(el, binding, vnode) {
switch (binding.arg) {
case 'success':
el._vClipBoard_success = binding.value;
break;
case 'error':
el._vClipBoard_error = binding.value;
break;
default: {
const clipboard = new Clipboard(el, {
text: () => binding.value,
action: () => binding.arg === 'cut' ? 'cut' : 'copy'
});
clipboard.on('success', e => {
const callback = el._vClipBoard_success;
callback && callback(e);
});
clipboard.on('error', e => {
const callback = el._vClipBoard_error;
callback && callback(e);
});
el._vClipBoard = clipboard;
}
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._vClipBoard_success = binding.value;
} else if (binding.arg === 'error') {
el._vClipBoard_error = binding.value;
} else {
el._vClipBoard.text = function () { return binding.value; };
el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy';
}
},
unbind(el, binding) {
if (!el._vClipboard) return
if (binding.arg === 'success') {
delete el._vClipBoard_success;
} else if (binding.arg === 'error') {
delete el._vClipBoard_error;
} else {
el._vClipBoard.destroy();
delete el._vClipBoard;
}
}
}

View File

@@ -0,0 +1,23 @@
/**
* v-hasPermi 操作权限处理
* Copyright https://www.fuint.cn
*/
export default {
inserted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*";
const permissions = localStorage.getItem("permissions") ? JSON.parse(localStorage.getItem("permissions")) : [];
if (permissions.length > 0 && value && value instanceof Array && value.length > 0) {
const permissionFlag = value
const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission)
})
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置操作权限标签值`)
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* v-hasRole 角色权限处理
* Copyright (c) 2023 https://www.fuint.cn
*/
export default {
inserted(el, binding, vnode) {
const { value } = binding
const super_admin = "admin";
const roles = localStorage.getItem("roles") ? localStorage.getItem("roles") : [];
if (roles.length > 0 && value && value instanceof Array && value.length > 0) {
const roleFlag = value
const hasRole = roles.some(role => {
return super_admin === role || roleFlag.includes(role)
})
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置角色权限标签值"`)
}
}
}

View File

@@ -0,0 +1,17 @@
import Vue from 'vue'
Vue.config.errorHandler = function (err, vm, info) {
Vue.nextTick(() => {
if (process.env.NODE_ENV === 'development') {
console.group('%c >>>>>> 错误信息 >>>>>>', 'color:red')
console.log(`%c ${info}`, 'color:blue')
console.groupEnd()
console.group('%c >>>>>> 发生错误的Vue 实例对象 >>>>>>', 'color:green')
console.log(vm)
console.groupEnd()
console.group('%c >>>>>> 发生错误的原因及位置 >>>>>>', 'color:red')
console.error(err)
console.groupEnd()
}
})
}

View File

@@ -0,0 +1,7 @@
import { getCurrentInstance } from 'vue'
export const useRoute = () => {
return getCurrentInstance()?.proxy.$route
}
export const useRouter = () => {
return getCurrentInstance()?.proxy.$router
}

View File

@@ -0,0 +1,25 @@
export default function loadLanguage() {
const context = require.context("./languages", false, /([a-z_]+)\.js$/i)
const languages = context
.keys()
.map((key) => ({ key, name: key.match(/([a-z_-]+)\.js$/i)[1] }))
.reduce(
(languages, {key, name}) => {
let lang;
try {
// 引入 element-ui 语言包
lang = Object.assign(context(key).lang, require(`element-ui/lib/locale/lang/${name}`).default);
} catch(err) {
lang = context(key).lang
}
return {
...languages,
[name]: lang
}
},
{}
)
return languages
}

View File

@@ -0,0 +1,30 @@
export const lang = {
welcome: "Welcome use the framework",
buttonTips: "You can click buttons to experience",
waitDataLoading: "Wait data loading",
about: {
system: "About system",
language: "language",
languageValue: "English",
currentPagePath: "current page path:",
currentPageName: "current page name:",
vueVersion: "Vue version:",
electronVersion: "Electron version:",
nodeVersion: "Node version:",
systemPlatform: "system platform:",
systemVersion: "system version:",
systemArch: "system arch:",
currentEnvironment:"current environment:"
},
buttons: {
console: "Console",
checkUpdate: "Check update",
checkUpdate2: "Check update(plan 2)",
startServer: "Start server",
stopServer: "Stop server",
viewMessage: "view message",
openNewWindow: "Open new window",
openDocument: "Open document",
changeLanguage: "Change language"
}
}

View File

@@ -0,0 +1,30 @@
export const lang = {
welcome: "欢迎进入本框架",
buttonTips: "您可以点击的按钮测试功能",
waitDataLoading: "等待数据读取",
about: {
system: "关于系统",
language: "语言:",
languageValue: "中文简体",
currentPagePath: "当前页面路径:",
currentPageName: "当前页面名称:",
vueVersion: "Vue版本",
electronVersion: "Electron版本",
nodeVersion: "Node版本",
systemPlatform: "系统平台:",
systemVersion: "系统版本:",
systemArch: "系统位数:",
currentEnvironment:'当前环境:'
},
buttons: {
console: "控制台打印",
checkUpdate: "检查更新",
checkUpdate2: "检查更新(第二种方法)",
startServer: "启动内置服务端",
stopServer: "关闭内置服务端",
viewMessage: "查看消息",
openNewWindow: "打开新窗口",
openDocument: "打开文档",
changeLanguage: "切换语言"
}
}

View File

@@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg组件
// register globally
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width='11' height='11' viewBox='0 0 11 11' class="icon" xmlns='http://www.w3.org/2000/svg'><path d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z'/></svg>

After

Width:  |  Height:  |  Size: 366 B

View File

@@ -0,0 +1,529 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"> <image id="image0" width="256" height="256" x="0" y="0"
href="data:image/png;base64,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" />
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504199105" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1815" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M770.56 460.8h250.88C998.4 220.16 803.84 25.6 563.2 2.56v250.88c104.96 20.48 186.88 102.4 207.36 207.36z m0 0M460.8 253.44V2.56C220.16 25.6 25.6 220.16 2.56 460.8h250.88c20.48-104.96 102.4-186.88 207.36-207.36z m0 0M563.2 770.56v250.88c243.2-23.04 435.2-217.6 460.8-460.8H773.12C750.08 668.16 668.16 750.08 563.2 770.56z m0 0M253.44 563.2H2.56c23.04 243.2 217.6 435.2 460.8 460.8V773.12C355.84 750.08 273.92 668.16 253.44 563.2z m0 0" fill="" p-id="1816"></path></svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577518173588" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6318" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085z m0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334z m0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333z m0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z" p-id="6319"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993826520" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7878" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M941.677063 391.710356c9.337669-14.005992 6.224772-32.68133-6.224772-43.575447-14.005992-10.894118-32.68133-7.78122-43.575447 6.224771-1.556449 1.556449-174.300768 205.426673-379.727441 205.426673-199.200878 0-379.727441-205.426673-381.28389-206.982098-10.894118-12.450567-31.124881-14.005992-43.575448-3.112898-12.450567 10.894118-14.005992 31.124881-3.112897 43.575448 3.112897 4.668323 40.46255 46.687322 99.600439 93.375667l-79.369676 82.48155c-12.450567 12.450567-10.894118 32.68133 1.556449 43.575448 3.112897 6.224772 10.894118 9.337669 18.675338 9.337669 7.78122 0 15.562441-3.112897 21.787213-9.337669l85.594447-88.706321c40.46255 28.013007 88.706321 54.469566 141.619438 73.14388L340.959485 707.631586c-4.668323 17.118889 4.669346 34.237779 21.787213 38.906101h9.337669c14.005992 0 26.456558-9.337669 29.568432-23.343661l32.68133-110.494556c24.90011 4.668323 51.356668 7.78122 77.813227 7.78122s52.913117-3.112897 77.813227-7.78122l32.68133 108.938108c3.112897 14.005992 17.118889 23.343661 29.569456 23.343661 3.112897 0 6.224772 0 7.78122-1.556449 17.118889-4.669346 26.456558-21.787212 21.788236-38.906102l-32.68133-108.938108c52.913117-18.675338 101.156888-45.131897 141.619438-73.14388l84.037998 87.150896c6.224772 6.224772 14.005992 9.337669 21.787212 9.337669 7.78122 0 15.562441-3.112897 21.787212-9.337669 12.450567-12.450567 12.450567-31.124881 1.556449-43.575448l-79.369675-82.48155c63.808258-46.688345 101.158934-91.820242 101.158934-91.820242z" p-id="7879"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504319223" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3230" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M942.827259 80.3367c-11.42419-11.406794-26.41051-17.117866-41.377386-17.117866-14.985296 0-29.952172 5.711072-41.358967 17.117866L719.392444 221.014696l-19.441794 19.441794L681.577187 258.832 569.516971 370.909611 375.99749 564.411697l0 0.019443 0 84.372619 81.145112 0 0.010233 0 95.418186-95.435583 213.398228-213.400275 3.14155-3.14155-0.019443 0 9.979282-9.977235 0 0L942.827259 163.073052C965.697129 140.259464 965.697129 103.186104 942.827259 80.3367z" p-id="3231"></path><path d="M793.542234 367.521444 580.14196 580.939115 484.72582 676.376745 473.299583 687.800935 457.152834 687.800935 375.99749 687.800935 337.000314 687.800935 337.000314 648.803759 337.000314 564.411697 337.000314 548.264948 348.424504 536.838711 541.943986 343.338672 654.004201 231.259014 665.428392 219.834824 64.020082 219.834824 64.020082 960.781166 804.966425 960.781166 804.966425 356.116697 796.607036 364.475062Z" p-id="3232"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1564902100209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="840" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300"><defs><style type="text/css"></style></defs><path d="M615.6 123.6h165.5L512 589.7 242.9 123.6H63.5L512 900.4l448.5-776.9z" fill="#41B883" p-id="841"></path><path d="M781.1 123.6H615.6L512 303 408.4 123.6H242.9L512 589.7z" fill="#34495E" p-id="842"></path></svg>

After

Width:  |  Height:  |  Size: 584 B

Some files were not shown because too many files have changed in this diff Show More