Merge branch 'main' into zouyf_dev

This commit is contained in:
“zouyf” 2024-12-23 10:11:48 +08:00
commit f6b08169b6
39 changed files with 1482 additions and 603 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相关后端接口处理 */
@ -59,6 +61,7 @@ export class PPTApi {
// 获取所有幻灯片列表 isUpdate为true不更新 // 获取所有幻灯片列表 isUpdate为true不更新
static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> { static getSlideList(parentid: (Number | String),isUpdate?:Boolean): Promise<Boolean> {
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 } const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params) const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
@ -79,12 +82,16 @@ export class PPTApi {
// 活动列表处理 // 活动列表处理
// const workList = (res.rows || []).map(o => o.activityContent) // const workList = (res.rows || []).map(o => o.activityContent)
const workItem = res.rows ? [...res.rows] : [] const workItem = res.rows ? [...res.rows] : []
// 写入作业列表数据
// slidesStore.setWorkList(workList)
// 获取所有的pptlist的数据 // 获取所有的pptlist的数据
slidesStore.setWorkItem(workItem) slidesStore.setWorkItem(workItem)
// 没有上课时调用-作业列表
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)
}) })
@ -232,10 +239,29 @@ export class PPTApi {
}) })
} }
// thumbnail-slide thumbnail 缩略图 // 批量更新缩略图-异步
static getSlideThumUrl(): Promise<Boolean> { static batchUpdateThumUrl() {
return nextTick().then(async () => { return nextTick().then(async () => {
const slideIndex = slidesStore.slideIndex const list = slidesStore.workItem || []
if (!list.length) return
const upList = []
for (const [ind,o] of list.entries()) {
const isCreate = !o.fileurl // 是否创建
if (isCreate) {
const thumUrl = await this.getSlideThumUrl(ind)
upList.push({ id: o.id, fileurl: thumUrl })
}
}
if (!upList.length) return
// 批量更新
return await API_entpcoursefile.batchUpdateNew(upList)
})
}
// thumbnail-slide thumbnail 缩略图
static getSlideThumUrl(index?:number): Promise<Boolean> {
return nextTick().then(async() => {
const slideIndex = index ?? slidesStore.slideIndex
const elements = document.querySelectorAll('.thumbnail-slide') const elements = document.querySelectorAll('.thumbnail-slide')
if (elements.length && slideIndex >= 0) { if (elements.length && slideIndex >= 0) {
const element = elements[slideIndex] const element = elements[slideIndex]

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

View File

@ -25,6 +25,12 @@
<div style="margin-top: 10px">常规作业</div> <div style="margin-top: 10px">常规作业</div>
</div> </div>
</el-button> </el-button>
<el-button size="small" title="科学实验" text style="height: 54px;margin-left: 0" @click="showDialog('科学实验')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
<div style="margin-top: 10px">科学实验</div>
</div>
</el-button>
</div> </div>
<Divider /> <Divider />
@ -157,6 +163,10 @@ const type = ref<WorkType[]>([
{ {
label: '框架梳理', label: '框架梳理',
value: 'primary' value: 'primary'
},
{
label: '科学实验',
value: 'primary'
} }
]) ])

View File

@ -36,8 +36,8 @@
</div> </div>
<div <div
class="tools-right" :class="{ 'visible': rightToolsVisible }" class="tools-right" :class="{ 'visible': rightToolsVisible }"
@mouseleave="rightToolsVisible = false" @mouseleave="toolTrigger('leave')"
@mouseenter="rightToolsVisible = true" @mouseenter="toolTrigger('enter')"
> >
<div class="content"> <div class="content">
<div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div> <div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
@ -50,6 +50,10 @@
<IconPower class="tool-btn" v-tooltip="'结束放映'" @click="exitScreening()" /> <IconPower class="tool-btn" v-tooltip="'结束放映'" @click="exitScreening()" />
<IconPower class="tool-btn close" v-if="chat.groupid" v-tooltip="'结束课堂'" @click="exitCourse()" /> <IconPower class="tool-btn close" v-if="chat.groupid" v-tooltip="'结束课堂'" @click="exitCourse()" />
</div> </div>
<div :class="['tools-icon',{opacity:iconHide}]" @click.stop="toolTrigger('icon')">
<circle-double-down v-if="rightToolsVisible" theme="outline" size="30" fill="#409EFF"/>
<circle-double-up v-else="!rightToolsVisible" theme="outline" size="30" fill="#E6A23C"/>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -71,6 +75,7 @@ import WritingBoardTool from './WritingBoardTool.vue'
import CountdownTimer from './CountdownTimer.vue' import CountdownTimer from './CountdownTimer.vue'
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
import Chat from '../../api/chat' // import Chat from '../../api/chat' //
import { CircleDoubleDown, CircleDoubleUp } from '@icon-park/vue-next' // icon-park
const props = defineProps<{ const props = defineProps<{
changeViewMode: (mode: 'base' | 'presenter') => void changeViewMode: (mode: 'base' | 'presenter') => void
@ -103,12 +108,15 @@ const { exitScreening } = useScreening()
const { fullscreenState, manualExitFullscreen } = useFullscreen() const { fullscreenState, manualExitFullscreen } = useFullscreen()
const chat:any = Chat() // const chat:any = Chat() //
const screenStore =useScreenStore()
const rightToolsVisible = ref(false) const rightToolsVisible = ref(false)
const writingBoardToolVisible = ref(false) const writingBoardToolVisible = ref(false)
const timerlVisible = ref(false) const timerlVisible = ref(false)
const slideThumbnailModelVisible = ref(false) const slideThumbnailModelVisible = ref(false)
const laserPen = ref(false) const laserPen = ref(false)
const screenStore =useScreenStore() const timer = ref(0) //
const iconHide = ref(false) //
const timerId = ref(null) // id
const contextmenus = (): ContextmenuItem[] => { const contextmenus = (): ContextmenuItem[] => {
return [ return [
{ {
@ -191,6 +199,30 @@ const contextmenus = (): ContextmenuItem[] => {
] ]
} }
const toolTrigger = (type:string) => {
const curT = Date.now()
if (curT - timer.value < 200) return
iconHide.value = false //
if (timerId.value) clearTimeout(timerId.value) //
switch (type) {
case 'icon': //
timer.value = curT
rightToolsVisible.value = !rightToolsVisible.value
break
case 'enter': //
timer.value = curT
rightToolsVisible.value = true
break
case 'leave': //
rightToolsVisible.value = false
break
default:
break
}
timerId.value = setTimeout(() => { //
iconHide.value = true //
}, 2000)
}
// //
const exitCourse = async () => { const exitCourse = async () => {
// console.log('', chat) // console.log('', chat)
@ -253,6 +285,18 @@ const exitCourse = async () => {
top: -66px; top: -66px;
} }
.tools-icon{
position: absolute;
right: 8px;
top: -35px;
z-index: 1;
cursor: pointer;
transition: opacity $transitionDelay;
&.opacity{
opacity: .35;
}
}
.content { .content {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -2,6 +2,8 @@ import axios from 'axios'
import request from '@/utils/request' import request from '@/utils/request'
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
let rootPath = import.meta.env.VITE_APP_ENV === 'production' ? 'https://ai.ysaix.com:7864' : ''
// 文生图片 // 文生图片
export function convertTextToPicture(data) { export function convertTextToPicture(data) {
return axios({ return axios({
@ -42,7 +44,7 @@ export function getPicture(data) {
// 大模型对话生成prompt模板 // 大模型对话生成prompt模板
export function chattoprompt(dataset_id,prompt) { export function chattoprompt(dataset_id,prompt) {
return axios({ return axios({
url: '/api/v1/parse/docs', url: rootPath + '/api/v1/parse/docs',
method: 'post', method: 'post',
headers: { headers: {
'Authorization': 'Bearer ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm', 'Authorization': 'Bearer ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm',
@ -68,7 +70,7 @@ export function textSensitiveWord(data) {
// 图片上传资源库 // 图片上传资源库
export function uploadPicture(data) { export function uploadPicture(data) {
return axios({ return axios({
url: '/dev-api/smarttalk/file/upload', url: import.meta.env.VITE_APP_BASE_API + '/smarttalk/file/upload',
method: 'post', method: 'post',
headers: { headers: {
'Accept': '*/*', 'Accept': '*/*',

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

@ -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

@ -117,6 +117,8 @@ export const editListItem = (row, courseObj) => {
classtaskObj.fileHomeworkList = JSON.parse(row.workcodes); classtaskObj.fileHomeworkList = JSON.parse(row.workcodes);
// //
// console.log('科学实验', classtaskObj); // console.log('科学实验', classtaskObj);
// 更新默认的科学实验( 学段 学科 以及实验科目)
console.log('科学实验', classtaskObj);
return resolve(classtaskObj); return resolve(classtaskObj);
} }
} }

View File

@ -94,6 +94,8 @@ const getHomeWorkList = async () => {
res.rows[i].workclass = 'info'; res.rows[i].workclass = 'info';
} else if (res.rows[i].worktype == '习题训练') { } else if (res.rows[i].worktype == '习题训练') {
res.rows[i].workclass = 'danger'; res.rows[i].workclass = 'danger';
} else if (res.rows[i].worktype == '科学实验') {
res.rows[i].workclass = 'danger';
} else { } else {
res.rows[i].workclass = 'primary'; res.rows[i].workclass = 'primary';
} }

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

@ -5,6 +5,11 @@ import { JYApiListCT, JYApiListOriginYear, JYApiListSO} from "@/utils/examQuesti
const useClassTaskStore = defineStore('classTask',{ const useClassTaskStore = defineStore('classTask',{
state: () => ({ state: () => ({
experimentObj:{
edustage: '小学', // 教育阶段
edusubject: '', // 学科
experimentList: [], // 实验科目列表
},
isOpenQuestUploadView: false, // 是否打开习题上传的页面 isOpenQuestUploadView: false, // 是否打开习题上传的页面
classListIds: [], classListIds: [],
entpCourseWorkTypeList: [ entpCourseWorkTypeList: [

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

@ -709,6 +709,7 @@ const acceptParams = async (params) => {
} else { } else {
// //
if (params.studentObj.worktype == '常规作业' || params.studentObj.worktype == '科学实验') { if (params.studentObj.worktype == '常规作业' || params.studentObj.worktype == '科学实验') {
if(params.studentObj.worktype == '常规作业'){
try { try {
// datacontent TODO // datacontent TODO
const res = await getClassworkdata(params.studentObj.id); const res = await getClassworkdata(params.studentObj.id);
@ -733,6 +734,9 @@ const acceptParams = async (params) => {
} catch (error) { } catch (error) {
console.error('Invalid JSON:', error) console.error('Invalid JSON:', error)
} }
}else{
// TODO 2024-12-20
}
params.studentQuizAllList.forEach((item) => { params.studentQuizAllList.forEach((item) => {
classWorkFormScore.teacherRating.push({ classWorkFormScore.teacherRating.push({

View File

@ -8,7 +8,7 @@
@change="onSelectOption" @change="onSelectOption"
> >
<el-option <el-option
v-for="item in props.list" v-for="item in classTaskStore.experimentList"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.label" :value="item.label"
@ -21,8 +21,10 @@
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import {ArrowDown} from '@element-plus/icons-vue' import {ArrowDown} from '@element-plus/icons-vue'
import { onMounted,ref } from 'vue'; import { onMounted,ref } from 'vue';
import useClassTaskStore from '@/store/modules/classTask'
const userStore = useUserStore().user const userStore = useUserStore().user
const subjectList = ref([]) const subjectList = ref([])
const classTaskStore = useClassTaskStore().experimentObj
// emit // emit
let emit = defineEmits(['selectItem']) let emit = defineEmits(['selectItem'])
@ -42,7 +44,7 @@ onMounted(() => {
const onSelectOption = (option) => { const onSelectOption = (option) => {
console.log(option,'选择的实验课-------') console.log(option,'选择的实验课-------')
emit('selectItem', props.list.filter(item => item.label === option)[0]) emit('selectItem', classTaskStore.experimentList.filter(item => item.label === option)[0])
} }
</script> </script>

View File

@ -3,7 +3,7 @@
<div style="margin-left: 15px"> <div style="margin-left: 15px">
<el-dropdown @command="handleUserEduStage"> <el-dropdown @command="handleUserEduStage">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<el-button class="custom-button" type="default" round >{{ userStore.edustage }} <el-button class="custom-button" type="default" round >{{ useClassTaskStore().experimentObj.edustage }}
<el-icon><ArrowDown /></el-icon> <el-icon><ArrowDown /></el-icon>
</el-button> </el-button>
</span> </span>
@ -20,14 +20,14 @@
<div style="margin-left: 15px"> <div style="margin-left: 15px">
<el-dropdown @command="handleUserEduSubject"> <el-dropdown @command="handleUserEduSubject">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<el-button class="custom-button" type="default" round>{{ userStore.edusubject }} <el-button class="custom-button" type="default" round>{{ useClassTaskStore().experimentObj.edusubject }}
<el-icon><ArrowDown /></el-icon> <el-icon><ArrowDown /></el-icon>
</el-button> </el-button>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<template v-for="(item, index) in subjectList"> <template v-for="(item, index) in subjectList">
<el-dropdown-item v-if="item.edustage == userStore.edustage" :command="item.itemtitle">{{ <el-dropdown-item v-if="item.edustage == useClassTaskStore().experimentObj.edustage && isExpList(item.itemtitle)" :command="item.itemtitle">{{
item.itemtitle }}</el-dropdown-item> item.itemtitle }}</el-dropdown-item>
</template> </template>
</el-dropdown-menu> </el-dropdown-menu>
@ -43,11 +43,15 @@ import {ArrowDown} from '@element-plus/icons-vue'
import { onMounted,ref } from 'vue'; import { onMounted,ref } from 'vue';
import { listEvaluation } from '@/api/subject/index' import { listEvaluation } from '@/api/subject/index'
import jsonData from "@/utils/phetData.json"; import jsonData from "@/utils/phetData.json";
import useClassTaskStore from '@/store/modules/classTask'
const userStore = useUserStore().user const userStore = useUserStore().user
// emit // emit
let emit = defineEmits(['experlist']) // let emit = defineEmits(['experlist'])
// const expObj = ref({
// edustage: useClassTaskStore().experimentObj.edustage,
// edusubject: useClassTaskStore().experimentObj.edusubject,
// })
const subjectList = ref([]) const subjectList = ref([])
const chooseGrade= ref({}) const chooseGrade= ref({})
const expList = ref([]) // const expList = ref([]) //
@ -57,68 +61,100 @@ const checkList = ref([])// 当前年级对应学科的课程,
// //
const getSubject = () => { const getSubject = () => {
// //
if(!userStore.subject) return // if(!userStore.subject) return
listEvaluation({ itemkey: 'subject', pageSize: 500 }).then((res) => { // listEvaluation({ itemkey: 'subject', pageSize: 500 }).then((res) => {
const arr = userStore.subject.split(',') // const arr = userStore.subject.split(',')
subjectList.value = res.rows.filter(item => arr.includes(String(item.id))).map(items => items) // subjectList.value = res.rows.filter(item => arr.includes(String(item.id))).map(items => items)
console.log(subjectList,'subjectList'); // console.log(subjectList,'subjectList');
// })
// list
const edustageList = ['小学','初中','高中'];
const subList = ['数学','物理','化学','生物','科学'];
edustageList.forEach((item) => {
subList.forEach((subItems) => {
subjectList.value.push({
edustage: item,
edusubject: subItems,
itemtitle: subItems
}) })
})
})
console.log(subjectList,'subjectList');
//
handleUserEduStage(useClassTaskStore().experimentObj.edustage)
}
//
const isExpList = (edusubject) => {
let list = [];
switch (edusubject){
case '数学':
list = expList.value.filter(item => item.subject === 'math')
break;
case '物理':
list = expList.value.filter(item => item.subject === 'physics')
break;
case '化学':
checkList.value = expList.value.filter(item => item.subject === 'chemistry')
break;
case '生物':
list = expList.value.filter(item => item.subject === 'biology')
break;
case '科学':
list = expList.value.filter(item => item.subject === 'sciences')
break;
}
return list.length > 0
} }
// //
const handleUserEduStage = (item) => { const handleUserEduStage = (item) => {
userStore.edustage = item // userStore.edustage = item
// sessionStore.set('edustageSelf',item) useClassTaskStore().experimentObj.edustage = item
// if(item === ''){
// //
// userStore.edusubject = ''
// // sessionStore.set('edusubjectSelf','')
// }
// else
if(item === '高中' && userStore.edusubject === "道德与法治"){
//
userStore.edusubject = '政治'
// sessionStore.set('edusubjectSelf','')
}
else if(item != '高中' && userStore.edusubject === "政治"){
//
userStore.edusubject = '道德与法治'
// sessionStore.set('edusubjectSelf','')
}
//
// //
expList.value = [] expList.value = []
if(item === '小学'){ if(item === '小学'){
chooseGrade.value = jsonData.data.primary chooseGrade.value = jsonData.data.primary
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '小学');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
}
}else if(item === '初中'){ }else if(item === '初中'){
chooseGrade.value = jsonData.data.junior chooseGrade.value = jsonData.data.junior
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '初中');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
}
}else if(item === '高中'){ }else if(item === '高中'){
chooseGrade.value = jsonData.data.senior chooseGrade.value = jsonData.data.senior
expList.value = chooseGrade.value
const newSubjectList = subjectList.value.filter(item => item.edustage === '高中');
for(let i in newSubjectList){
const name = newSubjectList[i].itemtitle
if(isExpList(name)){
useClassTaskStore().experimentObj.edusubject = name;
//
handleUserEduSubject(name)
}
} }
for(let i in chooseGrade.value){
expList.value.push(chooseGrade.value[i])
} }
console.log(expList.value,'expList.value')
// const queryGrages = this.subjectList.filter(itemSubject => itemSubject.edustage === item)
// selectSubject(item, subjectList.value)
} }
// //
const handleUserEduSubject = (item) => { const handleUserEduSubject = (item) => {
userStore.edusubject = item; // userStore.edusubject = item;
// sessionStore.set('edusubjectSelf',item) useClassTaskStore().experimentObj.edusubject = item;
console.log(item,'选择的学科-------') console.log(item,'选择的学科-------')
//
// this.chooseSubject = item
checkList.value = [] checkList.value = []
// const queryGrages = expList.value.filter(itemSubject => itemSubject.edustage === item) || []
//
// const subjectNameList = queryGrages.map(itemSubName => {
// return itemSubName.edusubject
// })
// if(!subjectNameList.includes(item)) return
switch (item){ switch (item){
case '数学': case '数学':
checkList.value = expList.value.filter(item => item.subject === 'math') checkList.value = expList.value.filter(item => item.subject === 'math')
@ -137,7 +173,8 @@ const handleUserEduSubject = (item) => {
break; break;
} }
console.log(checkList.value,'checkList') console.log(checkList.value,'checkList')
emit('experlist',checkList.value) useClassTaskStore().experimentObj.experimentList = checkList.value;
// emit('experlist',checkList.value)
} }

View File

@ -2,14 +2,16 @@
<div class="experiment-page"> <div class="experiment-page">
<div class="activeExp-header"> <div class="activeExp-header">
<div class="infomation" v-if="isStadium() !== true" > <div class="infomation" v-if="isStadium() !== true" >
<selectClass v-if="!isSubject" @experlist="getExperimentList" /> <!-- <selectClass v-if="!isSubject" @experlist="getExperimentList" /> -->
<selectClass v-if="!isSubject" />
</div> </div>
<div> <div>
<selectExperiment :list="experimentList" @selectItem="getExperimentListItem" /> <selectExperiment @selectItem="getExperimentListItem" />
</div> </div>
</div> </div>
<div ref="mainDiv" class="activeExp-main" style="overflow: auto"> <div ref="mainDiv" class="activeExp-main" style="overflow: auto">
<iframe :src="activeExp.fileurl" ref="myuunity" width="100%" height="100%" scrolling="no" frameborder="0"></iframe> <div v-if="!activeExp.fileurl"><el-empty description="暂无学科实验"></el-empty></div>
<iframe v-else :src="activeExp.fileurl" ref="myuunity" width="100%" height="100%" scrolling="no" frameborder="0"></iframe>
<!-- <phet/>--> <!-- <phet/>-->
</div> </div>
</div> </div>
@ -18,21 +20,10 @@
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
//import html2canvas from 'html2canvas'; //import html2canvas from 'html2canvas';
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue' import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import { getBindlist } from '@/api/education/knowledgePoint'
import examDetailsDrawer from '@/components/exam-question/examDetailsDrawer.vue'
import { processList } from '@/hooks/useProcessList'
import { useGetHomework } from '@/hooks/useGetHomework'
import { sessionStore } from '@/utils/store'
import {throttle,debounce } from '@/utils/comm'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import useClassTaskStore from '@/store/modules/classTask' import useClassTaskStore from '@/store/modules/classTask'
import selectClass from './components/selectClass.vue' //
// import selectExperiment from './components/experimentList.vue' //
import selectClass from './components/selectClass.vue'
//
import selectExperiment from './components/experimentList.vue'
// emit // emit
@ -51,7 +42,7 @@ const props = defineProps({
} }
}) })
// //
const experimentList = ref([]); // const experimentList = ref([]);
const activeExp = ref({}); const activeExp = ref({});
const isStadium = () => { const isStadium = () => {
@ -77,20 +68,19 @@ onMounted(() => {
// } // }
// //
const getExperimentList = (val) => { // const getExperimentList = (val) => {
console.log(val,'选择的学科获取实验课程list信息') // console.log(val,'list')
experimentList.value = val; // // experimentList.value = val;
} // }
// //
const getExperimentListItem = (val) => { const getExperimentListItem = (val) => {
console.log(val,'选择的实验课程信息') console.log(val,'选择的实验课程信息')
activeExp.value = val; activeExp.value = val;
emit('clickExpObj', val) emit('clickExpObj', val)
} }
//
console.log(props.expObj,'科学props.expObj实验科目')
watch(() => props.expObj.fileurl, (newVal, oldVal) => { watch(() => props.expObj.fileurl, (newVal, oldVal) => {
console.log(newVal,'科学实验科目') console.log(props.expObj,'科学实验科目')
activeExp.value = props.expObj; activeExp.value = props.expObj;
}) })

View File

@ -200,7 +200,7 @@ const fileLoading = ref(false); // 常规作业loading
onMounted(() => { onMounted(() => {
console.log("----onMounted-------") console.log("----onMounted-------")
currentRow.value = {id:0}; currentRow.value.id = 0
if(propsQueryCourseObj){ if(propsQueryCourseObj){
if(JSON.parse(propsQueryCourseObj)){ if(JSON.parse(propsQueryCourseObj)){
courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj // courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj //
@ -235,7 +235,7 @@ const isInToMyQuestion = () => {
if(useClassTaskStores.isOpenQuestUploadView){ if(useClassTaskStores.isOpenQuestUploadView){
useClassTaskStores.isOpenQuestUploadView = false; useClassTaskStores.isOpenQuestUploadView = false;
currentRow.value = {id:1}; // currentRow.value.id = 1; //
activeAptTab.value = "个人题库"; activeAptTab.value = "个人题库";
// //
classWorkForm.id = 0; classWorkForm.id = 0;
@ -282,7 +282,7 @@ const handleItemClick = (itemName) => {
return; return;
} }
currentRow.value = {id:1}; // currentRow.value.id = 1; //
/** /**
* 智能推荐AI设计作业 * 智能推荐AI设计作业
* 习题训练 自主搜题 校本题库 个人题库 * 习题训练 自主搜题 校本题库 个人题库
@ -325,7 +325,7 @@ const initHomeWork = async()=> {
const handleNewAllClass = () => { const handleNewAllClass = () => {
taskTable.value.setCurrentRow({});// taskTable.value.setCurrentRow({});//
currentRow.value = {id:0}; // currentRow.value.id = 0; //
//-------- //--------
classWorkForm.id = 0; classWorkForm.id = 0;
classWorkForm.uniquekey = ""; // classWorkForm.uniquekey = ""; //
@ -352,7 +352,7 @@ const handleDelete =() => {
return delClasswork(ids.join(',')); return delClasswork(ids.join(','));
}).then(() => { }).then(() => {
taskTable.value.setCurrentRow({});// taskTable.value.setCurrentRow({});//
currentRow.value = {id:0}; // currentRow.value.id = 0; //
taskList.value = []; taskList.value = [];
// initHomeWork(); // initHomeWork();
setTimeout(() => { setTimeout(() => {
@ -446,7 +446,7 @@ const handleCurrentChange = (val) => {
console.log(val,'???????????') console.log(val,'???????????')
if(val && val.id >0 ) { if(val && val.id >0 ) {
currentRow.value = val; currentRow.value.id = 1;
classWorkForm.worktype = val.worktype; // classWorkForm.worktype = val.worktype; //
editListItem(val, courseObj).then((obj) => { editListItem(val, courseObj).then((obj) => {
if(obj){ if(obj){
@ -607,7 +607,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
addClassworkReturnId(cform).then((res) => { addClassworkReturnId(cform).then((res) => {
ElMessage({ type: 'success', message: '作业设计成功!'}); ElMessage({ type: 'success', message: '作业设计成功!'});
// //
classWorkForm.worktype = "常规作业"; classWorkForm.worktype = "科学实验";
classWorkForm.uniquekey = ''; // props.propsformobj.uniquekey, // classWorkForm.uniquekey = ''; // props.propsformobj.uniquekey, //
classWorkForm.title = ""; classWorkForm.title = "";
classWorkForm.quizlist = [], // classWorkForm.quizlist = [], //
@ -617,7 +617,7 @@ const handleClassWorkFormQuizRemove = (index) =>{
classWorkForm.whiteboardObj = ''; // ? // classWorkForm.whiteboardObj = ''; // ? //
classWorkForm.fileHomeworkList = []; // list classWorkForm.fileHomeworkList = []; // list
classWorkForm.id = res classWorkForm.id = res
// emits('getData',classWorkForm) emits('getData',classWorkForm)
// TODO // TODO
}) })
} finally{ } finally{
@ -670,9 +670,9 @@ const handleClassWorkFormQuizRemove = (index) =>{
console.log('该清空左侧列表数据了'); console.log('该清空左侧列表数据了');
// //
if(isShow.value){ if(isShow.value){
currentRow.value = {id:1}; currentRow.value.id = 1;
}else{ }else{
currentRow.value = {id:0}; currentRow.value.id = 0;
} }
initHomeWork(); initHomeWork();
@ -814,7 +814,11 @@ const editWork = async (cform) =>{
if (res.code == 200) { if (res.code == 200) {
ElMessage.success('更新成功'); ElMessage.success('更新成功');
// //
currentRow.value = {id:0}; if(isShow.value){
currentRow.value.id = 1;
}else{
currentRow.value.id = 0;
}
initHomeWork(); initHomeWork();
// // // //
// router.back() // router.back()

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" />
@ -194,7 +195,7 @@ import outLink from '@/utils/linkConfig'
import { createWindow, sessionStore, getAppInstallUrl, ipcMsgSend } from '@/utils/tool' import { createWindow, sessionStore, getAppInstallUrl, ipcMsgSend } from '@/utils/tool'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { delClasswork, listEntpcourse } from '@/api/teaching/classwork' import { delClasswork, listEntpcourse } from '@/api/teaching/classwork'
import { updateClasscourse } from '@/api/teaching/classcourse' import { updateClasscourse, getClasscourse } from '@/api/teaching/classcourse'
import { getClassInfo, getSelfReserv, endClass } from '@/api/classManage' import { getClassInfo, getSelfReserv, endClass } from '@/api/classManage'
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
import { editListItem } from '@/hooks/useClassTask' import { editListItem } from '@/hooks/useClassTask'
@ -389,12 +390,13 @@ export default {
if (res.code == 200) { if (res.code == 200) {
const resource = res.data const resource = res.data
if (resource.filetype != 'aippt') this.$refs.calssRef.open(aptFileId, row) if (resource.filetype != 'aippt') this.$refs.calssRef.open(aptFileId, row)
else { else { // aippt
if (!!sessionStore.get('curr.classcourse')) return ElMessage.warning('公屏已打开,请勿重复操作') if (!!sessionStore.get('curr.classcourse')) return ElMessage.warning('公屏已打开,请勿重复操作')
const { data:classcourse } = await getClasscourse(row.id) //
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0}) const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(()=>{ setTimeout(()=>{
msgEl.close() msgEl.close()
this.openPublicScreen('class', resource, row) // - this.openPublicScreen('class', resource, classcourse||row) // -
}, 2000) }, 2000)
} }
} else ElMessage.error(res.msg||'获取课件信息失败') } else ElMessage.error(res.msg||'获取课件信息失败')
@ -554,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)
@ -572,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) {
@ -618,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)
@ -664,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)