This commit is contained in:
lyc 2024-07-08 16:24:56 +08:00
commit 355cc0532a
65 changed files with 1767 additions and 0 deletions

9
.editorconfig Normal file
View 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

12
.env.development Normal file
View File

@ -0,0 +1,12 @@
# 页面标题
VITE_APP_TITLE = AIx智慧教育
# 开发环境配置
VITE_APP_ENV = 'development'
# AIx融合数字管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'
VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktxt/'
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
node_modules
dist
out
.gitignore

15
.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@vue/eslint-config-prettier'
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off'
}
}

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
node_modules
dist
out
.DS_Store
*.log*
/node_modules/
/.idea/.gitignore
/.idea/Alx.iml
/.idea/modules.xml
/.idea/vcs.xml
/dist/
/.idea/workspace.xml
yarn.lock
pnpm-lock.yaml
node_modules
.vscode/*
!.vscode/extensions.json
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
/.idea/deployment.xml
/.idea/encodings.xml
/.idea/misc.xml
/dist.zip
/package-lock.json
/pnpm-lock.json
/build
/out

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json

4
.prettierrc.yaml Normal file
View File

@ -0,0 +1,4 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

68
README.md Normal file
View File

@ -0,0 +1,68 @@
# electron-app
- [Vue3](https://cn.vuejs.org) + [ElementPlus](https://element-plus.org/zh-CN/) + [electron-vite](https://cn-evite.netlify.app/) + [Electron](https://www.electronjs.org/zh/)
### 安装
```bash
$ npm install
```
### 启动
```bash
$ npm run dev
```
### 打包
```bash
# For windows
$ npm run build:win
# For macOS
$ npm run build:mac
# For Linux
$ npm run build:linux
```
### 项目目录结构
```bash
├── build // 编译过程输出文件目录
├── dist // 打包后输出目录
├── node_modules // 依赖模块
├── out //编译过程输出文件目录
├── resources // 公共资源文件,主进程使用
│ └── icon.png //默认图标
├──src
│ ├── main // 主进程开发目录
│ │ └── index.js //主进程入口文件
│ ├── preload // 预加载脚本开发目录
│ │ └── index.js // 预加载默认脚本
│ └── renderer // 渲染进程开发目录类似纯web项目根目录
│ ├── src
│ │ ├── assets //资源文件目录
│ │ ├── components //组件目录
│ │ ├── App.vue // 入口页面
│ │ └── main.js // 入口文件
│ └── index.js.html // 默认html文件
├── .editorconfig
├── .eslintignore //eslint代码检查忽略配置文件
├── .eslintrc.cjs //eslint代码检查配置文件
├── .gitignore //git忽略配置文件
├── .npmrc // npm源配置文件
├── .prettierignore //prettier代码格式化忽略配置文件
├── .prettierrc.yaml //prettier代码格式化配置文件
├── dev-app-update.yml
├── electron-builder.yml //打包配置文件
├──electron.vite.config.mjs //electron-vite配置文件
├── package-lock.json
├── package.json
└──README.md //项目说明
```

3
dev-app-update.yml Normal file
View File

@ -0,0 +1,3 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: electron-app-updater

46
electron-builder.yml Normal file
View File

@ -0,0 +1,46 @@
appId: com.electron.app
productName: electron-app
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}'
asarUnpack:
- resources/**
win:
executableName: electron-app
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
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/

33
electron.vite.config.mjs Normal file
View File

@ -0,0 +1,33 @@
import { resolve } from 'path'
// const path = require('path')
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import WindiCSS from "vite-plugin-windicss"
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@': resolve('./src/renderer/src'),
// '@': path.join(__dirname, './src/renderer/src'),
}
},
server: {
proxy: {
'/dev-api': {
target: 'http://27.128.240.72:7865',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
},
},
plugins: [vue(), WindiCSS()],
assetsInclude:('**/*.woff', '**/*.woff2', '**/*.ttf'),
}
})

49
package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "electron-app",
"version": "1.0.0",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "example.com",
"homepage": "https://electron-vite.org",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "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.1",
"@electron-toolkit/utils": "^3.0.0",
"electron-updater": "^6.1.7",
"element-plus": "^2.7.6",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",
"@rushstack/eslint-patch": "^1.10.3",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"axios": "^1.7.2",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"prettier": "^3.3.2",
"sass": "^1.77.6",
"vite": "^5.3.1",
"vite-plugin-windicss": "^1.9.3",
"vue": "^3.4.30",
"windicss": "^3.5.6"
}
}

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

109
src/main/index.js Normal file
View File

@ -0,0 +1,109 @@
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() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 888,
height: 520,
show: false,
frame: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
nodeIntegration: true
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.openDevTools()
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'])
// mainWindow.loadURL('https://file.ysaix.com:7868/')
} else {
// mainWindow.loadURL('https://file.ysaix.com:7868/')
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)
})
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()
}
})
ipcMain.on('toggle-top', (event) => {
const win = BrowserWindow.getFocusedWindow();
const isAlwaysOnTop = win.isAlwaysOnTop();
win.setAlwaysOnTop(!isAlwaysOnTop);
event.sender.send('top-status-changed', !isAlwaysOnTop);
})
ipcMain.on('minimize-window', () => {
const win = BrowserWindow.getFocusedWindow();
win.minimize();
});
ipcMain.on('maximize-window', () => {
const win = BrowserWindow.getFocusedWindow();
if (win.isMaximized()) {
win.unmaximize();
} else {
win.maximize();
}
});
ipcMain.on('close-window', () => {
const win = BrowserWindow.getFocusedWindow();
win.close();
});
console.log(100)
ipcMain.on('set-winsize', (e, {x, y})=>{
const win = BrowserWindow.getFocusedWindow();
win.setSize(x,y);
})

20
src/preload/index.js Normal file
View File

@ -0,0 +1,20 @@
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 {
window.electron = electronAPI
window.api = api
}

19
src/renderer/index.html Normal file
View File

@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<!-- <meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/> -->
<meta http-equiv="Content-Security-Policy" content="connect-src *; default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

24
src/renderer/src/App.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<el-config-provider :size="size">
<router-view></router-view>
</el-config-provider>
</template>
<script setup>
import { ref } from 'vue'
const size = ref('default')
// const size = computed(() => store.state.app.elementSize)
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
width: 100%;
height: 100vh;
}
</style>

View File

@ -0,0 +1,59 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 注册方法
export function register(data) {
return request({
url: '/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
url: '/getInfo',
method: 'get'
})
}
// 退出方法
export function logout() {
return request({
url: '/logout',
method: 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}

View File

@ -0,0 +1,10 @@
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="64" cy="64" r="64" fill="#2F3242"/>
<ellipse cx="63.9835" cy="23.2036" rx="4.48794" ry="4.495" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path d="M51.3954 39.5028C52.3733 39.6812 53.3108 39.033 53.4892 38.055C53.6676 37.0771 53.0194 36.1396 52.0414 35.9612L51.3954 39.5028ZM28.6153 43.5751L30.1748 44.4741L30.1748 44.4741L28.6153 43.5751ZM28.9393 60.9358C29.4332 61.7985 30.5329 62.0976 31.3957 61.6037C32.2585 61.1098 32.5575 60.0101 32.0636 59.1473L28.9393 60.9358ZM37.6935 66.7457C37.025 66.01 35.8866 65.9554 35.1508 66.6239C34.415 67.2924 34.3605 68.4308 35.029 69.1666L37.6935 66.7457ZM53.7489 81.7014L52.8478 83.2597L53.7489 81.7014ZM96.9206 89.515C97.7416 88.9544 97.9526 87.8344 97.3919 87.0135C96.8313 86.1925 95.7113 85.9815 94.8904 86.5422L96.9206 89.515ZM52.0414 35.9612C46.4712 34.9451 41.2848 34.8966 36.9738 35.9376C32.6548 36.9806 29.0841 39.1576 27.0559 42.6762L30.1748 44.4741C31.5693 42.0549 34.1448 40.3243 37.8188 39.4371C41.5009 38.5479 46.1547 38.5468 51.3954 39.5028L52.0414 35.9612ZM27.0559 42.6762C24.043 47.9029 25.2781 54.5399 28.9393 60.9358L32.0636 59.1473C28.6579 53.1977 28.1088 48.0581 30.1748 44.4741L27.0559 42.6762ZM35.029 69.1666C39.6385 74.24 45.7158 79.1355 52.8478 83.2597L54.6499 80.1432C47.8081 76.1868 42.0298 71.5185 37.6935 66.7457L35.029 69.1666ZM52.8478 83.2597C61.344 88.1726 70.0465 91.2445 77.7351 92.3608C85.359 93.4677 92.2744 92.6881 96.9206 89.515L94.8904 86.5422C91.3255 88.9767 85.4902 89.849 78.2524 88.7982C71.0793 87.7567 62.809 84.8612 54.6499 80.1432L52.8478 83.2597ZM105.359 84.9077C105.359 81.4337 102.546 78.6127 99.071 78.6127V82.2127C100.553 82.2127 101.759 83.4166 101.759 84.9077H105.359ZM99.071 78.6127C95.5956 78.6127 92.7831 81.4337 92.7831 84.9077H96.3831C96.3831 83.4166 97.5892 82.2127 99.071 82.2127V78.6127ZM92.7831 84.9077C92.7831 88.3817 95.5956 91.2027 99.071 91.2027V87.6027C97.5892 87.6027 96.3831 86.3988 96.3831 84.9077H92.7831ZM99.071 91.2027C102.546 91.2027 105.359 88.3817 105.359 84.9077H101.759C101.759 86.3988 100.553 87.6027 99.071 87.6027V91.2027Z" fill="#A2ECFB"/>
<path d="M91.4873 65.382C90.8456 66.1412 90.9409 67.2769 91.7002 67.9186C92.4594 68.5603 93.5951 68.465 94.2368 67.7058L91.4873 65.382ZM99.3169 43.6354L97.7574 44.5344L99.3169 43.6354ZM84.507 35.2412C83.513 35.2282 82.6967 36.0236 82.6838 37.0176C82.6708 38.0116 83.4661 38.8279 84.4602 38.8409L84.507 35.2412ZM74.9407 39.8801C75.9127 39.6716 76.5315 38.7145 76.323 37.7425C76.1144 36.7706 75.1573 36.1517 74.1854 36.3603L74.9407 39.8801ZM53.7836 46.3728L54.6847 47.931L53.7836 46.3728ZM25.5491 80.9047C25.6932 81.8883 26.6074 82.5688 27.5911 82.4247C28.5747 82.2806 29.2552 81.3664 29.1111 80.3828L25.5491 80.9047ZM94.2368 67.7058C97.8838 63.3907 100.505 58.927 101.752 54.678C103.001 50.4213 102.9 46.2472 100.876 42.7365L97.7574 44.5344C99.1494 46.9491 99.3603 50.0419 98.2974 53.6644C97.2323 57.2945 94.9184 61.3223 91.4873 65.382L94.2368 67.7058ZM100.876 42.7365C97.9119 37.5938 91.7082 35.335 84.507 35.2412L84.4602 38.8409C91.1328 38.9278 95.7262 41.0106 97.7574 44.5344L100.876 42.7365ZM74.1854 36.3603C67.4362 37.8086 60.0878 40.648 52.8826 44.8146L54.6847 47.931C61.5972 43.9338 68.5948 41.2419 74.9407 39.8801L74.1854 36.3603ZM52.8826 44.8146C44.1366 49.872 36.9669 56.0954 32.1491 62.3927C27.3774 68.63 24.7148 75.2115 25.5491 80.9047L29.1111 80.3828C28.4839 76.1026 30.4747 70.5062 35.0084 64.5802C39.496 58.7143 46.2839 52.7889 54.6847 47.931L52.8826 44.8146Z" fill="#A2ECFB"/>
<path d="M49.0825 87.2295C48.7478 86.2934 47.7176 85.8059 46.7816 86.1406C45.8455 86.4753 45.358 87.5055 45.6927 88.4416L49.0825 87.2295ZM78.5635 96.4256C79.075 95.5732 78.7988 94.4675 77.9464 93.9559C77.0941 93.4443 75.9884 93.7205 75.4768 94.5729L78.5635 96.4256ZM79.5703 85.1795C79.2738 86.1284 79.8027 87.1379 80.7516 87.4344C81.7004 87.7308 82.71 87.2019 83.0064 86.2531L79.5703 85.1795ZM84.3832 64.0673H82.5832H84.3832ZM69.156 22.5301C68.2477 22.1261 67.1838 22.535 66.7799 23.4433C66.3759 24.3517 66.7848 25.4155 67.6931 25.8194L69.156 22.5301ZM45.6927 88.4416C47.5994 93.7741 50.1496 98.2905 53.2032 101.505C56.2623 104.724 59.9279 106.731 63.9835 106.731V103.131C61.1984 103.131 58.4165 101.765 55.8131 99.0249C53.2042 96.279 50.8768 92.2477 49.0825 87.2295L45.6927 88.4416ZM63.9835 106.731C69.8694 106.731 74.8921 102.542 78.5635 96.4256L75.4768 94.5729C72.0781 100.235 68.0122 103.131 63.9835 103.131V106.731ZM83.0064 86.2531C85.0269 79.7864 86.1832 72.1831 86.1832 64.0673H82.5832C82.5832 71.8536 81.4723 79.0919 79.5703 85.1795L83.0064 86.2531ZM86.1832 64.0673C86.1832 54.1144 84.4439 44.922 81.4961 37.6502C78.5748 30.4436 74.3436 24.8371 69.156 22.5301L67.6931 25.8194C71.6364 27.5731 75.3846 32.1564 78.1598 39.0026C80.9086 45.7836 82.5832 54.507 82.5832 64.0673H86.1832Z" fill="#A2ECFB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.559 84.9077C103.559 82.4252 101.55 80.4127 99.071 80.4127C96.5924 80.4127 94.5831 82.4252 94.5831 84.9077C94.5831 87.3902 96.5924 89.4027 99.071 89.4027C101.55 89.4027 103.559 87.3902 103.559 84.9077V84.9077Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.8143 89.4027C31.2929 89.4027 33.3023 87.3902 33.3023 84.9077C33.3023 82.4252 31.2929 80.4127 28.8143 80.4127C26.3357 80.4127 24.3264 82.4252 24.3264 84.9077C24.3264 87.3902 26.3357 89.4027 28.8143 89.4027V89.4027V89.4027Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M64.8501 68.0857C62.6341 68.5652 60.451 67.1547 59.9713 64.9353C59.4934 62.7159 60.9007 60.5293 63.1167 60.0489C65.3326 59.5693 67.5157 60.9798 67.9954 63.1992C68.4742 65.4186 67.066 67.6052 64.8501 68.0857Z" fill="#A2ECFB"/>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,31 @@
@font-face {
font-family: "iconfont"; /* Project id 2794390 */
src: url('./iconfont.woff2?t=1719991760511') format('woff2'),
url('./iconfont.woff?t=1719991760511') format('woff'),
url('./iconfont.ttf?t=1719991760511') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-zuidahua:before {
content: "\e600";
}
.icon-window-max_line:before {
content: "\e695";
}
.icon-zuixiaohua:before {
content: "\e650";
}
.icon-close:before {
content: "\e608";
}

View File

@ -0,0 +1 @@
window._iconfont_svg_string_2794390='<svg><symbol id="icon-zuidahua" viewBox="0 0 1024 1024"><path d="M959.720175 0H294.216115a63.960025 63.960025 0 0 0-63.960025 63.960025v127.92005H64.279825a63.960025 63.960025 0 0 0-63.960025 63.960025v703.560275a63.960025 63.960025 0 0 0 63.960025 63.960025h703.560275a63.960025 63.960025 0 0 0 63.960025-63.960025v-166.935665h127.92005a63.960025 63.960025 0 0 0 63.960025-63.960025V63.960025a63.960025 63.960025 0 0 0-63.960025-63.960025z m-191.880075 728.504685V959.400375H64.279825V255.8401h703.560275z m189.321674 0H831.800125V255.8401a63.960025 63.960025 0 0 0-63.960025-63.960025H294.216115V63.960025H959.720175z" fill="" ></path></symbol><symbol id="icon-window-max_line" viewBox="0 0 1024 1024"><path d="M800 928H224c-70.692 0-128-57.308-128-128V224c0-70.692 57.308-128 128-128h576c70.692 0 128 57.308 128 128v576c0 70.692-57.308 128-128 128z m64-704c0-35.346-28.654-64-64-64H224c-35.346 0-64 28.654-64 64v576c0 35.346 28.654 64 64 64h576c35.346 0 64-28.654 64-64V224z" fill="#707070" ></path></symbol><symbol id="icon-zuixiaohua" viewBox="0 0 1024 1024"><path d="M863.7 552.5H160.3c-10.6 0-19.2-8.6-19.2-19.2v-41.7c0-10.6 8.6-19.2 19.2-19.2h703.3c10.6 0 19.2 8.6 19.2 19.2v41.7c0 10.6-8.5 19.2-19.1 19.2z" fill="#707070" ></path></symbol><symbol id="icon-close" viewBox="0 0 1024 1024"><path d="M147.85595703 147.85595703a39.55078125 39.55078125 0 0 1 55.92480469 0L511.40673828 455.44238281 819.05249023 147.85595703a39.55078125 39.55078125 0 0 1 55.92480469 55.92480469L567.37109375 511.40673828l307.62597656 307.64575195a39.55078125 39.55078125 0 0 1 2.31372071 53.41333008l-2.31372071 2.51147461a39.55078125 39.55078125 0 0 1-55.92480469 0L511.40673828 567.37109375 203.78076172 874.97729492a39.55078125 39.55078125 0 0 1-55.92480469-55.92480469L455.44238281 511.40673828 147.85595703 203.78076172a39.55078125 39.55078125 0 0 1-2.3137207-53.41333008z" fill="#707070" ></path></symbol></svg>',function(n){var t=(t=document.getElementsByTagName("script"))[t.length-1],e=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var i,o,a,c,d,l=function(t,e){e.parentNode.insertBefore(t,e)};if(e&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}i=function(){var t,e=document.createElement("div");e.innerHTML=n._iconfont_svg_string_2794390,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?l(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(i,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),i()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(a=i,c=n.document,d=!1,r(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,s())})}function s(){d||(d=!0,a())}function r(){try{c.documentElement.doScroll("left")}catch(t){return void setTimeout(r,50)}s()}}(window);

View File

@ -0,0 +1,37 @@
{
"id": "2794390",
"name": "test",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "4485682",
"name": "最大化",
"font_class": "zuidahua",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "7538862",
"name": "窗口-最大化_line",
"font_class": "window-max_line",
"unicode": "e695",
"unicode_decimal": 59029
},
{
"icon_id": "11490918",
"name": "最小化",
"font_class": "zuixiaohua",
"unicode": "e650",
"unicode_decimal": 58960
},
{
"icon_id": "11727010",
"name": "关闭",
"font_class": "close",
"unicode": "e608",
"unicode_decimal": 58888
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

View File

@ -0,0 +1,26 @@
html,body{
height: 100%;
}
#app {
height: 100%;
}
@media (max-width: 720px) {
.text {
font-size: 20px;
}
}
@media (max-width: 620px) {
.versions {
display: none;
}
}
@media (max-width: 350px) {
.tip,
.actions {
display: none;
}
}

View File

@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1422 800" opacity="0.3">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="oooscillate-grad">
<stop stop-color="hsl(206, 75%, 49%)" stop-opacity="1" offset="0%"></stop>
<stop stop-color="hsl(331, 90%, 56%)" stop-opacity="1" offset="100%"></stop>
</linearGradient>
</defs>
<g stroke-width="1" stroke="url(#oooscillate-grad)" fill="none" stroke-linecap="round">
<path d="M 0 448 Q 355.5 -100 711 400 Q 1066.5 900 1422 448" opacity="0.05"></path>
<path d="M 0 420 Q 355.5 -100 711 400 Q 1066.5 900 1422 420" opacity="0.11"></path>
<path d="M 0 392 Q 355.5 -100 711 400 Q 1066.5 900 1422 392" opacity="0.18"></path>
<path d="M 0 364 Q 355.5 -100 711 400 Q 1066.5 900 1422 364" opacity="0.24"></path>
<path d="M 0 336 Q 355.5 -100 711 400 Q 1066.5 900 1422 336" opacity="0.30"></path>
<path d="M 0 308 Q 355.5 -100 711 400 Q 1066.5 900 1422 308" opacity="0.37"></path>
<path d="M 0 280 Q 355.5 -100 711 400 Q 1066.5 900 1422 280" opacity="0.43"></path>
<path d="M 0 252 Q 355.5 -100 711 400 Q 1066.5 900 1422 252" opacity="0.49"></path>
<path d="M 0 224 Q 355.5 -100 711 400 Q 1066.5 900 1422 224" opacity="0.56"></path>
<path d="M 0 196 Q 355.5 -100 711 400 Q 1066.5 900 1422 196" opacity="0.62"></path>
<path d="M 0 168 Q 355.5 -100 711 400 Q 1066.5 900 1422 168" opacity="0.68"></path>
<path d="M 0 140 Q 355.5 -100 711 400 Q 1066.5 900 1422 140" opacity="0.75"></path>
<path d="M 0 112 Q 355.5 -100 711 400 Q 1066.5 900 1422 112" opacity="0.81"></path>
<path d="M 0 84 Q 355.5 -100 711 400 Q 1066.5 900 1422 84" opacity="0.87"></path>
<path d="M 0 56 Q 355.5 -100 711 400 Q 1066.5 900 1422 56" opacity="0.94"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,13 @@
<script setup>
import { reactive } from 'vue'
const versions = reactive({ ...window.electron.process.versions })
</script>
<template>
<ul class="versions">
<li class="electron-version">Electron v{{ versions.electron }}</li>
<li class="chrome-version">Chromium v{{ versions.chrome }}</li>
<li class="node-version">Node v{{ versions.node }}</li>
</ul>
</template>

View File

@ -0,0 +1,13 @@
<template>
<section class="app-main">
<transition mode="out-in" name="fade-transform">
<keep-alive>
<router-view />
</keep-alive>
</transition>
</section>
</template>
<script setup></script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,124 @@
<template>
<div class="title-bar flex">
<div class="left-section">
<h3 class="title" @click="changeTab">AIX智慧课堂</h3>
<div class="change-tab">
<el-radio-group v-model="radio1">
<el-radio-button label="资源" value="resource" />
<el-radio-button label="备课" value="prepare" />
<el-radio-button label="授课" value="teach" />
</el-radio-group>
</div>
</div>
<div class="right-section flex">
<div class="header-tool flex">
<span @click="minimizeWindow" title="最小化"><i class="iconfont">&#xe650;</i></span>
<span @click="maximizeWindow" :title="isMaxSize ? '向下还原':'最大化'"><i class="iconfont">{{ isMaxSize ? '&#xe600'
:"&#xe695"}}</i></span>
<span @click="closeWindow" title="关闭"><i class="iconfont">&#xe608;</i></span>
</div>
<div class="user flex">
<el-icon color="#409EFF">
<UserFilled />
</el-icon>
<div class="user-info flex">
<span>{{ userInfo.nickName}}</span>
<span>{{ userInfo.postnames }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const userInfo = userStore.user
const { ipcRenderer } = window.electron
const radio1 = ref('resource')
const isMaxSize = ref(false)
const router = useRouter();
//
const minimizeWindow = () => {
ipcRenderer.send('minimize-window')
}
//
const maximizeWindow = () => {
ipcRenderer.send('maximize-window')
isMaxSize.value = !isMaxSize.value
}
//
const closeWindow = () => {
ipcRenderer.send('close-window')
}
const changeTab = ()=>{
router.push('/resource')
}
</script>
<style lang="scss" scoped>
.title-bar {
height: 80px;
background: #ebf0f9;
justify-content: space-between;
align-items: center;
-webkit-app-region: drag;
}
.left-section {
display: flex;
align-items: center;
width: 50%;
.change-tab{
-webkit-app-region: no-drag;
}
ul{
-webkit-app-region: no-drag;
}
.title {
color: #4b73df;
font-size: 18px;
padding: 0 20px;
}
}
.right-section {
height: 100%;
display: flex;
justify-content: space-between;
padding-bottom: 5px;
flex-direction: column;
.header-tool {
padding-top: 5px;
-webkit-app-region: no-drag;
span {
border-radius: 3px;
cursor: pointer;
&:hover{
background-color: #c4c4c4;
}
.iconfont {
margin: 0 10px;
}
}
}
.user {
.user-info {
padding-right: 5px;
align-items: center;
flex-direction: column;
font-size: 12px;
}
}
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<el-container>
<el-header>
<Header/>
</el-header>
<el-main>
<AppMain/>
</el-main>
</el-container>
</template>
<script setup>
import Header from './components/Header.vue'
import AppMain from './components/AppMain.vue'
</script>
<style lang="scss" scoped>
.el-header{
padding: 0
}
</style>

21
src/renderer/src/main.js Normal file
View File

@ -0,0 +1,21 @@
import './assets/main.css'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import './utils/messageConfig'
import 'element-plus/dist/index.css'
import './assets/iconfont/iconfont.css'
import 'virtual:windi.css'
import { store } from '@/store'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
.use(store)
.use(ElementPlus).mount('#app')

View File

@ -0,0 +1,77 @@
const sessionCache = {
set (key, value) {
if (!sessionStorage) {
return
}
if (key != null && value != null) {
sessionStorage.setItem(key, value)
}
},
get (key) {
if (!sessionStorage) {
return null
}
if (key == null) {
return null
}
return sessionStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
},
remove (key) {
sessionStorage.removeItem(key);
}
}
const localCache = {
set (key, value) {
if (!localStorage) {
return
}
if (key != null && value != null) {
localStorage.setItem(key, value)
}
},
get (key) {
if (!localStorage) {
return null
}
if (key == null) {
return null
}
return localStorage.getItem(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null) {
return JSON.parse(value)
}
},
remove (key) {
localStorage.removeItem(key);
}
}
export default {
/**
* 会话级缓存
*/
session: sessionCache,
/**
* 本地缓存
*/
local: localCache
}

View File

@ -0,0 +1,33 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from '../layout/index.vue'
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
// component: ()=> import('../login/index.vue'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/login',
children: [
{
path: '/index',
component: () => import('@/views/resource/index.vue'),
name: 'index',
}
]
},
]
const router = createRouter({
history: createWebHashHistory(), //hash 模式
routes: constantRoutes
})
export default router

View File

@ -0,0 +1,3 @@
import { createPinia } from 'pinia';
export const store = createPinia();

View File

@ -0,0 +1,99 @@
import { defineStore } from "pinia"
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import defAva from '@/assets/images/user.png'
const useUserStore = defineStore(
'user',
{
state: () => ({
token: getToken(),
id: '',
name: '',
avatar: '',
roles: [],
permissions: [],
user: {}
}),
actions: {
// 登录
login(userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
this.token = res.token
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
getInfo() {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
this.user = user
const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar;
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
this.roles = res.roles
this.permissions = res.permissions
} else {
this.roles = ['ROLE_DEFAULT']
}
this.id = user.userId
this.userName = user.userName
this.nickName = user.nickName;
this.avatar = avatar;
this.userType = user.userType;
this.deptId = user.deptId;
this.deptName = user.deptName;
this.deptLogo = user.deptLogo;
this.deptSlogan = user.deptSlogan;
this.activeDeptId = user.activeDeptId;
this.activeDeptName = user.activeDeptName;
this.parentDeptId = user.parentDeptId;
this.parentDeptName = user.parentDeptName;
this.edusubject = user.edusubject;
this.edudegree = user.edudegree;
this.edustage = user.edustage;
this.userType = user.userType;
this.studentId = user.studentId;
this.timUserId = user.timuserid;
this.plainpwd = user.plainpwd;
this.roles = res.roles;
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
logOut() {
return new Promise((resolve, reject) => {
logout(this.token).then(() => {
this.token = ''
this.roles = []
this.permissions = []
removeToken()
resolve()
}).catch(error => {
removeToken() // zdg: 网络异常时,清除前端退出进入登录页
reject(error)
})
})
}
},
persist: true
})
export default useUserStore

View File

@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

View File

@ -0,0 +1,6 @@
export default {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}

View File

@ -0,0 +1,30 @@
import JSEncrypt from "jsencrypt"
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
}
// 解密
export function decrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置私钥
return encryptor.decrypt(txt) // 对数据进行解密
}

View File

@ -0,0 +1,6 @@
import { ElMessage } from 'element-plus';
ElMessage.defaultOptions = {
plain : true
}

View File

@ -0,0 +1,128 @@
import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi'
import cache from '@/plugins/cache'
import useUserStore from '@/store/modules/user'
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 100000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
useUserStore().logOut().then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('')
} else if (code === 500) {
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code !== 200) {
ElNotification.error({ title: msg })
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
// 通用下载方法
export default service

View File

@ -0,0 +1,247 @@
/**
* 通用js方法封装处理
* Copyright (c) 2019 ruoyi
*/
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
// 表单重置
export function resetForm(refName) {
if (this.$refs[refName]) {
this.$refs[refName].resetFields();
}
}
// 添加日期范围
export function addDateRange(params, dateRange, propName) {
let search = params;
search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
dateRange = Array.isArray(dateRange) ? dateRange : [];
if (typeof (propName) === 'undefined') {
search.params['beginTime'] = dateRange[0];
search.params['endTime'] = dateRange[1];
} else {
search.params['begin' + propName] = dateRange[0];
search.params['end' + propName] = dateRange[1];
}
return search;
}
// 回显数据字典
export function selectDictLabel(datas, value) {
if (value === undefined) {
return "";
}
var actions = [];
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + value)) {
actions.push(datas[key].label);
return true;
}
})
if (actions.length === 0) {
actions.push(value);
}
return actions.join('');
}
// 回显数据字典(字符串数组)
export function selectDictLabels(datas, value, separator) {
if (value === undefined || value.length ===0) {
return "";
}
if (Array.isArray(value)) {
value = value.join(",");
}
var actions = [];
var currentSeparator = undefined === separator ? "," : separator;
var temp = value.split(currentSeparator);
Object.keys(value.split(currentSeparator)).some((val) => {
var match = false;
Object.keys(datas).some((key) => {
if (datas[key].value == ('' + temp[val])) {
actions.push(datas[key].label + currentSeparator);
match = true;
}
})
if (!match) {
actions.push(temp[val] + currentSeparator);
}
})
return actions.join('').substring(0, actions.join('').length - 1);
}
// 字符串格式化(%s )
export function sprintf(str) {
var args = arguments, flag = true, i = 1;
str = str.replace(/%s/g, function () {
var arg = args[i++];
if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
}
// 转换字符串undefined,null等转化为""
export function parseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") {
return "";
}
return str;
}
// 数据合并
export function mergeRecursive(source, target) {
for (var p in target) {
try {
if (target[p].constructor == Object) {
source[p] = mergeRecursive(source[p], target[p]);
} else {
source[p] = target[p];
}
} catch (e) {
source[p] = target[p];
}
}
return source;
};
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export function handleTree(data, id, parentId, children) {
let config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children'
};
var childrenListMap = {};
var nodeIds = {};
var tree = [];
for (let d of data) {
let parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (let d of data) {
let parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
tree.push(d);
}
}
for (let t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (let c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
/*if (value !== null && value !== "" && typeof (value) !== "undefined") {
}*/
}
return result
}
// 返回项目路径
export function getNormalPath(p) {
if (p.length === 0 || !p || p == 'undefined') {
return p
};
let res = p.replace('//', '/')
if (res[res.length - 1] === '/') {
return res.slice(0, res.length - 1)
}
return res;
}
// 验证是否为blob格式
export function blobValidate(data) {
return data.type !== 'application/json'
}

View File

@ -0,0 +1,261 @@
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录 AIX智慧课堂</p>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login">
<div class="header-tool flex">
<span @click="minimizeWindow" title="最小化"><i class="iconfont">&#xe650;</i></span>
<span @click="closeWindow" title="关闭"><i class="iconfont">&#xe608;</i></span>
</div>
<div class="login-title">账号登录</div>
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
<el-form-item prop="username">
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item prop="password" style="margin-bottom: 15px">
<el-input
v-model="loginForm.password"
autocomplete="on"
type="password"
placeholder="请输入密码"
/>
</el-form-item>
<el-checkbox class="flex mb-5" v-model="loginForm.rememberMe">记住密码</el-checkbox>
<el-form-item>
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)"
>登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
import leftBg2 from '@/assets/images/login/left-bg2.png'
const formRef = ref()
const userStore = useUserStore()
const router = useRouter()
const { ipcRenderer } = window.electron
const isMaxSize = ref(false)
const btnLoading = ref(false)
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
})
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
Cookies.set('username', loginForm.username, { expires: 30 })
Cookies.set('password', encrypt(loginForm.password), { expires: 30 })
Cookies.set('rememberMe', loginForm.rememberMe, { expires: 30 })
} else {
//
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}
try{
await userStore.login(loginForm)
await userStore.getInfo()
ElMessage.success('登录成功')
router.push('/index')
}finally{
btnLoading.value = false
}
}
})
}
const getCookie = () => {
const username = Cookies.get('username')
const password = Cookies.get('password')
const rememberMe = Cookies.get('rememberMe')
loginForm.username = username ? username : loginForm.username
loginForm.password = password ? decrypt(password) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe) : false
}
onMounted(()=>{
getCookie()
})
//
const minimizeWindow = () => {
ipcRenderer.send('minimize-window')
}
//
const maximizeWindow = () => {
ipcRenderer.send('maximize-window')
isMaxSize.value = !isMaxSize.value
}
//
const closeWindow = () => {
ipcRenderer.send('close-window')
}
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
.box-item {
width: 444px;
height: 520px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #003b94;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
}
.login-qr {
position: absolute;
top: 0;
right: 0;
width: 66px;
height: 66px;
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
.sub-welcome {
padding: 10px;
font-size: 18px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
opacity: 0.96;
background: #1748fd;
border-radius: 22px 22px 2px 22px;
}
}
.welcome-img {
margin-top: 20px;
width: 350px;
height: 350px;
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
margin-top: 50px;
}
.login-form {
-webkit-app-region: no-drag;
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
}
.btn {
width: 350px;
height: 50px;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
.more {
margin-top: 30px;
.title-box {
display: flex;
align-items: center;
justify-content: center;
> p {
margin-bottom: 0;
}
}
.line {
width: 114px;
height: 1px;
background: #e6e6e6;
}
.title {
font-size: 14px;
font-weight: 500;
color: #a1aebe;
margin: 0 19px;
}
.login-type {
padding: 0 5px;
margin-top: 25px;
display: flex;
align-items: center;
justify-content: space-between;
> img {
width: 30px;
height: 30px;
}
}
}
}
.header-tool {
position: absolute;
right: 0;
top: 0;
-webkit-app-region: no-drag;
span {
padding: 5px 10px;
cursor: pointer;
}
}
.el-form-item {
margin-bottom: 40px;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<div>
这是资源page
</div>
</template>
<script setup>
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const { ipcRenderer } = window.electron
ipcRenderer.send('set-winsize',{x:1000,y: 700})
const userInfo = userStore.user
console.log(userInfo)
</script>
<style lang="scss" scoped>
</style>