implement CreateStart.vue feature

This commit is contained in:
Jeffrey Hsu 2025-02-04 03:44:33 +08:00
parent 5786e77cb1
commit b3a91b2cb4
6 changed files with 198 additions and 25 deletions

View File

@ -1,13 +1,15 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import * as XLSX from 'xlsx'
import { WorkBook } from 'xlsx'
function createWindow(): void {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
width: 1024,
height: 728,
show: false,
autoHideMenuBar: true,
titleBarStyle: 'hidden',
@ -52,10 +54,7 @@ app.whenReady().then(() => {
// IPC
ipcMain.on('title_bar', (_, ev: 'minimize' | 'maximize' | 'close') => {
const win = BrowserWindow.getFocusedWindow()
if (win === null) {
return
}
const win = BrowserWindow.getFocusedWindow() as BrowserWindow
if (ev === '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()
app.on('activate', function () {

View File

@ -1,8 +1,13 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import { WorkBook } from 'xlsx'
interface ElectronAPIWithOthers extends ElectronAPI {
readXLSX(): Promise<WorkBook | null>
}
declare global {
interface Window {
electron: ElectronAPI
electron: ElectronAPIWithOthers;
api: unknown
}
}

View File

@ -1,4 +1,4 @@
import { contextBridge } from 'electron'
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
@ -9,7 +9,10 @@ const api = {}
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('electron', {
...electronAPI,
readXLSX: () => ipcRenderer.invoke('read-xlsx')
})
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)

View File

@ -4,5 +4,6 @@ import router from '@renderer/router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.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')

View File

@ -1,17 +1,106 @@
import { defineStore } from 'pinia'
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', () => {
const courseInfo = ref<ICourse | null>(null)
const studentList = ref<IStudent[]>([])
const saveCourseInfo = (new_course: ICourse) => {
function saveCourseInfo(new_course: ICourse) {
courseInfo.value = new_course
}
const clearCourseInfo = () => {
function clearCourseInfo() {
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 }
})

View File

@ -1,7 +1,14 @@
<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: '',
className: '',
credit: 0,
@ -10,13 +17,54 @@ const tempCourseInfo = reactive({
name: '',
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>
<template>
<div class="flex flex-col">
<p class="text-2xl text-center">创建新的文件</p>
<div class="relative flex flex-col items-center justify-center">
<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>
<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-item label="课号">
<el-input
@ -34,8 +82,21 @@ const tempCourseInfo = reactive({
<el-form-item label="任课教师">
<el-input v-model="tempCourseInfo.teacher" type="text" placeholder="任课教师" />
</el-form-item>
<el-form-item label="课程负责人">
<el-input v-model="tempCourseInfo.master" type="text" placeholder="课程负责人" />
<el-form-item label="课程负责人" class="relative">
<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 label="学分">
<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-form-item>
<div class="flex justify-end">
<el-button>重置</el-button>
<el-button type="primary" disabled>下一步</el-button>
<el-button @click="handleFormReset">重置</el-button>
<el-button type="primary" :disabled="enableSubmit">下一步</el-button>
</div>
</el-form>
</div>
<el-divider></el-divider>
<div class="flex justify-center">
<el-button> Excel 中导入</el-button>
<el-button @click="handleCreateFrom('xlsx')"> Excel 中导入</el-button>
<el-button disabled>从教务系统中导入</el-button>
</div>
</div>