Browse Source

11.18

master
lichong 1 month ago
commit
18d8ff7750
  1. 9
      .editorconfig
  2. 5
      .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. 38
      package.json
  15. BIN
      public/icon/icon.jpg
  16. 71
      src/main/index.js
  17. 21
      src/preload/dl.js
  18. 21
      src/preload/index.js
  19. 18
      src/renderer/index.html
  20. 65
      src/renderer/src/App.vue
  21. 5
      src/renderer/src/assets/css/base.css
  22. BIN
      src/renderer/src/assets/excel/WKM25471-02D ASN .xlsx
  23. BIN
      src/renderer/src/assets/excel/WKM25471-02D装箱单(1).xlsx
  24. 50
      src/renderer/src/assets/json/shujuguanli.json
  25. 75
      src/renderer/src/components/tablecomponent.vue
  26. 33
      src/renderer/src/main.js
  27. 220
      src/renderer/src/views/shujuguanli.vue

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

5
.gitignore

@ -0,0 +1,5 @@
node_modules
dist
out
*.log*
package-lock.json

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: 表格数据管理-${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"
]
}

38
package.json

@ -0,0 +1,38 @@
{
"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": {
"npmi": "npm i",
"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",
"dexie": "^4.0.8",
"element-plus": "^2.7.1",
"lodash": "^4.17.21",
"vxe-table": "^4.7.87"
},
"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

71
src/main/index.js

@ -0,0 +1,71 @@
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'))
// 主窗口
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
minWidth: 1366,
minHeight: 900,
height: 1260,
width: 1730,
show: false,
autoHideMenuBar: true,
icon: logoIcon,
webPreferences: {
preload: path.resolve(__dirname, '../preload/index.js'),
sandbox: false,
nodeIntegration: true,
contextIsolation: 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
}

18
src/renderer/index.html

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>表格数据管理</title>
<!-- <link rel="icon" href="/icon/icon.jpg" /> -->
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self';img-src 'self' data:; style-src 'self' 'unsafe-inline';font-src 'self' data:;" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

65
src/renderer/src/App.vue

@ -0,0 +1,65 @@
<template>
<div class="appClass" v-if="isVip">
<shujuguanliComponent ref="shujuguanli"> </shujuguanliComponent>
</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 { myDatabase } from './assets/js/db.js'
import shujuguanliComponent from "./views/shujuguanli.vue"
import { ElMessage } from 'element-plus';
export default {
name: 'app',
components: { shujuguanliComponent },
data() {
return {
_: _,
dayjs: dayjs,
isVip: true,
jihuoma: "",
zhucema: "",
}
},
watch: {},
computed: {},
methods: {
},
async mounted() {
let fiveDay = dayjs('2024-11-7T00:00:00').valueOf()
if (!this.isVip) {
if (dayjs().valueOf() > fiveDay) {
this.isVip = false
return
} else {
this.isVip = true
}
}
},
}
</script>
<style scoped>
.appClass {
width: 100vw;
height: 100vh;
position: relative;
}
.noVip {
text-align: center;
margin-top: calc(50vh - 98px);
}
.noVipTag {
font-size: 48px;
line-height: 48px;
padding: 36px;
}
</style>

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

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

BIN
src/renderer/src/assets/excel/WKM25471-02D ASN .xlsx

Binary file not shown.

BIN
src/renderer/src/assets/excel/WKM25471-02D装箱单(1).xlsx

Binary file not shown.

50
src/renderer/src/assets/json/shujuguanli.json

@ -0,0 +1,50 @@
[
{
"label": "姓名",
"prop": "xingming",
"type": "text",
"isSort": false,
"tableShow": true,
"formShow": true
},
{
"label": "处方照片",
"prop": "chufang",
"type": "photo",
"isSort": false,
"tableShow": true,
"formShow": true
},
{
"label": "舌苔照片",
"prop": "shetai",
"type": "photo",
"isSort": false,
"tableShow": true,
"formShow": true
},
{
"label": "备注",
"prop": "note",
"type": "textarea",
"isSort": false,
"tableShow": true,
"formShow": true
},
{
"label": "创建时间",
"prop": "create_at",
"type": "date",
"isSort": true,
"tableShow": true,
"formShow": true
},
{
"label": "更新时间",
"prop": "update_at",
"type": "date",
"isSort": true,
"tableShow": true,
"formShow": true
}
]

75
src/renderer/src/components/tablecomponent.vue

@ -0,0 +1,75 @@
<template>
<div class="tableClass">
<el-auto-resizer>
<template #default="{ height, width }">
<vxe-table show-overflow :data="tableData" round :height="height" :scroll-y="{ enabled: true, gt: 20 }"
:checkbox-config="{ labelField: 'seq', highlight: true }" ref="tableRef" border
:column-config="{ resizable: true }">
<vxe-column type="seq" width="70"></vxe-column>
<vxe-column :field="headerItem.prop" :title="headerItem.label"
:min-width="`${headerItem.label.length * 23 + 24}`" v-for="(headerItem, headerIndex) in tableHeader"
:key="headerIndex" :sortable="headerItem.isSort">
<template #default="{ row }">
<template v-if="headerItem.type === 'text'">
<span>{{ row[headerItem.prop] }}</span>
</template>
<template v-else-if="headerItem.type === 'date'">
<span>{{ dayjs(row[headerItem.prop]).format("YYYY-MM-DD HH:mm:ss") }}</span>
</template>
<template v-else-if="headerItem.type === 'photo'">
<span v-if="row[headerItem.prop]">
<el-image style="width: 45px" :src="row[headerItem.prop]" :zoom-rate="1.2" :max-scale="7"
:min-scale="0.2" :preview-src-list="[row[headerItem.prop]]" :initial-index="4" fit="cover" />
</span>
<span v-else></span>
</template>
<template v-else>{{ row[headerItem.prop] }}</template>
</template>
</vxe-column>
</vxe-table>
</template>
</el-auto-resizer>
</div>
</template>
<script>
import _ from 'lodash'
import dayjs from 'dayjs'
export default {
name: 'tablecomponent',
components: {},
emits: [],
props: {
tableHeader: {
type: Array,
default: () => {
return []
}
},
tableData: {
type: Array,
default: () => {
return []
}
},
},
data() {
return {
_: _,
dayjs: dayjs,
}
},
methods: {},
async mounted() { },
watch: {},
computed: {}
}
</script>
<style scoped>
.tableClass {
height: calc(100vh - 70px);
text-align: center;
text-align-last: center;
}
</style>

33
src/renderer/src/main.js

@ -0,0 +1,33 @@
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
import App from './App.vue'
import './assets/css/base.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import VxeUI from 'vxe-pc-ui'
import 'vxe-pc-ui/lib/style.css'
// ...
// 完整导入 表格库
import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'
const app = createApp(App)
// window.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus, {
locale: zhCn,
})
app.use(VxeUI).use(VxeUITable)
app.mount('#app')
document.addEventListener('keydown', (event) => {
const { ctrlKey, shiftKey, key } = event
if ((ctrlKey && shiftKey && key === 'I') || key === 'F12') {
return event.preventDefault()
}
})

220
src/renderer/src/views/shujuguanli.vue

@ -0,0 +1,220 @@
<template>
<div class="rightClass">
<div>
<el-row style="margin-bottom: 8px;">
<el-col>
<div class="inputClass uploadClass">
<el-upload :show-file-list="false" v-model="fileOriData" :before-upload="beforeAvatarUpload"
:http-request="successSubmit1" accept=".xls,.xlsx,.csv">
<el-button type="success">
<el-icon>
<Upload />
</el-icon>
<span>导入A表</span>
</el-button>
</el-upload>
</div>
<div class="inputClass uploadClass">
<el-upload :show-file-list="false" v-model="fileOriData" :before-upload="beforeAvatarUpload"
:http-request="successSubmit2" accept=".xls,.xlsx,.csv">
<el-button type="success">
<el-icon>
<Upload />
</el-icon>
<span>导入B表</span>
</el-button>
</el-upload>
</div>
<el-button type="primary" @click="exportData" :disabled="!tableData.length">
<el-icon>
<Download />
</el-icon>
<span>导出</span>
</el-button>
</el-col>
</el-row>
</div>
<div v-loading="loading">
<tablecomponent :tableHeader="tableHeader" :tableData="tableData" ref="tableComponentRef">
</tablecomponent>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import tableHeaderLocal from '../assets/json/shujuguanli.json'
import dayjs from 'dayjs'
import tablecomponent from "../components/tablecomponent.vue"
import { ElMessage, ElMessageBox } from 'element-plus'
export default {
name: 'chufang',
components: { tablecomponent },
watch: {},
computed: {},
data() {
return {
_: _,
dayjs: dayjs,
tableHeader: [],
tableData: [],
loading: false,
}
},
methods: {
/**
* 上传表格检查
*/
beforeAvatarUpload(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
},
//A
async successSubmit1(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])
for (let i = 0; i < sheetData.length; i++) {
let element = sheetData[i];
let dealItem = {}
for (let key in element) {
let headerItem = _.find(tableHeaderLocal, o => o.label === key)
if (headerItem) {
dealItem[headerItem.prop] = element[key]
}
}
try {
await myDatabase.student.add({
...dealItem,
...that.dealRow(dealItem),
create_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
update_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
})
} catch (error) {
ElMessage({
message: `${dealItem.name}(${dealItem.xuejihao})学籍号重复,不可再次新增该学生信息`,
type: 'error',
})
}
}
await that.searchData(true)
}
fileReader.onerror = function (error) {
ElMessage({
message: `Error reading file:, ${error}`,
type: 'error',
})
}
fileReader.readAsArrayBuffer(file)
},
//B
async successSubmit2(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])
for (let i = 0; i < sheetData.length; i++) {
let element = sheetData[i];
let dealItem = {}
for (let key in element) {
let headerItem = _.find(tableHeaderLocal, o => o.label === key)
if (headerItem) {
dealItem[headerItem.prop] = element[key]
}
}
try {
await myDatabase.student.add({
...dealItem,
...that.dealRow(dealItem),
create_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
update_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
})
} catch (error) {
ElMessage({
message: `${dealItem.name}(${dealItem.xuejihao})学籍号重复,不可再次新增该学生信息`,
type: 'error',
})
}
}
await that.searchData(true)
}
fileReader.onerror = function (error) {
ElMessage({
message: `Error reading file:, ${error}`,
type: 'error',
})
}
fileReader.readAsArrayBuffer(file)
},
//
async exportData() {
let listCopy = _.cloneDeep(this.tableData)
let lilstLocal = []
for (let i = 0; i < listCopy.length; i++) {
let item = listCopy[i];
let listItem = {}
for (let j = 0; j < this.exportHeader.length; j++) {
let headerItem = this.exportHeader[j];
listItem[headerItem.label] = item[`${headerItem.prop}_org`] || item[`${headerItem.prop}`]
}
lilstLocal.push(listItem)
}
let jsonWorkSheet = XLSX.utils.json_to_sheet(lilstLocal);
let workBook = {
SheetNames: ["sheet1"],
Sheets: {
["sheet1"]: jsonWorkSheet,
}
};
XLSX.writeFile(workBook, `数据表格管理${dayjs().format("YYYY-MM-DD_HH-mm-ss")}.xls`);
},
},
async mounted() {
this.tableHeader = _.filter(tableHeaderLocal, o => o.tableShow)
this.formHeader = _.filter(tableHeaderLocal, o => o.formShow)
await this.updateSeach()
},
}
</script>
<style scoped>
.rightClass {
padding: 8px;
}
.uploadClass {
top: 3px;
}
.inputClass {
display: inline-block;
margin: 0 8px;
position: relative;
}
.tagClass {
display: inline-block;
margin: 0 8px;
width: 200px;
position: relative;
}
</style>
Loading…
Cancel
Save