lichong 8 months ago
commit
855b6e6d03
  1. 9
      .editorconfig
  2. 4
      .gitignore
  3. 1
      .npmrc
  4. 6
      .prettierignore
  5. 6
      .prettierrc
  6. 34
      README.md
  7. 12
      build/entitlements.mac.plist
  8. BIN
      build/icon.icns
  9. BIN
      build/icon.ico
  10. 36
      build/notarize.js
  11. 43
      electron-builder.yml
  12. 28
      electron.vite.config.js
  13. 6
      jsconfig.json
  14. 6610
      package-lock.json
  15. 36
      package.json
  16. BIN
      public/icon/icon.jpg
  17. 68
      src/main/index.js
  18. 21
      src/preload/dl.js
  19. 21
      src/preload/index.js
  20. 16
      src/renderer/index.html
  21. 265
      src/renderer/src/App.vue
  22. 5
      src/renderer/src/assets/css/base.css
  23. 25
      src/renderer/src/main.js

9
.editorconfig

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

4
.gitignore

@ -0,0 +1,4 @@
node_modules
dist
out
*.log*

1
.npmrc

@ -0,0 +1 @@
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/

6
.prettierignore

@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json

6
.prettierrc

@ -0,0 +1,6 @@
{
"singleQuote": true,
"semi": false,
"printWidth": 100,
"trailingComma": "none"
}

34
README.md

@ -0,0 +1,34 @@
# my-app
An Electron application with Vue
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
## Project Setup
### Install(node 16.20.1)
```bash
$ npm install
```
### Development
```bash
$ npm run dev
```
### Build
```bash
# For windows
$ npm run build:win
# For macOS
$ npm run build:mac
# For Linux
$ npm run build:linux
```

12
build/entitlements.mac.plist

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

BIN
build/icon.icns

Binary file not shown.

BIN
build/icon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

36
build/notarize.js

@ -0,0 +1,36 @@
const { notarize } = require('electron-notarize')
module.exports = async (context) => {
if (process.platform !== 'darwin') return
console.log('aftersign hook triggered, start to notarize app.')
if (!process.env.CI) {
console.log(`skipping notarizing, not in CI.`)
return
}
if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {
console.warn('skipping notarizing, APPLE_ID and APPLE_ID_PASS env variables must be set.')
return
}
const appId = 'com.electron.app'
const { appOutDir } = context
const appName = context.packager.appInfo.productFilename
try {
await notarize({
appBundleId: appId,
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLEIDPASS
})
} catch (error) {
console.error(error)
}
console.log(`done notarizing ${appId}.`)
}

43
electron-builder.yml

@ -0,0 +1,43 @@
appId: com.electron.lichong
productName: 桌面应用
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
asarUnpack:
- '**/*.{node,dll}'
afterSign: build/notarize.js
win:
executableName: lichong-app
nsis:
oneClick: false
artifactName: ${name}-${version}-setup.${ext}
allowToChangeInstallationDirectory: true
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates

28
electron.vite.config.js

@ -0,0 +1,28 @@
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
dl: resolve(__dirname, 'src/preload/dl.js'),
index: resolve(__dirname, 'src/preload/index.js')
}
}
}
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
},
plugins: [vue()]
}
})

6
jsconfig.json

@ -0,0 +1,6 @@
{
"exclude": [
"node_modules",
"public"
]
}

6610
package-lock.json

File diff suppressed because it is too large

36
package.json

@ -0,0 +1,36 @@
{
"name": "electron",
"version": "1.0.0",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "lichong",
"homepage": "https://www.electronjs.org",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"build:win": "npm run build && electron-builder --win --config",
"build:mac": "npm run build && electron-builder --mac --config",
"build:linux": "npm run build && electron-builder --linux --config"
},
"dependencies": {
"@electron-toolkit/preload": "^1.0.2",
"@electron-toolkit/utils": "^1.0.2",
"dayjs": "^1.11.11",
"element-plus": "^2.7.1",
"lodash": "^4.17.21",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@vitejs/plugin-vue": "^3.1.2",
"@vue/eslint-config-prettier": "^7.0.0",
"electron": "^20.3.2",
"electron-builder": "^23.6.0",
"electron-notarize": "^1.2.1",
"electron-vite": "^1.0.11",
"less": "^4.1.3",
"prettier": "^2.7.1",
"vite": "^3.1.8",
"vue": "^3.2.41"
}
}

BIN
public/icon/icon.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

68
src/main/index.js

@ -0,0 +1,68 @@
import { app, shell, BrowserWindow, nativeImage } from 'electron'
import * as path from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
// logo
const logoIcon = nativeImage.createFromPath(path.join(__dirname, '../../public/icon/icon.jpg'))
console.log(process.versions.node)
// 主窗口
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
minWidth: 1366,
minHeight: 768,
show: false,
autoHideMenuBar: true,
icon: logoIcon,
webPreferences: {
preload: path.resolve(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
// mainWindow.webContents.openDevTools()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// mainWindow.loadURL('http://localhost:5173/')
mainWindow.on('close', () => {
app.exit()
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.

21
src/preload/dl.js

@ -0,0 +1,21 @@
import { contextBridge, clipboard } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
contextBridge.exposeInMainWorld('elecClipboard', clipboard)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}

21
src/preload/index.js

@ -0,0 +1,21 @@
import { contextBridge, clipboard } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
contextBridge.exposeInMainWorld('elecClipboard', clipboard)
} catch (error) {
console.error(error)
}
} else {
window.electron = electronAPI
window.api = api
}

16
src/renderer/index.html

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>卡号卡密</title>
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

265
src/renderer/src/App.vue

@ -0,0 +1,265 @@
<template>
<div class="appClass" v-if="isVip">
<div class="mainClass">
<el-row>
<el-col :span="8">
<el-upload :show-file-list="false" :before-upload="beforeUpload" :http-request="fachuSubmit"
accept=".xls,.xlsx,.csv">
<el-button type="success">
<el-icon>
<Upload />
</el-icon>
<span>发出导入</span>
</el-button>
</el-upload>
</el-col>
<el-col :span="8">
<el-upload :show-file-list="false" :before-upload="beforeUpload" :http-request="huishouSubmit"
accept=".xls,.xlsx,.csv">
<el-button type="success">
<el-icon>
<Upload />
</el-icon>
<span>回收导入</span>
</el-button>
</el-upload>
</el-col>
<el-col :span="4">
<el-button type="danger" @click="delData">
<el-icon>
<el-icon>
<Delete />
</el-icon>
</el-icon>
<span>删除</span>
</el-button>
</el-col>
<el-col :span="4">
<el-tag type="success">总数据{{ tableData.length }}</el-tag>
</el-col>
</el-row>
<el-row>
<el-col>
<el-table :data="tableData" class="tableClass" ref="kahaoTable">
<el-table-column type="selection" width="55" />
<el-table-column type="index" width="85" label="序号" />
<el-table-column v-for="(item, index) in tableHeader" :label="item.lable" :key="index">
<template #default="scope">{{ scope.row[item.prop] }}</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</div>
<div v-else class="noVip">
<h3>
<el-tag type="danger" class="noVipTag">体验已过期请联系管理员</el-tag>
</h3>
</div>
</template>
<script>
import _ from 'lodash'
import dayjs from 'dayjs'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as XLSX from 'xlsx';
export default {
name: 'app',
data() {
return {
_: _,
dayjs: dayjs,
isVip: false,
tableHeader: [
{
lable: '卡号',
prop: 'kahao',
type: "text",
},
{
lable: '卡密',
prop: 'kami',
type: "text",
},
{
lable: '创建时间',
prop: 'createTime',
type: "time",
}
],
fachu: [],
huishou: [],
tableData: [],
selectionData: [],
}
},
methods: {
/**
* 上传表格检查
*/
beforeUpload(rawFile) {
let imgList = ['text/csv', 'application/vnd.ms-excel', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
if (imgList.indexOf(rawFile.type) === -1) {
this.$msgbox.alert('请上传excel,csv格式的表格文件!')
return false
} else if (rawFile.size / 1024 / 1024 > 50) {
this.$msgbox.alert('表格文件的大小为小于50MB,数据过多时会处理过慢')
return true
}
return true
},
//
async fachuSubmit(opts) {
let that = this
let file = opts.file
this.fileDealData = []
let fileReader = new FileReader()
fileReader.onload = async function () {
let data = this.result
let workbook = XLSX.read(data, { type: 'binary' })
let sheetName = workbook.SheetNames[0]
let sheetData = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName])
let list = []
for (let i = 0; i < sheetData.length; i++) {
let element = sheetData[i];
list.push({
kahao: element.卡号,
kami: element.卡密,
createTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
})
}
//
let compareObjects = (obj1, obj2) => {
return obj1.kahao === obj2.kahao && obj1.kami === obj2.kami;
};
// 使
that.tableData = _.unionWith(that.tableData, list, compareObjects);
}
fileReader.onerror = function (error) {
console.error('Error reading file:', error)
}
fileReader.readAsArrayBuffer(file)
},
//
async huishouSubmit(opts) {
let that = this
let file = opts.file
this.fileDealData = []
let fileReader = new FileReader()
fileReader.onload = async function () {
let data = this.result
let workbook = XLSX.read(data, { type: 'binary' })
let sheetName = workbook.SheetNames[0]
let sheetData = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName])
let list = []
for (let i = 0; i < sheetData.length; i++) {
let element = sheetData[i];
list.push({
kahao: element.卡号,
kami: element.卡密,
createTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
})
}
//
let compareObjects = (obj1, obj2) => {
return obj1.kahao === obj2.kahao && obj1.kami === obj2.kami;
};
// 使
that.tableData = _.differenceWith(that.tableData, list, compareObjects);
}
fileReader.onerror = function (error) {
console.error('Error reading file:', error)
}
fileReader.readAsArrayBuffer(file)
},
//
dealTableData() {
let tableDataLocalTemp = localStorage.getItem('tableDataLocal')
if (tableDataLocalTemp) {
try {
this.tableData = JSON.parse(tableDataLocalTemp)
} catch (e) {
this.tableData = []
}
}
},
//
delData() {
if (this.$refs.kahaoTable) {
let list = this.$refs.kahaoTable.getSelectionRows()
ElMessageBox.confirm(`是否删除卡号为(${_.join(_.map(list, 'kahao'), '、')})的数据`, '警告', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'error',
draggable: true,
}).then(() => {
//
let compareObjects = (obj1, obj2) => {
return obj1.kahao === obj2.kahao && obj1.kami === obj2.kami;
};
// 使
this.tableData = _.differenceWith(this.tableData, list, compareObjects);
ElMessage({
type: 'success',
message: `已删除卡号为(${_.join(_.map(list, 'kahao'), '、')})的数据`,
})
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除操作',
})
})
}
},
},
async mounted() {
let fiveDay = dayjs('2024-08-05T00:00:00').valueOf()
if (!this.isVip) {
if (dayjs().valueOf() > fiveDay) {
this.isVip = false
return
} else {
this.isVip = true
}
}
this.dealTableData()
},
watch: {
"tableData": {
handler(nvl, ovl) {
localStorage.setItem('tableDataLocal', JSON.stringify(nvl))
},
deep: true
}
},
computed: {}
}
</script>
<style scoped>
.appClass {
width: 100vw;
height: 100vh;
color: #000;
}
.mainClass {
padding: 8px 20px;
}
.tableClass {
width: 100%;
height: calc(100vh - 56px);
}
.noVip {
text-align: center;
margin-top: 40vh;
}
.noVipTag {
font-size: 3em;
line-height: 1.6em;
height: 1.6em;
}
</style>

5
src/renderer/src/assets/css/base.css

@ -0,0 +1,5 @@
html,
body {
margin: 0;
padding: 0;
}

25
src/renderer/src/main.js

@ -0,0 +1,25 @@
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import App from './App.vue'
import './assets/css/base.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus, {
locale: zhCn,
size: 'large'
})
app.mount('#app')
document.addEventListener('keydown', (event) => {
const { ctrlKey, shiftKey, key } = event
if ((ctrlKey && shiftKey && key === 'I') || key === 'F12') {
return event.preventDefault()
}
})
Loading…
Cancel
Save