implement CreateStart.vue feature
This commit is contained in:
parent
5786e77cb1
commit
b3a91b2cb4
@ -1,13 +1,15 @@
|
|||||||
import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
|
import * as XLSX from 'xlsx'
|
||||||
|
import { WorkBook } from 'xlsx'
|
||||||
|
|
||||||
function createWindow(): void {
|
function createWindow(): void {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 900,
|
width: 1024,
|
||||||
height: 670,
|
height: 728,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: 'hidden',
|
||||||
@ -52,10 +54,7 @@ app.whenReady().then(() => {
|
|||||||
|
|
||||||
// IPC
|
// IPC
|
||||||
ipcMain.on('title_bar', (_, ev: 'minimize' | 'maximize' | 'close') => {
|
ipcMain.on('title_bar', (_, ev: 'minimize' | 'maximize' | 'close') => {
|
||||||
const win = BrowserWindow.getFocusedWindow()
|
const win = BrowserWindow.getFocusedWindow() as BrowserWindow
|
||||||
if (win === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev === 'close') {
|
if (ev === 'close') {
|
||||||
win.close()
|
win.close()
|
||||||
@ -70,6 +69,21 @@ app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('read-xlsx', async (): Promise<WorkBook | null> => {
|
||||||
|
const win = BrowserWindow.getFocusedWindow() as BrowserWindow
|
||||||
|
const result = await dialog.showOpenDialog(win, {
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'Excel文件', extensions: ['xlsx', 'xls'] }]
|
||||||
|
})
|
||||||
|
if (result.filePaths.length) {
|
||||||
|
try {
|
||||||
|
return XLSX.readFile(result.filePaths[0])
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
createWindow()
|
createWindow()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
|
7
src/preload/index.d.ts
vendored
7
src/preload/index.d.ts
vendored
@ -1,8 +1,13 @@
|
|||||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
import { ElectronAPI } from '@electron-toolkit/preload'
|
||||||
|
import { WorkBook } from 'xlsx'
|
||||||
|
|
||||||
|
interface ElectronAPIWithOthers extends ElectronAPI {
|
||||||
|
readXLSX(): Promise<WorkBook | null>
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: ElectronAPI
|
electron: ElectronAPIWithOthers;
|
||||||
api: unknown
|
api: unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { contextBridge } from 'electron'
|
import { contextBridge, ipcRenderer } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
import { electronAPI } from '@electron-toolkit/preload'
|
||||||
|
|
||||||
// Custom APIs for renderer
|
// Custom APIs for renderer
|
||||||
@ -9,7 +9,10 @@ const api = {}
|
|||||||
// just add to the DOM global.
|
// just add to the DOM global.
|
||||||
if (process.contextIsolated) {
|
if (process.contextIsolated) {
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
|
...electronAPI,
|
||||||
|
readXLSX: () => ipcRenderer.invoke('read-xlsx')
|
||||||
|
})
|
||||||
contextBridge.exposeInMainWorld('api', api)
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -4,5 +4,6 @@ import router from '@renderer/router'
|
|||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import '@renderer/assets/main.css'
|
import '@renderer/assets/main.css'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
createApp(App).use(router).use(ElementPlus).mount('#app')
|
createApp(App).use(router).use(ElementPlus).use(createPinia()).mount('#app')
|
||||||
|
@ -1,17 +1,106 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ICourse } from '@renderer/types'
|
import { ICourse, IStudent } from '@renderer/types'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
export const useGlobalStore = defineStore('global', () => {
|
export const useGlobalStore = defineStore('global', () => {
|
||||||
const courseInfo = ref<ICourse | null>(null)
|
const courseInfo = ref<ICourse | null>(null)
|
||||||
|
const studentList = ref<IStudent[]>([])
|
||||||
|
|
||||||
const saveCourseInfo = (new_course: ICourse) => {
|
function saveCourseInfo(new_course: ICourse) {
|
||||||
courseInfo.value = new_course
|
courseInfo.value = new_course
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearCourseInfo = () => {
|
function clearCourseInfo() {
|
||||||
courseInfo.value = null
|
courseInfo.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
return { courseInfo, saveCourseInfo, clearCourseInfo }
|
async function createCourseFromXlsx(): Promise<boolean> {
|
||||||
|
const wb = await window.electron.readXLSX()
|
||||||
|
if (wb === null) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: '无法打开文件'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const sheet = wb.Sheets['Sheet1']
|
||||||
|
if (!sheet) {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: '没有名为 Sheet1 的工作簿,请检查文件是否正确'
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
studentList.value = []
|
||||||
|
const classList: string[] = []
|
||||||
|
|
||||||
|
for (let i = 6; ; i++) {
|
||||||
|
if (sheet[`A${i}`] === undefined) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const stu = {
|
||||||
|
className: sheet[`E${i}`]['v'],
|
||||||
|
major: sheet[`D${i}`]['v'],
|
||||||
|
name: sheet[`C${i}`]['v'],
|
||||||
|
resitTag: false,
|
||||||
|
serialNumber: sheet[`B${i}`]['v']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!classList.includes(stu.className)) {
|
||||||
|
classList.push(stu.className)
|
||||||
|
}
|
||||||
|
|
||||||
|
studentList.value.push(stu)
|
||||||
|
}
|
||||||
|
|
||||||
|
let classStr = ''
|
||||||
|
if (classList.length > 0) {
|
||||||
|
try {
|
||||||
|
// 假设字符串格式为:两位数字 + 非空白字符(基础部分),后面紧跟中文括号括起的数字部分
|
||||||
|
const reg = /^([0-9]{2}\S+?)((\d+))$/
|
||||||
|
const firstMatch = classList[0].match(reg)
|
||||||
|
if (!firstMatch) {
|
||||||
|
throw new Error('输入字符串格式不正确')
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = firstMatch[1]
|
||||||
|
const startNum = parseInt(firstMatch[2], 10)
|
||||||
|
|
||||||
|
// 合并后保留一个基础部分,并按数组长度顺序拼接递增的括号数字
|
||||||
|
classStr = base
|
||||||
|
for (let i = 0; i < classList.length; i++) {
|
||||||
|
classStr += `(${startNum + i})`
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
classStr = ''
|
||||||
|
for (let i = 0; i < classList.length; i++) {
|
||||||
|
classStr += classList[i]
|
||||||
|
if (i !== classList.length - 1) {
|
||||||
|
classStr += '、'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const info: ICourse = {
|
||||||
|
campus: sheet['A4']['v'].split(':')[1],
|
||||||
|
className: classStr,
|
||||||
|
credit: parseFloat(sheet['E4']['v'].split(':')[1]),
|
||||||
|
id: sheet['A3']['v'].split(':')[1],
|
||||||
|
master: '',
|
||||||
|
name: sheet['E3']['v'].split(':')[1],
|
||||||
|
teacher: sheet['D4']['v'].split(':')[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(classList)
|
||||||
|
courseInfo.value = info
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return { courseInfo, saveCourseInfo, clearCourseInfo, createCourseFromXlsx }
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive } from 'vue'
|
import { useGlobalStore } from '@renderer/store'
|
||||||
|
import router from '@renderer/router'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { ICourse } from '@renderer/types'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
const store = useGlobalStore()
|
||||||
|
const { courseInfo } = storeToRefs(store)
|
||||||
|
|
||||||
const tempCourseInfo = reactive({
|
const tempCourseInfo = ref<ICourse>({
|
||||||
campus: '',
|
campus: '',
|
||||||
className: '',
|
className: '',
|
||||||
credit: 0,
|
credit: 0,
|
||||||
@ -10,13 +17,54 @@ const tempCourseInfo = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
teacher: ''
|
teacher: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isShowCopyFrom = ref<boolean>(false)
|
||||||
|
|
||||||
|
const enableSubmit = computed(() => !Object.values(tempCourseInfo.value).every((v) => v))
|
||||||
|
|
||||||
|
const handleCreateFrom = async (from: 'xlsx' | 'jwxt') => {
|
||||||
|
if (from === 'xlsx') {
|
||||||
|
if (await store.createCourseFromXlsx()) {
|
||||||
|
ElMessage({ type: 'success', message: '成功' })
|
||||||
|
tempCourseInfo.value = courseInfo.value!
|
||||||
|
}
|
||||||
|
} else if (from === 'jwxt') {
|
||||||
|
console.log(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => router.back()
|
||||||
|
const handleFormReset = () => {
|
||||||
|
tempCourseInfo.value = {
|
||||||
|
campus: '',
|
||||||
|
className: '',
|
||||||
|
credit: 0,
|
||||||
|
id: '',
|
||||||
|
master: '',
|
||||||
|
name: '',
|
||||||
|
teacher: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleShowCopyBtn = () => {
|
||||||
|
isShowCopyFrom.value = (!tempCourseInfo.value.master && tempCourseInfo.value.teacher) as boolean
|
||||||
|
}
|
||||||
|
const handleCopyFromTeacher = () => {
|
||||||
|
tempCourseInfo.value.master = tempCourseInfo.value.teacher
|
||||||
|
isShowCopyFrom.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="relative flex flex-col items-center justify-center">
|
||||||
<p class="text-2xl text-center">创建新的文件</p>
|
<div
|
||||||
|
class="absolute top-0 left-32 cursor-pointer rounded border border-gray-300 px-4 shadow hover:bg-gray-100"
|
||||||
|
@click="handleBack"
|
||||||
|
>
|
||||||
|
返回
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-2xl">创建新的文件</p>
|
||||||
<el-divider> 填写基本课程信息以继续 </el-divider>
|
<el-divider> 填写基本课程信息以继续 </el-divider>
|
||||||
<div class="max-w-lg mx-auto">
|
<div class="mx-auto w-full max-w-lg">
|
||||||
<el-form :model="tempCourseInfo" label-width="auto">
|
<el-form :model="tempCourseInfo" label-width="auto">
|
||||||
<el-form-item label="课号">
|
<el-form-item label="课号">
|
||||||
<el-input
|
<el-input
|
||||||
@ -34,8 +82,21 @@ const tempCourseInfo = reactive({
|
|||||||
<el-form-item label="任课教师">
|
<el-form-item label="任课教师">
|
||||||
<el-input v-model="tempCourseInfo.teacher" type="text" placeholder="任课教师" />
|
<el-input v-model="tempCourseInfo.teacher" type="text" placeholder="任课教师" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="课程负责人">
|
<el-form-item label="课程负责人" class="relative">
|
||||||
<el-input v-model="tempCourseInfo.master" type="text" placeholder="课程负责人" />
|
<el-input
|
||||||
|
v-model="tempCourseInfo.master"
|
||||||
|
type="text"
|
||||||
|
placeholder="课程负责人"
|
||||||
|
@focus="handleShowCopyBtn"
|
||||||
|
@blur="handleShowCopyBtn"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="absolute right-4 text-gray-400 cursor-pointer invisible"
|
||||||
|
:class="{ visible: isShowCopyFrom }"
|
||||||
|
@click="handleCopyFromTeacher"
|
||||||
|
>
|
||||||
|
从任课教师复制
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="学分">
|
<el-form-item label="学分">
|
||||||
<el-input v-model="tempCourseInfo.credit" type="number" placeholder="学分" />
|
<el-input v-model="tempCourseInfo.credit" type="number" placeholder="学分" />
|
||||||
@ -44,14 +105,14 @@ const tempCourseInfo = reactive({
|
|||||||
<el-input v-model="tempCourseInfo.className" type="text" placeholder="教学班级" />
|
<el-input v-model="tempCourseInfo.className" type="text" placeholder="教学班级" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<el-button>重置</el-button>
|
<el-button @click="handleFormReset">重置</el-button>
|
||||||
<el-button type="primary" disabled>下一步</el-button>
|
<el-button type="primary" :disabled="enableSubmit">下一步</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<el-divider>或</el-divider>
|
<el-divider>或</el-divider>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<el-button>从 Excel 中导入</el-button>
|
<el-button @click="handleCreateFrom('xlsx')">从 Excel 中导入</el-button>
|
||||||
<el-button disabled>从教务系统中导入</el-button>
|
<el-button disabled>从教务系统中导入</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user