Browse Source

11.25

master
lichong 4 weeks ago
commit
bf671dcf59
  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. 42
      package.json
  15. BIN
      public/icon/icon.jpg
  16. 73
      src/main/index.js
  17. 21
      src/preload/dl.js
  18. 21
      src/preload/index.js
  19. 18
      src/renderer/index.html
  20. 137
      src/renderer/src/App.vue
  21. 13
      src/renderer/src/assets/css/base.css
  22. 9
      src/renderer/src/assets/js/db.js
  23. 6144
      src/renderer/src/assets/json/jifen.json
  24. 545
      src/renderer/src/assets/json/student.json
  25. 84
      src/renderer/src/components/formcomponent.vue
  26. 210
      src/renderer/src/components/tablecomponent.vue
  27. 34
      src/renderer/src/main.js
  28. 774
      src/renderer/src/views/student.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"
]
}

42
package.json

@ -0,0 +1,42 @@
{
"name": "electron",
"version": "1.0.5",
"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",
"js-sha256": "^0.11.0",
"lodash": "^4.17.21",
"node-machine-id": "^1.1.12",
"vue-web-screen-shot": "^1.5.3",
"vxe-table": "^4.7.87",
"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

73
src/main/index.js

@ -0,0 +1,73 @@
import { app, shell, BrowserWindow, nativeImage, ipcMain } from 'electron'
import * as path from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { machineIdSync } from 'node-machine-id';
// 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,
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()
})
ipcMain.on('deviceIdentify', () => {
let res = machineIdSync()
mainWindow.webContents.send(`deviceResult`, res)
})
})
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>

137
src/renderer/src/App.vue

@ -0,0 +1,137 @@
<template>
<div class="appClass" v-if="isVip">
<studentComponent ref="student"> </studentComponent>
</div>
<div v-else class="noVip">
<div style="margin: 98px 0;">
<el-input v-model="zhucema" disabled style="width: 840px" placeholder="请输入激活码" size="large">
<template #prepend>
<el-button type="primary" @click="copyzuce">复制注册码</el-button>
</template>
<template #append>
<el-button type="primary" @click="zhuce">生成注册码</el-button>
</template>
</el-input>
</div>
<div>
<el-input v-model.trim="jihuoma" style="width: 840px" placeholder="请输入激活码" size="large">
<template #append>
<el-button type="primary" @click="jihuo">激活</el-button>
</template>
</el-input>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import dayjs from 'dayjs'
import { sha256 } from 'js-sha256';
import { myDatabase } from './assets/js/db.js'
import studentComponent from "./views/student.vue"
import { ElMessage } from 'element-plus';
export default {
name: 'app',
components: { studentComponent },
data() {
return {
_: _,
dayjs: dayjs,
isVip: false,
jihuoma: "",
zhucema: "",
}
},
watch: {},
computed: {},
methods: {
zhuce() {
window.electron.ipcRenderer.send('deviceIdentify')
},
//
watchDevice() {
let that = this
window.electron.ipcRenderer.on('deviceResult', (eve, res) => {
that.zhucema = res
localStorage.setItem('zhucema', res)
})
},
async copyzuce() {
if (!_.trim(this.zhucema)) {
ElMessage({
type: "error",
message: "请生成注册码后,在复制",
duration: 3000
})
} else {
try {
await navigator.clipboard.writeText(_.trim(this.zhucema));
ElMessage(
{
type: 'success',
message: '注册码复制成功,请发送给管理员进行激活',
duration: 3000
}
);
} catch (err) {
alert('无法复制,请手动选择复制');
}
}
},
jihuo() {
let minsha = sha256(_.trim(this.zhucema))
minsha = _.toUpper(minsha)
if (minsha === _.trim(this.jihuoma)) {
this.isVip = true
localStorage.setItem('isVip', JSON.stringify({ isVip: this.isVip }))
} else {
this.isVip = false
ElMessage({
type: 'error',
message: '激活码错误,请输入正确的激活码后使用',
showClose: true,
duration: 3000
})
}
let params = {
jihuoma: _.trim(this.jihuoma),
zhucema: _.trim(this.zhucema),
isVip: this.isVip,
create_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
update_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
}
myDatabase.jihuoshijian.add(params)
},
},
async mounted() {
this.watchDevice()
this.zhucema = localStorage.getItem('zhucema')
let isVipObj = JSON.parse(localStorage.getItem('isVip') || "{}")
if (!_.isEmpty(isVipObj)) {
this.isVip = isVipObj.isVip
}
if (!this.isVip) {
this.zhuce()
}
},
}
</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>

13
src/renderer/src/assets/css/base.css

@ -0,0 +1,13 @@
html,
body {
margin: 0;
padding: 0;
}
.red {
color: #f56c6c;
}
.blue {
color: #409eff;
}

9
src/renderer/src/assets/js/db.js

@ -0,0 +1,9 @@
import Dexie from 'dexie';
export const myDatabase = new Dexie('myDatabase');
// 定义数据库版本
myDatabase.version(1).stores({
student: '++id, nianjicode, banjicode, nianji, banji, &xuejihao, minzudaima, minzu, name, gender, birth, address, height, weight, BMI, BMIscore, lungs, lungsscore, fifty, fiftyscore, sitforward, sitforwardscore, jump, jumpscore, situp, situpscore, fiftyeight, fiftyeightscore, stand, standscore, eighty, eightyscore, thousand ,thousandscore, pullup, pullupscore, totalScore, level, note1, nodt2, create_at, update_at',
jihuoshijian: '++id, jihuoma, isVip, zhucema, create_at, update_at'
});

6144
src/renderer/src/assets/json/jifen.json

File diff suppressed because it is too large

545
src/renderer/src/assets/json/student.json

@ -0,0 +1,545 @@
[
{
"label": "年级编号",
"prop": "nianjicode",
"type": "text",
"index": "0",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"searchType": "select",
"openPopoverVisible": false,
"searchValue": "",
"formShow": true,
"export": true
},
{
"label": "班级编号",
"prop": "banjicode",
"type": "text",
"index": "1",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"searchType": "select",
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": true
},
{
"label": "年级",
"prop": "nianji",
"type": "text",
"index": "-1",
"fixed": false,
"tableShow": false,
"setTableShow": false,
"formShow": false,
"export": false
},
{
"label": "班级名称",
"prop": "banji",
"type": "text",
"index": "2",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": true,
"export": true
},
{
"label": "学籍号",
"prop": "xuejihao",
"type": "text",
"index": "3",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": true
},
{
"label": "民族代码",
"prop": "minzudaima",
"type": "text",
"index": "4",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": true
},
{
"label": "民族",
"prop": "minzu",
"type": "text",
"index": "-1",
"fixed": false,
"tableShow": false,
"formShow": false,
"export": false
},
{
"label": "姓名",
"prop": "name",
"type": "text",
"index": "5",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": true,
"export": true
},
{
"label": "性别",
"prop": "gender",
"tableDisplay": [
{
"label": "男",
"value": "1"
},
{
"label": "女",
"value": "2"
}
],
"type": "select",
"index": "6",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": true,
"export": true
},
{
"label": "出生日期",
"prop": "birth",
"type": "date",
"default": 10,
"index": "7",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": true
},
{
"label": "家庭住址",
"prop": "address",
"type": "text",
"index": "8",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"isSort": true,
"isSearch": true,
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": true
},
{
"label": "身高",
"prop": "height",
"type": "number",
"index": "9",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "体重",
"prop": "weight",
"type": "number",
"index": "10",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "BMI",
"prop": "BMI",
"type": "number",
"index": "11",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "BMI得分",
"prop": "BMIscore",
"type": "number",
"index": "12",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "肺活量",
"prop": "lungs",
"type": "number",
"index": "13",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "肺活量得分",
"prop": "lungsscore",
"type": "number",
"index": "14",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "50米跑",
"prop": "fifty",
"type": "text",
"index": "15",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "50米跑得分",
"prop": "fiftyscore",
"type": "number",
"index": "16",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "坐位体前屈",
"prop": "sitforward",
"type": "number",
"index": "17",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "坐位体前屈得分",
"prop": "sitforwardscore",
"type": "number",
"index": "18",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "一分钟跳绳",
"prop": "jump",
"type": "number",
"index": "19",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "一分钟跳绳得分",
"prop": "jumpscore",
"type": "number",
"index": "20",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "一分钟仰卧起坐",
"prop": "situp",
"type": "number",
"index": "21",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "一分钟仰卧起坐得分",
"prop": "situpscore",
"type": "number",
"index": "22",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "50米×8往返跑",
"prop": "fiftyeight",
"type": "text",
"index": "23",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "50米×8往返跑得分",
"prop": "fiftyeightscore",
"type": "number",
"index": "24",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "立定跳远",
"prop": "stand",
"type": "number",
"index": "25",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "立定跳远得分",
"prop": "standscore",
"type": "number",
"index": "26",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "800米跑",
"prop": "eighty",
"type": "text",
"index": "27",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "800米跑得分",
"prop": "eightyscore",
"type": "number",
"index": "28",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "1000米跑",
"prop": "thousand",
"type": "text",
"index": "29",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "1000米跑得分",
"prop": "thousandscore",
"type": "number",
"index": "30",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "引体向上",
"prop": "pullup",
"type": "number",
"index": "31",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": true,
"export": true
},
{
"label": "得分",
"label1": "引体向上得分",
"prop": "pullupscore",
"type": "number",
"index": "32",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "加分项",
"prop": "addScore",
"type": "text",
"index": "-1",
"fixed": false,
"tableShow": true,
"setTableShow": true,
"formShow": false,
"export": false
},
{
"label": "总分",
"prop": "totalScore",
"type": "number",
"index": "33",
"tableShow": true,
"fixed": "right",
"setTableShow": false,
"sort": "",
"formShow": false,
"export": false
},
{
"label": "等级",
"prop": "level",
"type": "text",
"index": "34",
"tableShow": true,
"fixed": "right",
"setTableShow": false,
"sort": "",
"isSearch": true,
"searchType": "select",
"openPopoverVisible": false,
"searchValue": "",
"formShow": false,
"export": false
},
{
"label": "备注2",
"prop": "nodt2",
"type": "text",
"index": "-1",
"fixed": false,
"tableShow": false,
"formShow": false,
"export": false
},
{
"label": "创建时间",
"prop": "create_at",
"type": "date",
"default": 0,
"tableShow": false,
"formShow": false,
"export": false
},
{
"label": "更新时间",
"prop": "update_at",
"type": "date",
"default": 0,
"tableShow": false,
"formShow": false,
"export": false
}
]

84
src/renderer/src/components/formcomponent.vue

@ -0,0 +1,84 @@
<template>
<div class="tableClass">
<el-form :model="formData" label-suffix="" :disabled="disabled" inline>
<el-form-item :label="formItem.label" :key="formIndex" v-for="(formItem, formIndex) in formHeader">
<template v-if="formItem.type === 'text'">
<el-input v-model="formData[formItem.prop]" />
</template>
<template v-if="formItem.type === 'select'">
<el-select v-model="formData[formItem.prop]" size="large" style="width: 240px">
<el-option v-for="item in formItem.tableDisplay" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</template>
<template v-else-if="formItem.type === 'number'">
<el-input-number v-model="formData[formItem.prop]" :min="formItem.min" :max="formItem.max">
<template #decrease-icon>
<el-icon>
<ArrowDown />
</el-icon>
</template>
<template #increase-icon>
<el-icon>
<ArrowUp />
</el-icon>
</template>
</el-input-number>
</template>
<template v-else-if="formItem.type === 'date'">
<el-date-picker v-model="formData[formItem.prop]" type="date"
:disabled="['create_at', 'update_at'].indexOf(formItem.prop) !== -1"
:placeholder="`请选择${formItem.label}`" />
</template>
</el-form-item>
</el-form>
</div>
</template>
<script>
import _, { max, min } from 'lodash'
import dayjs from 'dayjs'
import { ElMessage, ElMessageBox } from 'element-plus'
export default {
name: 'formcomponent',
components: {},
props: {
formHeader: {
type: Array,
default: () => {
return []
}
},
formData: {
type: Object,
default: () => {
return {}
}
},
disabled: {
type: Boolean,
default: () => {
return false
}
}
},
data() {
return {
_: _,
dayjs: dayjs,
}
},
methods: {},
async mounted() { },
watch: {},
computed: {}
}
</script>
<style scoped>
.tableClass {
text-align: left;
text-align-last: left;
}
</style>

210
src/renderer/src/components/tablecomponent.vue

@ -0,0 +1,210 @@
<template>
<div class="tableClass">
<vxe-table show-overflow :data="tableData" round :height="690" :scroll-y="{ enabled: true, gt: 20 }"
:checkbox-config="{ labelField: 'seq', highlight: true }" @checkbox-all="selectAllChangeEvent"
@checkbox-change="selectChange" ref="tableRef" border :column-config="{ resizable: true }">
<vxe-column type="seq" width="70" v-if="hiddenXuhao"></vxe-column>
<vxe-column type="checkbox" width="70" fixed="left"></vxe-column>
<vxe-column field="totalScore" title="总分" width="70" fixed="left" class-name="red"
header-class-name="red"></vxe-column>
<vxe-column field="level" title="等级" width="70" fixed="left" class-name="blue"
header-class-name="blue"></vxe-column>
<template v-for="(headerItem, headerIndex) in tableHeader" :key="headerIndex">
<vxe-column :field="headerItem.prop" :title="headerItem.label" :width="`${headerItem.label.length * 23 + 24}`"
:class-name="colorList[headerItem.label] || ''" :header-class-name="colorList[headerItem.label] || ''"
:fixed="!!headerItem.fixed ? 'left' : ''" :sortable="headerItem.isSort"
v-if="['level', 'totalScore'].indexOf(headerItem.prop) === -1">
<template #default="{ row }">
<template v-if="headerItem.type === 'text'">
<span>{{ row[headerItem.prop] }}</span>
</template>
<template v-else-if="headerItem.type === 'select'">
<span>{{ (_.find(headerItem.tableDisplay, { value: `${row[headerItem.prop]}` }) || {}).label }}</span>
</template>
<template v-else-if="headerItem.type === 'date'">
<span>{{ row[headerItem.prop] }}</span>
</template>
<template v-else-if="headerItem.type === 'number'">
<span>{{ row[headerItem.prop] }}</span>
</template>
<template v-else>{{ row[headerItem.prop] }}</template>
</template>
</vxe-column>
</template>
<vxe-column fixed="right" title="操作" width="145">
<template #default="{ row }">
<el-button type="primary" circle @click="optClick(row, 'edit')">
<el-icon>
<Edit />
</el-icon>
</el-button>
<!-- <el-button type="info" circle @click="optClick(row, 'info')">
<el-icon>
<InfoFilled />
</el-icon>
</el-button> -->
<el-button type="danger" circle @click="optClick(row, 'del')">
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</vxe-column>
</vxe-table>
<!-- <el-pagination style="margin:8px 0 0 0;place-content:center;" v-model:current-page="currentPage"
v-model:page-size="pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper"
:page-sizes="pageSizes"></el-pagination> -->
</div>
</template>
<script>
import _ from 'lodash'
import dayjs from 'dayjs'
import { ElMessage, ElMessageBox } from 'element-plus'
export default {
name: 'tablecomponent',
components: {},
emits: ["selectChange", "handleCurrentChange", "handleSizeChange", "edit", "info", "del", "searchValue", "headerSort"],
props: {
tableHeader: {
type: Array,
default: () => {
return []
}
},
tableData: {
type: Array,
default: () => {
return []
}
},
pageSizes: {
type: Array,
default: () => {
return [15, 50, 75, 100]
}
},
total: {
type: Number,
default: () => {
return 0
}
},
searchParams: {
type: Object,
default: () => {
return {}
}
},
hiddenXuhao: {
type: Boolean,
default: () => {
return false
}
},
},
data() {
return {
_: _,
dayjs: dayjs,
currentPage: 1,
pageSize: 15,
colorList: {
得分: "red",
BMI得分: "red",
总分: "red",
等级: "blue",
}
}
},
methods: {
selectChange({ checked }) {
let $table = this.$refs.tableRef
if ($table) {
let selection = $table.getCheckboxRecords()
this.$emit("selectChange", selection)
}
},
selectAllChangeEvent({ checked }) {
let $table = this.$refs.tableRef
if ($table) {
let selection = $table.getCheckboxRecords()
this.$emit("selectChange", selection)
}
},
handleSizeChange(pageSize) {
this.$emit("handleSizeChange", pageSize)
},
handleCurrentChange(currentPage) {
this.$emit("handleCurrentChange", currentPage)
},
optClick(row, type) {
this.$emit(type, row)
},
// key
getRowKeys(row) {
return row.id; //
},
clearSelection() {
const $table = this.$refs.tableRef
if ($table) {
$table.clearCheckboxRow()
}
},
searchValue(item) {
item.popoverVisible = false
this.$emit("searchValue")
},
// popoverp
openPopoverVisible(item) {
for (let i = 0; i < this.tableHeader.length; i++) {
let headerItem = this.tableHeader[i];
headerItem.popoverVisible = false
}
item.popoverVisible = true
},
headerSort(headerItem) {
for (let i = 0; i < this.tableHeader.length; i++) {
let item = this.tableHeader[i];
if (headerItem.prop !== item.prop) {
item.sort = ""
}
}
let sortOld = headerItem.sort
if (sortOld === "") {
headerItem.sort = 1
} else if (sortOld === 1) {
headerItem.sort = -1
} else if (sortOld === -1) {
headerItem.sort = ""
}
this.$emit("headerSort", headerItem)
},
},
async mounted() { },
watch: {
currentPage: {
handler(val) {
this.$emit("handleCurrentChange", val)
},
immediate: true
},
pageSize: {
handler(val) {
this.$emit("handleSizeChange", val)
},
immediate: true
},
},
computed: {}
}
</script>
<style scoped>
.tableClass {
height: calc(100vh - 325px);
text-align: center;
text-align-last: center;
}
</style>

34
src/renderer/src/main.js

@ -0,0 +1,34 @@
// 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'
import screenShort from "vue-web-screen-shot";
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(screenShort, { enableWebRtc: false, level: 300001, hiddenScrollBar: true }).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()
}
})

774
src/renderer/src/views/student.vue

@ -0,0 +1,774 @@
<template>
<div class="rightClass">
<div>
<el-row>
<el-col v-for="(headerItem, headerIndex) in _.filter(tableHeader, item => item.isSearch)" :key="headerIndex"
:span="headerItem.searchType === 'select' ? 5 : 4">
<div>
<span style="margin-right: 4px;">{{ headerItem.label }}:</span>
<template v-if="headerItem.searchType === 'select'">
<el-select v-model="searchParams[headerItem.prop]" size="large" multiple
style="width: 150px;padding-right: 8px;margin:8px 0" clearable collapse-tags collapse-tags-tooltip
:max-collapse-tags="1" @change="searchData()">
<el-option v-for="item in headerItem.options" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</template>
<template v-else-if="headerItem.type === 'text'">
<el-input v-model="searchParams[headerItem.prop]" style="width: 150px;padding-right: 8px;margin:8px 0"
clearable @keyup.enter="searchData()" size="large" />
</template>
<template v-else-if="headerItem.type === 'select'">
<el-select v-model="searchParams[headerItem.prop]" size="large"
style="width: 150px;padding-right: 8px;margin:8px 0" clearable @change="searchData()">
<el-option v-for="item in headerItem.tableDisplay" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</template>
<template v-else-if="headerItem.type === 'date'">
<el-date-picker v-model="searchParams[headerItem.prop]" type="date"
:disabled="['create_at', 'update_at'].indexOf(headerItem.prop) !== -1"
:placeholder="`请选择${headerItem.label}`" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
style="width: 150px;padding-right: 8px;margin:8px 0" size="large" clearable @change="searchData()" />
</template>
</div>
</el-col>
<el-col :span="3">
<el-button type="primary" @click="searchData()" size="large" style="margin-top: 8px;">
<el-icon>
<Search />
</el-icon>
<span>搜索</span>
</el-button>
</el-col>
</el-row>
<el-button type="primary" @click="addData">
<el-icon>
<Plus />
</el-icon>
<span>新增</span>
</el-button>
<el-button type="danger" @click="deleteData(selectionData)" :disabled="selectionData.length == 0">
<el-icon>
<Delete />
</el-icon>
<span>删除</span>
</el-button>
<el-button type="danger" @click="delAll">
<el-icon>
<Delete />
</el-icon>
<span>清空</span>
</el-button>
<div class="inputClass uploadClass">
<el-upload :show-file-list="false" v-model="fileOriData" :before-upload="beforeAvatarUpload"
:http-request="successSubmit" accept=".xls,.xlsx,.csv">
<el-button type="success">
<el-icon>
<Upload />
</el-icon>
<span>导入</span>
</el-button>
</el-upload>
</div>
<el-button type="primary" @click="exportData">
<el-icon>
<Download />
</el-icon>
<span>导出</span>
</el-button>
<el-button type="primary" @click="searchData">
<el-icon>
<Refresh />
</el-icon>
<span>刷新</span>
</el-button>
<el-button type="primary" @click="clearSelection">
<el-icon>
<Refresh />
</el-icon>
<span>清空搜索</span>
</el-button>
<el-button type="primary" @click="clearLocalstory">
<el-icon>
<Refresh />
</el-icon>
<span>清空表头缓存</span>
</el-button>
<el-tooltip effect="dark" content="设置表头" placement="top">
<el-button type="primary" @click="setHeader">
<el-icon>
<Setting />
</el-icon>
<span>设置表头</span>
</el-button>
</el-tooltip>
<!-- <div class="inputClass">
<el-input v-model.trim="searchParams.name" style="max-width: 600px" placeholder="请输入姓名" clearable
@clear="searchData" @keyup.enter="searchData">
<template #append>
<el-button @click="searchData">
<el-icon>
<Search />
</el-icon>
</el-button>
</template>
</el-input>
</div> -->
</div>
<div>
<el-descriptions direction="vertical" :column="5" border style="margin: 8px 0;">
<el-descriptions-item label="总人数">{{ allData.length }}</el-descriptions-item>
<el-descriptions-item label="优秀人数(优秀率)">
<span style="margin: 0 8px 0 0;">
{{ _.filter(allData, { level: "优秀" }).length }}
</span>
<span>
<el-tag size="small" style="position: relative;top: -1px;">
{{
(Number(((_.filter(allData, { level: "优秀" }).length) / (allData.length || 1)).toFixed(2)) *
100).toFixed(0)
}}%
</el-tag>
</span>
</el-descriptions-item>
<el-descriptions-item label="良好人数(良好率)">
<span style="margin: 0 8px 0 0;">
{{ _.filter(allData, { level: "良好" }).length }}
</span>
<span>
<el-tag size="small" style="position: relative;top: -1px;">
{{
(Number(((_.filter(allData, { level: "良好" })).length / (allData.length || 1)).toFixed(2)) *
100).toFixed(0)
}}%
</el-tag>
</span>
</el-descriptions-item>
<el-descriptions-item label="及格人数(及格率)">
<span style="margin: 0 8px 0 0;">
{{ _.filter(allData, { level: "及格" }).length }}
</span>
<span>
<el-tag size="small" style="position: relative;top: -1px;">
{{
(Number(((_.filter(allData, { level: "及格" })).length / (allData.length || 1)).toFixed(2)) *
100).toFixed(0)
}}%
</el-tag>
</span>
</el-descriptions-item>
<el-descriptions-item label="不及格人数(不及格率)">
<span style="margin: 0 8px 0 0;">
{{ _.filter(allData, { level: "不及格" }).length }}
</span>
<span>
<el-tag size="small" style="position: relative;top: -1px;">
{{
(Number(((_.filter(allData, { level: "不及格" })).length / (allData.length || 1)).toFixed(2)) *
100).toFixed(0)
}}%
</el-tag>
</span>
</el-descriptions-item>
</el-descriptions>
</div>
<div v-loading="loading">
<tablecomponent :tableHeader="tableHeader" :tableData="tableData" :searchParams="searchParams"
:pageSizes="pageSizes" :total="total" @selectChange="selectChange" @handleCurrentChange="handleCurrentChange"
@handleSizeChange="handleSizeChange" @edit="edit" @info="info" @del="del" @searchValue="searchData"
ref="tableComponentRef" @headerSort="headerSort">
</tablecomponent>
</div>
<div style="text-align: center;line-height: 60px;font-size: 1.2em">
<span>守望者网络科技有限公司 Copyright@{{ dayjs().format("YYYY") }} 版权所有北京市房山区 资深教育工作者 李竞飞</span>
</div>
<el-dialog v-model="dialogFrom.visible" :title="dialogFrom.title" width="80%" :close-on-click-modal="false">
<formcomponent :formHeader="dialogFrom.formHeader" :formData="dialogFrom.formData"
:disabled="dialogFrom.disabled">
</formcomponent>
<template #footer>
<div>
<el-button @click="cancelDialog">取消</el-button>
<el-button type="primary" @click="submitDialog"> 确认 </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="setHeaderVisible" title="设置表头" width="80%">
<el-row>
<el-col v-for="(item, index) in setTableHeader" :key="index" :span="8">
<el-checkbox v-model="item.setTableShow" :label="item.label1 || item.label" size="large" />
<el-checkbox v-model="item.fixed" label="固定在左侧" size="large" />
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script>
import _ from 'lodash'
import * as XLSX from 'xlsx';
import tableHeaderLocal from '../assets/json/student.json'
import jifen from '../assets/json/jifen.json'
import { myDatabase } from '../assets/js/db.js'
import dayjs from 'dayjs'
import tablecomponent from "../components/tablecomponent.vue"
import formcomponent from "../components/formcomponent.vue"
import { ElMessage, ElMessageBox } from 'element-plus'
export default {
name: 'student',
components: { tablecomponent, formcomponent },
watch: {},
computed: {},
data() {
return {
_: _,
dayjs: dayjs,
tableHeader: [],
setTableHeader: [],
formHeader: [],
exportHeader: [],
tableData: [],
allData: [],
currentPage: 1,
pageSize: 15,
pageSizes: [15, 50, 75, 100],
total: 0,
fileOriData: null,
searchParams: {},
selectionData: [],
dialogFrom: {
visible: false,
title: "新增学生",
type: "add",
formHeader: [],
formData: {}
},
setHeaderVisible: false,
sortObj: {},
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
},
//
async successSubmit(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)
},
//
dealRow(row) {
row["addScore"] = 0
row["BMI"] = Number(Number(row.weight / (row.height * row.height / 10000)).toFixed(2)) || 0
// let nianjicodeList = ["11", "12","13","14", "15","16","21", "22","23","31", "32","33","41","42"]
let nianjicodeList = ["11", "12", "13", "14", "15", "16", "21", "22", "23"]
if (nianjicodeList.includes(`${row.nianjicode}`)) {
for (let key in jifen) {
let value = jifen[key]
let scoreItem = {}
for (let i = 0; i < value.list.length; i++) {
let item = value.list[i];
let rowValue = this.transTime(value, row[value.prop], true)
let itemValue = this.transTime(value, item[`${row.nianjicode}`], false)
if (`${item.gender}` === `${row.gender}` && `${item.gender}` === `${row.gender}` && this.createComparator(value.fuhao)(rowValue, itemValue)) {
scoreItem = { ...item, [`${key.slice(0, key.length - 5)}_org`]: row[value.prop] }
break
}
}
row[key] = scoreItem.score || ""
row["addScore"] = (Number(row["addScore"] || 0) || 0) + (Number(scoreItem.add_score || 0) || 0)
}
}
// todo
// G2_M2_BMIo2_q2_50s2_u2_w2_y2_50X8AA2_Ac2_800AE2_1000Ag2_
//
// =M2 * 0.15 + O2 * 0.15 + Q2 * 0.2 + S2 * 0.3 + U2 * 0.2
//
// =M2 * 0.15 + O2 * 0.15 + Q2 * 0.2 + S2 * 0.2 + U2 * 0.2 + W2 * 0.1
//
// =M2 * 0.15 + O2 * 0.15 + Q2 * 0.2 + S2 * 0.1 + U2 * 0.1 + W2 * 0.2 + Y2 * 0.1
//
// =IF(G2 = 1, M2 * 0.15 + O2 * 0.15 + Q2 * 0.2 + S2 * 0.1 + AA2 * 0.1 + AG2 * 0.1 + AE2 * 0.2, IF(G2 = 2, M2 * 0.15 + O2 * 0.15 + Q2 * 0.2 + S2 * 0.1 + AA2 * 0.1 + W2 * 0.1 + AC2 * 0.2))
if (["11", "12"].indexOf(`${row.nianjicode}`) !== -1) {
row["totalScore"] = Number(Number(row.BMIscore * 0.15 + row.lungsscore * 0.15 + row.fiftyscore * 0.2 + row.sitforwardscore * 0.3 + row.jumpscore * 0.2).toFixed(2))
} else if (["13", "14"].indexOf(`${row.nianjicode}`) !== -1) {
row["totalScore"] = Number(Number(row.BMIscore * 0.15 + row.lungsscore * 0.15 + row.fiftyscore * 0.2 + row.sitforwardscore * 0.2 + row.jumpscore * 0.2 + row.situpscore * 0.1).toFixed(2))
} else if (["15", "16"].indexOf(`${row.nianjicode}`) !== -1) {
row["totalScore"] = Number(Number(row.BMIscore * 0.15 + row.lungsscore * 0.15 + row.fiftyscore * 0.2 + row.sitforwardscore * 0.1 + row.jumpscore * 0.1 + row.situpscore * 0.2 + row.fiftyeightscore * 0.1).toFixed(2))
} else if (["21", "22", "23"].indexOf(`${row.nianjicode}`) !== -1) {
if (`${row.gender}` === "1") {
row["totalScore"] = Number(Number(row.BMIscore * 0.15 + row.lungsscore * 0.15 + row.fiftyscore * 0.2 + row.sitforwardscore * 0.1 + row.standscore * 0.1 + row.pullupscore * 0.1 + row.thousandscore * 0.2).toFixed(2))
} else if (`${row.gender}` === "2") {
row["totalScore"] = Number(Number(row.BMIscore * 0.15 + row.lungsscore * 0.15 + row.fiftyscore * 0.2 + row.sitforwardscore * 0.1 + row.standscore * 0.1 + row.situpscore * 0.1 + row.eightyscore * 0.2).toFixed(2))
}
}
row["totalScore"] = Number(Number(row["totalScore"] + row["addScore"]).toFixed(2))
//
// = IF(AH2 >= 90, "", IF(AH2 >= 80, "", IF(AH2 >= 60, "", "")))
let totalScoreNumber = Number(row.totalScore)
if (totalScoreNumber >= 90) {
row["level"] = "优秀"
} else if (totalScoreNumber >= 80) {
row["level"] = "良好"
} else if (totalScoreNumber >= 60) {
row["level"] = "及格"
} else if (totalScoreNumber >= 0) {
row["level"] = "不及格"
} else {
row["level"] = ""
}
return row
},
transTime(item, value, isReturn) {
if (item.prop === "fifty" && isReturn) {
return Number(value)
}
if (typeof (value) === "number") {
return value
} else if (typeof (value) === "string") {
let time = value.trim();
let seconds = 0;
if (time.includes("'") && time.includes('"')) {
// 2'20"
let parts = time.split(/['"]/).filter(Boolean);
let minutes = parseInt(parts[0], 10);
let secondsPart = parseInt(parts[1], 10);
seconds = minutes * 60 + secondsPart;
} else if (time.includes('.')) {
// 2.20
let parts = time.split('.');
let minutes = parseInt(parts[0], 10);
let secondsPart = parseFloat('.' + parts[1]) * 60;
seconds = minutes * 60 + secondsPart;
} else {
seconds = Number(time)
}
return seconds
}
},
createComparator(operator) {
switch (operator) {
case '<=':
return (a, b) => a <= b;
case '>=':
return (a, b) => a >= b;
case '==':
return (a, b) => a == b;
case '!=':
return (a, b) => a != b;
case '<':
return (a, b) => a < b;
case '>':
return (a, b) => a > b;
default:
throw new Error('Invalid operator');
}
},
//
async updateSeach(params = {}, isAll = false) {
this.loading = true
let allStudentList = _.cloneDeep(await myDatabase.student.toArray())
if (!_.isEmpty(params)) {
let paramsTemp = {}
for (let key in params) {
let value = params[key]
if (["undefined", "", "null"].indexOf(`${value}`) === -1) {
if (value.indexOf(",")) {
paramsTemp[key] = [..._.split(value, ","), ..._.split(value, ",").map(item => Number(item))]
} else {
paramsTemp[key] = [`${value}`, Number(value)]
}
}
}
let dataTemp = []
if (!_.isEmpty(paramsTemp)) {
for (let i = 0; i < allStudentList.length; i++) {
let element = allStudentList[i];
let istrue = true
for (let key in paramsTemp) {
if (paramsTemp[key].includes(element[key])) {
} else {
istrue = false
}
}
if (istrue) {
dataTemp.push({ ...element })
}
}
allStudentList = _.cloneDeep(dataTemp)
}
}
if (_.get(this.sortObj, ["sort"], "")) {
//
allStudentList = _.cloneDeep(_.orderBy(allStudentList, [this.sortObj.prop], [_.get(this.sortObj, ["sort"], "") === 1 ? 'desc' : 'asc']))
}
let allData = _.uniqBy(allStudentList, "id")
this.allData = _.cloneDeep(allData)
this.total = allData.length
this.tableData = allData
if (isAll) {
let groupObj = {
nianjicodeGroupyData: _.groupBy(allStudentList, "nianjicode"),
banjicodeGroupyData: _.groupBy(allStudentList, "banjicode"),
levelGroupyData: _.groupBy(allStudentList, "level"),
}
for (let key in groupObj) {
let value = groupObj[key]
let findIndex = _.findIndex(this.tableHeader, { prop: `${key.slice(0, key.length - 10)}` })
let headerItem = this.tableHeader[findIndex]
let keys = _.keys(value)
let options = []
for (let j = 0; j < keys.length; j++) {
let element = keys[j];
if ((!_.find(options, { label: element })) && element) {
options.push(
{
label: `${element}`,
value: `${element}`
}
)
}
}
headerItem["options"] = options
}
}
this.loading = false
// this.tableData = allStudentList.slice((this.currentPage - 1) * this.pageSize, this.currentPage * this.pageSize)
},
//
async searchData(isAll = false) {
await this.updateSeach(this.searchParams, isAll)
},
//
async headerSort(headerSort) {
this.sortObj = headerSort
await this.updateSeach(this.searchParams)
},
//
async clearSelection() {
for (let key in this.searchParams) {
this.searchParams[key] = ""
}
for (let i = 0; i < this.tableHeader.length; i++) {
let item = this.tableHeader;
item.sort = ""
}
this.sortObj = {}
await this.searchData()
},
//
selectChange(selection) {
this.selectionData = _.cloneDeep(selection)
},
//
async handleCurrentChange(currentPage) {
this.currentPage = currentPage
await this.searchData()
},
//
async handleSizeChange(pageSize) {
this.currentPage = 1
this.pageSize = pageSize
await this.searchData()
},
//
cancelDialog() {
this.dialogFrom = {
visible: false,
title: "新增学生",
type: "add",
formHeader: [],
formData: {}
}
},
//
async submitDialog() {
for (let i = 0; i < this.formHeader.length; i++) {
let headItem = this.formHeader[i];
if (headItem.type === "date") {
this.dialogFrom.formData[headItem.prop] = dayjs(this.dialogFrom.formData[headItem.prop]).format("YYYY-MM-DD")
}
}
let params = {
...this.dialogFrom.formData, ...this.dealRow(this.dialogFrom.formData), create_at: dayjs().format('YYYY-MM-DD HH:mm:ss'), update_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
}
if (this.dialogFrom.type === "put") {
delete params.create_at
}
try {
await myDatabase.student[this.dialogFrom.type]({ ...params, update_at: dayjs().format('YYYY-MM-DD HH:mm:ss') })
await this.searchData()
this.cancelDialog()
} catch (e) {
ElMessage(
{
type: "error",
message: "时间不允许重复,请重新设置时间"
}
)
}
},
//
addData() {
let formData = {}
for (let i = 0; i < this.formHeader.length; i++) {
let element = this.formHeader[i];
if (element.type === "text") {
formData[element.prop] = ""
} else if (element.type === "select") {
formData[element.prop] = ""
} else if (element.type === "date") {
formData[element.prop] = dayjs().subtract(element.default || 0, 'year').format("YYYY-MM-DD")
} else if (element.type === "number") {
formData[element.prop] = 0
}
}
this.dialogFrom = {
visible: true,
title: "新增学生",
type: "add",
disabled: false,
formHeader: this.formHeader,
formData
}
},
//
edit(row) {
this.dialogFrom = {
visible: true,
title: "编辑学生",
type: "put",
disabled: false,
formHeader: this.formHeader,
formData: { ...row }
}
},
//
info(row) {
this.dialogFrom = {
visible: true,
title: "查看学生",
type: "info",
disabled: true,
formHeader: this.formHeader,
formData: { ...row }
}
},
//
del(row) {
this.deleteData([row])
},
//
deleteData(delList) {
let tooltipList = []
let idList = []
for (let i = 0; i < delList.length; i++) {
let element = delList[i];
tooltipList.push(element.name)
idList.push(element.id)
}
ElMessageBox.confirm(
`是否删除(${_.join(tooltipList, ",")})?`,
'danger',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'danger',
}
)
.then(async () => {
this.$refs.tableComponentRef.clearSelection()
await myDatabase.student.bulkDelete(idList)
await this.searchData()
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
},
//
delAll() {
ElMessageBox.confirm(
`是否删除全部数据?此操作不可逆!`,
'危险操作',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'danger',
}
)
.then(async () => {
this.$refs.tableComponentRef.clearSelection()
await myDatabase.student.clear()
await this.searchData()
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
},
//
async exportData() {
let listCopy = _.cloneDeep(this.selectionData)
if (!listCopy.length) {
listCopy = await myDatabase.student.toArray()
}
let lilstLocal = []
if (listCopy.length) {
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`);
} else {
ElMessage({
type: 'error',
message: '当前无数据,请输入数据后导出',
})
}
},
//
setHeader() {
this.setHeaderVisible = true
},
//
clearLocalstory() {
//
localStorage.removeItem("setTableHeader")
this.setHeaderFun()
ElMessage({
type: 'success',
message: '清除表头缓存成功',
})
},
setHeaderFun() {
this.setTableHeader = []
if (!localStorage.getItem('setTableHeader')) {
for (let i = 0; i < tableHeaderLocal.length; i++) {
let headerItem = tableHeaderLocal[i];
if (headerItem.isSearch) {
this.searchParams[headerItem.prop] = ""
}
if (headerItem.setTableShow) {
this.setTableHeader.push({ ...headerItem, fixed: false })
}
}
localStorage.setItem('setTableHeader', JSON.stringify(this.setTableHeader))
} else {
this.setTableHeader = JSON.parse(localStorage.getItem('setTableHeader'))
}
}
},
async mounted() {
this.setHeaderFun()
this.tableHeader = _.filter(tableHeaderLocal, o => o.tableShow)
this.formHeader = _.filter(tableHeaderLocal, o => o.formShow)
this.exportHeader = _.filter(tableHeaderLocal, o => o.export)
await this.updateSeach({}, true)
this.$watch("setTableHeader", (val) => {
// this.tableHeader = _.filter(val, o => o.setTableShow)
for (let i = 0; i < val.length; i++) {
let element = val[i];
let item = _.find(this.tableHeader, o => o.prop === element.prop)
item.fixed = element.fixed
}
localStorage.setItem('setTableHeader', JSON.stringify(val))
}, { deep: true })
},
}
</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