Compare commits

..

13 Commits

28 changed files with 1248 additions and 483 deletions

25
.env.yc Normal file
View File

@ -0,0 +1,25 @@
# 页面标题
VITE_APP_TITLE = 文枢课堂
# 生产环境配置
VITE_APP_ENV = 'production'
# AIx融合数字管理系统/生产环境
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
VITE_APP_DOMAIN = 'prev.ysaix.com'
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
# websocket 地址
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
# 是否显示开发工具
VITE_SHOW_DEV_TOOLS = 'false'

25
.env.yc2 Normal file
View File

@ -0,0 +1,25 @@
# 页面标题
VITE_APP_TITLE = 实训教学
# 生产环境配置
VITE_APP_ENV = 'production'
# AIx融合数字管理系统/生产环境
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
VITE_APP_DOMAIN = 'prev.ysaix.com'
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
# websocket 地址
VITE_APP_WS_URL = 'wss://prev.ysaix.com:7868'
# 是否显示开发工具
VITE_SHOW_DEV_TOOLS = 'false'

54
electron-builder-yc.yml Normal file
View File

@ -0,0 +1,54 @@
appId: com.electron.app.yc
productName: 文枢课堂
directories:
output: dist
buildResources: build
win:
executableName: 文枢课堂
icon: resources/yc-logo.png
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/**
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
artifactName: ${name}-yc-${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://prev.ysaix.com:7868/src/assets/smarttalkyc/
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
# 额外依赖打包到输出目录
extraFiles:
- from: ./node_modules/im_electron_sdk/lib/
to: ./resources
filter:
- '**/*'

54
electron-builder-yc2.yml Normal file
View File

@ -0,0 +1,54 @@
appId: com.electron.app.yc2
productName: 实训教学
directories:
output: dist
buildResources: build
win:
executableName: 实训教学
icon: resources/yc-logo.png
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/**
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
artifactName: ${name}-ycsx-${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://prev.ysaix.com:7868/src/assets/smarttalkycsx/
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
# 额外依赖打包到输出目录
extraFiles:
- from: ./node_modules/im_electron_sdk/lib/
to: ./resources
filter:
- '**/*'

View File

@ -1,6 +1,6 @@
{ {
"name": "aix-win-ws", "name": "aix-win-ws",
"version": "2.5.7", "version": "2.5.8",
"description": "", "description": "",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "上海交大重庆人工智能研究院", "author": "上海交大重庆人工智能研究院",
@ -16,6 +16,8 @@
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml", "build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml", "build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml", "build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml", "build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml", "build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
"build:linux": "npm run build && electron-builder --linux" "build:linux": "npm run build && electron-builder --linux"

BIN
resources/yc-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -7,6 +7,7 @@ import Logger from './logger' // 日志封装
import chat from './chat' // chat封装 import chat from './chat' // chat封装
import Store from './store' // Store封装 import Store from './store' // Store封装
import updateInit from './update' import updateInit from './update'
// 代理 electron/remote // 代理 electron/remote
// 第一步引入remote // 第一步引入remote
import remote from '@electron/remote/main' import remote from '@electron/remote/main'
@ -41,19 +42,19 @@ if(!gotTheLock){
} }
}) })
} }
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'../../resources/yc-logo.png':'../../resources/logo2.ico'
//登录窗口 //登录窗口
function createLoginWindow() { function createLoginWindow() {
if (loginWindow) return if (loginWindow) return
loginWindow = new BrowserWindow({ loginWindow = new BrowserWindow({
width: 888, width: import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?1060:888,
height: 520, height: 520,
show: false, show: false,
frame: false, frame: false,
autoHideMenuBar: true, autoHideMenuBar: true,
maximizable: false, maximizable: false,
resizable: false, resizable: false,
icon: join(__dirname, '../../resources/logo2.ico'), icon: join(__dirname, logoIco),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
defaultEncoding: 'utf-8', defaultEncoding: 'utf-8',
@ -95,7 +96,7 @@ function createMainWindow() {
frame: false, // 无边框 frame: false, // 无边框
autoHideMenuBar: true, autoHideMenuBar: true,
maximizable: false, maximizable: false,
icon: join(__dirname, '../../resources/logo2.ico'), icon: join(__dirname, logoIco),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
defaultEncoding: 'utf-8', defaultEncoding: 'utf-8',

View File

@ -74,8 +74,6 @@ const initLoad: Function = () => {
!!(opt.ratio??null) && slidesStore.setViewportRatio(opt.ratio)// !!(opt.ratio??null) && slidesStore.setViewportRatio(opt.ratio)//
} }
return PPTApi.getSlideList(resource.id) return PPTApi.getSlideList(resource.id)
// PPTApi.updateWorkList()
// return Promise.resolve()
} }
return Promise.resolve() return Promise.resolve()
} }

View File

@ -50,6 +50,8 @@ export class Utils {
}, delay) }, delay)
} }
} }
// 延时
static sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
} }
/** ppt相关后端接口处理 */ /** ppt相关后端接口处理 */
@ -84,6 +86,12 @@ export class PPTApi {
slidesStore.setWorkItem(workItem) slidesStore.setWorkItem(workItem)
// 没有上课时调用-作业列表 // 没有上课时调用-作业列表
if(!classcourse) this.updateWorkList() if(!classcourse) this.updateWorkList()
// 没有上课时调用-批量更新缩略图
if(!classcourse) {
Utils.sleep(1500).then(() => {
this.batchUpdateThumUrl()
})
}
resolve(true) resolve(true)
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false) } else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
}) })
@ -233,13 +241,20 @@ export class PPTApi {
// 批量更新缩略图-异步 // 批量更新缩略图-异步
static batchUpdateThumUrl() { static batchUpdateThumUrl() {
return new Promise(async resolve => { return nextTick().then(async () => {
const list = slidesStore.workItem || [] const list = slidesStore.workItem || []
if (!list.length) return resolve() if (!list.length) return
const upList = [] const upList = []
for (const [ind,o] of list.entries()) { for (const [ind,o] of list.entries()) {
const isCreate = !o.fileurl // 是否创建
if (isCreate) {
const thumUrl = await this.getSlideThumUrl(ind) const thumUrl = await this.getSlideThumUrl(ind)
upList.push({ id: o.id, fileurl: thumUrl })
} }
}
if (!upList.length) return
// 批量更新
return await API_entpcoursefile.batchUpdateNew(upList)
}) })
} }

View File

@ -126,6 +126,8 @@ export class MsgEnum {
MSG_homework : 'HOMEWORK', MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */ /** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work', MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */ /** @desc: 点赞 */
MSG_dz : 'dz', MSG_dz : 'dz',
/** @desc: 疑惑 */ /** @desc: 疑惑 */

View File

@ -10,6 +10,7 @@ import { MsgEnum } from './types' // 消息枚举
import ChatWs from '@/plugins/socket' // 聊天socket import ChatWs from '@/plugins/socket' // 聊天socket
import Classcourse from './classcourse' // 课程相关 import Classcourse from './classcourse' // 课程相关
import msgUtils from '@/plugins/modal' // 消息工具 import msgUtils from '@/plugins/modal' // 消息工具
import * as dialogUtils from '@/utils/dialog' // 弹窗-函数
import { Homework } from './index' // api-作业相关 import { Homework } from './index' // api-作业相关
// import emitter from '@/utils/mitt' //mitt 事件总线 // import emitter from '@/utils/mitt' //mitt 事件总线
import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制 import useExecPlay from '../views/Screen/hooks/useExecPlay' // 播放控制
@ -114,6 +115,10 @@ export default () => {
if (!content.id) return if (!content.id) return
Homework.showHomework(content.id) Homework.showHomework(content.id)
break break
case MsgEnum.HEADS.MSG_pushSreen_experiment: // 打开实验:
if (!content.url) return
dialogUtils.openLink(content.url)
break
case MsgEnum.HEADS.MSG_closed: // 下课: case MsgEnum.HEADS.MSG_closed: // 下课:
close() close()
break break

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

View File

@ -83,7 +83,7 @@
<!--AI 对话调整--> <!--AI 对话调整-->
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/> <AdjustDialog v-model="isAdjust" :type="type" :item="editItem" :curMode="curMode" :conversation_id="conversation_id"/>
<!--添加编辑提示词--> <!--添加编辑提示词-->
<keywordDialog v-model="isWordDialog" :item="editItem" /> <keywordDialog v-model="isWordDialog" :item="editItem" :modeType="type" />
</template> </template>
<script setup> <script setup>
@ -104,7 +104,7 @@ import { cloneDeep } from 'lodash'
const props = defineProps(['type']) const props = defineProps(['type'])
const { user } = useUserStore() const { user } = useUserStore()
const curMode = ref(1) const curMode = ref(2)
const modeOptions = ref([ const modeOptions = ref([
{ {
label: '教学大模型', label: '教学大模型',

View File

@ -38,6 +38,7 @@ const closeWindow = () => {
ElMessageBox.confirm('确认退出系统吗?', '提示', { ElMessageBox.confirm('确认退出系统吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
customClass: 'login-close-tool',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
userStore.logOut().then(() => { userStore.logOut().then(() => {
@ -54,7 +55,11 @@ onMounted(() =>{
}) })
</script> </script>
<style>
.login-close-tool {
-webkit-app-region: no-drag;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.header-tool { .header-tool {
width: 100%; width: 100%;

View File

@ -100,6 +100,8 @@ export class MsgEnum {
MSG_homework : 'HOMEWORK', MSG_homework : 'HOMEWORK',
/** @desc: 公屏 - 课堂作业|活动 */ /** @desc: 公屏 - 课堂作业|活动 */
MSG_pushSreen_work : 'pushSreen_work', MSG_pushSreen_work : 'pushSreen_work',
/** @desc: 公屏 - 实验 */
MSG_pushSreen_experiment : 'pushSreen_experiment',
/** @desc: 点赞 */ /** @desc: 点赞 */
MSG_dz : 'dz', MSG_dz : 'dz',
/** @desc: 疑惑 */ /** @desc: 疑惑 */

View File

@ -403,5 +403,11 @@ export const dataSetJson = {
"教材-高中-数学": "e03aa4fe9fd011ef91270242ac140006", "教材-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
"教材-高中-地理": "270516829fd111efb13c0242ac140006", "教材-高中-地理": "270516829fd111efb13c0242ac140006",
"教材-高中-政治": "a2f0b247b85d11ef84290242ac140005", "教材-高中-政治": "a2f0b247b85d11ef84290242ac140005",
"课标-小学-科学": "935cfec8bf6a11ef98950242ac140006",
"课标-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
"课标-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
"教材-小学-科学": "935cfec8bf6a11ef98950242ac140006",
"教材-小学-数学": "3c4e298fbf7911ef8e8b0242ac140002",
"教材-小学-语文": "f76f1aa5bf7111ef90c80242ac140002",
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm" "鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
} }

View File

@ -0,0 +1,49 @@
/**
* 弹窗-函数
*/
import { h, render } from 'vue'
import { ElDialog } from 'element-plus'
// 打开弹窗-函数
export const openDialog = (option, content) => {
let vNode
const body = document.body
const dOpts = {
modelValue: true,
width: 800,
height: 600,
title: '添加-超连接',
draggable: true,
'onUpdate:modelValue': val => {
if (vNode && !val) render(null, body)
},
...option
}
vNode = h(ElDialog, dOpts, {
default: typeof content == 'function' ? content(h) : content
})
render(vNode, body)
}
// 打开链接
export const openLink = (option, title) => {
// https://phet.colorado.edu/sims/html/number-play/latest/number-play_zh_CN.html
const isStr = typeof option == 'string'
const opt = isStr ? {} : option
const url = isStr ? option : option?.url || option?.src || option?.href
const titleNew = isStr? title||'实验室' : option?.title || '添加-超连接'
openDialog({
title: titleNew,
...opt
}, (h) => {
return h('iframe', {
src: url,
width: '100%',
style: {
height: 'calc(80vh - 75px)',
},
scrolling: 'no',
frameborder: '0',
})
})
}

View File

@ -9,7 +9,7 @@ export const asyncLocalFile = (item) => {
if (isAsync === true) { if (isAsync === true) {
item.async = 'on' item.async = 'on'
if (type === 'down') { if (type === 'down') {
console.log(item) // console.log(item)
ipcRenderer.send('download-file-default', { ipcRenderer.send('download-file-default', {
url: item.fileFullPath, url: item.fileFullPath,
fileName: item.fileNewName fileName: item.fileNewName

View File

@ -248,10 +248,11 @@ export function toolWindow(type, {url, isConsole, isWeb=true, option={}}) {
const devUrl = `${BaseUrl}${url}` const devUrl = `${BaseUrl}${url}`
const buildUrl = path.join(__dirname, 'index.html') const buildUrl = path.join(__dirname, 'index.html')
const urlAll = isDev ? devUrl : buildUrl const urlAll = isDev ? devUrl : buildUrl
let logoIco = import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?'/resources/yc-logo.png':'/resources/logo2.ico'
return new Promise(async(resolve) => { return new Promise(async(resolve) => {
const config = { const config = {
width, height, width, height,
icon: path.join(appPath, '/resources/logo2.ico'), icon: path.join(appPath, logoIco),
webPreferences: { webPreferences: {
preload: path.join(API.preloadPath, '/index.js'), preload: path.join(API.preloadPath, '/index.js'),
sandbox: false, sandbox: false,

View File

@ -734,6 +734,10 @@ const msgHandle = (msg) => {
Homework.win = null Homework.win = null
window.close() // window.close() //
break break
case MsgEnum.HEADS.MSG_pushSreen_experiment: // :
Homework.win = null
window.close() //
break
// case 'TIMAddRecvNewMsgCallback': // data=[] // case 'TIMAddRecvNewMsgCallback': // data=[]
// { // {
// (data||[]).forEach(o => { // (data||[]).forEach(o => {

View File

@ -0,0 +1,427 @@
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录 {{ homeTitle }}</p>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<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>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item>
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" :disabled="codeName=='发送验证码'?false:true" type="primary" @click="sendyzm">{{ codeName }}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题1</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
import leftBg2 from '@/assets/images/login/left-bg2.png'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
const { session } = require('@electron/remote')
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
// codeName.value=60
// timer.value=setInterval(()=>{
// codeName.value--
// if(codeName.value==0){
// codeName.value=''
// clearInterval(timer.value);
// }
// },1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject || isStadium(userStore.user)) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (timer.value){
clearInterval(timer.value);
}
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</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;
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.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;
}
}
}
.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

@ -1,427 +1,13 @@
<template> <template>
<div class="login-container"> <ycLogin v-if="buildMode === 'yc'||buildMode === 'yc2'">
<div class="box-item desc"> </ycLogin>
<div class="welcome"> <defultLogin v-else>
<p>欢迎登录 {{ homeTitle }}</p> </defultLogin>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<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>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item>
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" :disabled="codeName=='发送验证码'?false:true" type="primary" @click="sendyzm">{{ codeName }}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题1</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, ref } from 'vue' import ycLogin from './yc-login.vue'
import { ElMessage } from 'element-plus' import defultLogin from './defult-login.vue'
import { encrypt, decrypt } from '@/utils/jsencrypt' const buildMode = import.meta.env.MODE
import useUserStore from '@/store/modules/user'
import leftBg2 from '@/assets/images/login/left-bg2.png'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
const { session } = require('@electron/remote')
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
// codeName.value=60
// timer.value=setInterval(()=>{
// codeName.value--
// if(codeName.value==0){
// codeName.value=''
// clearInterval(timer.value);
// }
// },1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject || isStadium(userStore.user)) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (timer.value){
clearInterval(timer.value);
}
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</script> </script>
<style lang="scss" scoped> <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;
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.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;
}
}
}
.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> </style>

View File

@ -0,0 +1,463 @@
<template>
<div class="login-container">
<div class="login-yc">
<img class="welcome-img" :src="buildMode === 'yc2'?leftBg2:leftBg1" />
</div>
<div class="box-item login" v-if="isRegister">
<WindowTools :is-has-max="false" />
<div style="display: flex;justify-content: center;"><img class="title-logo" :src="yclogo" /></div>
<div class="login-title">永川中小学</div>
<div class="login-title2">{{buildMode === 'yc2'?'虚拟仿真AI实训教学管理系统':'人工智能赋能科学素养与劳动技能系统'}}</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>
<div class="flex mb-5">
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
<!-- <el-checkbox >阅读并同意xxx</el-checkbox> -->
</div>
<el-form-item style="margin-bottom: 20px">
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoreRegister">注册账号</a>
</div>
<div class="title-bottom">
重庆市永川区教育委员会
</div>
</el-form>
</div>
<div class="box-item login" v-else>
<WindowTools :is-has-max="false" />
<div class="login-title">账号注册</div>
<el-form ref="ruleFormRef" class="login-form" :model="ruleForm" label-width="auto" :rules="rules" size="large">
<el-form-item label="手机号" prop="username">
<el-input v-model="ruleForm.username" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码" prop="smsCode" style="display: flex">
<el-input style="width:185px" v-model="ruleForm.smsCode" placeholder="请输入验证码" /><el-button style="margin-left:10px;width:100px" :disabled="codeName=='发送验证码'?false:true" type="primary" @click="sendyzm">{{ codeName }}</el-button>
</el-form-item>
<el-form-item label="密码" prop="password" >
<el-input autocomplete="on" type="password" v-model="ruleForm.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button class="btn" type="primary" @click="RegisterForm(ruleFormRef)">立即注册</el-button>
</el-form-item>
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="gotoLogin"> 返回登录 </a>
</div>
</el-form>
</div>
</div>
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center>
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
status="success" />
</el-dialog>
<el-dialog
v-model="isImg"
title="人机验证"
width="500"
style=" -webkit-app-region: no-drag;"
>
<span>根据图片回答相关问题1</span>
<div style="display: flex;align-items: center;;margin-top:30px">
<img :src="isPeopleImg" style="width:200px;height:60px;cursor: pointer;" alt="" srcset="" @click="refreshImg">
<el-input v-model="ruleForm.imgCode" style="width: 250px;height:40px;margin-left:20px" placeholder="请根据图片填入答案" />
</div>
<div style="display: flex;justify-content: center;margin-top:30px">
<el-button type="primary" @click="sbmitImg">确定</el-button>
</div>
</el-dialog>
<!--选择学科-->
<SelectSubject v-model="isSubject" :login-data="loginForm" />
<!--注册弹框-->
<Register ref="RegModel"></Register>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
import WindowTools from '@/components/window-tools/index.vue'
import SelectSubject from '@/components/select-subject/index.vue'
import Register from './components/Register.vue'
import { sessionStore } from '@/utils/store'
import {sendcode,instructorregister,getCodeImg} from '@/api/login'
import yclogo from '@/assets/images/login/yc-logo.png'
import leftBg1 from '@/assets/images/login/ycpeitu.png'
import leftBg2 from '@/assets/images/login/ycpeitu2.jpg'
const { session } = require('@electron/remote')
const buildMode = import.meta.env.MODE
const downloadProp = ref(0)
const showDownLoading = ref(false)
const { ipcRenderer } = window.electron || {}
const formRef = ref()
const userStore = useUserStore()
const btnLoading = ref(false)
const isSubject = ref(false)
const RegModel = ref(false)
const isRegister = ref(true)
const ruleFormRef = ref(null)
const codeName=ref('发送验证码')
const timer=ref(null)
const isImg=ref(false)
const isPeopleImg=ref(null)
const type=ref(1) // 1 2
const resImg = reactive({ imgData: {} });
//
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
//
const ruleForm = reactive({
})
//
const rules = reactive({
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
smsCode: [{ required: true, trigger: 'blur', message: '请输入您的验证码' }],
})
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
ipcRenderer.on('update-app-progress', (e, prop) => {
downloadProp.value = prop
showDownLoading.value = prop !== 100
})
const gotoreRegister=()=>{
codeName.value='发送验证码'
if(timer.value){
clearInterval(timer.value);
}
isRegister.value=false
}
//
const refreshImg=()=>{
getCodeImg().then(res=>{
isPeopleImg.value='data:image/jpg;base64,'+res.img
resImg.imgData=res
})
}
//
const sbmitImg=()=>{
if(ruleForm.imgCode){
// {mobile:ruleForm.phoneNumber,code:ruleForm.imgCode,uuid:resImg.imgData.uuid}
const { username:username,imgCode:code } = ruleForm
const params = {
username, code,
uuid: resImg.imgData.uuid,
source:4
}
sendcode(params).then(res=>{
if(res.code==200){
ElMessage.success('短信发送成功')
ruleForm.Code=res.data
isImg.value=false
codeName.value=60
timer.value=setInterval(()=>{
codeName.value--
if(codeName.value==0){
codeName.value='发送验证码'
clearInterval(timer.value);
}
},1000)
}
})
}else{
ElMessage.error('请根据图片输入验证码')
}
//
}
//
const sendyzm=()=>{
if(ruleForm.username){
const pattern = /^1[3-9]\d{9}$/;
if( pattern.test(ruleForm.username) ){
getCodeImg().then(res=>{
if(res.code==200){
ruleForm.imgCode=null
isPeopleImg.value='data:image/jpg;base64,'+res.img
isImg.value=true
resImg.imgData=res
// codeName.value=60
// timer.value=setInterval(()=>{
// codeName.value--
// if(codeName.value==0){
// codeName.value=''
// clearInterval(timer.value);
// }
// },1000)
}else{
ElMessage.error(res.msg)
}
})
}else{
ElMessage.error('请输入正确的手机号码')
}
// captchaImg({mobile:ruleForm.phoneNumber}).then(res=>{
// console.log('res->', res)
// })
}else{
ElMessage.error('请输入手机号码')
}
}
//
const RegisterModel = type => {
RegModel.value.OpenModel(type)
}
//
const submitForm = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (valid) {
btnLoading.value = true
// cookie
if (loginForm.rememberMe) {
await setCookie('username', loginForm.username)
await setCookie('password', encrypt(loginForm.password))
await setCookie('rememberMe', loginForm.rememberMe.toString())
} else {
//
await session.defaultSession.clearStorageData({
origin: curWinUrl,
storages: ['cookies']
})
}
try {
await userStore.login(loginForm)
await userStore.getInfo()
if (userStore.user.edustage || userStore.user.edusubject || isStadium(userStore.user)) {
ElMessage.success('登录成功')
ipcRenderer && ipcRenderer.send('openMainWindow')
} else {
isSubject.value = true
}
} finally {
btnLoading.value = false
}
}
})
}
const isStadium = (user) => {
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')
}
const getCookie = async () => {
const username = (await getCookieDetail('username'))[0]
const password = (await getCookieDetail('password'))[0]
const rememberMe = (await getCookieDetail('rememberMe'))[0]
loginForm.username = username ? username.value : loginForm.username
loginForm.password = password ? decrypt(password.value) : loginForm.password
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
}
// cookie
const getCookieDetail = (name) => {
return session.defaultSession.cookies.get({ url: curWinUrl, name })
}
// cookie
const setCookie = (name, value) => {
// 30
let Days = 30
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
const cookie = {
url: curWinUrl,
name,
value,
expirationDate: times
}
return session.defaultSession.cookies.set(cookie)
}
const gotoLogin = () => {
codeName.value='发送验证码'
if (timer.value){
clearInterval(timer.value);
}
if (ruleFormRef.value) ruleFormRef.value.resetFields()
isRegister.value = true
}
//
const RegisterForm = async (formEl) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
instructorregister(ruleForm).then(res=>{
if(res.code==200){
ElMessage.success('您已注册成功')
if (ruleFormRef.value) ruleFormRef.value.resetFields()
gotoLogin()
}else{
ElMessage.error(res.msg)
}
})
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
onMounted(() => {
localStorage.clear()
sessionStore.set('subject', {
bookList: null,
curBook: null,
curNode: null,
defaultExpandedKeys: [],
subjectTree: []
})
getCookie()
})
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
.login-yc{
width: 100%;
height: 100%;
img{
width: 100%;
height: 100%;
}
}
.box-item {
width: 370px;
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;
.title-logo{
width: 50px;
}
}
.welcome {
padding-top: 35px;
p {
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
}
.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-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 10px;
margin-top: 5px;
font-width: bold;
}
.login-title2 {
margin-bottom: 20px;
}
.login-form {
-webkit-app-region: no-drag;
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
.title-bottom{
text-align: center;
width: 100%;
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
}
}
.btn {
width: 350px;
height: 50px;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
}
.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

@ -126,11 +126,17 @@
<span>下载</span> <span>下载</span>
</el-button> </el-button>
</div> </div>
<div v-if="item.fileSuffix === 'ppt' || item.fileSuffix === 'pptx'" class="item-popover-item"> <!-- <div v-if="item.fileSuffix === 'ppt' || item.fileSuffix === 'pptx'" class="item-popover-item">
<el-button text @click="adToKj(item)"> <el-button text @click="adToKj(item)">
<i class="iconfont icon-jiahao"></i> <i class="iconfont icon-jiahao"></i>
<span>加入课件</span> <span>加入课件</span>
</el-button> </el-button>
</div>-->
<div v-if="item.fileSuffix === 'ppt' || item.fileSuffix === 'pptx'" class="item-popover-item">
<el-button text @click="importPPT(item)">
<i class="iconfont icon-jiahao"></i>
<span>导入PPT</span>
</el-button>
</div> </div>
<div class="item-popover-item"> <div class="item-popover-item">
<el-button text @click="moveSmarttalkFun(item)"> <el-button text @click="moveSmarttalkFun(item)">
@ -181,7 +187,7 @@ export default {
} }
} }
}, },
emits: { 'on-move': null, 'on-delete': null, 'on-set': null, 'on-reSet': null, 'on-delhomework': null,'on-filearg': null }, emits: { 'on-move': null, 'on-delete': null, 'on-set': null, 'on-reSet': null, 'on-delhomework': null,'on-filearg': null,'on-importPPT': null },
data() { data() {
return { return {
listenList: [], listenList: [],
@ -217,6 +223,9 @@ export default {
}) })
.catch(() => {}) .catch(() => {})
}, },
importPPT(item) {
this.$emit('on-importPPT', item)
},
downloadFile(item) { downloadFile(item) {
ipcRenderer.send('save-as', item.fileFullPath, item.fileShowName) ipcRenderer.send('save-as', item.fileFullPath, item.fileShowName)
}, },

View File

@ -99,6 +99,7 @@
@on-delete="deleteTalk" @on-delete="deleteTalk"
@on-set="openSet" @on-set="openSet"
@on-delhomework="delhomework" @on-delhomework="delhomework"
@on-importPPT="importPPT"
@on-filearg="isOpenHomework = true" @on-filearg="isOpenHomework = true"
> >
<el-checkbox v-if="!item.uniquekey" label="" :value="item" /> <el-checkbox v-if="!item.uniquekey" label="" :value="item" />
@ -555,6 +556,15 @@ export default {
progDownFile(e, num) { progDownFile(e, num) {
this.downloadNum = num this.downloadNum = num
}, },
importPPT(item) {
let _this = this;
fetch(item.fileFullPath)
.then(res => res.arrayBuffer())
.then(buffer => {
let name = item.fileShowName.substring(0, item.fileShowName.lastIndexOf('.')) + '.aippt'
_this.createAIPPTByFile(buffer, name)
})
},
createFile() { createFile() {
creatPPT(this.currentNode.itemtitle + '.pptx', this.uploadData).then((res) => { creatPPT(this.currentNode.itemtitle + '.pptx', this.uploadData).then((res) => {
this.currentFileList.unshift(res.resData) this.currentFileList.unshift(res.resData)
@ -573,7 +583,7 @@ export default {
console.log('文件名:', file.name); console.log('文件名:', file.name);
console.log('文件类型:', file.type); console.log('文件类型:', file.type);
console.log('文件大小:', file.size); console.log('文件大小:', file.size);
this.createAIPPTByFile(file) this.createAIPPTByFile(file, this.currentNode.itemtitle + '.aippt')
} }
}, },
async toRousrceUrl(o) { async toRousrceUrl(o) {
@ -619,7 +629,7 @@ export default {
} }
} }
}, },
async createAIPPTByFile(file) { async createAIPPTByFile(file,fileShowName) {
this.pgDialog.visible = true this.pgDialog.visible = true
this.pgDialog.pg.percentage = 0 this.pgDialog.pg.percentage = 0
const resPptJson = await PPTXFileToJson(file) const resPptJson = await PPTXFileToJson(file)
@ -665,7 +675,7 @@ export default {
...this.uploadData, ...this.uploadData,
fileId: slideid, fileId: slideid,
fileFlag: 'aippt', fileFlag: 'aippt',
fileShowName: this.currentNode.itemtitle + '.aippt' fileShowName: fileShowName
}).then(async (res) => { }).then(async (res) => {
const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide)) const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide))

View File

@ -92,7 +92,9 @@ import * as API_entpcourse from '@/api/education/entpcourse' // 相关api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import * as Api_server from '@/api/apiService' // api import * as Api_server from '@/api/apiService' // api
import * as API_smarttalk from '@/api/file' // api import * as API_smarttalk from '@/api/file' // api
import msgUtils from '@/plugins/modal' // import msgUtils from '@/plugins/modal'
import { getEntpcoursefile } from '@/api/education/entpcoursefile'
import { createWindow } from '@/utils/tool' //
const userStore = useUserStore() const userStore = useUserStore()
const pptDialog = ref(false) const pptDialog = ref(false)
@ -117,7 +119,7 @@ const pgDialog = reactive({ // 弹窗-进度条
} }
}) })
const curMode = ref(1) const curMode = ref(2)
const modeOptions = ref([ const modeOptions = ref([
{ {
label: '教学大模型', label: '教学大模型',
@ -346,13 +348,20 @@ const addAiPPT = async (res) => {
const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params) const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params)
if (!!parentid ?? null) { // if (!!parentid ?? null) { //
// -Smarttalk // -Smarttalk
HTTP_SERVER_API('addSmarttalk', { fileId: parentid }) const smarttalk = await HTTP_SERVER_API('addSmarttalk', { fileId: parentid })
if (slides.length > 0) { if (slides.length > 0) {
const resSlides = slides.map(({ id, ...slide }) => JSON.stringify(slide)) const resSlides = slides.map(({ id, ...slide }) => JSON.stringify(slide))
const params = { parentid, filetype: 'slide', title: '', slides: resSlides } const params = { parentid, filetype: 'slide', title: '', slides: resSlides }
const res_3 = await HTTP_SERVER_API('batchAddNew', params) const res_3 = await HTTP_SERVER_API('batchAddNew', params)
if (res_3 && res_3.code == 200) { if (res_3 && res_3.code == 200) {
msgUtils.msgSuccess('生成PPT课件成功') msgUtils.msgSuccess('生成PPT课件成功')
//TODO
const res = await getEntpcoursefile(parentid)
if (res && res.code === 200) {
openPublicScreen('edit', res.data, smarttalk.resData) // -
} else {
ElMessage.warning(res.msg||'文件获取异常!')
}
} else { } else {
msgUtils.msgWarning('生成PPT课件失败') msgUtils.msgWarning('生成PPT课件失败')
} }
@ -361,7 +370,20 @@ const addAiPPT = async (res) => {
}) })
} }
const openPublicScreen = (type, resource, currData)=> {
sessionStore.set('curr.resource', resource) //
if (type=='edit') sessionStore.set('curr.smarttalk', currData) // smarttalk
else sessionStore.set('curr.classcourse', currData) //
createWindow('open-win', {
url: '/pptist', //
close: () => {
sessionStore.set('curr.resource', null) //
if (type=='edit') {
sessionStore.set('curr.smarttalk', null) //
} else sessionStore.set('curr.classcourse', null) //
}
})
}
const isEdit = ref(false) const isEdit = ref(false)
// //
const curIndex = ref(-1) const curIndex = ref(-1)