mirror of
https://hub.gitmirror.com/https://github.com/ExamAware/ExamAware2-Desktop
synced 2025-04-28 23:46:37 +00:00
Add files
This commit is contained in:
commit
0d7e7f2ae0
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -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
.eslintignore
Normal file
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
17
.eslintrc.cjs
Normal file
17
.eslintrc.cjs
Normal file
@ -0,0 +1,17 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@electron-toolkit',
|
||||
'@electron-toolkit/eslint-config-ts/eslint-recommended',
|
||||
'@vue/eslint-config-typescript/recommended',
|
||||
'@vue/eslint-config-prettier'
|
||||
],
|
||||
rules: {
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.DS_Store
|
||||
*.log*
|
3
.npmrc
Normal file
3
.npmrc
Normal file
@ -0,0 +1,3 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
shamefully-hoist=true
|
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
||||
out
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
4
.prettierrc.yaml
Normal file
4
.prettierrc.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
singleQuote: true
|
||||
semi: false
|
||||
printWidth: 100
|
||||
trailingComma: none
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||
}
|
39
.vscode/launch.json
vendored
Normal file
39
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Main Process",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||
},
|
||||
"runtimeArgs": ["--sourcemap"],
|
||||
"env": {
|
||||
"REMOTE_DEBUGGING_PORT": "9222"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Renderer Process",
|
||||
"port": 9222,
|
||||
"request": "attach",
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}/src/renderer",
|
||||
"timeout": 60000,
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug All",
|
||||
"configurations": ["Debug Main Process", "Debug Renderer Process"],
|
||||
"presentation": {
|
||||
"order": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# examaware2-desktop
|
||||
|
||||
An Electron application with Vue and TypeScript
|
||||
|
||||
## 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) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin)
|
||||
|
||||
## Project Setup
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# For windows
|
||||
$ pnpm build:win
|
||||
|
||||
# For macOS
|
||||
$ pnpm build:mac
|
||||
|
||||
# For Linux
|
||||
$ pnpm build:linux
|
||||
```
|
12
build/entitlements.mac.plist
Normal file
12
build/entitlements.mac.plist
Normal file
@ -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
Normal file
BIN
build/icon.icns
Normal file
Binary file not shown.
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
BIN
build/icon.png
Normal file
BIN
build/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
3
dev-app-update.yml
Normal file
3
dev-app-update.yml
Normal file
@ -0,0 +1,3 @@
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
updaterCacheDirName: examaware2-desktop-updater
|
45
electron-builder.yml
Normal file
45
electron-builder.yml
Normal file
@ -0,0 +1,45 @@
|
||||
appId: com.electron.app
|
||||
productName: examaware2-desktop
|
||||
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}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
win:
|
||||
executableName: examaware2-desktop
|
||||
nsis:
|
||||
artifactName: ${name}-${version}-setup.${ext}
|
||||
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.
|
||||
notarize: false
|
||||
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
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
44
electron.vite.config.ts
Normal file
44
electron.vite.config.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { TDesignResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
AutoImport({
|
||||
resolvers: [
|
||||
TDesignResolver({
|
||||
library: 'vue-next'
|
||||
})
|
||||
]
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
TDesignResolver({
|
||||
library: 'vue-next'
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
67
package.json
Normal file
67
package.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "examaware2-desktop",
|
||||
"version": "2.0.0",
|
||||
"description": "DSZ知试",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "hello8693 <hello8693@hello8693.xyz>",
|
||||
"homepage": "https://dsz.hello8693.xyz/",
|
||||
"scripts": {
|
||||
"format": "prettier --write src/",
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "electron-vite dev",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"electron-updater": "^6.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
"@electron-toolkit/eslint-config-ts": "^2.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@rushstack/eslint-patch": "^1.10.3",
|
||||
|
||||
"electron": "^31.0.2",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-vite": "^2.3.0",
|
||||
"@imengyu/vue3-context-menu": "^1.4.4",
|
||||
"misans": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"pinia": "^2.3.1",
|
||||
"tdesign-icons-vue-next": "^0.3.4",
|
||||
"tdesign-vue-next": "^1.10.7",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-code-layout": "^1.1.2",
|
||||
"vue-router": "^4.5.0",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-oxlint": "^0.15.6",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"jiti": "^2.4.2",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"oxlint": "^0.15.6",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-vue-devtools": "^7.7.0",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
6041
pnpm-lock.yaml
generated
Normal file
6041
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
resources/icon.png
Normal file
BIN
resources/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
80
src/main/index.ts
Normal file
80
src/main/index.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
|
||||
function createWindow(): void {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 960,
|
||||
height: 700,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false
|
||||
},
|
||||
// titleBarStyle: 'hidden',
|
||||
// titleBarOverlay: {
|
||||
// color: 'rgba(0,0,0,0)',
|
||||
// height: 35,
|
||||
// symbolColor: 'white'
|
||||
// }
|
||||
})
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html#'))
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
// Set app user model id for windows
|
||||
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)
|
||||
})
|
||||
|
||||
// IPC test
|
||||
ipcMain.on('ping', () => console.log('pong'))
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
// 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.
|
8
src/preload/index.d.ts
vendored
Normal file
8
src/preload/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
api: unknown
|
||||
}
|
||||
}
|
22
src/preload/index.ts
Normal file
22
src/preload/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { contextBridge } 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)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore (define in dts)
|
||||
window.electron = electronAPI
|
||||
// @ts-ignore (define in dts)
|
||||
window.api = api
|
||||
}
|
10
src/renderer/auto-imports.d.ts
vendored
Normal file
10
src/renderer/auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
26
src/renderer/components.d.ts
vendored
Normal file
26
src/renderer/components.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AboutDialog: typeof import('./src/components/AboutDialog.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SideExamInfoPanel: typeof import('./src/components/SideExamInfoPanel.vue')['default']
|
||||
SideExamsPanel: typeof import('./src/components/SideExamsPanel.vue')['default']
|
||||
TDatePicker: typeof import('tdesign-vue-next')['DatePicker']
|
||||
TDialog: typeof import('tdesign-vue-next')['Dialog']
|
||||
TForm: typeof import('tdesign-vue-next')['Form']
|
||||
TFormItem: typeof import('tdesign-vue-next')['FormItem']
|
||||
TInput: typeof import('tdesign-vue-next')['Input']
|
||||
TInputNumber: typeof import('tdesign-vue-next')['InputNumber']
|
||||
TLink: typeof import('tdesign-vue-next')['Link']
|
||||
TList: typeof import('tdesign-vue-next')['List']
|
||||
TListItem: typeof import('tdesign-vue-next')['ListItem']
|
||||
TListItemMeta: typeof import('tdesign-vue-next')['ListItemMeta']
|
||||
}
|
||||
}
|
13
src/renderer/index.html
Normal file
13
src/renderer/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DSZ ExamAware</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
7
src/renderer/src/App.vue
Normal file
7
src/renderer/src/App.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
51
src/renderer/src/assets/logo.svg
Normal file
51
src/renderer/src/assets/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 31 KiB |
44
src/renderer/src/assets/main.css
Normal file
44
src/renderer/src/assets/main.css
Normal file
@ -0,0 +1,44 @@
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Misans', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.root {
|
||||
/* 使用 flex 来实现 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.code-layout-title-bar {
|
||||
/* display: flex;
|
||||
align-items: center; */
|
||||
/* 避免被收缩 */
|
||||
flex-shrink: 0;
|
||||
/* 高度与 main.js 中 titleBarOverlay.height 一致 */
|
||||
height: 35px;
|
||||
width: 100%;
|
||||
/* 标题栏始终在最顶层(避免后续被 Modal 之类的覆盖) */
|
||||
/* z-index: 9999; */
|
||||
/* padding-left: 4px; */
|
||||
/* font-size: 14px; */
|
||||
-webkit-app-region: drag !important;
|
||||
|
||||
}
|
||||
|
||||
#app > div.code-layout-root > div.code-layout-title-bar > div:nth-child(2) {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
/*
|
||||
.content {
|
||||
overflow: auto;
|
||||
}
|
||||
*/
|
95
src/renderer/src/components/AboutDialog.vue
Normal file
95
src/renderer/src/components/AboutDialog.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<t-dialog
|
||||
v-bind:visible="visible"
|
||||
theme="info"
|
||||
header="关于 DSZ知试"
|
||||
:cancel-btn="{
|
||||
content: '确定',
|
||||
variant: 'outline',
|
||||
}"
|
||||
:confirm-btn="{
|
||||
content: '复制',
|
||||
variant: 'base',
|
||||
}"
|
||||
:on-close="close1"
|
||||
:on-confirm="copyToClipboard"
|
||||
>
|
||||
<template #body>
|
||||
<t-list :split="true">
|
||||
<t-list-item>
|
||||
<t-list-item-meta title="App 版本" :description="versionInfo.appVersion" />
|
||||
</t-list-item>
|
||||
<t-list-item>
|
||||
<t-list-item-meta title="浏览器" :description="versionInfo.browserVersion" />
|
||||
</t-list-item>
|
||||
<t-list-item>
|
||||
<t-list-item-meta title="User Agent" :description="versionInfo.userAgent" />
|
||||
</t-list-item>
|
||||
</t-list>
|
||||
</template>
|
||||
</t-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { NotifyPlugin } from 'tdesign-vue-next'
|
||||
|
||||
import packageJson from '../../../../package.json'
|
||||
|
||||
const versionInfo = ref({
|
||||
appVersion: packageJson.version,
|
||||
userAgent: navigator.userAgent,
|
||||
browserVersion: (() => {
|
||||
const ua = navigator.userAgent
|
||||
let tem,
|
||||
M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []
|
||||
if (/trident/i.test(M[1])) {
|
||||
tem = /\brv[ :]+(\d+)/g.exec(ua) || []
|
||||
return `IE ${tem[1] || ''}`
|
||||
}
|
||||
if (M[1] === 'Chrome') {
|
||||
tem = ua.match(/\b(OPR|Edge)\/(\d+)/)
|
||||
if (tem != null) return tem.slice(1).join(' ').replace('OPR', 'Opera')
|
||||
}
|
||||
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']
|
||||
if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1])
|
||||
return M.join(' ')
|
||||
})(),
|
||||
})
|
||||
|
||||
const readableVersionInfo = computed(() => {
|
||||
return `About DSZ ExamAware\n============\nApp Version: ${versionInfo.value.appVersion}\nBrowser: ${versionInfo.value.browserVersion}\nUser Agent: ${versionInfo.value.userAgent}`
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
visible: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:closedialog'])
|
||||
|
||||
function close1() {
|
||||
console.log('close1')
|
||||
emit('update:closedialog')
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
navigator.clipboard
|
||||
.writeText(readableVersionInfo.value)
|
||||
.then(() => {
|
||||
NotifyPlugin.success({
|
||||
title: '复制成功',
|
||||
content: '“关于”信息已复制到您的剪贴板',
|
||||
placement: 'bottom-right',
|
||||
closeBtn: true,
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
NotifyPlugin.error({
|
||||
title: '复制失败',
|
||||
content: err,
|
||||
placement: 'bottom-right',
|
||||
closeBtn: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
52
src/renderer/src/components/SideExamInfoPanel.vue
Normal file
52
src/renderer/src/components/SideExamInfoPanel.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="exam_info_editor">
|
||||
<t-form labelAlign="top">
|
||||
<t-form-item label="考试名称" name="examName">
|
||||
<t-input v-model="localProfile.examName"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="考试信息" name="message">
|
||||
<t-input v-model="localProfile.message"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="考场名称" name="room">
|
||||
<t-input v-model="localProfile.room"></t-input>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, watch, ref } from 'vue'
|
||||
import type { ExamConfig } from '@renderer/core/configTypes'
|
||||
|
||||
const props = defineProps({
|
||||
profile: Object as () => ExamConfig,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:profile'])
|
||||
|
||||
const localProfile = ref({ ...props.profile })
|
||||
|
||||
watch(
|
||||
() => props.profile,
|
||||
(newProfile) => {
|
||||
localProfile.value = { ...newProfile }
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
localProfile,
|
||||
(newProfile) => {
|
||||
console.log('更新考试信息', newProfile)
|
||||
emit('update:profile', newProfile)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.exam_info_editor {
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
73
src/renderer/src/components/SideExamsPanel.vue
Normal file
73
src/renderer/src/components/SideExamsPanel.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<t-list v-for="(item, index) in localProfile.examInfos" :key="index" :split="true">
|
||||
<t-list-item>
|
||||
{{ item.name }}
|
||||
<template #action>
|
||||
<t-link
|
||||
theme="primary"
|
||||
hover="color"
|
||||
style="margin-left: 16px"
|
||||
@click="switchExamInfo(index)"
|
||||
>
|
||||
编辑
|
||||
</t-link>
|
||||
<t-link theme="danger" hover="color" style="margin-left: 16px" @click="deleteExam(index)">
|
||||
删除
|
||||
</t-link>
|
||||
</template>
|
||||
</t-list-item>
|
||||
</t-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, watch, ref } from 'vue'
|
||||
import type { ExamConfig } from '@renderer/core/configTypes'
|
||||
import { ArrowUpIcon, ArrowDownIcon } from 'tdesign-icons-vue-next'
|
||||
|
||||
const props = defineProps({
|
||||
profile: Object as () => ExamConfig,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['switch-exam-info', 'update:profile'])
|
||||
|
||||
function switchExamInfo(examId: number) {
|
||||
emit('switch-exam-info', { examId }) // 传递需要切换的 examInfo ID
|
||||
}
|
||||
|
||||
function deleteExam(examId: number) {
|
||||
if (localProfile.value.examInfos) {
|
||||
localProfile.value.examInfos.splice(examId, 1)
|
||||
}
|
||||
emit('update:profile', localProfile.value)
|
||||
emit('switch-exam-info', { examId: null }) // 删除考试后将 currentExamIndex 设为 null
|
||||
}
|
||||
|
||||
const localProfile = ref({ ...props.profile })
|
||||
|
||||
watch(
|
||||
localProfile,
|
||||
(newProfile) => {
|
||||
emit('update:profile', newProfile)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.profile,
|
||||
(newProfile) => {
|
||||
localProfile.value = { ...newProfile }
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
localProfile,
|
||||
(newProfile) => {
|
||||
console.log('更新考试信息', newProfile)
|
||||
emit('update:profile', newProfile)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
31
src/renderer/src/core/configTypes.ts
Normal file
31
src/renderer/src/core/configTypes.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export interface ExamInfo {
|
||||
name: string
|
||||
start: string
|
||||
end: string
|
||||
alertTime: number // 考试结束前几分钟提醒
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the configuration for an exam.
|
||||
*/
|
||||
export interface ExamConfig {
|
||||
/**
|
||||
* The name of the exam.
|
||||
*/
|
||||
examName: string
|
||||
|
||||
/**
|
||||
* A message related to the exam.
|
||||
*/
|
||||
message: string
|
||||
|
||||
/**
|
||||
* The room where the exam will take place.
|
||||
*/
|
||||
room: string
|
||||
|
||||
/**
|
||||
* An array of information related to the exam.
|
||||
*/
|
||||
examInfos: ExamInfo[]
|
||||
}
|
52
src/renderer/src/core/parser.ts
Normal file
52
src/renderer/src/core/parser.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import type { ExamConfig } from './configTypes'
|
||||
|
||||
/**
|
||||
* 解析考试配置的 JSON 字符串,并返回 `ExamConfig` 对象。
|
||||
*
|
||||
* @param jsonString - 包含考试配置信息的 JSON 字符串。
|
||||
* @returns 如果解析成功且包含 `examInfos` 字段,则返回 `ExamConfig` 对象;否则返回 `null`。
|
||||
*/
|
||||
export function parseExamConfig(jsonString: string): ExamConfig | null {
|
||||
try {
|
||||
const data = JSON.parse(jsonString)
|
||||
if (!data.examInfos) return null
|
||||
return data as ExamConfig
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function validateExamConfig(config: ExamConfig): boolean {
|
||||
if (!config.examName || !config.examInfos?.length) return false
|
||||
return config.examInfos.every((info) => info.name && info.start && info.end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查考试时间是否有重叠
|
||||
*
|
||||
* @param config - 包含考试信息的配置对象
|
||||
* @returns 如果考试时间有重叠则返回 true,否则返回 false
|
||||
*/
|
||||
export function hasExamTimeOverlap(config: ExamConfig): boolean {
|
||||
const sortedExams = config.examInfos
|
||||
.slice()
|
||||
.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime())
|
||||
for (let i = 0; i < sortedExams.length - 1; i++) {
|
||||
if (new Date(sortedExams[i].end).getTime() > new Date(sortedExams[i + 1].start).getTime()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据考试配置信息获取排序后的考试信息列表。
|
||||
*
|
||||
* @param config - 考试配置信息对象。
|
||||
* @returns 排序后的考试信息列表,按考试开始时间升序排列。
|
||||
*/
|
||||
export function getSortedExamInfos(config: ExamConfig) {
|
||||
return config.examInfos
|
||||
.slice()
|
||||
.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime())
|
||||
}
|
28
src/renderer/src/main.ts
Normal file
28
src/renderer/src/main.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import 'misans/lib/Normal/MiSans-Normal.min.css'
|
||||
// import 'misans/lib/Normal/MiSans-Thin.min.css'
|
||||
// import 'misans/lib/Normal/MiSans-Semibold.min.css'
|
||||
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import 'vue-code-layout/lib/vue-code-layout.css'
|
||||
import CodeLayout from 'vue-code-layout'
|
||||
|
||||
// import TDesign from 'tdesign-vue-next'
|
||||
import 'tdesign-vue-next/es/style/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(CodeLayout)
|
||||
// app.use(TDesign)
|
||||
|
||||
document.documentElement.setAttribute('theme-mode', 'dark')
|
||||
|
||||
app.mount('#app')
|
16
src/renderer/src/router/index.ts
Normal file
16
src/renderer/src/router/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import HomeView from '../views/EditorView.vue'
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
12
src/renderer/src/stores/counter.ts
Normal file
12
src/renderer/src/stores/counter.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
313
src/renderer/src/views/EditorView.vue
Normal file
313
src/renderer/src/views/EditorView.vue
Normal file
@ -0,0 +1,313 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type CodeLayoutConfig,
|
||||
type CodeLayoutInstance,
|
||||
defaultCodeLayoutConfig,
|
||||
} from 'vue-code-layout'
|
||||
import { ref, reactive, h, onMounted, nextTick } from 'vue'
|
||||
import { FileIcon, SearchIcon, InfoCircleIcon, AddIcon } from 'tdesign-icons-vue-next'
|
||||
import SideExamsPanel from '@renderer/components/SideExamsPanel.vue'
|
||||
import type { Component } from 'vue'
|
||||
import type { ExamConfig } from '@renderer/core/configTypes'
|
||||
import type { MenuOptions } from '@imengyu/vue3-context-menu'
|
||||
import { parseExamConfig, getSortedExamInfos } from '@renderer/core/parser'
|
||||
import AboutDialog from '@renderer/components/AboutDialog.vue'
|
||||
import SideExamInfoPanel from '@renderer/components/SideExamInfoPanel.vue'
|
||||
|
||||
// 配置 CodeLayout 的默认设置
|
||||
const config = reactive<CodeLayoutConfig>({
|
||||
...defaultCodeLayoutConfig,
|
||||
primarySideBarSwitchWithActivityBar: true,
|
||||
primarySideBarPosition: 'left',
|
||||
bottomAlignment: 'center',
|
||||
titleBar: true,
|
||||
titleBarShowCustomizeLayout: true,
|
||||
activityBar: true,
|
||||
primarySideBar: true,
|
||||
secondarySideBar: false,
|
||||
bottomPanel: true,
|
||||
statusBar: true,
|
||||
menuBar: true,
|
||||
bottomPanelMaximize: false,
|
||||
})
|
||||
|
||||
// 定义 ref 和 reactive 变量
|
||||
const codeLayout = ref<CodeLayoutInstance>()
|
||||
const showAboutDialog = ref(false)
|
||||
const windowTitle = ref('ExamAware Editor')
|
||||
const eaProfile = reactive<ExamConfig>({
|
||||
examName: '未命名考试',
|
||||
message: '考试信息',
|
||||
room: '114考场',
|
||||
examInfos: [],
|
||||
})
|
||||
const currentExamIndex = ref<number | null>(null)
|
||||
|
||||
// 定义面板组件
|
||||
const panelComponents: Record<string, Component> = {
|
||||
'explorer.examlist': SideExamsPanel,
|
||||
'explorer.examinfo': SideExamInfoPanel,
|
||||
}
|
||||
|
||||
// 处理配置文件上传
|
||||
function handleConfigFileUpload(file: File) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const result = reader.result?.toString() || ''
|
||||
const parsed = parseExamConfig(result)
|
||||
if (parsed) {
|
||||
Object.assign(eaProfile, {
|
||||
examName: parsed.examName,
|
||||
message: parsed.message,
|
||||
room: parsed.room,
|
||||
examInfos: parsed.examInfos,
|
||||
})
|
||||
saveProfileToLocalStorage()
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
// 加载布局
|
||||
function loadLayout() {
|
||||
if (codeLayout.value) {
|
||||
const groupExplorer = codeLayout.value.addGroup(
|
||||
{
|
||||
title: 'Explorer',
|
||||
tooltip: 'Explorer',
|
||||
name: 'explorer',
|
||||
badge: '2',
|
||||
iconLarge: () => h(FileIcon, { size: '16pt' }),
|
||||
},
|
||||
'primarySideBar',
|
||||
)
|
||||
|
||||
groupExplorer.addPanel({
|
||||
title: '考试列表',
|
||||
tooltip: 'vue-code-layout',
|
||||
name: 'explorer.examlist',
|
||||
noHide: true,
|
||||
startOpen: true,
|
||||
iconLarge: () => h(FileIcon, { size: '16pt' }),
|
||||
iconSmall: () => h(FileIcon),
|
||||
actions: [
|
||||
{
|
||||
name: 'add-exam',
|
||||
icon: () => h(AddIcon),
|
||||
onClick() {
|
||||
const now = new Date()
|
||||
const lastExam = eaProfile.examInfos[eaProfile.examInfos.length - 1]
|
||||
const start = lastExam ? new Date(new Date(lastExam.end).getTime() + 10 * 60000) : now
|
||||
const end = new Date(start.getTime() + 60 * 60000)
|
||||
eaProfile.examInfos.push({
|
||||
name: '未命名考试' + (eaProfile.examInfos.length + 1),
|
||||
start: start.toISOString(),
|
||||
end: end.toISOString(),
|
||||
alertTime: 15,
|
||||
})
|
||||
currentExamIndex.value = eaProfile.examInfos.length - 1
|
||||
saveProfileToLocalStorage()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
groupExplorer.addPanel({
|
||||
title: '考试信息',
|
||||
tooltip: 'Exam info',
|
||||
name: 'explorer.examinfo',
|
||||
noHide: true,
|
||||
startOpen: true,
|
||||
iconSmall: () => h(InfoCircleIcon),
|
||||
iconLarge: () => h(InfoCircleIcon, { size: '16pt' }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取面板组件
|
||||
function getPanelComponent(name: string) {
|
||||
return panelComponents[name] || 'div'
|
||||
}
|
||||
|
||||
// 处理切换考试信息
|
||||
function handleSwitchExamInfo(payload: { examId: number }) {
|
||||
currentExamIndex.value = payload.examId
|
||||
const examInfo = eaProfile.examInfos[payload.examId]
|
||||
if (examInfo) {
|
||||
console.log('切换到考试信息:', examInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭关于对话框
|
||||
function closeAboutDialog() {
|
||||
showAboutDialog.value = false
|
||||
}
|
||||
|
||||
// 定义菜单数据
|
||||
const menuData: MenuOptions = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
items: [
|
||||
{
|
||||
label: '文件',
|
||||
children: [
|
||||
{
|
||||
label: '新建',
|
||||
onClick: () => {
|
||||
eaProfile.examName = '未命名考试'
|
||||
eaProfile.message = '考试信息'
|
||||
eaProfile.room = '114考场'
|
||||
currentExamIndex.value = null
|
||||
eaProfile.examInfos = []
|
||||
saveProfileToLocalStorage()
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '打开...',
|
||||
onClick: () => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.json'
|
||||
input.onchange = (e) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (files && files.length > 0) {
|
||||
handleConfigFileUpload(files[0])
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '另存为...',
|
||||
divided: true,
|
||||
onClick: () => {
|
||||
const blob = new Blob([JSON.stringify(getSortedExamInfos(eaProfile))], {
|
||||
type: 'application/json',
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'exam.json'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '放映',
|
||||
children: [{ label: '开始全屏放映' }],
|
||||
},
|
||||
{
|
||||
label: '帮助',
|
||||
children: [
|
||||
{
|
||||
label: 'Github',
|
||||
onClick: () => {
|
||||
window.open('https://github.com/ExamAware/')
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '关于 DSZ知试',
|
||||
onClick: () => {
|
||||
showAboutDialog.value = true
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
zIndex: 3,
|
||||
minWidth: 230,
|
||||
}
|
||||
|
||||
// 更新配置文件
|
||||
function updateProfile(newProfile: ExamConfig) {
|
||||
eaProfile.examName = newProfile.examName
|
||||
eaProfile.message = newProfile.message
|
||||
eaProfile.room = newProfile.room
|
||||
eaProfile.examInfos = newProfile.examInfos
|
||||
saveProfileToLocalStorage()
|
||||
}
|
||||
|
||||
// 保存配置文件到本地存储
|
||||
function saveProfileToLocalStorage() {
|
||||
localStorage.setItem('eaProfile', JSON.stringify(eaProfile))
|
||||
}
|
||||
|
||||
// 从本地存储加载配置文件
|
||||
function loadProfileFromLocalStorage() {
|
||||
const storedProfile = localStorage.getItem('eaProfile')
|
||||
if (storedProfile) {
|
||||
const parsedProfile = JSON.parse(storedProfile) as ExamConfig
|
||||
Object.assign(eaProfile, parsedProfile)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载布局和配置文件
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
loadLayout()
|
||||
loadProfileFromLocalStorage()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CodeLayout ref="codeLayout" :layout-config="config" :mainMenuConfig="menuData">
|
||||
<template #statusBar></template>
|
||||
<template #panelRender="{ panel }">
|
||||
<component
|
||||
:is="getPanelComponent(panel.name)"
|
||||
:profile="eaProfile"
|
||||
@switch-exam-info="handleSwitchExamInfo"
|
||||
@update:profile="updateProfile"
|
||||
/>
|
||||
</template>
|
||||
<template #titleBarIcon>
|
||||
<img src="@renderer/assets/logo.svg" style="margin: 10px" alt="logo" width="25px" />
|
||||
</template>
|
||||
<template #titleBarCenter>
|
||||
<span>{{ windowTitle }}</span>
|
||||
</template>
|
||||
<template #centerArea>
|
||||
<div style="padding: 20px">
|
||||
<p v-if="currentExamIndex === null">
|
||||
请从左侧的考试列表中选择一个考试进行编辑。如果列表中没有考试,请先添加一个考试。
|
||||
</p>
|
||||
<div v-else>
|
||||
<t-form labelAlign="top">
|
||||
<t-form-item label="考试名称" name="examName">
|
||||
<t-input v-model="eaProfile.examInfos[currentExamIndex].name"></t-input>
|
||||
</t-form-item>
|
||||
<t-form-item label="开始时间" name="start">
|
||||
<t-date-picker
|
||||
enable-time-picker
|
||||
allow-input
|
||||
clearable
|
||||
v-model="eaProfile.examInfos[currentExamIndex].start"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="结束时间" name="end">
|
||||
<t-date-picker
|
||||
enable-time-picker
|
||||
allow-input
|
||||
clearable
|
||||
v-model="eaProfile.examInfos[currentExamIndex].end"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="考试结束提醒时间" name="alertTime">
|
||||
<t-input-number
|
||||
v-model="eaProfile.examInfos[currentExamIndex].alertTime"
|
||||
suffix="分钟"
|
||||
style="width: 200px"
|
||||
:min="5"
|
||||
/>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CodeLayout>
|
||||
<AboutDialog :visible="showAboutDialog" @update:closedialog="closeAboutDialog" />
|
||||
</template>
|
||||
|
3
src/renderer/src/views/HomeView.vue
Normal file
3
src/renderer/src/views/HomeView.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<p>这是主窗口</p>
|
||||
</template>
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
|
||||
}
|
8
tsconfig.node.json
Normal file
8
tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["electron-vite/node"]
|
||||
}
|
||||
}
|
18
tsconfig.web.json
Normal file
18
tsconfig.web.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
|
||||
"include": [
|
||||
"src/renderer/src/env.d.ts",
|
||||
"src/renderer/src/**/*",
|
||||
"src/renderer/src/**/*.vue",
|
||||
"src/preload/*.d.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@renderer/*": [
|
||||
"src/renderer/src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user