Compare commits

...

37 Commits

Author SHA1 Message Date
“zouyf” 26c388f31c Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk 2024-10-16 09:23:28 +08:00
“zouyf” d9ddb1982b [作业批改] - 重新优化常规作业逻辑 2024-10-16 09:22:18 +08:00
朱浩 3942f894b0 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk 2024-10-15 18:40:07 +08:00
朱浩 ec8f7b5727 无法上课PPT的问题解决 2024-10-15 18:39:50 +08:00
yangws 1a447b30fb Merge pull request 'fix:作业批阅柱状图改为百分比显示;' (#321) from yangws into main
Reviewed-on: #321
2024-10-15 16:36:21 +08:00
小杨 1c9a3762ef fix:作业批阅柱状图改为百分比显示; 2024-10-15 16:34:31 +08:00
lyc ea88aa959d Merge pull request 'lyc-dev' (#320) from lyc-dev into main 2024-10-15 16:30:12 +08:00
lyc d534340f88 Merge branch 'main' into lyc-dev 2024-10-15 16:30:17 +08:00
lyc ad2c37be82 ai-ppt 2024-10-15 16:30:07 +08:00
zhengdegang 540229cd9b Merge pull request 'apt ppt 上下课、继续上课' (#319) from zdg into main
Reviewed-on: #319
2024-10-15 15:35:12 +08:00
zdg 2115d2c76a apt ppt 上下课、继续上课 2024-10-15 15:33:07 +08:00
yangws d068d4e71a Merge pull request 'fix: 作业批阅展示修改;' (#318) from yangws into main
Reviewed-on: #318
2024-10-15 15:25:10 +08:00
小杨 1ef4615587 fix: 作业批阅展示修改; 2024-10-15 15:24:06 +08:00
baigl a853214741 Merge pull request 'baigl' (#317) from baigl into main
Reviewed-on: #317
2024-10-15 15:09:25 +08:00
白了个白 7d71cbd398 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk into baigl 2024-10-15 15:07:54 +08:00
白了个白 2f3b2e8c8f 1 2024-10-15 11:27:03 +08:00
白了个白 5b818d2dd8 作业布置:新增 常规作业 列表详情预览 2024-10-15 11:08:23 +08:00
白了个白 b0415ecafa 作业布置:新增 框架梳理 列表详情预览 2024-10-14 17:26:49 +08:00
qinqing 1724419848 Merge pull request 'qinqing_dev' (#316) from qinqing_dev into main
Reviewed-on: #316
2024-10-14 17:18:38 +08:00
qinqing 506f95060b 优化PDF插件工具栏 2024-10-14 17:17:04 +08:00
qinqing d68e4fef0b Merge pull request '修改默认展示判定逻辑' (#315) from qinqing_dev into main
Reviewed-on: #315
2024-10-14 16:25:37 +08:00
qinqing fcc0af04cb Merge branch 'main' into qinqing_dev 2024-10-14 16:25:00 +08:00
qinqing 944d94a202 修改默认展示判定逻辑 2024-10-14 16:24:00 +08:00
白了个白 c3787eef0f 作业布置:列表编辑,ui显示null标题修复 2024-10-14 15:55:54 +08:00
lyc 14c4ede80e Merge pull request '删除 hooks、useGetClassWork.js' (#314) from lyc-dev into main 2024-10-14 15:11:49 +08:00
lyc bf642ddc79 删除 hooks、useGetClassWork.js 2024-10-14 15:12:05 +08:00
白了个白 ed035f94ed 作业布置:列表排序修改 2024-10-14 11:22:37 +08:00
baigl 35af866a53 Merge pull request '作业布置:推送多个班级的学生bug修复' (#313) from baigl into main
Reviewed-on: #313
2024-10-14 10:59:55 +08:00
白了个白 58988313b7 作业布置:推送多个班级的学生bug修复 2024-10-14 10:57:02 +08:00
qinqing eca1a927c0 Merge pull request 'qinqing_dev' (#312) from qinqing_dev into main
Reviewed-on: #312
2024-10-11 16:39:10 +08:00
qinqing 7cab923fa4 合并代码 2024-10-11 16:35:42 +08:00
baigl 3fb3af9cae Merge pull request 'baigl' (#311) from baigl into main
Reviewed-on: #311
2024-10-11 16:33:56 +08:00
白了个白 96ef7da95a Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk into baigl 2024-10-11 16:32:17 +08:00
zouyf ee4088ad75 Merge pull request 'zouyf_dev' (#310) from zouyf_dev into main
Reviewed-on: #310
2024-10-11 16:31:18 +08:00
白了个白 2ad1904802 Merge branch 'main' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk into baigl 2024-10-11 16:28:04 +08:00
白了个白 8ff117ab53 作业批改:页面缓存读取bug修复 2024-10-11 16:27:39 +08:00
qinqing 6911a92ecb 新增打开新课件,优化PDF插件查看器 2024-10-11 16:22:26 +08:00
41 changed files with 2013 additions and 593 deletions

View File

@ -43,6 +43,11 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/baidubce/, '') rewrite: (p) => p.replace(/^\/baidubce/, '')
}, },
'/parth': {
target: 'https://zwapi.xfyun.cn', // 第三方API的地址
changeOrigin: true, // 改变请求的起源
rewrite: (path) => path.replace(/^\/parth/, '') // 重写路径
},
}, },
}, },
plugins: [vue(), WindiCSS()], plugins: [vue(), WindiCSS()],

File diff suppressed because one or more lines are too long

View File

@ -2782,7 +2782,8 @@
--editor-toolbar-base-offset:105px; --editor-toolbar-base-offset:105px;
--main-color:rgb(12 12 13); --main-color:rgb(12 12 13);
--body-bg-color:rgb(255, 255, 255); /* --body-bg-color:rgb(255, 255, 255); */
--body-bg-color:rgb(241, 241, 252);
--progressBar-color:rgb(10 132 255); --progressBar-color:rgb(10 132 255);
--progressBar-bg-color:rgb(221 221 222); --progressBar-bg-color:rgb(221 221 222);
--progressBar-blend-color:rgb(116 177 239); --progressBar-blend-color:rgb(116 177 239);
@ -3148,12 +3149,12 @@ body{
} }
#toolbarViewer{ #toolbarViewer{
width: 300px; width: auto;
height: 32px; height: 60px;
position: fixed; position: fixed;
z-index: 9999; z-index: 9999;
bottom: 15px; bottom: 15px;
left: 50%; right: 2%;
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
transform: translateX(-50%); transform: translateX(-50%);
@ -3477,8 +3478,9 @@ body{
} }
#toolbarViewerLeft{ #toolbarViewerLeft{
padding-inline-start:1px; /* padding-inline-start:1px; 修改样式*/
} }
#toolbarViewerRight{ #toolbarViewerRight{
padding-inline-end:1px; padding-inline-end:1px;
} }
@ -3693,7 +3695,7 @@ body{
mask-image:var(--findbarButton-next-icon); mask-image:var(--findbarButton-next-icon);
} }
#previous::before{ /* #previous::before{
-webkit-mask-image:var(--toolbarButton-pageUp-icon); -webkit-mask-image:var(--toolbarButton-pageUp-icon);
mask-image:var(--toolbarButton-pageUp-icon); mask-image:var(--toolbarButton-pageUp-icon);
} }
@ -3701,8 +3703,27 @@ body{
#next::before{ #next::before{
-webkit-mask-image:var(--toolbarButton-pageDown-icon); -webkit-mask-image:var(--toolbarButton-pageDown-icon);
mask-image:var(--toolbarButton-pageDown-icon); mask-image:var(--toolbarButton-pageDown-icon);
} */
/* 修改样式 */
#toolbarViewerLeft .toolbarButton{
width: 80px;
height: 60px;
margin: 0;
}
#toolbarViewerLeft .toolbarButton::before{
opacity: 0;
}
#toolbarViewerLeft .toolbarButton span{
width: auto;
height: auto;
}
.toolpageBtn{
height: 60px;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
} }
#zoomOut::before{ #zoomOut::before{
-webkit-mask-image:var(--toolbarButton-zoomOut-icon); -webkit-mask-image:var(--toolbarButton-zoomOut-icon);
mask-image:var(--toolbarButton-zoomOut-icon); mask-image:var(--toolbarButton-zoomOut-icon);

View File

@ -44,12 +44,12 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="toolbarSidebar"> <div id="toolbarSidebar">
<div id="toolbarSidebarLeft"> <div id="toolbarSidebarLeft">
<div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup"> <div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="viewThumbnail" class="toolbarButton toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
<span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
</button>
<button id="viewOutline" class="toolbarButton" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView"> <button id="viewOutline" class="toolbarButton" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView">
<span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span> <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
</button> </button>
<button id="viewThumbnail" class="toolbarButton toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
<span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
</button>
<button id="viewAttachments" class="toolbarButton" title="Show Attachments" tabindex="4" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView"> <button id="viewAttachments" class="toolbarButton" title="Show Attachments" tabindex="4" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
<span data-l10n-id="pdfjs-attachments-button-label">Attachments</span> <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
</button> </button>
@ -273,14 +273,17 @@ See https://github.com/adobe-type-tools/cmap-resources
<div id="toolbarContainer"> <div id="toolbarContainer">
<div id="toolbarViewer"> <div id="toolbarViewer">
<div id="toolbarViewerLeft"> <div id="toolbarViewerLeft">
<button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer"> <button id="sidebarToggle" class="toolbarButton" tabindex="11" aria-expanded="false" aria-controls="sidebarContainer">
<span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span> <span>目录</span>
</button> </button>
<div class="toolbarButtonSpacer"></div> <!-- <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer">
<button id="viewFind" class="toolbarButton" title="Find in Document" tabindex="12" data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar"> <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
</button> -->
<!-- <div class="toolbarButtonSpacer"></div> -->
<button style="display: none;" id="viewFind" class="toolbarButton" title="Find in Document" tabindex="12" data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
<span data-l10n-id="pdfjs-findbar-button-label">Find</span> <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
</button> </button>
<div class="splitToolbarButton hiddenSmallView"> <div class="splitToolbarButton toolpageBtn">
<button class="toolbarButton" title="Previous Page" id="previous" tabindex="13" data-l10n-id="pdfjs-previous-button"> <button class="toolbarButton" title="Previous Page" id="previous" tabindex="13" data-l10n-id="pdfjs-previous-button">
<span data-l10n-id="pdfjs-previous-button-label">Previous</span> <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
</button> </button>
@ -289,12 +292,12 @@ See https://github.com/adobe-type-tools/cmap-resources
<span data-l10n-id="pdfjs-next-button-label">Next</span> <span data-l10n-id="pdfjs-next-button-label">Next</span>
</button> </button>
</div> </div>
<span class="loadingInput start"> <span class="loadingInput start" style="display: none;">
<input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off"> <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off">
</span> </span>
<span id="numPages" class="toolbarLabel"></span> <span id="numPages" class="toolbarLabel" style="display: none;"></span>
</div> </div>
<div id="toolbarViewerRight"> <div id="toolbarViewerRight" style="display: none;">
<div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup"> <div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
<button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button"> <button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button">
<span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span> <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>

View File

@ -7407,7 +7407,8 @@ class PDFSidebar {
this.isInitialViewSet = false; this.isInitialViewSet = false;
this.isInitialEventDispatched = false; this.isInitialEventDispatched = false;
this.#hideUINotification(true); this.#hideUINotification(true);
this.switchView(SidebarView.THUMBS); // this.switchView(SidebarView.THUMBS);默认开启书签模式
this.switchView(SidebarView.OUTLINE);
this.outlineButton.disabled = false; this.outlineButton.disabled = false;
this.attachmentsButton.disabled = false; this.attachmentsButton.disabled = false;
this.layersButton.disabled = false; this.layersButton.disabled = false;
@ -13095,12 +13096,16 @@ const PDFViewerApplication = {
this.pdfViewer.scrollMode = scroll; this.pdfViewer.scrollMode = scroll;
} }
if (isValidSpreadMode(spread)) { if (isValidSpreadMode(spread)) {
this.pdfViewer.spreadMode = spread; //默认双页
// this.pdfViewer.spreadMode = spread;
this.pdfViewer.spreadMode = 1;
} }
}; };
this.isInitialViewSet = true; this.isInitialViewSet = true;
this.pdfSidebar?.setInitialView(sidebarView); this.pdfSidebar?.setInitialView(sidebarView);
setViewerModes(scrollMode, spreadMode); //默认双页
// setViewerModes(scrollMode, spreadMode);
setViewerModes(scrollMode, 1);
if (this.initialBookmark) { if (this.initialBookmark) {
setRotation(this.initialRotation); setRotation(this.initialRotation);
delete this.initialRotation; delete this.initialRotation;

View File

@ -18,6 +18,14 @@ export function listClassworkdataByDeadDate(query) {
}) })
} }
// 查询classworkdata详细
export function getClassworkdata(id) {
return request({
url: '/education/classworkdata/' + id,
method: 'get'
})
}
// 查询classworkdata列表 班级作业列表 // 查询classworkdata列表 班级作业列表
export function listClassworkdata(query) { export function listClassworkdata(query) {
return request({ return request({

View File

@ -12,15 +12,20 @@ const props = defineProps({
type: String, type: String,
required: true, required: true,
default: '' default: ''
},
isWin: {
type: Boolean,
default: false
} }
}) })
/**pdf文件地址 */ /**pdf文件地址 */
const pdfUrl = ref(''); const pdfUrl = ref('');
/**pdfjs文件地址 */ /**pdfjs文件地址 */
const fileUrl = getAppInstallUrl('pdfjs-dist/web/viewer.html', 'user', '\\out\\renderer', true) + "?file=" // const fileUrl = props.isWin ? props.url : getAppInstallUrl('pdfjs-dist/web/viewer.html', 'user', '\\out\\renderer', true) + "?file=" //
onMounted(() => { onMounted(() => {
/** 将传入的pdf地址进行编码防止中文识别错误 */ /** 将传入的pdf地址进行编码防止中文识别错误 */
pdfUrl.value = fileUrl + encodeURIComponent(props.url) if(props.isWin) pdfUrl.value = fileUrl
else pdfUrl.value = fileUrl + encodeURIComponent(props.url) + '#pageMode=outline'
}) })
</script> </script>

View File

@ -1,202 +0,0 @@
import { nextTick, toRaw } from 'vue'
import useUserStore from '@/store/modules/user'
import { listEvaluation } from '@/api/subject'
const userStore = useUserStore()
const { edustage, edusubject } = userStore.user
let evaluationList = []; // 教材版本list
let subjectList = []; // 教材list
//当前教材ID
let curBookId = -1;
/**
* 外部链接初始化获取 跳转web端的 unitId 专用
* 暂时 初始化作业设计专用后期可能会取消
*/
export const useGetClassWork = async () => {
const params = {
edusubject,
edustage,
// entpcourseedituserid: userId,
itemgroup: 'textbook',
orderby: 'orderidx asc',
pageSize: 10000
}
if(localStorage.getItem('evaluationList')){
evaluationList = JSON.parse(localStorage.getItem('evaluationList'))
}else{
const { rows } = await listEvaluation(params)
localStorage.setItem('evaluationList', JSON.stringify(rows))
evaluationList = rows
}
//获取教材版本
await getSubject()
//上册
/**
* 不区分上下册
* 2024/08/20调整
*/
// volumeOne = data.filter(item => item.level == 1 && item.semester == '上册')
// volumeTwo = data.filter(item => item.level == 1 && item.semester == '下册')
getTreeData()
}
//获取教材
const getSubject = async () => {
if(localStorage.getItem('subjectList')){
subjectList = JSON.parse(localStorage.getItem('subjectList'))
}else{
const { rows } = await listEvaluation({ itemkey: "version", edusubject, edustage, pageSize: 10000,orderby: 'orderidx asc', })
// subjectList = rows.filter(item => item.edustage == edustage && item.edusubject == edusubject)
subjectList = rows
localStorage.setItem('subjectList', JSON.stringify(subjectList))
}
// 默认第一个
if(!subjectList.length) return
// curBookName = subjectList[0].itemtitle
curBookId = subjectList[0].id
// curBookImg = BaseUrl + subjectList[0].avartar
// curBookPath = subjectList[0].fileurl
}
const getTreeData = () => {
//数据过滤
let upData = transData(evaluationList)
if(upData.length){
// treeData = [...upData]
}else{
// treeData = []
return
}
nextTick(() => {
// defaultExpandedKeys = [treeData[0].id]
// let currentNodeObj = {...getLastLevelData(upData)[0]}
let currentNodeId = getLastLevelData(upData)[0].id
let currentNodeName = getLastLevelData(upData)[0].label
let curNode = {
id: currentNodeId,
label: currentNodeName,
// itemtitle: currentNodeObj.itemtitle,
// edudegree: currentNodeObj.edudegree,
// edustage: currentNodeObj.edustage,
// edusubject: currentNodeObj.edusubject,
}
let parentNode = findParentByChildId(upData, currentNodeId)
curNode.parentNode = toRaw(parentNode)
let levelFirstId = '';
let levelSecondId = '';
if (curNode.parentNode) {
levelFirstId = curNode.parentNode.id
} else {
levelFirstId = curNode.id
levelSecondId = ''
}
let bookeId = curBookId
// 头部 教材分析、作业设计打开外部链接需要当前章节ID
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId, bookeId}))
// const data = {
// textBook: {
// curBookId: curBookId,
// curBookName: curBookName,
// curBookImg: curBookImg,
// curBookPath: curBookPath
// },
// node: curNode
// }
// emit('changeBook', data)
})
}
const getLastLevelData = (tree) => {
let lastLevelData = [];
// 递归函数遍历树形结构
function traverseTree(nodes) {
nodes.forEach((node) => {
// 如果当前节点有子节点,继续遍历
if (node.children && node.children.length > 0) {
traverseTree(node.children);
} else {
// 如果没有子节点,说明是最后一层的节点
lastLevelData.push(node);
}
});
}
// 调用递归函数开始遍历
traverseTree(tree);
// 返回最后一层的数据
return lastLevelData;
}
// 根据id 拿到父节点数据
const findParentByChildId = (treeData, targetNodeId) => {
// 递归查找函数
// 遍历树中的每个节点
for (let node of treeData) {
// 检查当前节点的子节点是否包含目标子节点 ID
if (node.children && node.children.some(child => child.id === targetNodeId)) {
// 如果当前节点的某个子节点的 ID 匹配目标子节点 ID则当前节点即为父节点
return node;
}
// 如果当前节点没有匹配的子节点,则递归检查当前节点的子节点
if (node.children) {
let parentNode = findParentByChildId(node.children, targetNodeId);
if (parentNode) {
return parentNode;
}
}
}
// 如果未找到匹配的父节点,则返回 null 或者适当的默认值
return null;
}
const transData = (data) => {
let ary = []
data.forEach(item => {
let obj = {}
// 根据当前教材ID 过滤出对应的单元、章节
if (item.rootid == curBookId) {
if(item.level == 1){
obj.label = item.itemtitle
obj.id = item.id
obj.itemtitle = item.itemtitle
obj.edudegree = item.edudegree
obj.edustage = item.edustage
obj.edusubject = item.edusubject
let ary2 = []
evaluationList.forEach(el => {
let obj2 = {}
if (item.id == el.parentid) {
obj2 = {
label: el.itemtitle,
id: el.id,
itemtitle : el.itemtitle,
edudegree : el.edudegree,
edustage : el.edustage,
edusubject : el.edusubject,
}
ary2.push(obj2)
}
obj.children = ary2
})
ary.push(obj)
}
}
})
return ary
}

View File

@ -10,12 +10,12 @@
<li class="flex" :class="[activeId == menu.path ? 'active-li' : '', menu.disabled ? 'disabled' : '']" <li class="flex" :class="[activeId == menu.path ? 'active-li' : '', menu.disabled ? 'disabled' : '']"
v-for="menu in headerMenus" :key="menu.id" @click="clickMenu(menu)"> v-for="menu in headerMenus" :key="menu.id" @click="clickMenu(menu)">
<div class="icon-box"> <div class="icon-box">
<svg class="icon iconfont" aria-hidden="true"> <svg class="icon iconfont" aria-hidden="true">
<use :xlink:href="menu.icon"></use> <use :xlink:href="menu.icon"></use>
</svg> </svg>
</div> </div>
<span class="text">{{ menu.name }}</span> <span class="text">{{ menu.name }}</span>
</li> </li>
@ -80,6 +80,8 @@ import { updateUserInfo } from '@/api/system/user'
import logoIco from '@/assets/images/logo.png' import logoIco from '@/assets/images/logo.png'
import { listEvaluation } from '@/api/classManage/index' import { listEvaluation } from '@/api/classManage/index'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
// import Chat from '@/utils/chat' // im
// if (!Chat.imChat) Chat.init()
let homeTitle = ref(import.meta.env.VITE_APP_TITLE) let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
@ -143,6 +145,7 @@ function handleCommand(command) {
break break
case 'logout': case 'logout':
logout() logout()
// Chat?.logout() // im 退
break break
default: default:
break break
@ -158,7 +161,9 @@ function logout() {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
.then(() => { .then(async () => {
const Chat = (await import('@/utils/chat')).default
if (!!Chat.imChat) Chat.logout()
userStore userStore
.logOut() .logOut()
.then(() => { .then(() => {
@ -169,8 +174,7 @@ function logout() {
// router.replace('/login') // router.replace('/login')
ipcRenderer && ipcRenderer.send('openLoginWindow') ipcRenderer && ipcRenderer.send('openLoginWindow')
}) })
}) }).catch(()=>{})
.catch(() => { })
} }
const emits = defineEmits(['setLayout']) const emits = defineEmits(['setLayout'])
@ -180,10 +184,10 @@ function setLayout() {
// //
const changeSubject = async (command) =>{ const changeSubject = async (command) =>{
let sessionSubject = { let sessionSubject = {
bookList: null, bookList: null,
curBook: null, curBook: null,
curNode: null, curNode: null,
defaultExpandedKeys: [], defaultExpandedKeys: [],
subjectTree: [] subjectTree: []
} }
sessionStore.set( 'subject', sessionSubject) sessionStore.set( 'subject', sessionSubject)

View File

@ -19,8 +19,11 @@ import AppMain from './components/AppMain.vue'
import Uploader from './components/Uploader.vue' import Uploader from './components/Uploader.vue'
import AiChart from '@/components/ai-chart/index.vue' import AiChart from '@/components/ai-chart/index.vue'
import uploaderState from '@/store/modules/uploader' import uploaderState from '@/store/modules/uploader'
// import Chat from '@/utils/chat'
let uploaderStore = ref(uploaderState()) let uploaderStore = ref(uploaderState())
// window.test = Chat
// Chat.init()
</script> </script>

View File

@ -70,7 +70,10 @@ export class ImChat {
// 日志监听 // 日志监听
this.timChat.TIMSetLogCallback({ this.timChat.TIMSetLogCallback({
callback: data => { callback: data => {
this.setConsole('%cchat-log ', data[1]) const [type, log] = data
if (type == log_level) { // 打印对应日志
this.setConsole('%cchat-log ', log)
}
}, },
user_data: '' user_data: ''
}) })
@ -86,7 +89,7 @@ export class ImChat {
if (code == 0) { // 初始化成功 if (code == 0) { // 初始化成功
this.setConsole('%cim-chat: init', '初始化成功') this.setConsole('%cim-chat: init', '初始化成功')
this.status.isConnect = true this.status.isConnect = true
this.setConfig() // 设置日志级别 // this.setConfig() // 设置日志级别
resolve(this) resolve(this)
} else { // 失败具体请看code } else { // 失败具体请看code
console.error('[im-chat]:初始化失败', code) console.error('[im-chat]:初始化失败', code)
@ -227,10 +230,11 @@ export class ImChat {
}) })
} }
// 删除群组 // 删除群组
deleteGroup() { deleteGroup(timGroupId) {
if (!this.timGroupId) return const groupId = timGroupId || this.timGroupId
if (!groupId) return
return this.timChat.TIMGroupDelete({ return this.timChat.TIMGroupDelete({
groupId: this.timGroupId, groupId,
data: '', // 用户自定义数据 data: '', // 用户自定义数据
}) })
} }
@ -249,7 +253,7 @@ export class ImChat {
} }
// 获取群组列表 // 获取群组列表
getGroupList() { getGroupList() {
return this.timChat.getGroupList().then(res => { return this.timChat.TIMGroupGetJoinedGroupList().then(res => {
console.log('获取群组列表', res) console.log('获取群组列表', res)
return res return res
}).catch(error => { }).catch(error => {

View File

@ -15,6 +15,12 @@ export const constantRoutes = [
// component: ()=> import('../login/index.vue'), // component: ()=> import('../login/index.vue'),
hidden: true hidden: true
}, },
{
path: '/fullscreenpdf',
component: () => import('@/views/fullScreenPdf/index.vue'),
name: 'fullscreenpdf',
meta: {title: '全屏显示PDF'}
},
{ {
path: '/teachClassTask', path: '/teachClassTask',
component: () => import('@/views/classTask/teachClassTask.vue'), component: () => import('@/views/classTask/teachClassTask.vue'),
@ -79,6 +85,7 @@ export const constantRoutes = [
name: 'testpdf', name: 'testpdf',
meta: {title: '测试PDF'} meta: {title: '测试PDF'}
}, },
{ {
path: '/classReserv', path: '/classReserv',
component: () => import('@/views/classManage/classReserv.vue'), component: () => import('@/views/classManage/classReserv.vue'),

View File

@ -0,0 +1,80 @@
import axios from "axios";
import { getSignature } from "./index";
let appId = "01ec9aa3";
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
let timestamp = Math.floor(Date.now() / 1000);
let signature = getSignature(appId, secret, timestamp);
export function text2Text(data) {
return axios({
url: "v1/chat/completions",
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer yjCwlZCeUtBYvjHAQZdk:FtWNmJSWcZMCmTBQZfoH",
Accept: "*/*",
},
data: {
model: "4.0Ultra",
messages: data,
stream: false,
},
});
}
export function uploadDoc(data) {
return axios({
url: "openapi/v1/file/upload",
method: "post",
headers: {
"Content-Type": "multipart/form-data",
appId: appId,
timestamp: timestamp,
signature: signature,
},
data: data,
});
}
export function queryDocStatus(data) {
return axios({
url: "openapi/v1/file/status",
method: "post",
headers: {
"Content-Type": "application/form-data",
appId: appId,
timestamp: timestamp,
signature: signature,
},
data: data
});
}
export function chatByDoc(fileId, data) {
const wsUrl = `wss://chatdoc.xfyun.cn/openapi/chat?fileId=${fileId}&appId=${appId}&timestamp=${timestamp}&signature=${signature}`;
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
const messageBody = {
fileIds: [fileId],
messages: data,
chatExtends: {
wikiPromptTpl:
"请将以下内容作为已知信息:\n<wikicontent>\n请根据以上内容回答用户的问题。\n问题:<wikiquestion>\n回答:",
wikiFilterScore: 0.82,
temperature: 0.5,
sparkWhenWithoutEmbedding: false,
},
};
ws.send(JSON.stringify(messageBody));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
console.log("WebSocket 消息:", response);
};
ws.onerror = (error) => {
console.error("WebSocket 错误:", error);
};
}

View File

@ -0,0 +1,97 @@
/**
* 实现单例模式
*/
import useUserStore from '@/store/modules/user'
import { ImChat } from '@/plugins/imChat'
import * as http from '@/api/apiService' // 自定义api service
export class Chat {
instance = null;
sdkAppId = 0; // 应用id
sign = ''; // 签名
imUserId = ''; // 用户id
imChat = null; // IM实例
constructor() {
if (!Chat.instance) { // 存在的时候
Chat.instance = this;
}
return Chat.instance;
}
/**
* 初始化 获取IM签名
* @param {*} isInit : 是否初始化IM
* @param {*} isLogin : 是否登录IM
* @param {*} callback: 监听消息回调函数
* @returns Promise<ImChat>
*/
async init(isInit = true, isLogin = true, callback) {
// 特殊处理只传1个参数且为函数则默认为callbackisInit和isLogin默认为true
if (typeof isInit == 'function'){
callback = isInit
isInit = true
isLogin = true
}
const userStore = useUserStore()
const { timuserid: imUserId } = userStore.user
// 获取腾讯云签名
const res = await http.imChat.getTxCloudSign({imUserId})
if (res && res.code == 200) {
const { sdkAppId, sign } = res.data
this.sdkAppId = sdkAppId
this.sign = sign
this.imUserId = imUserId
// 初始化IM
if (isInit) return await this.initIM(isLogin, callback)
}
}
// 初始化IM
async initIM(isLogin, callback) {
const imChat = new ImChat(this.sdkAppId, this.sign, this.imUserId)
this.imChat = imChat
await imChat.init() // 初始化IM
callback && this.listenMsg(callback) // 监听消息
if(isLogin) await imChat.login() // 登录IM
return imChat
}
// 监听消息
async listenMsg(callback) {
if (!callback) return
if (!this.imChat) return
await this.imChat?.watch(msg => callback(msg))
}
// 解散群
async dismissGroup(groupId) {
if (!this.imChat) return
await this.imChat?.deleteGroup(groupId)
}
// 退出登录
async logout() {
if (!this.imChat) return
await this.imChat?.logout()
this.imChat = null
}
// 发群消息
async sendMsg(conv_id, msg) {
if (!this.imChat) return
await this.imChat?.sendMsg(conv_id, msg)
}
// 发群消息
async sendMsgGroup(msg, head, type) {
if (!this.imChat) return
this.imChat?.sendMsgGroup(msg, head, type)
}
// 发群消息
async sendMsgGroupId(groupId, msg, head, type) {
if (!this.imChat) return
const msgObj = this.imChat?.getMsgObj(head, msg, type)
this.imChat?.sendMsg(groupId, msgObj)
}
// 获取群列表
async getGroupList() {
if (!this.imChat) return
return await this.imChat?.getGroupList()
}
}
export default new Chat()

View File

@ -0,0 +1,33 @@
import CryptoJS from 'crypto-js';
// 生成签名的主方法
export function getSignature(appId, secret, ts) {
try {
const auth = md5(appId + ts);
return hmacSHA1Encrypt(auth, secret);
} catch (error) {
console.error('Error generating signature:', error);
return null;
}
}
// MD5 加密
function md5(cipherText) {
try {
return CryptoJS.MD5(cipherText).toString(); // 使用 CryptoJS 进行 MD5 加密
} catch (error) {
console.error('Error in MD5 hashing:', error);
return null;
}
}
// HMAC-SHA1 加密
function hmacSHA1Encrypt(encryptText, encryptKey) {
try {
// 使用 CryptoJS 进行 HMAC-SHA1 加密,并转换为 Base64 格式
return CryptoJS.HmacSHA1(encryptText, encryptKey).toString(CryptoJS.enc.Base64);
} catch (error) {
console.error('Error in HMAC-SHA1 encryption:', error);
return null;
}
}

View File

@ -0,0 +1,78 @@
import axios from "axios";
import { getSignature } from "./index";
import { ElMessage } from "element-plus";
let appId = "01ec9aa3";
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
let timestamp = Math.floor(Date.now() / 1000);
let signature = getSignature(appId, secret, timestamp);
const instance = axios.create({
baseURL: "",
headers: {
"Content-Type": "application/json",
appId: appId,
timestamp: timestamp,
signature: signature,
},
});
const createOutline = async (data) => {
console.log("createOutline data:", data);
try {
const response = await instance.post(
"/parth/api/aippt/createOutline",
data
);
console.log("createOutline response:", response);
if (response.code == 81002) {
ElMessage.error("并发数量超过限制");
}
return response.data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
};
const getBackGround = async () => {
try {
const response = await instance.get("/parth/api/aippt/themeList");
return response.data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
};
const createPPT = async (data) => {
try {
const response = await instance.post("/parth/api/aippt/create", data);
console.log("createOutline response:", response);
return response.data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
};
const createByOutline = async (data) => {
try {
const response = await instance.post("/parth/api/aippt/createByOutline", data);
console.log("createByOutline response:", response);
return response.data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
};
const getProgress = async (id) => {
try {
const response = await instance.get(`/parth/api/aippt/progress?sid=${id}`);
return response.data;
} catch (error) {
console.error("请求失败:", error);
throw error;
}
};
export { instance, createOutline, getBackGround, createPPT, getProgress, createByOutline };

View File

@ -378,7 +378,22 @@ export const toLinkWeb = (path) => {
cookieData: { ...config } cookieData: { ...config }
}) })
} }
/**
* @description 外部跳转-web网页
* @param {*} path
* @param {*} params
*/
export const toRoter = (path) => {
const config = baseConfig()
// console.log(config)
// const fullPath = config.url + path
// 通知主进程
ipcRenderer.send('openWindow', {
key: `win-${Date.now()}`,
fullPath: path,
cookieData: { ...config }
})
}
// const taskHandles = () => { // const taskHandles = () => {
// // 设置任务栏上下文菜单 // // 设置任务栏上下文菜单
// const contextMenu = new Remote.Menu() // const contextMenu = new Remote.Menu()

View File

@ -11,6 +11,7 @@
v-if="item.bookImg" v-if="item.bookImg"
@open-edit="reservDialog.openDialog(item)" @open-edit="reservDialog.openDialog(item)"
@delete-reserv="deleteReserv(item)" @delete-reserv="deleteReserv(item)"
@change="(...o) => emit('change', ...o)"
></reserv-item> ></reserv-item>
<reserv-item-apt <reserv-item-apt
v-if="!item.bookImg" v-if="!item.bookImg"
@ -18,6 +19,7 @@
:item="item" :item="item"
@open-edit="reservDialog.openDialog(item)" @open-edit="reservDialog.openDialog(item)"
@delete-reserv="deleteReserv(item)" @delete-reserv="deleteReserv(item)"
@change="(...o) => emit('change', ...o)"
></reserv-item-apt> ></reserv-item-apt>
</template> </template>
</div> </div>
@ -34,6 +36,10 @@ import Reserv from '@/views/prepare/container/reserv.vue'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import ReservItemApt from '@/views/classManage/reserv-item-apt.vue' import ReservItemApt from '@/views/classManage/reserv-item-apt.vue'
// import Chat from '@/utils/chat' // im
// if (!Chat.imChat) Chat.init()
const emit = defineEmits(['change'])
const reservDialog = ref(null) const reservDialog = ref(null)
const tabOptions = ref(['进行中', '已结束']) const tabOptions = ref(['进行中', '已结束'])
const tabActive = ref('进行中') const tabActive = ref('进行中')

View File

@ -7,14 +7,14 @@
<span>{{item.caption}}</span> <span>{{item.caption}}</span>
</div> </div>
<div class="class-reserv-item-tool" style="width: 200px;max-width: 300px"> <div class="class-reserv-item-tool" style="width: 200px;max-width: 300px">
<el-tag v-if="item.status === 'close'" style="margin-right: 5px" type="success">已结束</el-tag> <el-tag v-if="item.status === 'closed'" style="margin-right: 5px" type="success">已结束</el-tag>
<el-tag v-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag> <el-tag v-if="item.status === 'open'" style="margin-right: 5px" type="danger">上课中</el-tag>
<el-button v-if="item.status === 'open'" :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)" <el-button v-if="item.status === 'open'" :disabled="toolStore.isToolWin" size="small" type="primary" @click="startClassR(item)"
>继续上课</el-button >继续上课</el-button
> >
<!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>--> <!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
<el-button v-if="item.status === 'open'" size="small" type="info" @click="endClassR(item)" <el-button v-if="item.status === 'open'" :loading="loading" size="small" type="info" @click="endClassR(item)"
>下课</el-button >下课{{ loading?'中...':'' }}</el-button
> >
</div> </div>
<div class="class-reserv-item-tool" style="width: 50px;"> <div class="class-reserv-item-tool" style="width: 50px;">
@ -25,13 +25,14 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { createWindow } from '@/utils/tool' import { createWindow } from '@/utils/tool'
import { deleteSmartReserv, startClass, endClass } from '@/api/classManage' import { deleteSmartReserv, startClass, endClass } from '@/api/classManage'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { listEntpcourse } from '@/api/teaching/classwork' import { listEntpcourse } from '@/api/teaching/classwork'
const emit = defineEmits(['openEdit', 'deleteReserv']) const emit = defineEmits(['openEdit', 'deleteReserv', 'change'])
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
@ -40,6 +41,7 @@ const props = defineProps({
}) })
const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH
const toolStore = useToolState() // -tool const toolStore = useToolState() // -tool
const loading = ref(false) // loading
const openEdit = () => { const openEdit = () => {
emit('openEdit', props.item) emit('openEdit', props.item)
} }
@ -54,42 +56,15 @@ const deleteReserv = () => {
} }
}) })
} }
//
const startClassR = (item) => { const startClassR = (item) => {
// startClass(item.id).then((res) => { emit('change', 'continue', item)
// if (res.data === true) {
// item.status = ''
// openLesson()
// }
// })
// item.status = ''
openLesson()
} }
//
const endClassR = (item) => { const endClassR = (item) => {
/*endClass(item.id).then((res) => { emit('change', 'close', item, { type: 2, loading })
if (res.data === true) {
ElMessage({
message: '下课成功',
type: 'success'
})
item.status = '已结束'
}
})*/
}
// const toolStore = useToolState()
let wins = null;
// -
const openLesson = () => {
// startClass(props.item.id)
/*listEntpcourse({
evalid: props.item.ex2,
edituserid: useUserStore().user.userId,
pageSize: 500
}).then(async res=>{
if (res.rows[0].id) {
wins = await createWindow('tool-sphere', { url: '/tool/sphere?entpcourseid=' + res.rows[0].id + "&reservId=" + props.item.id })
}
})*/
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.class-reserv-item { .class-reserv-item {

View File

@ -13,8 +13,8 @@
>继续上课</el-button >继续上课</el-button
> >
<!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>--> <!-- <el-button v-if="item.status === '未开始'" @click="openEdit">编辑</el-button>-->
<el-button v-if="item.status === '上课中'" size="small" type="info" @click="endClassR(item)" <el-button v-if="item.status === '上课中'" :loading="loading" size="small" type="info" @click="endClassR(item)"
>下课</el-button >下课{{ loading?'中...':'' }}</el-button
> >
</div> </div>
<div class="class-reserv-item-tool" style="width: 50px;"> <div class="class-reserv-item-tool" style="width: 50px;">
@ -25,13 +25,14 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { createWindow } from '@/utils/tool' import { createWindow } from '@/utils/tool'
import { deleteSmartReserv, startClass, endClass } from '@/api/classManage' import { deleteSmartReserv, startClass, endClass } from '@/api/classManage'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { listEntpcourse } from '@/api/teaching/classwork' import { listEntpcourse } from '@/api/teaching/classwork'
const emit = defineEmits(['openEdit', 'deleteReserv']) const emit = defineEmits(['openEdit', 'deleteReserv','change'])
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
@ -40,6 +41,7 @@ const props = defineProps({
}) })
const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH const basePath = import.meta.env.VITE_APP_BUILD_BASE_PATH
const toolStore = useToolState() // -tool const toolStore = useToolState() // -tool
const loading = ref(false) // loading
const openEdit = () => { const openEdit = () => {
emit('openEdit', props.item) emit('openEdit', props.item)
} }
@ -80,15 +82,7 @@ const openLesson = () => {
}) })
} }
const endClassR = (item) => { const endClassR = (item) => {
endClass(item.id).then((res) => { emit('change', 'close', item, { type: 2, loading })
if (res.data === true) {
ElMessage({
message: '下课成功',
type: 'success'
})
item.status = '已结束'
}
})
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -1,8 +1,3 @@
<!--
* @Author: 苦逼程序猿
* @Date: 2024-09-06 16:58:59
* @Warning: 千行代码Bug露锋芒
-->
<template> <template>
<el-container class="class-reserv-wrap"> <el-container class="class-reserv-wrap">
<div style="display: flex; justify-content: space-between;"> <div style="display: flex; justify-content: space-between;">
@ -65,13 +60,14 @@
import { ref, onMounted, onUnmounted, computed, watch } from 'vue' import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { listByDeadDate, listClassworkdata, listClassworkdataNew } from '@/api/classTask' import { listByDeadDate, listClassworkdata, listClassworkdataNew } from '@/api/classTask'
import TaskItem from '@/views/classTask/container/task-item.vue' import TaskItem from '@/views/classTask/container/classTask/task-item.vue'
// import ItemDialog from '@/views/classTask/container/item-dialog.vue' // import ItemDialog from '@/views/classTask/container/item-dialog.vue'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import { getCurrentTime, getTomorrow } from '@/utils/date' import { getCurrentTime, getTomorrow } from '@/utils/date'
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 {sessionStore, createWindow} from '@/utils/tool' import {createWindow} from '@/utils/tool'
import {sessionStore} from '@/utils/store'
import {throttle,debounce } from '@/utils/comm' import {throttle,debounce } from '@/utils/comm'
@ -120,7 +116,7 @@ const getData = async () => {
classWorkList.value = [] classWorkList.value = []
loading.value = true loading.value = true
// 1 // 1
//getClassList() // getClassList()
// 2 // 2
await getClassWorkList() await getClassWorkList()
// 3 // 3

View File

@ -99,13 +99,9 @@
</el-table> </el-table>
</div> </div>
<!-- 作业内容编辑 --> <!-- 作业内容详情 -->
<el-dialog v-model="workEdit" title="作业内容详情" width="90%" append-to-body> <el-dialog v-model="workEdit" title="作业内容详情" width="90%" append-to-body>
<div v-if="currentTag=='学习目标定位'" style="display: flex;">
<degreeevolution :attainmentList="attainmentList" :show-class="true" :courseQualityList="courseQualityList"/>
</div>
<!-- 课标研读 目标设定 教材研读 框架梳理 学科定位 --> <!-- 课标研读 目标设定 教材研读 框架梳理 学科定位 -->
<div v-if="currentTag=='习题训练'" :style="{'padding': '15px', 'overflow': 'auto'}"> <div v-if="currentTag=='习题训练'" :style="{'padding': '15px', 'overflow': 'auto'}">
<el-table :data="workConfObj.quizlist" style="width: 100%;"> <el-table :data="workConfObj.quizlist" style="width: 100%;">
<el-table-column type="index" width="60" /> <el-table-column type="index" width="60" />
@ -129,6 +125,75 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<!-- 框架梳理 -->
<div v-if="currentTag=='框架梳理'" :style="{'padding': '15px', 'overflow': 'auto'}">
<div style="margin: 5px; background-color: white">
<template v-for="(_item,index) in workConfObj.chooseWorkLists" :key="index">
<div v-if="_item.worktype=='框架梳理'">
<div style="color: silver; display: flex;align-items: center;">
<div style="flex: 1;">{{ _item.worktype }}</div>
<div style="flex: 1;">分值{{ _item.score }}</div>
<div style="display: flex;align-items: center;flex: 1;justify-content: flex-end;">
<el-button @click="prevRead(_item)">预览</el-button>
</div>
</div>
</div>
</template>
</div>
</div>
<!-- TODO 课堂展示 常规作业 预览待开发-->
<!-- 常规作业包含多个格式图片略缩图展示点击放大其他附件跳转另外弹窗查看 -->
<div v-if="currentTag=='常规作业'" :style="{'padding': '15px', 'overflow': 'auto'}">
<div style="margin: 5px; background-color: white">
<div v-if="workConfObj.teacherFeedContentList.length > 0">
<div class="image_list">
<div v-if="workConfObj.teachImageList.length > 0">
<div style="margin-bottom: 5px;text-align: left;">
<span style="color: red">温馨提示点击图片可放大预览 </span>
</div>
<div style="display: flex; flex-wrap: nowrap; flex-direction: row;">
<div v-for="(imageItem, index) in workConfObj.teachImageList" :key="index" style="margin: 0 15px;">
<el-image
style="width: 100px; height: 100px"
:src="imageItem.url"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="
workConfObj.teachImageList
.filter(
(item) =>
item.name.indexOf('jpg') > -1 ||
item.name.indexOf('jpeg') > -1 ||
item.name.indexOf('png') > -1
)
.map((item) => item.url)
"
:initial-index="4"
fit="contain"
/>
</div>
<!-- 其他类型附件 -->
<div v-if="workConfObj.teachFileList.length > 0">
<div style="margin: 10px 0;text-align: left; margin: auto 0; cursor: pointer;">
<span style="color: #409eff" @click="openFile">预览其他类型附件</span>
</div>
</div>
</div>
</div>
<div v-else>
<div v-if="workConfObj.teachFileList.length > 0">
<div style="margin: 10px 0;text-align: left; margin: auto 0; cursor: pointer;">
<span style="color: #409eff" @click="openFile">预览其他类型附件</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer" style="text-align: right; margin-top: 20px;"> <div slot="footer" class="dialog-footer" style="text-align: right; margin-top: 20px;">
<div style="display: flex"> <div style="display: flex">
@ -137,7 +202,6 @@
<el-button type="primary" style="margin-left: auto" :disabled="checkTaskAssigned(currentWorkEdit.currentTask)" <el-button type="primary" style="margin-left: auto" :disabled="checkTaskAssigned(currentWorkEdit.currentTask)"
@click="submitStudy('submit')"> </el-button> @click="submitStudy('submit')"> </el-button>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
@ -229,6 +293,12 @@
<el-button type="primary" @click="handleTaskAssignToAllClassType()" >{{'推送'}}</el-button> <el-button type="primary" @click="handleTaskAssignToAllClassType()" >{{'推送'}}</el-button>
</div> </div>
</el-dialog> </el-dialog>
<!-- 预览框 -->
<prevReadMsgDialog ref="prevReadMsgDialogRef" :bookobj="courseObj"/>
<!-- 其他附件预览框 acceptParams-->
<prevReadImgFileDialog ref="prevReadImgFileDialogRef"/>
</div> </div>
</div> </div>
</template> </template>
@ -247,6 +317,9 @@ import { useGetHomework } from '@/hooks/useGetHomework'
import { processList } from '@/hooks/useProcessList' import { processList } from '@/hooks/useProcessList'
import { uniqBy, groupBy } from 'lodash' import { uniqBy, groupBy } from 'lodash'
import { getCurrentTime } from '@/utils/date' import { getCurrentTime } from '@/utils/date'
import prevReadMsgDialog from '@/views/classTask/container/newTask/prevReadMsg-Dialog.vue'
import prevReadImgFileDialog from '@/views/classTask/container/classTask/prevReadImgFileDialog.vue'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user const userStore = useUserStore().user
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
@ -263,7 +336,8 @@ const props = defineProps({
// --------------------------------------------------- // ---------------------------------------------------
const isCollapse = ref(false) const isCollapse = ref(false)
const prevReadMsgDialogRef = ref(null);// ref
const prevReadImgFileDialogRef = ref(null);// ref
const courseObj = reactive({ const courseObj = reactive({
// : id,id,id, // : id,id,id,
@ -287,9 +361,13 @@ const currentWorkEdit = reactive({
currentIndex: 0, currentIndex: 0,
})// })//
const currentTag = ref('');// const currentTag = ref('');//
// -
const workConfObj = reactive({ const workConfObj = reactive({
quizlist: [], // list quizlist: [], // list
chooseWorkLists: [], //
teacherFeedContentList: [],//
teachFileList: [], // list
teachImageList: [],// list
}); });
// //
@ -370,6 +448,10 @@ const getTaskList = async () => {
let model = []; let model = [];
let mission = []; let mission = [];
if(res.rows&&res.rows.length > 0){
//
res.rows.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
}
for (let item of res.rows){ for (let item of res.rows){
item.taskconfig = []; item.taskconfig = [];
@ -452,6 +534,7 @@ const getTaskList = async () => {
} }
list.push(item); list.push(item);
} }
//
taskList.value = list; taskList.value = list;
loading.value = false; loading.value = false;
}) })
@ -467,8 +550,22 @@ const handleWorkTitleEdit = (row, index) => {
}; };
// - // --
const handleWorkEdit = (row, index) =>{ let classtaskObj = reactive({
id: '', //
bookName: '', //
uniquekey: '', //
title: '', //
worktype: '', //
quizlist: [], //
chooseWorkLists: [],// list
fileHomeworkList: [],// list
whiteboardObj: '',//
})
/**
* 作业内容-查看详情
* */
const handleWorkEdit = (row, index) =>{
console.log(row, index) console.log(row, index)
workEdit.value = true workEdit.value = true
currentWorkEdit.currentTask = row; currentWorkEdit.currentTask = row;
@ -477,24 +574,27 @@ const handleWorkEdit = (row, index) =>{
// this.attainmentList = row.workcodesList?.attlist; // this.attainmentList = row.workcodesList?.attlist;
// this.courseQualityList = row.workcodesList?.qualist; // this.courseQualityList = row.workcodesList?.qualist;
if (row.worktype == '框架梳理') { if (row.worktype == '框架梳理') {
this.$nextTick(()=>{ // entpcourseworklistarray
this.getFlowData() var listCourseWork = [];
for (var i=0; i < row.entpcourseworklistarray.length; i++) {
listCourseWork.push(row.entpcourseworklistarray[i]);
}
nextTick(()=>{
// id
getEvaluationclue(listCourseWork[0].id).then(res => {
if ( res.data==null || res.data==undefined ) {
return;
}
res.data.worktype = '框架梳理';
res.data.score = listCourseWork[0].score;
console.log('框架梳理的列表', res.data);
// list
workConfObj.chooseWorkLists = [res.data];
});
}) })
} }
// if (row.worktype == '') {
// // TODO
// rootid:entpcourseworkid rootid: row.entpcourseworklistarray[0].id,
// listEvaluationclue({ cluegroup: 'graph', edusubject: this.courseObj.edusubject, pageSize: 1000 }).then((res) => {
// var glist = [];
// for (var i = 0; i < res.rows.length; i++) {
// glist.push(res.rows[i]);
// }
// this.isEditable = false;
// this.preKnowList = glist;
// this.$refs.jsMind.updateFromParent(this.preKnowList, this.courseObj.edusubject);
// this.$refs.jsMind.initJsMindMap();
// })
// }
// // // //
if (row.worktype == '习题训练') { if (row.worktype == '习题训练') {
@ -532,63 +632,38 @@ const handleWorkEdit = (row, index) =>{
}) })
} }
// //TODO
if(row.worktype == '常规作业' || row.worktype == '课堂展示'){ if(row.worktype == '常规作业'){
console.log(row,'常规作业-课堂展示'); console.log(row,'常规作业');
// workcodes workConfObj.teacherFeedContentList = [];
workConfObj.teachImageList = [];
workConfObj.teachFileList = [];
if(row.workcodes != ''){ if(row.workcodes != ''){
const teachWorkFileList = JSON.parse(row.workcodes); const teachWorkFileList = JSON.parse(row.workcodes);
teachWorkFileList&&teachWorkFileList.forEach(item => { teachWorkFileList&&teachWorkFileList.forEach(item => {
if(item.name.indexOf('jpg') > -1 || item.name.indexOf('jpeg') > -1 || item.name.indexOf('png') > -1){ if(item.name.indexOf('jpg') > -1 || item.name.indexOf('jpeg') > -1 || item.name.indexOf('png') > -1){
this.teachImageList.push(item); workConfObj.teachImageList.push(item);
}else{ }else{
this.teachFileList.push(item); workConfObj.teachFileList.push(item);
} }
}) })
this.teacherFeedContentList.push(teachWorkFileList); workConfObj.teacherFeedContentList.push(teachWorkFileList);
} }
} }
}; };
// --
let classtaskObj = reactive({
id: '', //
bookName: '', //
uniquekey: '', //
title: '', //
worktype: '', //
quizlist: [], //
chooseWorkLists: [],// list
fileHomeworkList: [],// list
whiteboardObj: '',//
})
// - // -
const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{ const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{
// this.newWorkSpace = true;
// this.newWorkSpaceEdit = true;
// this.currentTask = row;
// this.currentIndex = index;
// this.currentTag = row.worktype;
// this.attainmentList = row.workcodesList?.attlist;
// this.courseQualityList = row.workcodesList?.qualist;
//
// this.classWorkForm.uniquekey = this.currentTask.uniquekey;
// this.classWorkForm.title = this.currentTask.title;
//
// this.classWorkForm.worktype = this.currentTask.worktype;
//[] //[]
var listCourseWork = []; var listCourseWork = [];
for (var i=0; i < row.entpcourseworklistarray.length; i++) { for (var i=0; i < row.entpcourseworklistarray.length; i++) {
listCourseWork.push(row.entpcourseworklistarray[i]); listCourseWork.push(row.entpcourseworklistarray[i]);
} }
if (listCourseWork.length > 0) { if (listCourseWork.length > 0) {
classtaskObj.id= row.id; // classtaskObj.id= row.id; //
classtaskObj.bookObj = courseObj; // classtaskObj.bookObj = courseObj; //
classtaskObj.bookName = row.evaltitle? row.evalparenttitle+'/'+row.evaltitle: row.evalparenttitle// :/ classtaskObj.bookName = row.evaltitle? row.evalparenttitle? row.evalparenttitle+'/'+row.evaltitle : row.evaltitle: row.evalparenttitle// :/
classtaskObj.uniquekey= row.uniquekey; // classtaskObj.uniquekey= row.uniquekey; //
classtaskObj.title= row.title; // classtaskObj.title= row.title; //
classtaskObj.worktype= row.worktype; // classtaskObj.worktype= row.worktype; //
@ -600,11 +675,6 @@ const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{
if (row.worktype == '框架梳理') { if (row.worktype == '框架梳理') {
// //
// let queryParams = {}
// queryParams.id = listCourseWork[0].id;
// queryParams.ppttype = '';
// queryParams.title = '';
// queryParams.filetype = 'draw';
const res = await getEvaluationclue(listCourseWork[0].id); const res = await getEvaluationclue(listCourseWork[0].id);
if ( res.data==null || res.data==undefined ) { if ( res.data==null || res.data==undefined ) {
return; return;
@ -615,6 +685,7 @@ const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{
res.data.score = listCourseWork[0].score; res.data.score = listCourseWork[0].score;
classtaskObj.chooseWorkLists.push(res.data); classtaskObj.chooseWorkLists.push(res.data);
// //
ToNewClassTask(classtaskObj);
} }
else if (row.worktype == '习题训练') { else if (row.worktype == '习题训练') {
const ids = listCourseWork.map(item => item.id).join(","); const ids = listCourseWork.map(item => item.id).join(",");
@ -636,6 +707,7 @@ const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{
classtaskObj.quizlist = idres.rows; classtaskObj.quizlist = idres.rows;
// //
ToNewClassTask(classtaskObj);
} }
}) })
} }
@ -644,23 +716,29 @@ const newHandleWorkEdit2ClassWorkQuizAdd = async (row, index) =>{
const workcodes = JSON.parse(row.workcodes); const workcodes = JSON.parse(row.workcodes);
classtaskObj.whiteboardObj = JSON.stringify(workcodes.json); classtaskObj.whiteboardObj = JSON.stringify(workcodes.json);
// //
ToNewClassTask(classtaskObj);
} }
else if (row.worktype == '常规作业') { else if (row.worktype == '常规作业') {
if(isJson(row.workcodes)){ if(isJson(row.workcodes)){
classtaskObj.fileHomeworkList = JSON.parse(row.workcodes); classtaskObj.fileHomeworkList = JSON.parse(row.workcodes);
// //
ToNewClassTask(classtaskObj);
} }
} }
//
router.push({
path: '/newClassTask',
query: {
classtaskObj: JSON.stringify(classtaskObj),
}
})
} }
}; };
/**
* 跳转 作业设计 进行编辑页面
* @param classtaskObj
*/
const ToNewClassTask = (classtaskObj) => {
router.push({
path: '/newClassTask',
query: {
classtaskObj: JSON.stringify(classtaskObj),
}
})
}
const isJson = (str) => { const isJson = (str) => {
if (typeof str == 'string') { if (typeof str == 'string') {
@ -877,6 +955,8 @@ const handleWorkConfigUpdate = (key,row) => {
classWorkConfigForm.classWorkConfigList = []; classWorkConfigForm.classWorkConfigList = [];
// //
for (var i=0; i<clist.length; i++) { for (var i=0; i<clist.length; i++) {
// clist classWorkConfigForm.studentlist studentlistid
const clsStuList = classWorkConfigForm.studentlist&&classWorkConfigForm.studentlist.filter(item=>item.classid == clist[i].id);
classWorkConfigForm.classWorkConfigList.push({ classWorkConfigForm.classWorkConfigList.push({
id: 0, id: 0,
classid: clist[i].id, classid: clist[i].id,
@ -892,7 +972,7 @@ const handleWorkConfigUpdate = (key,row) => {
workdate: '', workdate: '',
title: '', title: '',
workcodes: '', workcodes: '',
studentlist: classWorkConfigForm.studentlist, studentlist: clsStuList,
timelength: classWorkConfigForm.timelength, timelength: classWorkConfigForm.timelength,
weights: classWorkConfigForm.weights, weights: classWorkConfigForm.weights,
feedtype: classWorkConfigForm.feedtype, feedtype: classWorkConfigForm.feedtype,
@ -1006,6 +1086,7 @@ const handleTaskAssignToAllClassType = async() => {
var wForm = {}; var wForm = {};
wForm.classworkarray = JSON.stringify(ttt); wForm.classworkarray = JSON.stringify(ttt);
console.log(wForm,'推送的配置信息------------')
saveByClassWorkArray(wForm).then(response => { saveByClassWorkArray(wForm).then(response => {
console.log(response,'===============response'); console.log(response,'===============response');
// this.classWorkToStudent_Loading = false; // this.classWorkToStudent_Loading = false;
@ -1047,6 +1128,24 @@ const handleNewClassWorkDialog = () => {
router.push({ path: '/newClassTask' }); router.push({ path: '/newClassTask' });
} }
/**
* 预览框
*/
const prevRead = (item) => {
proxy.$refs.prevReadMsgDialogRef.openDialog(item);
}
/**
* 常规作业其他附件预览
*/
const openFile = () => {
const obj = {
teacherFeedContentList: workConfObj.teacherFeedContentList,//
teachFileList: workConfObj.teachFileList, // list
teachImageList: workConfObj.teachImageList,// list
}
proxy.$refs.prevReadImgFileDialogRef.acceptParams(obj);
}
// - // -
const submitWorkTitle = () => { const submitWorkTitle = () => {
if(currentWorkEdit.currentTask.title == currentWorkEdit.currentTitle){ if(currentWorkEdit.currentTask.title == currentWorkEdit.currentTitle){

View File

@ -15,11 +15,11 @@ const chartRef = ref(null);
// //
const dataList = ref([ const dataList = ref([
{name: '', value: 0,rating:1}, {name: '完美', value: 0,rating:1,max:100,min:100,},
{name: '优-', value: 0,rating:2}, {name: '优', value: 0,rating:2,max:99,min:80,},
{name: '良', value: 0,rating:3}, {name: '良', value: 0,rating:3,max:79,min:70,},
{name: '良-', value: 0,rating:4}, {name: '及格', value: 0,rating:4,max:69,min:60,},
{name: '', value: 0,rating:5}, {name: '不及格', value: 0,rating:5,max:59,min:0,},
]); ]);
// //
@ -32,6 +32,8 @@ function getColor(index) {
// //
function initChart() { function initChart() {
const myChart = echarts.init(chartRef.value); const myChart = echarts.init(chartRef.value);
const total = dataList.value.reduce((acc, cur) => acc + cur.value, 0); //
const options = { const options = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -70,7 +72,11 @@ function initChart() {
label: { label: {
show: true, show: true,
position: 'top', position: 'top',
formatter: '{c}人', formatter: params => {
const value = dataList.value[params.dataIndex].value;
const percentage = ((value / total) * 100).toFixed(2); //
return `${percentage}%`; //
},
color: '#333', color: '#333',
fontSize: 12 fontSize: 12
} }
@ -81,10 +87,13 @@ function initChart() {
} }
// //
const showEcharts =() => { const showEcharts =() => {
useOverview.tableList.forEach(item => { useOverview.tableList.forEach((item,index) => {
const index = dataList.value.findIndex(item1 => item1.rating === item.rating) if(item.rating === 0) return //
if(index !== -1) dataList.value.forEach((item1,index1) => {
dataList.value[index].value ++ if(item1.min <= Number(item.scoingRate) && Number(item.scoingRate) <= item1.max ){
item1.value ++
}
})
}) })
} }
watch(() => useOverview.tableList,() => { watch(() => useOverview.tableList,() => {

View File

@ -1,7 +1,7 @@
<template> <template>
<el-tabs :tab-position="tabPosition" style="height: 100%" class="demo-tabs" @tabChange="handelChange"> <el-tabs :tab-position="tabPosition" style="height: 100%" class="demo-tabs" @tabChange="handelChange">
<template v-for="(item,index) in leftList" :key="index"> <template v-for="(item,index) in leftList" :key="index">
<el-tab-pane :label="item.label" style="text-align:left"> <el-tab-pane :label="item.label" style="text-align:left" stretch="true">
<template v-if="item.stuList.length > 0"> <template v-if="item.stuList.length > 0">
<template v-for="(stuItem,stuIndex) in item.stuList" :key="stuIndex"> <template v-for="(stuItem,stuIndex) in item.stuList" :key="stuIndex">
<el-tag style="margin:5px 10px 0 0" type="primary">{{stuItem.studentname}}</el-tag> <el-tag style="margin:5px 10px 0 0" type="primary">{{stuItem.studentname}}</el-tag>
@ -23,29 +23,40 @@ const useOverview = overviewStore()
const tabPosition = ref('left') const tabPosition = ref('left')
const leftList = ref([ const leftList = ref([
{ {
label:'', label:'完美',
stuList:[], stuList:[],
rating:1 rating:1,
max:100,
min:100,
}, },
{ {
label:'优-', label:'优',
stuList:[], stuList:[],
rating:2 rating:2,
max:99,
min:80,
}, },
{ {
label:'良', label:'良',
stuList:[], stuList:[],
rating:3 rating:3,
max:79,
min:70,
}, },
{ {
label:'良-', label:'及格',
stuList:[], stuList:[],
rating:4 rating:4,
max:69,
min:60,
}, },
{ {
label:'', label:'不及格',
stuList:[], stuList:[],
rating:5 rating:5,
max:59,
min:0,
}, },
]) ])
// //
@ -54,9 +65,12 @@ const handelChange = (item) => {
} }
// //
const showStudents = (index) => { const showStudents = (index) => {
console.log(useOverview.tableList,'lef')
leftList.value[index].stuList = useOverview.tableList.filter(item => { leftList.value[index].stuList = useOverview.tableList.filter(item => {
if(item.rating == leftList.value[index].rating) return item if(item.rating > 0){
if(leftList.value[index].min <= Number(item.scoingRate || 0) && Number(item.scoingRate || 0) <= leftList.value[index].max ){
return item
}
}
}) })
} }
watch(() => useOverview.tableList,() => { watch(() => useOverview.tableList,() => {
@ -107,9 +121,9 @@ watch(() => useOverview.tableList,() => {
:deep(.el-tabs__item.is-active){ :deep(.el-tabs__item.is-active){
background-color: rgb(238, 241, 246); background-color: rgb(238, 241, 246);
} }
:deep(.el-tabs--left .el-tabs__item.is-left){ :deep(.el-tabs--left .el-tabs__item.is-left, .el-tabs--right .el-tabs__item.is-left){
text-align: left; text-align: left;
justify-content: flex-start; justify-content: flex-start !important;
} }
</style> </style>

View File

@ -196,7 +196,7 @@
</div> </div>
<div v-if="teachFileList.length > 0"> <div v-if="teachFileList.length > 0">
<div style="margin: 10px 0;text-align: left;"> <div style="margin: 10px 0;text-align: left; cursor: pointer;">
<span style="color: red" @click="openFile" <span style="color: red" @click="openFile"
>温馨提示点击此处 可预览其他类型附件 >温馨提示点击此处 可预览其他类型附件
</span> </span>
@ -245,7 +245,7 @@
</div> </div>
<div v-if="fileList.length > 0"> <div v-if="fileList.length > 0">
<div style="margin: 10px 0;text-align: left;"> <div style="margin: 10px 0;text-align: left; cursor: pointer;">
<span style="color: red" @click="openFile" <span style="color: red" @click="openFile"
>温馨提示点击此处 可预览其他类型附件 >温馨提示点击此处 可预览其他类型附件
</span> </span>
@ -460,12 +460,13 @@ import useUserStore from '@/store/modules/user'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
// import { Plus } from '@element-plus/icons-vue' // import { Plus } from '@element-plus/icons-vue'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { updateClassworkeval, updateClassworkdata } from '@/api/classTask' import { updateClassworkeval, updateClassworkdata, getClassworkdata } from '@/api/classTask'
import { getTimeDate } from '@/utils/date' import { getTimeDate } from '@/utils/date'
import ReFilePreview from '@/components/refile-preview/index.vue' import ReFilePreview from '@/components/refile-preview/index.vue'
import { quizStrToList } from '@/utils/comm'; import { quizStrToList } from '@/utils/comm';
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -644,7 +645,7 @@ const selectScore = (score) => {
} }
// //
const acceptParams = (params) => { const acceptParams = async (params) => {
console.log(params) console.log(params)
console.log(dialogProps, 'dialogProps') console.log(dialogProps, 'dialogProps')
// //
@ -709,8 +710,9 @@ const acceptParams = (params) => {
if (params.studentObj.worktype == '常规作业') { if (params.studentObj.worktype == '常规作业') {
try { try {
// datacontent TODO // datacontent TODO
if (params.studentObj.datacontent != '') { const res = await getClassworkdata(params.studentObj.id);
const teachWorkFileList = JSON.parse(params.studentObj.datacontent) if(res.data.datacontent != ''){
const teachWorkFileList = JSON.parse(res.data.datacontent);
console.log(teachWorkFileList, '老师filelist-------------') console.log(teachWorkFileList, '老师filelist-------------')
teachWorkFileList && teachWorkFileList &&
teachWorkFileList.forEach((item) => { teachWorkFileList.forEach((item) => {
@ -727,7 +729,6 @@ const acceptParams = (params) => {
teacherFeedContentList.value.push(teachWorkFileList) teacherFeedContentList.value.push(teachWorkFileList)
} }
dialogProps.value.studentObj.datacontent = dialogProps.value.studentObj.datacontent
} catch (error) { } catch (error) {
console.error('Invalid JSON:', error) console.error('Invalid JSON:', error)
} }

View File

@ -0,0 +1,179 @@
<template>
<el-dialog
v-model="fileReadopen"
title="文件预览"
width="80%"
:style="{ height: '72vh' }"
append-to-body
>
<div class="file-read-dialog">
<div>
<!-- 老师附件 -->
<div v-if="teachFileList.length > 0">
<el-card style="max-width: 480px">
<template #header>
<div class="card-header">
<span>文件列表</span>
</div>
</template>
<div
v-for="item in teachFileList"
:key="item"
style="margin: 10px; display: flex; align-items: center"
>
<span style="margin-right: 10px">{{ item.name }}</span>
<el-button type="primary" @click="onFileRead(item)">预览</el-button>
</div>
</el-card>
</div>
</div>
<div style="width: 100%" :style="{ height: '72vh' }">
<div style="margin-left: 10px">
预览展示区域<span style="color: red; margin-left: 10px">
温馨提示若预览失败<span style="margin-left: 10px">{{ props.name }}</span
>可点击此处<a
:href="fileitem.url ? fileitem.url : ''"
target="_blank"
style="color: blue"
>下载</a
></span
>
</div>
<ReFilePreview
:name="fileitem.name"
:type="fileitem.type"
:file-type="fileitem.type"
:file-path="fileitem.url"
:text-content="textContent"
/>
</div>
</div>
</el-dialog>
</template>
<script setup name="prevReadImgFileDialogRef">
import { ref, reactive } from 'vue'
import ReFilePreview from '@/components/refile-preview/index.vue'
const props = defineProps({})
const fileReadopen = ref(false)
// list
const teacherFeedContentList = ref([])
const teachImageList = ref([])
const teachFileList = ref([])
// 线
//#region
const fileitem = reactive({
name: '',
type: '',
url: ''
})
//
const onFileRead = (file) => {
textContent.value = '1'
//
fileitem.type = file.name.split('.').pop().toLowerCase()
fileitem.url = file.url
fileitem.name = file.name
// txt
if (fileitem.type == 'txt') {
loadFileTextContent(fileitem.url)
}
}
// txt
const textContent = ref('')
const loadFileTextContent = async (url) => {
try {
const response = await fetch(url)
if (!response.ok) {
textContent.value = '文件读取失败,您可以点击上方链接跳到另外页面查看'
throw new Error('文件读取失败')
}
textContent.value = await response.text()
} catch (error) {
console.error('读取文件时出错:', error)
textContent.value = '文件读取失败,您可以点击上方链接跳到另外页面查看'
}
}
//
const acceptParams = (params) => {
console.log(params)
fileReadopen.value = true;
teacherFeedContentList.value = params.teacherFeedContentList
teachImageList.value = params.teachImageList
teachFileList.value = params.teachFileList
}
//
// onMounted(() => {})
// ()
defineExpose({
acceptParams
})
</script>
<style scoped>
.card-header{
text-align: left;
}
.image_list {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
}
.file-read-dialog {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
}
.score-container {
display: flex;
justify-content: center;
align-items: center;
/* margin-bottom: 20px; */
}
.score-circle {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: pink;
color: red;
display: flex;
justify-content: center;
align-items: center;
font-size: 13px;
margin: 0 10px;
cursor: pointer;
transition: background-color 0.3s;
}
.score-circle.active {
background-color: red;
color: white;
}
.card-header{
align-items: left;
}
</style>

View File

@ -167,7 +167,7 @@ import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { getCurrentTime, getAfterMinutes } from '@/utils/date' import { getCurrentTime, getAfterMinutes } from '@/utils/date'
import { processList } from '@/hooks/useProcessList' import { processList } from '@/hooks/useProcessList'
import ItemDialogScore from '@/views/classTask/container/item-dialog-score.vue' import ItemDialogScore from '@/views/classTask/container/classTask/item-dialog-score.vue'
// zdg: // zdg:
import quizStats from '@/views/classTask/container/quizStats.vue' import quizStats from '@/views/classTask/container/quizStats.vue'
import ClassOverview from '@/views/classTask/container/classOverview.vue' import ClassOverview from '@/views/classTask/container/classOverview.vue'
@ -418,15 +418,15 @@ const getStudentClassWorkDataDetail = (row) => {
wevalres.rows[w].score = wevalres.rows[w].score ? wevalres.rows[w].score : 0 wevalres.rows[w].score = wevalres.rows[w].score ? wevalres.rows[w].score : 0
// html // html
wevalres.rows[w].rightanswer = // wevalres.rows[w].rightanswer =
wevalres.rows[w].rightanswer != '' && wevalres.rows[w].rightanswer != null // wevalres.rows[w].rightanswer != '' && wevalres.rows[w].rightanswer != null
? wevalres.rows[w].rightanswer.replace(/<[^>]+>/g, '') // ? wevalres.rows[w].rightanswer.replace(/<[^>]+>/g, '')
: wevalres.rows[w].rightanswer // : wevalres.rows[w].rightanswer
// html // // html
wevalres.rows[w].feedcontent = // wevalres.rows[w].feedcontent =
wevalres.rows[w].feedcontent != '' && wevalres.rows[w].feedcontent != null // wevalres.rows[w].feedcontent != '' && wevalres.rows[w].feedcontent != null
? wevalres.rows[w].feedcontent.replace(/<[^>]+>/g, '') // ? wevalres.rows[w].feedcontent.replace(/<[^>]+>/g, '')
: wevalres.rows[w].feedcontent // : wevalres.rows[w].feedcontent
if (classWorkAnalysis.row.worktype == '常规作业') { if (classWorkAnalysis.row.worktype == '常规作业') {
wevalres.rows[w].feedcontent = JSON.parse(wevalres.rows[w].feedcontent) wevalres.rows[w].feedcontent = JSON.parse(wevalres.rows[w].feedcontent)

View File

@ -0,0 +1,148 @@
<template>
<!-- 预览框 -->
<el-dialog v-if="prevReadMsg.visible" v-model="prevReadMsg.visible" class="prev-read-zy-wrap" width="90%" style="height: 80vh" append-to-body>
<!-- <div v-if="prevReadMsg.type=='课标研读'" style="height: 100%;">
<standard book-type="课标研读" :show-cata="true" :show-tools="false" :course-obj="courseObj" :bookdatahtml="versionObj.bookdata" :teachResObj="activeTeachResOfStandard"></standard>
</div>
<div v-if="prevReadMsg.type=='目标设定'" style="height: 100%;display: flex;">
<degreeevolution :courseObj="courseObj" :show-class="true" :teachResObj="activeTeachResOfStandard" :attainmentList="attainmentList" :courseQualityList="courseQualityList"/>
</div>
<div v-if="prevReadMsg.type=='教材研读'" style="height: 100%;">
<standard book-type="教材研读" :course-obj="courseObj" :show-tools="false" :bookdatahtml="versionObj.bookdata" :teachResObj="activeTeachResOfStandard"></standard>
</div> -->
<div v-if="prevReadMsg.type=='框架梳理'" style="height: 100%;">
<FlowChart ref="flowref" :flowHeight="mainHeight" :dataSource="flowData"/>
</div>
<!-- <div v-if="prevReadMsg.type=='学科定位'" style="height: 100%;">
<teachJsMind :course-obj="courseObj" :teachResObj="activeTeachResOfStandard"></teachJsMind>
</div> -->
<!-- <div v-if="prevReadMsg.type=='习题训练'">习题训练</div> -->
</el-dialog>
</template>
<script setup name="prevReadMsgDialogRef">
import { onMounted, ref, watch, reactive, getCurrentInstance } from 'vue'
import { listEntpcoursefile } from '@/api/education/entpcoursefile'
import { useGetHomework } from '@/hooks/useGetHomework'
import FlowChart from "@/components/Flowchart/index.vue";
// import {listEntpcoursework, listEntpcourseworkNew, getEntpcoursework} from '@/api/education/entpCourseWork'
// import { addClassworkReturnId } from '@/api/teaching/classwork'
// import { updateClasswork, listEvaluationclue,readFile, listClassworkeval,delClassworkeval,addClassworkeval,updateClassworkeval } from '@/api/classTask'
// import { listEvaluation } from '@/api/subject'
// import { listKnowledgePoint } from "@/api/knowledge/knowledgePoint";
import FileUpload from "@/components/FileUpload/index.vue";
import whiteboard from '@/components/whiteboard/whiteboard.vue'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user
const { proxy } = getCurrentInstance()
const props = defineProps({
bookobj: {
type: Object,
default: () => ({})
},
})
const mainHeight = ref(document.documentElement.clientHeight - 110)
//
const prevReadMsg = reactive({
visible: false,
type: ""
});// msg
// ----------
const flowData = ref({})//
// 1
const openDialog = async (item) => {
prevReadMsg.visible = true;
prevReadMsg.type = item.worktype;
// // if (item.worktype===''){
// // //
// // listEvaluation({itemkey: 'subject', edusubject: userStore.edusubject, edustage: userStore.edustage}).then(res => {
// // // TODO -
// // console.log("-",res);
// // // this.versionObj = res.rows[0];
// // // //
// // // if (this.versionObj.fileurl.length > 0) {
// // // readFile({cluelink: this.versionObj.fileurl}).then(fileres => {
// // // this.versionObj.bookdata = fileres;
// // // this.activeTeachResOfStandard = item;
// // // })
// // // }
// // })
// // }
// // if (item.worktype===''){
// // // TODO -
// // // this.activeTeachResOfStandard = item;
// // }
// if (item.worktype===''){
// // TODO -
// // getEvaluation(this.courseObj.evalrootid).then(bookres => {
// // this.versionObj = bookres.data;
// // if (this.versionObj.fileurl.length > 0) {
// // readFile({cluelink: this.versionObj.fileurl}).then(fileres => {
// // this.versionObj.bookdata = fileres;
// // this.activeTeachResOfStandard = item;
// // })
// // }
// // })
// }
if (item.worktype==='框架梳理'){
flowData.value = {};
const { chapterId } = await useGetHomework(props.bookobj.node)
// this.entpcourseid = chapterId
let queryParams = {
entpcourseid: chapterId,
ppttype: '教材分析',
parentid: item.id,
title: '逻辑框架建构',
filetype: 'draw'
}
listEntpcoursefile(queryParams).then(response=>{
if (response.rows.length == 0) {
return;
}
flowData.value = JSON.parse(response.rows[0].datacontent)
})
}
// if (item.worktype===''){
// // TODO -
// // this.activeTeachResOfStandard = item;
// }
}
onMounted(() => {
})
watch(() => props.bookobj.levelSecondId, (newVal) => {
console.log(props.bookobj,'课程选择')
})
defineExpose({
openDialog,
})
</script>
<style>
.prev-read-zy-wrap .el-dialog__header{
padding: 0!important;
}
.prev-read-zy-wrap .el-dialog__header button{
z-index: 99;
}
.prev-read-zy-wrap .el-dialog__body{
padding: 0!important;
height: 100%;
}
</style>

View File

@ -222,25 +222,8 @@
</div> </div>
</el-form> </el-form>
<!-- 预览框 --> <!-- 预览框 -->
<el-dialog v-if="prevReadMsg.visible" v-model="prevReadMsg.visible" class="prev-read-zy-wrap" width="90%" style="height: 80vh" append-to-body> <prevReadMsgDialog ref="prevReadMsgDialogRef" :bookobj="props.bookobj"/>
<!-- <div v-if="prevReadMsg.type=='课标研读'" style="height: 100%;">
<standard book-type="课标研读" :show-cata="true" :show-tools="false" :course-obj="courseObj" :bookdatahtml="versionObj.bookdata" :teachResObj="activeTeachResOfStandard"></standard>
</div>
<div v-if="prevReadMsg.type=='目标设定'" style="height: 100%;display: flex;">
<degreeevolution :courseObj="courseObj" :show-class="true" :teachResObj="activeTeachResOfStandard" :attainmentList="attainmentList" :courseQualityList="courseQualityList"/>
</div>
<div v-if="prevReadMsg.type=='教材研读'" style="height: 100%;">
<standard book-type="教材研读" :course-obj="courseObj" :show-tools="false" :bookdatahtml="versionObj.bookdata" :teachResObj="activeTeachResOfStandard"></standard>
</div> -->
<div v-if="prevReadMsg.type=='框架梳理'" style="height: 100%;">
<FlowChart ref="flowref" :flowHeight="mainHeight" :dataSource="flowData"/>
</div>
<!-- <div v-if="prevReadMsg.type=='学科定位'" style="height: 100%;">
<teachJsMind :course-obj="courseObj" :teachResObj="activeTeachResOfStandard"></teachJsMind>
</div> -->
<!-- <div v-if="prevReadMsg.type=='习题训练'">习题训练</div> -->
</el-dialog>
</div> </div>
</template> </template>
@ -260,16 +243,16 @@ import { listKnowledgePoint } from "@/api/knowledge/knowledgePoint";
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
import { processList } from '@/hooks/useProcessList' import { processList } from '@/hooks/useProcessList'
import { getCurrentTime } from '@/utils/date' import { getCurrentTime } from '@/utils/date'
import FlowChart from "@/components/Flowchart/index.vue";
import FileUpload from "@/components/FileUpload/index.vue"; import FileUpload from "@/components/FileUpload/index.vue";
import whiteboard from '@/components/whiteboard/whiteboard.vue' import whiteboard from '@/components/whiteboard/whiteboard.vue'
import prevReadMsgDialog from '@/views/classTask/container/newTask/prevReadMsg-Dialog.vue'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user const userStore = useUserStore().user
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const router = useRouter() const router = useRouter()
const toolStore = useToolState()
const props = defineProps({ const props = defineProps({
bookobj: { bookobj: {
@ -286,12 +269,8 @@ const props = defineProps({
} }
}) })
const prevReadMsgDialogRef = ref(null);// ref
const isDialogOpen = ref(false)
const toolStore = useToolState()
const openDialog = () => {
isDialogOpen.value = true
}
const classWorkFormRef = ref(null); const classWorkFormRef = ref(null);
const entpCourseWorkTypeList = ref([ const entpCourseWorkTypeList = ref([
{value: 0, label: "不限"}, {value: 0, label: "不限"},
@ -372,12 +351,6 @@ const listWorkType = ref(['习题训练', '框架梳理', '课堂展示', '常
// ------- // -------
const boardLoading = ref(false); const boardLoading = ref(false);
const prevReadMsg = reactive({
visible: false,
type: ""
});// msg
// ----------
const flowData = ref({})//
//---------- //----------
const fileLoading = ref(false); // loading const fileLoading = ref(false); // loading
@ -388,8 +361,6 @@ const changeFormType = (val) => {
classWorkForm.worktype = val; classWorkForm.worktype = val;
} }
console.log(props.propsformobj)
console.log(classWorkForm,'==============zizujian===================')
/** /**
* @desc: 根据查询参数查询试题 * @desc: 根据查询参数查询试题
* @return: {*} * @return: {*}
@ -565,61 +536,7 @@ const handleClassWorkQuizAdd = (fromsrc, entpcourseworkid) => {
* 预览资源 * 预览资源
*/ */
const prevRead = async (item) => { const prevRead = async (item) => {
prevReadMsg.visible = true; proxy.$refs.prevReadMsgDialogRef.openDialog(item);
prevReadMsg.type = item.worktype;
if (item.worktype==='课标研读'){
//
listEvaluation({itemkey: 'subject', edusubject: userStore.edusubject, edustage: userStore.edustage}).then(res => {
// TODO -
console.log("课标研读-还未接入",res);
// this.versionObj = res.rows[0];
// //
// if (this.versionObj.fileurl.length > 0) {
// readFile({cluelink: this.versionObj.fileurl}).then(fileres => {
// this.versionObj.bookdata = fileres;
// this.activeTeachResOfStandard = item;
// })
// }
})
}
if (item.worktype==='目标设定'){
// TODO -
// this.activeTeachResOfStandard = item;
}
if (item.worktype==='教材研读'){
// TODO -
// getEvaluation(this.courseObj.evalrootid).then(bookres => {
// this.versionObj = bookres.data;
// if (this.versionObj.fileurl.length > 0) {
// readFile({cluelink: this.versionObj.fileurl}).then(fileres => {
// this.versionObj.bookdata = fileres;
// this.activeTeachResOfStandard = item;
// })
// }
// })
}
if (item.worktype==='框架梳理'){
flowData.value = {};
const { chapterId } = await useGetHomework(props.bookobj.node)
// this.entpcourseid = chapterId
let queryParams = {
entpcourseid: chapterId,
ppttype: '教材分析',
parentid: item.id,
title: '逻辑框架建构',
filetype: 'draw'
}
listEntpcoursefile(queryParams).then(response=>{
if (response.rows.length == 0) {
return;
}
flowData.value = JSON.parse(response.rows[0].datacontent)
})
}
if (item.worktype==='学科定位'){
// TODO -
// this.activeTeachResOfStandard = item;
}
}; };
/** /**
* 添加到作业 * 添加到作业
@ -831,7 +748,10 @@ const handleClassWorkSave = async () => {
} }
}); });
}; };
/**
* 编辑作业内容
* @param cform 表单数据
*/
const editWork = async (cform) =>{ const editWork = async (cform) =>{
// //
cform.id= classWorkForm.id; cform.id= classWorkForm.id;

View File

@ -152,11 +152,11 @@ import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { getCurrentTime, getAfterMinutes } from '@/utils/date' import { getCurrentTime, getAfterMinutes } from '@/utils/date'
import { processList } from '@/hooks/useProcessList' import { processList } from '@/hooks/useProcessList'
import ItemDialogScore from '@/views/classTask/container/item-dialog-score.vue' import ItemDialogScore from '@/views/classTask/container/classTask/item-dialog-score.vue'
// zdg: // zdg:
import quizStats from '@/views/classTask/container/quizStats.vue' import quizStats from '@/views/classTask/container/quizStats.vue'
import ClassOverview from '@/views/classTask/container/classOverview.vue' import ClassOverview from '@/views/classTask/container/classOverview.vue'
import {sessionStore} from '@/utils/tool' import {sessionStore} from '@/utils/store'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const emit = defineEmits(['cle-click']) const emit = defineEmits(['cle-click'])
@ -410,15 +410,15 @@ const getStudentClassWorkDataDetail = (row) => {
wevalres.rows[w].score = wevalres.rows[w].score ? wevalres.rows[w].score : 0 wevalres.rows[w].score = wevalres.rows[w].score ? wevalres.rows[w].score : 0
// html // html
wevalres.rows[w].rightanswer = // wevalres.rows[w].rightanswer =
wevalres.rows[w].rightanswer != '' && wevalres.rows[w].rightanswer != null // wevalres.rows[w].rightanswer != '' && wevalres.rows[w].rightanswer != null
? wevalres.rows[w].rightanswer.replace(/<[^>]+>/g, '') // ? wevalres.rows[w].rightanswer.replace(/<[^>]+>/g, '')
: wevalres.rows[w].rightanswer // : wevalres.rows[w].rightanswer
// html // // html
wevalres.rows[w].feedcontent = // wevalres.rows[w].feedcontent =
wevalres.rows[w].feedcontent != '' && wevalres.rows[w].feedcontent != null // wevalres.rows[w].feedcontent != '' && wevalres.rows[w].feedcontent != null
? wevalres.rows[w].feedcontent.replace(/<[^>]+>/g, '') // ? wevalres.rows[w].feedcontent.replace(/<[^>]+>/g, '')
: wevalres.rows[w].feedcontent // : wevalres.rows[w].feedcontent
if (classWorkAnalysis.row.worktype == '常规作业') { if (classWorkAnalysis.row.worktype == '常规作业') {
wevalres.rows[w].feedcontent = JSON.parse(wevalres.rows[w].feedcontent) wevalres.rows[w].feedcontent = JSON.parse(wevalres.rows[w].feedcontent)
@ -636,7 +636,7 @@ onMounted(() => {
// const data = JSON.parse(localStorage.getItem('teachClassWorkItem')); // const data = JSON.parse(localStorage.getItem('teachClassWorkItem'));
const data = sessionStore.get('teachClassWorkItem'); const data = sessionStore.get('teachClassWorkItem');
// console.log(data,'????????????????????' ) console.log(data,'????????????????????' )
if(data){ if(data){
openDialog(data) openDialog(data)
} }

View File

@ -35,7 +35,8 @@ import { ref, onMounted, watch } from 'vue'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { homeworklist } from '@/api/teaching/classwork' import { homeworklist } from '@/api/teaching/classwork'
import { getCurrentTime, getTomorrow } from '@/utils/date' import { getCurrentTime, getTomorrow } from '@/utils/date'
import {sessionStore, createWindow} from '@/utils/tool' import {sessionStore} from '@/utils/store'
import {createWindow} from '@/utils/tool'
import { useToolState } from '@/store/modules/tool' import { useToolState } from '@/store/modules/tool'
import {throttle,debounce } from '@/utils/comm' import {throttle,debounce } from '@/utils/comm'

View File

@ -0,0 +1,35 @@
<template>
<PDF :url="pdfUrl" :isWin="true" v-if="pdfUrl" />
</template>
<script setup>
import { onMounted, ref } from 'vue'
import PDF from '@/components/PdfJs/index.vue'
import { useRoute } from 'vue-router';
import { getStaticUrl } from '@/utils/tool'
const route = useRoute();
const pdfUrl = ref('');
const loadPdfAnimation = (path) => {
console.log('书本地址====',path);
const timer = setTimeout(() => {
pdfUrl.value = path
clearTimeout(timer);
},2000)
}
onMounted(() => {
const bookpath = localStorage.getItem('PDF-LOCAL-PATH')
// const filepath = import.meta.env.VITE_APP_RES_FILE_PATH + bookpath
// const isDev = process.env.NODE_ENV == 'development'
// if (isDev)
// pdfUrl.value = getStaticUrl(bookpath, 'user', 'selfFile', true) //
// else
// pdfUrl.value = getStaticUrl(bookpath, 'user', 'selfFile', true) //线
// const newpath = getStaticUrl(bookpath, 'user', 'selfFile', true)
loadPdfAnimation(bookpath)
// pdfUrl.value = filepath
// console.log('',newpath);
})
</script>
<style>
</style>

View File

@ -0,0 +1,697 @@
<template>
<div class="ai-container">
<el-steps style="max-width:100% " :active="activeStep" align-center>
<el-step title="开始创作" />
<el-step title="输入主题" />
<el-step title="编辑大纲" />
<el-step title="选择模板" />
<el-step title="制作PPT" />
</el-steps>
<div class="card-box">
<el-card class="card1" v-if="activeStep == 0">
<el-tabs v-model="activeName" type="card" class="demo-tabs">
<el-tab-pane label="输入主题与要求" name="first">
<div style="padding: 20px;">输入主题</div>
<el-input type="textarea" v-model="inputTheme" :rows="3" placeholder="在此输入您的PPT主题..."
@keydown.enter.exact.prevent="addMessage" @keydown.enter.shift.exact.prevent="inputTheme += '\n'" />
<div style="padding: 20px;">具体生成要求</div>
<el-input type="textarea" v-model="inputRequire" :rows="3" placeholder="请输入对生成大纲的具体要求,比如要包含那些内容"
@keydown.enter.exact.prevent="addMessage" @keydown.enter.shift.exact.prevent="inputRequire += '\n'" />
<div>
<el-button style="margin:15px 0" type="primary" @click="addMessage">生成大纲</el-button>
</div>
</el-tab-pane>
<!-- <el-tab-pane label="上传文件并解析" name="second">
<el-upload action="#" :on-change="onFileChange" :before-upload="beforeUpload" :show-file-list="false">
<el-button type="primary">点击上传并解析文件</el-button>
<text>(支持 doc/docxpdfmdtxt 格式文档,不超过 20M,不超过 100W 字符)</text>
</el-upload>
<br/>
<div v-if="enableButton">{{docName}}文档已解析完毕</div>
<br/>
<div style="padding: 20px;">输入主题</div>
<textarea style="width:50vw" v-model="inputTheme" :rows="3" placeholder="在此输入您的PPT主题..."
@keydown.enter.shift.exact.prevent="inputTheme += '\n'">
</textarea>
<div style="padding: 20px;">具体生成要求</div>
<textarea style="width:50vw; margin:20px" v-model="docRequire" :rows="3"
placeholder="请输入对生成大纲的具体要求,比如要包含那些内容" @keydown.enter.shift.exact.prevent="inputRequire += '\n'">
</textarea>
<div>
<el-button style="padding:15px" type="primary" :disabled="!enableButton"
@click="chatDoc">生成大纲</el-button>
</div>
</el-tab-pane> -->
</el-tabs>
</el-card>
<el-card class="card2" v-if="activeStep == 1">
<div class="paragraphs">
{{ outputText }}
</div>
</el-card>
<el-card class="card3" v-if="activeStep == 2">
<div class="outline">
<el-scrollbar height="250px">
<el-row :gutter="20" class="outline-row">
<el-col :span="8">
<div v-for="item in firstArray" :key="item" class="item-with-dash">{{ item }}</div>
</el-col>
<el-col :span="16">
<div v-for="(item, index) in secondArray" :key="index">
<el-input v-model="item.value"></el-input>
</div>
</el-col>
</el-row>
</el-scrollbar>
</div>
<el-input type="textarea" v-model="fixRequire" :rows="3" placeholder="与AI对话告诉AI您想如何修改"
@keydown.enter.exact.prevent="fixOutline" @keydown.enter.shift.exact.prevent="inputRequire += '\n'" />
<br />
<el-button v-if="fixRequire.length > 0" style="padding:15px" type="primary"
@click="fixOutline()">发送修改</el-button>
<el-button style="margin-top:15px" type="primary" @click="combineOutline()">下一步</el-button>
</el-card>
<el-card v-if="activeStep == 3">
<div style="padding-bottom: 10px">ppt模板选择</div>
<div class="themes">
<div v-for="item in backGroundList" :key="item.key" :style="{
padding: '20px',
paddingRight: '30px',
paddingLeft: '30px',
margin: '10px',
backgroundColor: getBackgroundColor(item.key),
borderRadius: '10px',
borderBlock: '10px solid #e6e6e6'
}" @click="chooseBackground(item.key)" @mouseenter="changeCursor('pointer')" @mouseleave="changeCursor('default')">
{{ item.name }}
<br />
<img style="width: 150px; height: auto" :src="item.thumbnail" alt="" />
</div>
</div>
<el-row class="el-row">
<el-col :span="6" class="el-col">
<div class="grid-content-1">
<div>演讲备注</div>
<el-switch v-model="outlineData.is_card_note" />
</div>
</el-col>
<el-col :span="6" class="el-col">
<div class="grid-content-2">
<div>生成封面</div>
<el-switch v-model="outlineData.is_cover_img" />
</div>
</el-col>
<el-col :span="6" class="el-col">
<div class="grid-content-1">
<div>自动配图</div>
<el-switch v-model="outlineData.is_figure" />
</div>
</el-col>
<el-col :span="6" class="el-col">
<div class="grid-content-2">
<div>PPT作者名</div>
<el-input v-model="outlineData.author" style="width: 50%" />
</div>
</el-col>
</el-row>
<div>
<el-button style="margin-bottom: 5px;" type="primary" @click="outlineCreatePPT()">生成PPT</el-button>
</div>
</el-card>
<el-card v-if="activeStep == 4">
<el-progress :percentage="30" type="circle" v-if="percentage == 30"></el-progress>
<el-progress :percentage="70" type="circle" v-if="percentage == 70"></el-progress>
<el-progress :percentage="100" type="circle" v-if="percentage == 100"></el-progress>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ElMessage } from 'element-plus'
import {
getBackGround,
createPPT,
getProgress,
} from "@/utils/ppt-request.js";
import { uploadDoc, queryDocStatus } from "@/utils/aichat.js";
import CryptoJS from "crypto-js"
import { getSignature } from "@/utils/index.js";
let appId = "01ec9aa3";
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
let apikey = "39d05b269fa229f431a56c21794a8ea5"
let timestamp = Math.floor(Date.now() / 1000);
let signature = getSignature(appId, secret, timestamp);
const { ipcRenderer } = window.electron || {}
const outputText = ref(""); //
const stagingData = ref([]); //
const stagOutputText = ref(""); //
let extractedParts = ref([]) //
let firstArray = ref([]); //
let secondArray = ref([]); //
const backGroundList = ref([]);
const inputTheme = ref(""); //
const inputRequire = ref("") //
const activeStep = ref(0); //
const fixRequire = ref(""); //
const combined = ref('') // ppt
const treeData = ref([]);
const status = ref("init");
const percentage = ref(0);
const activeName = ref("first");
const getBackground = () => {
treeData.value = [];
getBackGround().then((res) => {
console.log(res);
backGroundList.value = res.data;
});
};
const getBackgroundColor = (key) => {
return outlineData.value.theme === key ? '#83e2b6' : '#f5f5f5';
};
const outlineData = ref({
query: '', // 8000
theme: 'auto', // ppt
author: 'AIX平台',
is_card_note: false, // ppt
is_cover_img: false, //
is_figure: false, //
}
)
//
function updateStagingData(role, newData) {
stagingData.value.push({ role: role, content: newData });
}
//ppt
const outlineCreatePPT = () => {
const newOutlineData = { ...outlineData.value, };
newOutlineData.query = combined.value;
createPPT(newOutlineData).then((res) => {
console.log(res, "正在生成中");
activeStep.value = 4
const checkProgress = () => {
getProgress(res.data.sid).then((response) => {
percentage.value = response.data.process;
if (response.data && response.data.pptUrl && response.data.pptUrl.length > 4) {
console.log('PPT',response)
//TODO window.location.href = response.data.pptUrl;
ElMessage.success("生成成功");
} else {
const sleepTime = 2000;
let remainingTime = sleepTime;
const intervalId = setInterval(() => {
remainingTime -= 100;
if (remainingTime <= 0) {
clearInterval(intervalId);
checkProgress();
}
}, 100);
}
});
};
checkProgress();
})
};
//
const addMessage = () => {
const themeValue = inputTheme.value;
const requireValue = inputRequire.value;
firstArray.value = []
secondArray.value = []
extractedParts.value = []
stagOutputText.value = ''
const combinedString = `请帮我生成一个ppt大纲主题为${themeValue}。具体内容要求为:${requireValue}。注意用三个等级大纲展示如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
updateStagingData("user", combinedString);
connectWebSocket(stagingData.value);
activeStep.value = 1
};
//ai
const fixOutline = () => {
outputText.value = '';
firstArray.value = [];
secondArray.value = [];
extractedParts.value = []
stagOutputText.value = ''
const fixValue = fixRequire.value;
updateStagingData('user', fixValue)
activeStep.value = 1
if (enableButton.value) {
uploadAndAskMainContent(stagingData.value)
} else { connectWebSocket(stagingData.value); }
fixRequire.value = ''
};
//
function extractAndRemove() {
let startIndex = -1;
let endIndex = -1;
for (let i = 0; i < stagOutputText.value.length; i++) {
const char = stagOutputText.value[i];
if (!isNaN(parseInt(char))) {
startIndex = i;
break;
}
}
if (startIndex !== -1) {
for (let j = startIndex; j < stagOutputText.value.length; j++) {
const char2 = stagOutputText.value[j];
if (char2 === '\n') {
endIndex = j;
break;
}
}
}
let extractedPart = '';
if (startIndex !== -1 && endIndex !== -1) {
extractedPart = stagOutputText.value.slice(startIndex, endIndex).replace(/\n/g, '');
extractedParts.value.push(extractedPart);
stagOutputText.value = stagOutputText.value.replace(extractedPart, '');
return true;
} else {
return false;
}
}
//
function startExtraction() {
stagOutputText.value = outputText.value
while (extractAndRemove()) { }
//
extractedParts.value.forEach(item => {
const parts = item.split(' ');
if (parts.length === 2) {
firstArray.value.push(parts[0]);
secondArray.value.push({ value: parts[1] });
}
})
}
//
function combineOutline() {
let tempCombined = '';
for (let i = 0; i < Math.max(firstArray.value.length, secondArray.value.length); i++) {
tempCombined += firstArray.value[i] || '';
tempCombined += secondArray.value[i] ? secondArray.value[i].value : '';
tempCombined += i < Math.max(firstArray.value.length, secondArray.value.length) - 1 ? ',' : '';
}
combined.value = tempCombined;
fixRequire.value = ''
activeStep.value = 3
}
let ttsWS
function connectWebSocket(data) {
outputText.value = ""; //
status.value = "ttsing";
return getWebsocketUrl().then((url) => {
ttsWS = new WebSocket(url);
ttsWS.onopen = () => {
webSocketSend(ttsWS, data);
};
ttsWS.onmessage = (e) => {
result1(e.data);
};
ttsWS.onerror = (e) => {
status.value = "error";
console.log("WebSocket error:", e);
};
ttsWS.onclose = () => {
status.value = "init";
};
});
}
function getWebsocketUrl() {
return new Promise((resolve, reject) => {
var apiKey = apikey;
var apiSecret = secret;
var url = "wss://spark-api.xf-yun.com/v4.0/chat";
var host = "spark-api.xf-yun.com";
var date = new Date().toGMTString();
var algorithm = "hmac-sha256";
var headers = "host date request-line";
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v4.0/chat HTTP/1.1`;
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
var authorization = CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(authorizationOrigin)
);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
console.log(url);
resolve(url);
});
}
function webSocketSend(ws, data) {
const params = {
header: {
app_id: appId,
},
parameter: {
chat: {
domain: "4.0Ultra",
temperature: 0.5,
max_tokens: 1024,
},
},
payload: {
message: {
text: data,
},
},
};
ws.send(JSON.stringify(params));
}
function result1(resultData) {
let jsonData = JSON.parse(resultData);
outputText.value += jsonData.payload.choices.text[0].content;
const div = document.querySelector('.paragraphs');
if (div) {
div.scrollTop = div.scrollHeight;
}
if (jsonData.payload && jsonData.payload.usage) {
startExtraction() //
console.log(firstArray.value, secondArray.value)
activeStep.value = 2
updateStagingData("assistant", outputText.value) //
}
if (jsonData.header.code !== 0) {
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
}
}
const selectedFile = ref(null);
const maxSize = 1024 * 1024 * 20;
const allowedTypes = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/markdown",
"text/plain",
];
const fileType = "wiki";
const parseType = "AUTO";
const upfileId = ref('');
const docName = ref('')
const docTheme = ref('') // ppt
const docRequire = ref('') //
const onFileChange = (file) => {
console.log(file);
if (!allowedTypes.includes(file.raw.type)) {
console.error("不支持的文件类型");
return;
}
if (file.size > maxSize) {
console.error("文件过大");
return;
}
docName.value = file.raw.name
selectedFile.value = file.raw;
uploadFile();
};
const beforeUpload = (file) => {
return false;
};
const uploadFile = async () => {
if (!selectedFile.value) {
console.error("请先选择文件");
return;
}
const formData = new FormData();
formData.append("file", selectedFile.value);
formData.append("fileType", fileType);
formData.append("parseType", parseType);
for (const pair of formData.entries()) {
console.log(pair[0], pair[1]);
}
try {
const response = await uploadDoc(formData);
const upfileData = new FormData();
upfileId.value = response.data.data.fileId;
upfileData.append("fileIds", upfileId.value);
askdoc(upfileData);
} catch (error) {
console.error(error);
}
};
//
const askdoc = async (data) => {
const response = await queryDocStatus(data);
if (response.data.data && response.data.data.length > 0) {
let foundVectored = false;
for (const item of response.data.data) {
if (item.fileStatus === 'vectored') {
foundVectored = true;
break;
}
}
if (foundVectored) {
enableButton.value = true
} else {
const sleepTime = 2000;
let remainingTime = sleepTime;
const intervalId = setInterval(() => {
remainingTime -= 100;
if (remainingTime <= 0) {
clearInterval(intervalId);
askdoc(data);
}
}, 100);
}
}
};
//
function chatDoc() {
const docthemeValue = docTheme.value;
const docrequireValue = docRequire.value;
firstArray.value = []
secondArray.value = []
extractedParts.value = []
stagOutputText.value = ''
const combinedString = `请帮我生成一个ppt大纲主题为${docthemeValue}。具体内容要求为:${docrequireValue}。注意用三个等级大纲展示如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
updateStagingData("user", combinedString);
activeStep.value = 1
uploadAndAskMainContent(stagingData.value);
}
function chatByDoc(fileId, data) {
const wsUrl = `wss://chatdoc.xfyun.cn/openapi/chat?fileId=${fileId}&appId=${appId}&timestamp=${timestamp}&signature=${signature}`;
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
const messageBody = {
fileIds: [fileId],
messages: data,
chatExtends: {
wikiPromptTpl:
"请将以下内容作为已知信息:\n<wikicontent>\n请根据以上内容回答用户的问题。\n问题:<wikiquestion>\n回答:",
wikiFilterScore: 0.82,
temperature: 0.5,
sparkWhenWithoutEmbedding: false,
},
};
ws.send(JSON.stringify(messageBody));
};
ws.onmessage = (event) => {
const response = JSON.parse(event.data);
result2(response)
console.log("WebSocket 消息:", response);
};
ws.onerror = (error) => {
console.error("WebSocket 错误:", error);
};
}
function result2(resultData) {
const div = document.querySelector('.paragraphs');
if (div) {
div.scrollTop = div.scrollHeight;
}
if (resultData.status == 1) {
outputText.value += resultData.content
}
if (resultData.status == 2) {
startExtraction() //
activeStep.value = 2
updateStagingData("assistant", outputText.value) //
}
if (resultData.code !== 0) {
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
}
}
//
const uploadAndAskMainContent = async (data) => {
try {
// const formData = new FormData();
// formData.append(
// "url",
// "https://bjcdn.openstorage.cn/xinghuo/chatdocs/2024-09-13/dad35a4f-e7c1-4efc-b6df-cae763cb984b/39052b09-d154-419f-9832-20884adeb2f41726226211177.pdf"
// );
// formData.append("fileName", "test.pdf");
// formData.append("appId", "2ff2cc26");
// formData.append("secret", "YTMyZWFiOGVlYTc5ZGM5NGIwOTU3NWMx");
// // formData.append("fileType", "wiki");
// // formData.append("parseType", "AUTO");
// const uploadResp = await uploadDoc(formData);
// const fileId = uploadResp.data.data.fileId;
const fileId = upfileId.value;
chatByDoc(fileId, data);
} catch (error) {
console.error("上传或提问过程中发生错误:", error);
}
};
const enableButton = ref(false);
const chooseBackground = (data) => {
outlineData.value.theme = data
}
const changeCursor = (cursorStyle) => {
document.documentElement.style.cursor = cursorStyle;
};
onMounted(() => {
connectWebSocket("");
getBackground();
});
</script>
<style scoped>
.ai-container {
width: 100%;
background-color: #f5f7f6;
padding: 20px
}
.card-box {
margin-top: 20px;
}
.card1 {
padding: 0;
width: 100%;
}
.paragraphs {
white-space: pre-wrap;
text-align: left;
max-height: 60vh;
overflow-y: auto;
border: 1px solid #409EFF;
padding: 10px;
margin: 5px
}
.themes {
display: flex;
flex-wrap: wrap;
height: 250px;
overflow-y: auto;
}
.outline {
white-space: pre-wrap;
text-align: left;
border: 1px solid #409EFF;
padding: 10px;
outline-style: none;
/* margin: 5px */
}
.outline-row {
display: flex;
}
.outline-row>.el-col {
display: flex;
flex-direction: column
}
.outline-row>.el-col>div,
.outline-row>.el-col>div>.el-input {
flex: 1;
display: flex;
align-items: center;
padding: 3px;
}
.item-with-dash {
margin-left: 100px
}
.item-with-dash::after {
content: "";
border-bottom: 1px dashed #000;
flex-grow: 1;
margin-left: 4px;
}
.grid-content-1 {
border-radius: 4px;
background-color: #c2dbf3;
}
.grid-content-2 {
border-radius: 4px;
background-color: #f5f5f5;
}
.el-row {
padding: 20px
}
:deep(.el-card__body){
padding: 10px 15px;
}
</style>

View File

@ -24,9 +24,10 @@
<el-col :span="24"> <el-col :span="24">
<c-form v-bind="classForm"> <c-form v-bind="classForm">
<template #item_classid="{prop, form}"> <template #item_classid="{prop, form}">
<el-select v-model="form[prop]" placeholder="请选择班级"> <span v-if="dt.ctCourse">{{ dt.ctCourse?.caption }}</span>
<el-select v-else v-model="form[prop]" placeholder="请选择班级">
<el-option v-for="item in listData.classList" :value="item.id" <el-option v-for="item in listData.classList" :value="item.id"
:label="`${item.caption} (${item.classstudentcount}人)`" /> :label="`${item.caption} (${item.classstudentcount}人)`" />
</el-select> </el-select>
</template> </template>
</c-form> </c-form>
@ -118,7 +119,9 @@ const dt = reactive({ // 其他数据
isHistory: false, // - isHistory: false, // -
loading: false, // -loading loading: false, // -loading
loadingDel: false, // -loading loadingDel: false, // -loading
atClass: {}, //
atCourse: {}, // atCourse: {}, //
ctCourse: null, //
}) })
let chat = null // im-chat let chat = null // im-chat
@ -128,9 +131,10 @@ onMounted(() => {
}) })
/** /**
* @description 暴露方法-打开对话框 * @description 暴露方法-打开对话框
* @param row 课件对象 * @param id 课件id
* @param classObj 课程对象-用于继续上课
*/ */
const open = async (id) => { const open = async (id, classObj) => {
visible.value = true visible.value = true
if (id) { if (id) {
// //
@ -139,6 +143,11 @@ const open = async (id) => {
await getAptInfo(id) await getAptInfo(id)
// //
getClassList() getClassList()
//
if (!!classObj) {
dt.ctCourse = classObj
teacherForm.form.classcourseid = classObj.id
}
// im-chat // im-chat
nextTick(async() => { nextTick(async() => {
chat = await imChatRef.value?.initImChat() chat = await imChatRef.value?.initImChat()
@ -148,8 +157,9 @@ const open = async (id) => {
// //
const handleClose = async () => { const handleClose = async () => {
reset() // reset() //
await chat?.logout() // await chat?.logout()
chat = null // chat = null
dt.ctCourse = null
emit('close') emit('close')
} }
// - // -
@ -177,8 +187,10 @@ const reset = () => {
teacherForm.form = { classcourseid: 0 } teacherForm.form = { classcourseid: 0 }
dt.isCreate = false dt.isCreate = false
dt.isHistory = false dt.isHistory = false
dt.atClass = {}
dt.atCourse = {} dt.atCourse = {}
} }
// APT // APT
const getAptInfo = async (id) => { const getAptInfo = async (id) => {
const res = await Http_Entpcoursefile.getEntpcoursefile(id) const res = await Http_Entpcoursefile.getEntpcoursefile(id)
@ -338,9 +350,9 @@ const chatChange = (type, data, ...args) => {
// -id // -id
watch(() => classForm.form.classid, (val)=> { watch(() => classForm.form.classid, (val)=> {
// //
dt.atCourse = listData.classList.find(o => o.id === val) || {} dt.atClass = listData.classList.find(o => o.id === val) || {}
// - // -
listData.activeStudentList = dt.atCourse?.classstudentlist || [] // listData.activeStudentList = dt.atClass?.classstudentlist || []
// //
listData.classcourseList = [] listData.classcourseList = []
// , // ,

View File

@ -102,7 +102,7 @@ import { deleteSmarttalk, updateSmarttalk, getPrepareById } from '@/api/file'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import outLink from '@/utils/linkConfig' import outLink from '@/utils/linkConfig'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import { listClasscourseNew } from '@/api/teaching/classcourse' import { listClasscourseNew, updateClasscourse } from '@/api/teaching/classcourse'
import { endClass, getSelfReserv } from '@/api/classManage' import { endClass, getSelfReserv } from '@/api/classManage'
import { listEntpcourse } from '@/api/teaching/classwork' import { listEntpcourse } from '@/api/teaching/classwork'
import { createWindow } from '@/utils/tool' import { createWindow } from '@/utils/tool'
@ -137,7 +137,7 @@ export default {
} }
}, },
expose: ['openFileWin'], expose: ['openFileWin'],
emits: { 'on-start-class': null, 'on-delete': null, 'on-set': null, 'on-delhomework': null,'on-filearg': null }, emits: { 'on-start-class': null, 'on-delete': null, 'on-set': null, 'on-delhomework': null,'on-filearg': null, 'change': null },
data() { data() {
return { return {
listenList: [], listenList: [],
@ -148,27 +148,24 @@ export default {
this.userInfo = useUserStore().user this.userInfo = useUserStore().user
}, },
methods: { methods: {
getOpenCourse() { //
return Promise.all([listClasscourseNew({teacherid: this.userInfo.userId,status:"open",evalid: this.curNode.id,pageSize:1000}), getSelfReserv({ex2:this.curNode.id})]).then(([res1,res2])=>{ getOpenCourse(isApt) {
let list2 = res1.rows || [] const curNodeId = this.curNode.id
let list = res2.data || [] if (isApt) { // APT
let one = list.find(item1 => { const params = {teacherid: this.userInfo.userId,status:"open",evalid: curNodeId,pageSize:1000}
if (item1.status === "上课中") { return listClasscourseNew(params).then(res => {
return true return (res.rows || [])
}
}) })
if (one) { } else { // PPT
return one return getSelfReserv({ex2: curNodeId}).then(res => {
} return (res.data || []).filter(o => o.status === "上课中")
if (list2.length>0) { })
one = list2[0] }
}
return one
})
}, },
clickStartClass(item) { clickStartClass(item) {
this.getOpenCourse().then(res=>{ const isApt = item.fileFlag === 'apt'
if(!res){ this.getOpenCourse(isApt).then(res => {
if(!res || res.length === 0){
this.$emit('on-start-class', item) this.$emit('on-start-class', item)
}else{ }else{
ElMessageBox.alert('<strong>上次课程尚未结束,是否继续上课?</strong>', '', { ElMessageBox.alert('<strong>上次课程尚未结束,是否继续上课?</strong>', '', {
@ -185,51 +182,80 @@ export default {
confirmButtonClass: "el-button--danger", confirmButtonClass: "el-button--danger",
center: true, center: true,
beforeClose: (action, instance, done) => { beforeClose: (action, instance, done) => {
const obj = res[0]
// console.log(action, obj, item)
if (action === 'confirm'){ if (action === 'confirm'){
// //
if (res.bookImg) { this.$emit('change', 'close', obj, { type: 1, instance, done })
//PPT // if (obj.bookImg) {
endClass(res.id).then((res1) => { // // //PPT
if (res1.data === true) { // // endClass(obj.id).then((res1) => {
ElMessage({ // // if (res1.data === true) {
message: '下课成功', // // ElMessage({
type: 'success' // // message: '',
}) // // type: 'success'
res.status = '已结束' // // })
done() // // obj.status = ''
} // // done()
}) // // }
}else { // // })
//APT // }else {
} // //APT -
// // this.$emit('change', 'close', obj, { type: 1, instance, done })
// // this.closeCourse(obj, instance, done)
// }
} }
if (action === 'cancel'){ if (action === 'cancel'){
// //
if (res.bookImg) { if (obj.bookImg) {
console.log('PPT')
//PPT //PPT
listEntpcourse({ listEntpcourse({
evalid: res.ex2, evalid: obj.ex2,
edituserid: useUserStore().user.userId, edituserid: useUserStore().user.userId,
pageSize: 500 pageSize: 500
}).then(async res1=>{ }).then(async res1=>{
if (res1.rows[0].id) { if (res1.rows[0].id) {
createWindow('tool-sphere', { url: '/tool/sphere?entpcourseid=' + res1.rows[0].id + "&reservId=" + res.id }) createWindow('tool-sphere', { url: '/tool/sphere?entpcourseid=' + res1.rows[0].id + "&reservId=" + obj.id })
done() done()
} }
}) })
}else { }else {
//APT //APT
this.$emit('on-start-class', item, obj)
done()
} }
} }
if (action === 'close') { if (action === 'close') {
done() done()
} }
}, },
}) }).catch(() => {})
} }
}) })
// this.$emit('on-start-class', item) // this.$emit('on-start-class', item)
}, },
// ()
// async closeCourse(row, instance, done) {
// instance.confirmButtonLoading = true
// instance.confirmButtonText = '...'
// // -
// if (!!row.timgroupid) {
// const msg = { msgKey: 'closed', actor: 'teacher', classcourseid: row.id }
// Chat.sendMsg(row.timgroupid, msg)
// }
// // -
// const params = { id: row.id, status: 'closed', timgroupid: '' }
// await updateClasscourse(params)
// //
// setTimeout(async() => {
// if (!!row.timgroupid) await Chat.dismissGroup(row.timgroupid)
// instance.confirmButtonLoading = false
// instance.confirmButtonText = ''
// done()
// ElMessage({ type: 'success', message: `` })
// }, 1000)
// },
editTalk(item) { editTalk(item) {
ElMessageBox.prompt('请输入新的标签', '添加标签', { ElMessageBox.prompt('请输入新的标签', '添加标签', {
confirmButtonText: '确认', confirmButtonText: '确认',

View File

@ -0,0 +1,33 @@
<template>
<div>
<el-dialog class="ppt-dialog" v-model="model" :show-close="false" width="900" destroy-on-close :top="'3vh'">
<template #header="{ close, titleId, titleClass }">
<div class="dialog-header">
<h4 :id="titleId" :class="titleClass">生成PPT(试验版)</h4>
<i class="iconfont icon-guanbi" @click="close"></i>
</div>
</template>
<AiPpt/>
</el-dialog>
</div>
</template>
<script setup>
import AiPpt from './ai-ppt.vue';
const model = defineModel()
</script>
<style lang="scss" scoped>
:deep(.ppt-dialog){
-webkit-app-region: no-drag;
}
.dialog-header{
display: flex;
justify-content: space-between;
align-items: center;
.icon-guanbi {
font-size: 20px;
cursor: pointer;
}
}
</style>

View File

@ -13,6 +13,10 @@
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>PPT</label></div> <div class="create-btn-title"><el-icon><Plus /></el-icon><label>PPT</label></div>
<div class="create-btn-info">传统课件</div> <div class="create-btn-info">传统课件</div>
</div> </div>
<div class="center-create-btn" style="background-color: #909399" @click="pptDialog = true">
<div class="create-btn-title"><el-icon><Plus /></el-icon><label>PPT</label></div>
<div class="create-btn-info">试验版</div>
</div>
</div> </div>
<div class="prepare-center-body"> <div class="prepare-center-body">
<kj-list-item <kj-list-item
@ -24,12 +28,14 @@
:curNode="currentNode" :curNode="currentNode"
@on-delete="deleteTalk" @on-delete="deleteTalk"
@on-start-class="startClass" @on-start-class="startClass"
@change="changeClass"
> >
</kj-list-item> </kj-list-item>
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="教学实录" name="教学实录" class="prepare-center-jxsl"> <el-tab-pane label="教学实录" name="教学实录" class="prepare-center-jxsl">
<class-reserv v-if="activeAptTab==='教学实录'" :curNode="currentNode"></class-reserv> <class-reserv v-if="activeAptTab==='教学实录'" :curNode="currentNode"
@change="changeClass"></class-reserv>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -131,6 +137,7 @@
></reserv> ></reserv>
<!-- 上课配置 --> <!-- 上课配置 -->
<class-start ref="calssRef" @close="closeChange"/> <class-start ref="calssRef" @close="closeChange"/>
<PptDialog v-model="pptDialog"/>
</template> </template>
<script setup> <script setup>
import { Check,Plus } from '@element-plus/icons-vue' import { Check,Plus } from '@element-plus/icons-vue'
@ -148,6 +155,7 @@ import { useToolState } from '@/store/modules/tool'
import MoveFile from '@/components/move-file/index.vue' import MoveFile from '@/components/move-file/index.vue'
import FileListItem from '@/views/prepare/container/file-list-item.vue' import FileListItem from '@/views/prepare/container/file-list-item.vue'
import KjListItem from '@/views/prepare/container/kj-list-item.vue' import KjListItem from '@/views/prepare/container/kj-list-item.vue'
import PptDialog from './container/ppt-dialog.vue'
import { getSmarttalkPage, moveSmarttalk, creatAPT } from '@/api/file' import { getSmarttalkPage, moveSmarttalk, creatAPT } from '@/api/file'
import { toTimeText } from '@/utils/date' import { toTimeText } from '@/utils/date'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
@ -155,16 +163,21 @@ import { parseCataByNode, creatPPT, asyncLocalFile } from '@/utils/talkFile'
import FileOperBatch from '@/views/prepare/container/file-oper-batch.vue' import FileOperBatch from '@/views/prepare/container/file-oper-batch.vue'
import SetHomework from '@/components/set-homework/index.vue' import SetHomework from '@/components/set-homework/index.vue'
import outLink from '@/utils/linkConfig' import outLink from '@/utils/linkConfig'
import { createWindow, sessionStore } 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 { getClassInfo, getSelfReserv } from '@/api/classManage' import { updateClasscourse } from '@/api/teaching/classcourse'
import { getClassInfo, getSelfReserv, endClass } from '@/api/classManage'
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
import { addEntpcoursefileReturnId } from '@/api/education/entpcoursefile' import { addEntpcoursefileReturnId } from '@/api/education/entpcoursefile'
import ClassReserv from '@/views/classManage/classReserv.vue' import ClassReserv from '@/views/classManage/classReserv.vue'
import classStart from './container/class-start.vue' // import classStart from './container/class-start.vue' //
const toolStore = useToolState() import MsgEnum from '@/plugins/imChat/msgEnum' // im
import Chat from '@/utils/chat' // im
import msgEnum from '@/plugins/imChat/msgEnum'
if (!Chat.imChat) Chat.init()
const toolStore = useToolState()
const fs = require('fs') const fs = require('fs')
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
@ -219,6 +232,7 @@ export default {
isOpenHomework: false, isOpenHomework: false,
// //
activeClass: null, activeClass: null,
pptDialog: false
} }
}, },
computed: { computed: {
@ -272,7 +286,8 @@ export default {
// } // }
// }, // },
methods: { methods: {
startClass(item) { //
startClass(item, classObj) {
// () // ()
const id = sessionStore.has('activeClass.id') ? sessionStore.get('activeClass.id') : null const id = sessionStore.has('activeClass.id') ? sessionStore.get('activeClass.id') : null
if (id && id == item.id) return ElMessage.warning('当前正在上课,请勿重复操作') if (id && id == item.id) return ElMessage.warning('当前正在上课,请勿重复操作')
@ -283,11 +298,68 @@ export default {
this.openReserv() this.openReserv()
} }
if(item.fileFlag === 'apt') { if(item.fileFlag === 'apt') {
this.$refs.calssRef.open(item.fileId) this.$refs.calssRef.open(item.fileId, classObj)
}
},
// -apt
async changeClass(type, row, other) {
switch(type) {
case 'continue': { //
const aptFileId = row.entpcoursefileid
this.$refs.calssRef.open(aptFileId, row)
break
}
case 'close': { //
const head = MsgEnum.HEADS.MSG_closed // closed
const msgT = msgEnum.TYPES.TEACHER // teacher
const isApt = !row.bookImg // bookImg ppt Apt
row.ex3 == 'undefined' && (row.ex3 = null)
const timgroupid = isApt ? row.timgroupid : row.ex3 // ex3 ppt Apt
if (other.type == 1) { // -
other.instance.confirmButtonLoading = true
other.instance.confirmButtonText = '下课中...'
} else { // -
other.loading.value = true
}
// -
if (!!timgroupid) {
const msg = { msgKey: head, actor: msgT, classcourseid: row.id }
Chat.sendMsg(timgroupid, msg)
}
if (isApt) { // Apt
// -
await updateClasscourse({ id: row.id, status: head, timgroupid: '' })
} else { // PPT
const toolStore = useToolState()
//
if (toolStore.isToolWin) {
toolStore.resetDef() //
ipcMsgSend('tool-sphere:close') //
}
// -
await endClass(row.id)
}
//
setTimeout(async() => {
if (!!timgroupid) await Chat.dismissGroup(timgroupid)
if (other.type == 1) { // -
other.instance.confirmButtonLoading = false
other.instance.confirmButtonText = '下课'
other.done()
} else {
other.loading.value = false
row.status = isApt ? head : '已结束'
}
ElMessage({ type: 'success', message: `下课成功!` })
}, 1000)
break
}
default:
break
} }
}, },
closeChange() { // - closeChange() { // -
console.log('关闭上课弹窗') // console.log('')
// this.activeClass = null // this.activeClass = null
sessionStore.delete('activeClass') sessionStore.delete('activeClass')
}, },
@ -298,8 +370,8 @@ export default {
this.$refs['kjItemRef'+this.activeClass.id][0].openFileWin(this.activeClass); this.$refs['kjItemRef'+this.activeClass.id][0].openFileWin(this.activeClass);
}) })
}, },
getBookPathFromServer() { getBookPathFromServer(path) {
let fileName = this.curBookPath let fileName = path ? path : this.curBookPath
if (!fileName) return if (!fileName) return
fileName = fileName.replace('.txt', '.pdf') fileName = fileName.replace('.txt', '.pdf')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -549,6 +621,9 @@ export default {
if (this.currentNode.id === data.node.id) return if (this.currentNode.id === data.node.id) return
this.curBookImg = data.textBook.curBookImg this.curBookImg = data.textBook.curBookImg
this.curBookPath = data.textBook.curBookPath this.curBookPath = data.textBook.curBookPath
const path = await this.getBookPathFromServer(data.textBook.curBookPath)
const localpath = getAppInstallUrl('pdfjs-dist/web/viewer.html', 'user', '\\out\\renderer', true) + "?file="
localStorage.setItem('PDF-LOCAL-PATH',localpath + encodeURIComponent(import.meta.env.VITE_APP_RES_FILE_PATH + path))
this.checkFileList = [] this.checkFileList = []
this.currentWorkList = [] this.currentWorkList = []
let cata = parseCataByNode(data.node) let cata = parseCataByNode(data.node)

View File

@ -7,7 +7,7 @@
<el-image class="imges" :src="bookInfo ? bookInfo.avartar : ''" /> <el-image class="imges" :src="bookInfo ? bookInfo.avartar : ''" />
</div> </div>
<div class="stand-head-right"> <div class="stand-head-right">
<div class="stand-head-right-tit">{{booktitle}}</div> <div class="stand-head-right-tit">{{bookInfo ? bookInfo.bookName: ''}}</div>
<i class="iconfont icon-yidongdaozu stand-head-right-icon" @click="dialogVisible = true"></i> <i class="iconfont icon-yidongdaozu stand-head-right-icon" @click="dialogVisible = true"></i>
<div class="stand-head-right-row"> <div class="stand-head-right-row">
<div class="stand-head-right-row-time">更新2024.9.10</div> <div class="stand-head-right-row-time">更新2024.9.10</div>
@ -72,7 +72,7 @@
<div class="booklist"> <div class="booklist">
<div :class="{'item': true,'active': booksel === idx}" v-for="item,idx in bookList" :key="idx" @click="bookChange(item,idx)"> <div :class="{'item': true,'active': booksel === idx}" v-for="item,idx in bookList" :key="idx" @click="bookChange(item,idx)">
<el-image class="bookimg" :src="item.avartar" /> <el-image class="bookimg" :src="item.avartar" />
<div class="bookname">{{item.fileurl.replace('.txt', '')}}</div> <div class="bookname">{{item.bookName}}</div>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
@ -84,11 +84,13 @@ import PDF from '@/components/PdfJs/index.vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import useResoureStore from '../resource/store' import useResoureStore from '../resource/store'
import { listEvaluationclue } from '@/api/teaching/classwork' import { listEvaluationclue } from '@/api/teaching/classwork'
import { uploadServer, getJSONFile } from '@/utils/common' // import { uploadServer, getJSONFile } from '@/utils/common'
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus'
import ChooseTextbook from "@/components/choose-textbook/index.vue"; import ChooseTextbook from "@/components/choose-textbook/index.vue";
import { listEvaluation } from '@/api/classManage/index' import { listEvaluation } from '@/api/classManage/index'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { sessionStore } from '@/utils/store'
import { useGetSubject } from '@/hooks/useGetSubject'
const userStore = useUserStore() const userStore = useUserStore()
const sourceStore = useResoureStore() const sourceStore = useResoureStore()
// import { getStaticUrl } from '@/utils/tool' // import { getStaticUrl } from '@/utils/tool'
@ -105,7 +107,6 @@ const headref = ref(null);
const searchref = ref(null); const searchref = ref(null);
const listHeight = ref(0); const listHeight = ref(0);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const booktitle = ref('');
const bookInfo = ref(null); const bookInfo = ref(null);
const booksel = ref(0); const booksel = ref(0);
const bookList = ref([]) const bookList = ref([])
@ -248,31 +249,45 @@ const getAllSubject = async () => {
const dataList = []; const dataList = [];
rows && rows.map((item,idx) => { rows && rows.map((item,idx) => {
if(item.fileurl !== ''){ if(item.fileurl !== ''){
dataList.push({...item,avartar: import.meta.env.VITE_APP_BUILD_BASE_PATH + item.avartar}) dataList.push({...item,avartar: import.meta.env.VITE_APP_BUILD_BASE_PATH + item.avartar,bookName: item.fileurl.replace('.txt','')})
} }
}) })
bookList.value = dataList bookList.value = dataList
bookInfo.value = {...dataList[0],avartar: dataList[0].avartar} const session = sessionStore.get('subject.curNode')
booktitle.value = dataList[0].fileurl.replace('.txt','') console.log('session',session);
const filePath = import.meta.env.VITE_APP_RES_FILE_PATH + dataList[0].fileurl.replace('.txt','.pdf') let filePath = import.meta.env.VITE_APP_RES_FILE_PATH;
await loadPdfAnimation(filePath) if(session.rootid){
const idx = dataList.findIndex(item => item.id === session.rootid)
if(idx > -1){
bookInfo.value = {...dataList[idx]}
filePath += dataList[idx].fileurl.replace('.txt','.pdf')
}else{
bookInfo.value = {...dataList[0]}
filePath += dataList[0].fileurl.replace('.txt','.pdf')
}
await loadPdfAnimation(filePath)
}else{
bookInfo.value = {...dataList[0]}
filePath += dataList[0].fileurl.replace('.txt','.pdf')
}
} }
const bookChange = async (item, idx) => { const bookChange = async (item, idx) => {
booksel.value = idx booksel.value = idx
bookInfo.value = {...item} bookInfo.value = {...item}
booktitle.value = item.fileurl.replace('.txt','')
pdfUrl.value = ''; pdfUrl.value = '';
const filepath = import.meta.env.VITE_APP_RES_FILE_PATH + item.fileurl.replace('.txt','.pdf') const filepath = import.meta.env.VITE_APP_RES_FILE_PATH + item.fileurl.replace('.txt','.pdf')
await loadPdfAnimation(filepath) await loadPdfAnimation(filepath)
dialogVisible.value = false dialogVisible.value = false
} }
const loadPdfAnimation = (path) => { const loadPdfAnimation = (path) => {
console.log('书本地址====',path);
const timer = setTimeout(() => { const timer = setTimeout(() => {
pdfUrl.value = path pdfUrl.value = path
clearTimeout(timer); clearTimeout(timer);
},2000) },2000)
} }
onMounted(async () => { onMounted(async () => {
await useGetSubject();
await getAllSubject(); await getAllSubject();
if(cardref.value && headref.value){ if(cardref.value && headref.value){
const cardH = cardref.value.offsetHeight; const cardH = cardref.value.offsetHeight;
@ -423,13 +438,18 @@ onMounted(async () => {
z-index: 2; z-index: 2;
&-tit{ &-tit{
width: 100%; width: 100%;
height: 44px;
line-height: 22px;
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
white-space: nowrap; display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
color: #3b3b3b; color: #3b3b3b;
text-align: left; text-align: left;
padding-right: 30px;
} }
&-icon{ &-icon{
position: absolute; position: absolute;
@ -443,7 +463,6 @@ onMounted(async () => {
height: 32px; height: 32px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: 12px;
&-time{ &-time{
font-size: var(--el-font-size-extra-small); font-size: var(--el-font-size-extra-small);
color: var(--el-color-info-rgb); color: var(--el-color-info-rgb);
@ -537,9 +556,10 @@ onMounted(async () => {
width: 130px; width: 130px;
height: 180px; height: 180px;
} }
.name{ .bookname{
height: 60px;
line-height: 20px;
font-size: 14px; font-size: 14px;
color: #3b3b3b;
} }
} }
.item:hover{ .item:hover{

View File

@ -38,7 +38,7 @@ import { computed, defineProps, ref, reactive, watchEffect, onMounted } from 'vu
import homework from './homework.vue'; import homework from './homework.vue';
// - // -
const colors = ['#00f389', '#ff7f00', '#ffff00', '#409EFF', '#00baff', '#13b189', '#F56C6C'] const colors = ['#409EFF','#00f389', '#ff7f00', '#ffff00', '#00baff', '#13b189', '#F56C6C']
const emit = defineEmits(['update:modelValue','change']) const emit = defineEmits(['update:modelValue','change'])
const props = defineProps({ const props = defineProps({
modelValue: { // modelValue: { //
@ -48,6 +48,7 @@ const props = defineProps({
data: { // data: { //
type: Array, type: Array,
default: () => [ default: () => [
{ label: '课件', prop: 'book', isExtra: true, icon: 'icon--kejian' },
{ label: '活动', prop: 'resource', icon: 'icon-kechengziyuan1' }, { label: '活动', prop: 'resource', icon: 'icon-kechengziyuan1' },
// { label: '', prop: 'interact', icon: 'icon-hudong' }, // { label: '', prop: 'interact', icon: 'icon-hudong' },
// { label: '', prop: 'win', icon: 'icon-tubiaozhizuomobanyihuifu-' }, // { label: '', prop: 'win', icon: 'icon-tubiaozhizuomobanyihuifu-' },

View File

@ -52,7 +52,7 @@ import imChat from './components/imChat.vue' // im-chat-子组件
import vDrag from './directive/drag' // - import vDrag from './directive/drag' // -
import vIgnore from './directive/ignore' // -穿 import vIgnore from './directive/ignore' // -穿
import { useToolState } from '@/store/modules/tool' // - import { useToolState } from '@/store/modules/tool' // -
import { ipcMsgSend, ipcMain, sessionStore } from '@/utils/tool' // import { ipcMsgSend, ipcMain, sessionStore, toRoter } from '@/utils/tool' //
import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem) import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem)
const route = useRoute(); const route = useRoute();
@ -62,6 +62,7 @@ const isDrag = ref(false) // 开始拖拽
const dragtime = ref(0) // - const dragtime = ref(0) // -
const isShow = ref(false) // - const isShow = ref(false) // -
const isOver = ref(false) // const isOver = ref(false) //
const isOpenBook = ref(false)
const toolStore = useToolState() // const toolStore = useToolState() //
const boardVueRef=ref(null) // ref const boardVueRef=ref(null) // ref
const upvoteRef = ref(null) // ref const upvoteRef = ref(null) // ref
@ -70,6 +71,7 @@ const classObj = reactive({ // 课程相关
id: route.query.reservId, // id id: route.query.reservId, // id
data: {} // data: {} //
}) })
const bookInfo = ref(null)
const msgIds = [] // id const msgIds = [] // id
const electron = window.electron // electron const electron = window.electron // electron
const btnList = [ // const btnList = [ //
@ -88,6 +90,7 @@ onMounted(async() => {
// window.test1 = toolStore // window.test1 = toolStore
getClassInfo() // ex3 getClassInfo() // ex3
resetStatus() // - resetStatus() // -
}) })
// ==== === // ==== ===
@ -204,6 +207,16 @@ const sideMouse = e => {
const sideChange = async o => { const sideChange = async o => {
// console.log(o) // console.log(o)
switch(o.prop) { switch(o.prop) {
case 'book':
if(isOpenBook.value) {
isOpenBook.value = false
ElMessage.info('已经打开课本了哦')
}else {
// createWindow('fullScreen-PDF',{url: '/fullscreenpdf'})
toRoter(`${process.env['ELECTRON_RENDERER_URL']}/#/fullscreenpdf`)
isOpenBook.value = true
}
break
case 'resource': // case 'resource': //
break break
case 'interact': // case 'interact': //
@ -226,7 +239,7 @@ const sideChange = async o => {
// 2 // 2
setTimeout(async() => { setTimeout(async() => {
await imChatRef.value?.deleteGroup() // await imChatRef.value?.deleteGroup() //
await imChatRef.value?.logout() // 退im // await imChatRef.value?.logout() // 退im
elMsg.close() elMsg.close()
ipcMsgSend('tool-sphere:close') // ipcMsgSend('tool-sphere:close') //
}, 500); }, 500);