Compare commits

...

282 Commits

Author SHA1 Message Date
陈昱达
5d81f72f5f feat(ai-call):优化AI对话组件初始化逻辑
- 添加组件准备就绪状态标识
- 确保组件挂载完成后再执行自动滚动
-重置对话时正确处理组件状态
- 避免未初始化完成时的滚动操作
2025-11-06 17:20:26 +08:00
陈昱达
c9c34501ce feat(ai-call): 实现对话框尺寸和位置状态持久化
- 添加对话框尺寸状态保存与恢复功能
- 支持通过 sessionStorage 存储对话框宽高及坐标
-优化欢迎消息区域高度计算逻辑- 移除旧的状态处理机制,采用事件驱动方式- 确保在无保存状态时使用默认尺寸配置
2025-11-06 16:42:14 +08:00
陈昱达
1812c0901c feat(portal): 调整AI通话弹窗宽度
- 设置AI通话弹窗默认宽度为800px
- 优化弹窗显示效果和用户体验
2025-11-06 16:29:50 +08:00
陈昱达
13281d8a7d feat(portal): 调整AI通话弹窗宽度
- 设置AI通话弹窗默认宽度为800px
- 优化弹窗显示效果和用户体验
2025-11-06 15:53:38 +08:00
陈昱达
5fdf8efedb fix(portal): 调整AI通话弹窗显示逻辑
- 修改弹窗显示条件,确保仅在最大化状态下显示
- 移除冗余的宽度设置
-优化窗口状态控制逻辑
2025-11-06 15:46:11 +08:00
陈昱达
1a475c8612 feat(ai-call):优化对话框拖拽与缩放功能- 增强对话框拖拽逻辑,防止事件冒泡
- 完善对话框缩放功能,动态调整欢迎消息区域高度
- 修正机器人欢迎文本中的错别字
- 调整消息列表区域样式,优化滚动条显示
- 监听对话框可见性变化,确保内容正确渲染
2025-11-04 17:14:38 +08:00
陈昱达
01e4c676fc feat(portal/case): 增强AI对话窗口交互功能
-为sendMessage组件添加textarea输入框,支持多行输入
- 为AI对话窗口添加拖拽和调整大小功能
- 在最小化窗口中添加关闭按钮- 优化窗口样式和布局,提升用户体验
- 添加拖拽手柄和窗口控制按钮
- 实现窗口位置和大小的动态调整
- 引入open.png图标用于最小化窗口操作
2025-11-04 14:54:51 +08:00
陈昱达
86e25f69f9 feat(portal): 添加消息组件最小化窗口功能
- 在消息组件中新增 getMinWindow事件触发
- 在 AI 呼叫页面监听并处理最小化事件
- 实现窗口状态管理,支持最小化显示
-优化组件间通信逻辑,确保状态同步准确- 修复可能引起状态异常的注释代码问题- 调整样式以适配最小化窗口显示效果
2025-10-23 10:22:40 +08:00
陈昱达
b8daef0983 更新lock 2025-10-15 10:33:22 +08:00
陈昱达
df45c9d896 feat(case): 支持消息内容Markdown渲染并优化样式
- 消息内容支持Markdown格式渲染
- 注释掉推荐问题模块,暂时不显示
- 调整消息气泡样式,去除背景色和边框
- 修改消息气泡圆角大小,提升视觉效果
- 调整链接颜色为黑色,增强可读性- 提高打字机效果速度,改善用户体验
2025-10-14 17:48:26 +08:00
陈昱达
b9caf2c4ad fix(portal): 禁用消息组件中的打字机效果
- 注释掉了启动打字机效果的函数调用
- 直接设置显示文本以避免延迟
2025-10-14 15:43:36 +08:00
陈昱达
0afd733f47 feat(portal):优化AI对话框宽度与消息展示
- 将AI对话框宽度从65%调整为800px- 修改初始欢迎消息格式,使用HTML标签替换Markdown语法-修复机器人消息显示逻辑,确保displayText正确赋值- 优化消息渲染流程,提升用户体验
2025-10-14 15:43:13 +08:00
陈昱达
3720b5667d feat(portal): 实现消息组件支持 Mermaid 图表渲染
- 添加 markdown-it-mermaid 插件支持 Mermaid 语法
- 集成 mermaid 库并初始化配置
- 实现打字机效果与 Mermaid 渲染结合
-优化案例引用展示逻辑与样式- 添加推荐问题展示功能
- 改进消息组件的响应式与样式细节
- 修复组件销毁时定时器未清除的问题
- 更新依赖包引入 Mermaid 相关库
2025-10-14 15:32:20 +08:00
陈昱达
72472979bd feat(portal): 支持消息内容的 Markdown 和 LaTeX 渲染- 引入 markdown-it 与 highlight.js 实现 Markdown 渲染- 集成 KaTeX 支持数学公式显示
- 更新消息组件以支持实时渲染 Markdown 与 LaTeX
- 调整 AI 对话框宽度为百分比布局
- 优化初始欢迎文案格式并添加段落间距
- 添加必要的依赖项:katex、markdown-it、markdown-it-highlightjs 等- 配置 vue-katex 插件并定义分隔符规则
- 使用 null-loader 处理部分资源加载问题
2025-10-14 11:51:03 +08:00
hz
70000e2e10 添加 Accept : EventStream 请求头 2025-10-14 09:25:05 +08:00
陈昱达
969c9f6797 feat(portal): 添加案例专区AI专家提示
- 在案例列表页头部添加AI案例专家提示文案
- 调整banner样式以支持绝对定位元素
- 设置提示文案位置和颜色以匹配设计要求
2025-09-29 16:12:27 +08:00
陈昱达
33406f6964 feat(portal): 初始化新对话时重置对话ID- 在开始新对话时将 conversationId 设置为空字符串
- 确保每次新建对话都有独立的标识符
-修复可能因对话ID重复导致的消息发送错误
2025-09-29 09:36:42 +08:00
陈昱达
e1f2e91648 feat(portal):优化AI对话窗口状态管理
- 移除硬编码的HTTP地址注释
- 修改最小化窗口点击事件处理逻辑- 移除窗口状态自动恢复的旧逻辑
- 新增restore事件机制处理窗口状态- 调整按钮图标颜色值- 完善AI Call组件的状态控制逻辑
- 保留
2025-09-29 09:33:12 +08:00
陈昱达
8c023d459f feat(portal): 添加AI Call最小化窗口控制功能
- 在app store中新增showAICallMinimized状态用于控制AI Call最小化窗口显示
- 添加对应的mutation和action用于更新最小化窗口显示状态
- 在AICall.vue组件中实现最小化窗口的条件显示逻辑
- 修改case消息组件中的链接跳转方式为路由跳转
- 更新AI聊天接口的请求地址为统一网关路径
- 在App.vue中添加路由监听,根据路由动态控制AI Call窗口显示状态- 实现case和caseDetail路由下显示AI Call最小化窗口的逻辑
2025-09-28 18:07:02 +08:00
陈昱达
47c1d29ef2 feat(app): 添加AI Call组件控制功能- 在app模块中新增showAICall状态用于控制AI Call组件显示
- 添加对应的mutation和action来修改showAICall状态
- 在App.vue中引入并条件渲染AICall组件
- 新增AICaseConsult组件用于触发AI Call显示
- 在Detail.vue页面中集成AICaseConsult组件-修复sendMessage.vue中conversationId的sessionStorage存储问题
- 调整AICall.vue组件的窗口显示逻辑
2025-09-28 17:50:13 +08:00
陈昱达
a3dab45af0 feat(portal): 实现AI对话窗口最小化功能
- 新增窗口最小化与最大化切换功能
- 调整对话框结构支持两种显示状态
-优化消息建议显示条件判断
- 修改API地址为本地开发环境地址- 更新错误提示文案提升用户体验
- 移除旧版AI入口相关逻辑和样式
- 引入新的AICaseConsult组件替换原有实现
- 修复消息列表为空时的默认展示逻辑
- 添加getLastUserMessage方法提取纯文本内容- 优化窗口控制按钮样式和交互逻辑
2025-09-28 16:39:55 +08:00
陈昱达
e3422d15ee fix(portal):修复AI案例点击事件缺失问题- 在AI案例卡片上添加@click事件处理
- 确保点击功能正常触发getAICase方法
- 移除重复的点击绑定以避免冲突
2025-09-28 15:07:22 +08:00
陈昱达
3cef730e61 feat(ai-chat): 实现案例专家功能入口权限控制及消息展示优化- 修改AI聊天接口地址为本地开发环境地址
- 新增showCaseAiEntrance接口用于控制案例专家功能入口显示
- 优化消息组件中的案例引用展示逻辑,支持展开/收起功能
- 增加案例引用的上传时间、作者机构等信息展示
- 实现打字机效果的文本逐字显示功能
- 优化AI消息响应处理逻辑,支持think标签内容解析
2025-09-28 11:28:36 +08:00
陈昱达
483b57f667 feat(ai-chat): 实现AI聊天对话与会话消息记录功能
新增AI聊天对话接口和会话消息记录查询接口,支持SSE流式响应处理。
在sendMessage组件中集成真实SE调用逻辑,替换原有模拟实现,
并完善conversationId的获取与保存机制。新增sseHelper工具类用于统一处理SSE连接和数据解析。
2025-09-24 14:26:37 +08:00
陈昱达
be411ec72d feat(portal-case): 新增AI智能问答对话功能新增消息展示组件和发送消息组件,实现机器人与用户的消息交互。
支持打字机效果展示AI回复内容,并可显示思考过程与相关案例推荐。
添加对话框背景样式及自动滚动功能,优化用户体验。
提供开启新对话和推荐问题功能,增强交互性。
2025-09-24 09:34:45 +08:00
dong.ai
d7e425ce9d 案例专家添加图片 2025-09-24 09:27:26 +08:00
dong.ai
8b68489b25 案例AI弹窗ui 2025-09-23 09:50:21 +08:00
joshen
c3f53515b9 Merge branch '202599-da' 2025-09-19 18:58:47 +08:00
087be5dd38 fix: 修复分数异常展示的问题 2025-09-19 13:43:23 +08:00
joshen
1772c972b9 Merge branch '202599-da' 2025-09-18 18:51:50 +08:00
dong.ai
91bafcb5b9 调整字体展示位置 2025-09-15 17:15:47 +08:00
dong.ai
8c533c5f3a 修改字体大小 2025-09-15 17:02:45 +08:00
dong.ai
bb17784501 修改图片大小,背景图片 2025-09-15 16:32:23 +08:00
dong.ai
69530fe6ad 标题展示隐藏判断2 2025-09-15 14:01:32 +08:00
dong.ai
b1cd8e2f63 标题展示隐藏判断 2025-09-15 13:57:19 +08:00
dong.ai
7335dd4eba 首页教师名称展示 2025-09-15 13:45:35 +08:00
3860087fac feat: 增加搜索标签展示,修复样式问题,提示语修改 2025-09-15 11:36:08 +08:00
046509f70d fix: 修复课程名称展示异常的问题 2025-09-15 10:06:35 +08:00
bf0ae91184 feat: 优化调用显示方案 2025-09-15 10:06:35 +08:00
f61742a0b9 fix: 修复教师无法展示的问题 2025-09-14 22:09:03 +08:00
dong.ai
c886a80193 教师展示 2025-09-14 21:30:12 +08:00
05ad90b025 fix: 修复标签无法正常高亮的问题 2025-09-14 21:23:56 +08:00
f3833a23fa style: 优化选择年份和边距 2025-09-14 21:16:02 +08:00
d957a8d44b fix: 修复 主页图片无法正常展示的问题 2025-09-14 21:16:01 +08:00
dong.ai
89ed79828f 修复跳转详情2 2025-09-14 21:09:57 +08:00
dong.ai
b926590edc 修改跳转详情 2025-09-14 21:04:21 +08:00
19bc757dc2 fix: 修复 systype 错误的问题 2025-09-14 20:59:38 +08:00
f3a1036b64 feat: 样式修改,功能修复 2025-09-14 20:50:42 +08:00
3c1650b751 Merge branch '2025-912-hz' into 202599-da
# Conflicts:
#	src/api/phase2/index.js
2025-09-14 20:30:44 +08:00
ff2bdb4ae5 fix: 修复获取课程精品课标记时间年份列表
# Conflicts:
#	src/api/phase2/index.js
2025-09-14 20:29:53 +08:00
dong.ai
14fb53ccd3 解决冲突 2025-09-14 20:10:29 +08:00
dong.ai
72f9661150 Merge branch '202599-da' of https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal into 202599-da 2025-09-14 19:43:10 +08:00
dong.ai
7806afc5f7 还原代码 2025-09-14 19:40:05 +08:00
e1af233c5f feat: 完成精品课程专区的内容 2025-09-14 19:39:41 +08:00
2738493030 fix: 修复刷新页面自动调整错误的问题 2025-09-14 19:39:08 +08:00
480a4f5564 chore: 修正路由名称拼写错误 2025-09-14 19:39:06 +08:00
dong.ai
205bf5469f 字段展示 2025-09-14 19:08:21 +08:00
dong.ai
30897a1fa5 修改展示 2025-09-14 19:02:38 +08:00
dong.ai
1c59cffd3e 标题展示 2025-09-14 18:54:18 +08:00
dong.ai
6e9f93d6c9 字段处理 2025-09-14 18:48:47 +08:00
dong.ai
3ee4a2fd6e 调整首页字段展示 2025-09-14 18:11:33 +08:00
dong.ai
1b442ef040 首页跳转详情 2025-09-14 17:50:31 +08:00
dong.ai
4693cb0db1 收藏图标展示 2025-09-14 17:43:35 +08:00
dong.ai
92fecbec80 收藏图表展示 2025-09-14 17:36:13 +08:00
dong.ai
be63f5a1aa 收藏图片展示 2025-09-14 17:28:04 +08:00
dong.ai
c9899eda6b 修改收藏 2025-09-14 17:20:15 +08:00
dong.ai
0f52a69beb 收藏展示 2025-09-14 17:07:30 +08:00
dong.ai
3410afedcf 收藏发消息接口字段 2025-09-14 16:45:13 +08:00
dong.ai
33866c0f49 修改收藏 2025-09-14 16:28:36 +08:00
dong.ai
c9e51fc21f 完善收藏 2025-09-14 16:03:49 +08:00
dong.ai
a42668c929 调整背景样式 2025-09-14 15:29:45 +08:00
dong.ai
01d4bc0536 调整背景图样式 2025-09-14 14:52:19 +08:00
dong.ai
d52e8b389b 修改首页展示样式 2025-09-14 13:55:28 +08:00
dong.ai
e9a86d0364 修改入参格式 2025-09-14 12:25:56 +08:00
dong.ai
0e43ca5e82 修改请求头类型 2025-09-14 12:01:08 +08:00
dong.ai
0771460f60 首页精品课部分 2025-09-14 11:45:27 +08:00
dong.ai
1a2829d70a 精品课列表接口 2025-09-13 19:58:11 +08:00
dong.ai
68eda7efcc 首页添加精品课模块 2025-09-13 15:39:35 +08:00
0b0789feda feat: 右侧菜单高度调整为 650 2025-09-09 17:51:26 +08:00
70bb87a17a feat: 溢出控制高度的代码, 防止内容塌陷 2025-09-09 17:51:25 +08:00
4e60811542 feat: 溢出控制高度的代码, 防止内容塌陷 2025-09-09 17:51:24 +08:00
670788339
075fdb1913 认证讲师库删除年份后缀 2025-09-09 17:39:58 +08:00
670788339
8c7569ae4e banner跳转 2025-09-04 16:46:09 +08:00
670788339
56f565cbf3 banner跳转暂时注释 2025-09-02 08:46:33 +08:00
670788339
c15f52e325 banner跳转 2025-09-01 15:37:12 +08:00
670788339
98c10e703e 教师专区banner点击跳转 2025-09-01 13:21:12 +08:00
670788339
2078c128c9 教师专区banner点击跳转 2025-09-01 13:12:22 +08:00
joshen
e8fe7b4fd3 fix: 修复火狐报错的弹窗提醒 2025-08-14 14:03:53 +08:00
joshen
029d5b0791 修复1181案例不显示案主信息 2025-08-05 15:26:27 +08:00
joshen
12a6ed8fea Merge remote-tracking branch '104/master-0626' into master-0626 2025-08-05 14:30:02 +08:00
joshen
363492866f 修复1181案例不显示案主信息 2025-08-05 14:29:24 +08:00
joshen
d237dc99ee 修改弹框可见 2025-07-25 21:01:58 +08:00
joshen
995933ae56 Merge remote-tracking branch '104-git/master-0626' into master-0626 2025-07-25 20:53:15 +08:00
joshen
dff81df91d 修改弹框可见 2025-07-25 20:52:43 +08:00
670788339
0864704c4c 仅内网可见-管理员端 调试 2025-07-23 17:53:43 +08:00
670788339
58fc6264fe 仅内网可见-管理员端 调试 2025-07-23 17:45:30 +08:00
670788339
5276813eba 仅内网可见-管理员端 调试 2025-07-23 17:28:36 +08:00
670788339
3485435c9e 仅内网可见-管理员端 调试 2025-07-23 17:24:51 +08:00
670788339
2ee3daedf6 仅内网可见-管理员端 调试 2025-07-23 17:10:19 +08:00
joshen
5d0d64abbf 修改背景颜色 2025-07-23 16:49:08 +08:00
joshen
ea97aee4af 修复评分会点错 2025-07-23 15:07:30 +08:00
joshen
a968062936 修复视频进度条拖动 2025-07-23 14:40:06 +08:00
joshen
6d11475456 修改边界颜色 2025-07-23 13:45:38 +08:00
joshen
a7396e0a6a 修改边界颜色 2025-07-23 13:37:42 +08:00
670788339
ac236e8d7c 仅内网可见-管理员端 调试 2025-07-21 21:17:18 +08:00
670788339
f537608e4f 仅内网可见-管理员端 调试 2025-07-21 21:10:41 +08:00
670788339
c7f4a224ff 仅内网可见-管理员端 调试 2025-07-21 20:59:06 +08:00
670788339
a4d088a3ae 仅内网可见-管理员端 调试 2025-07-21 20:44:20 +08:00
670788339
3cd2c5f433 仅内网可见-管理员端 调试 2025-07-21 20:33:47 +08:00
670788339
8c69fac9be 仅内网可见-管理员端 调试 2025-07-21 20:24:39 +08:00
670788339
4033eb2294 仅内网可见-管理员端 调试 2025-07-21 20:18:33 +08:00
670788339
ec469db72a 仅内网可见-管理员端 调试 2025-07-21 20:15:20 +08:00
670788339
5e1ea2469b 仅内网可见-管理员端 调试 2025-07-21 20:11:41 +08:00
670788339
2baa5c61a4 仅内网可见-管理员端 调试 2025-07-21 20:10:25 +08:00
670788339
b627398b7d 仅内网可见-管理员端 调试 2025-07-21 19:57:51 +08:00
670788339
7be5c072d9 仅内网可见-管理员端 调试 2025-07-21 19:37:32 +08:00
670788339
de14f9f561 仅内网可见-管理员端 调试 2025-07-21 19:26:19 +08:00
joshen
44f79c93a5 修改前端 2025-07-21 19:24:24 +08:00
joshen
e501b8d23f 修改前端 2025-07-21 19:16:49 +08:00
joshen
ea8143db2b 修改前端 2025-07-21 18:57:40 +08:00
joshen
89277f8868 修改前端 2025-07-21 18:35:09 +08:00
joshen
13af8ba4e0 前端参数调整 2025-07-21 15:21:19 +08:00
joshen
e3735e4b92 前端参数调整 2025-07-21 15:09:39 +08:00
joshen
f4c9b921bb 前端参数调整 2025-07-21 15:02:46 +08:00
joshen
ed899cdd2c Merge remote-tracking branch 'aliyun/master-0720-lyc' into master-0720-lyc 2025-07-21 15:00:08 +08:00
王卓煜
c24b54957a 内网环境判断 2025-07-21 14:57:51 +08:00
joshen
1dc9c941d7 Merge remote-tracking branch 'aliyun/master-0720-lyc' into master-0720-lyc 2025-07-21 14:45:56 +08:00
王卓煜
33c9d2140f 内网环境判断 2025-07-21 14:42:42 +08:00
王卓煜
afd1bec458 内网环境判断 2025-07-21 14:33:05 +08:00
joshen
202bf7b123 test2 2025-07-21 11:19:59 +08:00
joshen
ce1d662350 Merge remote-tracking branch 'aliyun/master-0720-lyc' into master-0720-lyc 2025-07-21 11:01:12 +08:00
王卓煜
81602506c7 内网环境判断 2025-07-21 11:00:33 +08:00
joshen
053a2a60b2 Merge remote-tracking branch 'aliyun/master-0720-lyc' into master-0720-lyc 2025-07-21 10:47:32 +08:00
王卓煜
b9f23eb657 内网环境判断 2025-07-21 10:46:30 +08:00
joshen
ceeb3efcf5 Merge remote-tracking branch 'aliyun/master-0720-lyc' into master-0720-lyc 2025-07-21 10:43:23 +08:00
王卓煜
147366f738 内网环境判断 2025-07-21 10:33:44 +08:00
joshen
4ac09a8793 test2 2025-07-20 18:27:45 +08:00
joshen
9c768337c6 test2 2025-07-20 18:23:55 +08:00
joshen
e202946fe7 test2 2025-07-20 18:16:54 +08:00
joshen
fbddf6806a test 2025-07-20 18:11:37 +08:00
670788339
3cfa3ffec3 仅内网可见-管理员端 调试 2025-07-20 17:47:53 +08:00
670788339
a8bcd3832b 仅内网可见-管理员端 调试 2025-07-20 16:56:47 +08:00
670788339
d9f69001a5 仅内网可见-管理员端 调试 2025-07-20 16:38:42 +08:00
670788339
93e769be42 仅内网可见-管理员端 调试 2025-07-20 16:15:02 +08:00
670788339
206f0e825d 仅内网可见-管理员端 2025-07-20 15:39:37 +08:00
670788339
3bb4b519f1 仅内网可见-管理员端 2025-07-20 14:16:02 +08:00
joshen
fe790389ca 日志打印 2025-07-17 11:24:07 +08:00
joshen
44a5baec18 日志打印 2025-07-17 10:14:42 +08:00
670788339
ee8a76c4df 作业提交按钮判断 2025-07-09 13:48:31 +08:00
670788339
12c1bdb1a8 查询考试限制加大 2025-06-27 16:53:34 +08:00
670788339
df0e1ad0ed Merge branch 'csg-250625-lyc' into master-0626 2025-06-26 15:47:36 +08:00
670788339
ea54ea2c20 热点-替换图片 2025-06-25 16:42:45 +08:00
670788339
5fe9d2eb96 热点-替换图片 2025-06-25 15:24:34 +08:00
chensg
b6562e5c9c 修改25年热点论坛组件名称 2025-06-16 14:07:25 +08:00
chensg
a0dcd27f8c 修改2025热点论坛 2025-06-16 13:37:22 +08:00
chensg
8bc2bc96a8 添加2025热点论坛 2025-06-16 11:25:28 +08:00
670788339
ab2ac961f5 替换图片 2025-06-13 09:17:26 +08:00
670788339
6727dd838b 替换图片 2025-06-12 09:36:58 +08:00
joshen
28aa639e1c Merge remote-tracking branch 'yx/master-20250428-shl' 2025-05-13 15:42:12 +08:00
joshen
05d48f5313 Merge remote-tracking branch 'yx/250415-bugfix-shl' 2025-05-13 15:41:28 +08:00
sunhonglai
7a67aca620 修改教师端我的授课记录查询和导出 2025-04-29 16:33:42 +08:00
sunhonglai
a810da8978 修改教师端我的授课记录查询和导出 2025-04-29 16:29:36 +08:00
sunhonglai
b4538150df 修改教师端我的授课记录查询和导出 2025-04-29 16:20:57 +08:00
sunhonglai
e79abe7aee 修改教师端我的授课记录查询和导出 2025-04-29 16:07:35 +08:00
sunhonglai
65dfa4f0e3 修改教师端我的授课记录查询和导出 2025-04-29 16:01:30 +08:00
sunhonglai
4784b02ca4 修改教师端我的授课记录查询和导出 2025-04-29 15:44:55 +08:00
sunhonglai
45e006a4e7 修改教师端我的授课记录查询和导出 2025-04-29 15:34:09 +08:00
sunhonglai
577357ccd0 修改教师端我的授课记录查询和导出 2025-04-29 15:03:58 +08:00
sunhonglai
f92954aa6b 修改教师端我的授课记录查询和导出 2025-04-29 14:53:51 +08:00
sunhonglai
d59b5a9e06 修改教师端我的授课记录查询和导出 2025-04-29 14:53:24 +08:00
sunhonglai
4be009a6d8 修改教师端我的授课记录查询和导出 2025-04-29 14:13:34 +08:00
sunhonglai
b5f01c6357 修改教师端我的授课记录查询和导出 2025-04-29 13:55:05 +08:00
sunhonglai
78cbb21155 修改教师端我的授课记录查询和导出 2025-04-29 13:47:56 +08:00
sunhonglai
adc33e5892 修改教师端我的授课记录查询和导出 2025-04-29 13:36:24 +08:00
sunhonglai
a9de4ad77c 修改教师端我的授课记录查询和导出 2025-04-29 11:49:39 +08:00
sunhonglai
a3c85cbd19 修改教师端我的授课记录查询和导出 2025-04-29 11:46:40 +08:00
sunhonglai
9a6727e52f 修改教师端我的授课记录查询和导出 2025-04-29 11:40:29 +08:00
sunhonglai
7cee4b5ad1 修改教师端我的授课记录查询和导出 2025-04-29 11:33:51 +08:00
sunhonglai
c3b5f1baf0 修改教师端我的授课记录查询和导出 2025-04-29 11:23:45 +08:00
sunhonglai
213990a7b0 每次加载pdf前,清空路径 2025-04-23 14:16:21 +08:00
sunhonglai
505e3ce00c 修改进度条文案 2025-04-21 14:37:53 +08:00
sunhonglai
ff908554fd 修改上报时长接口 2025-04-21 11:10:21 +08:00
sunhonglai
e031dcd935 修改上报时长接口 2025-04-18 09:42:06 +08:00
sunhonglai
2139e15813 修改上报时长接口 2025-04-18 09:41:46 +08:00
sunhonglai
8497deea52 增加日志打印 2025-04-18 09:27:01 +08:00
sunhonglai
8746904cb9 学习时长上报接口合并 2025-04-18 08:51:48 +08:00
sunhonglai
213e5abce3 去掉无用注释影响后绪修改逻辑 2025-04-16 14:01:43 +08:00
sunhonglai
dc7f942303 先修改对应的后端接口 2025-04-16 10:19:56 +08:00
sunhonglai
4c1c370879 清除无用注释,影响排查要搜索修改的代码 2025-04-16 08:51:10 +08:00
sunhonglai
ffbc4ef808 增加乐享强制走sso 2025-04-15 16:20:23 +08:00
sunhonglai
5936183912 增加乐享转向 2025-04-15 09:12:11 +08:00
sunhonglai
702881c100 修改排行榜展示数据不对问题 2025-04-09 14:28:42 +08:00
670788339
b0229f6cde 测试 2025-04-03 16:36:38 +08:00
670788339
9c6c1708fc 测试 2025-04-03 16:33:42 +08:00
670788339
b67d7f4311 测试 2025-04-03 16:24:13 +08:00
670788339
c5e54f0170 测试 2025-04-03 16:17:04 +08:00
670788339
5c5a71ec55 测试 2025-04-03 15:21:51 +08:00
joshen
b6a3a32412 Merge remote-tracking branch 'aliyun/master_20250319-lyc' 2025-04-02 16:59:49 +08:00
sunhonglai
d6fd44830b 去掉无用日志 2025-04-01 08:30:37 +08:00
sunhonglai
d01fd6f07b 修改视频播放拖拽问题 2025-03-31 19:13:55 +08:00
670788339
f9e332ab7c SZX-1045 2025-03-28 11:47:43 +08:00
sunhonglai
0989360183 Merge remote-tracking branch 'origin/250321-bugfix-shl' into 250321-bugfix-shl
# Conflicts:
#	src/views/Login.vue
2025-03-24 08:31:59 +08:00
sunhonglai
5e31f7d1a8 注掉废弃的登录页面 2025-03-24 08:31:27 +08:00
sunhonglai
eaa7f04cd6 删除废弃的登录页面 2025-03-21 16:52:47 +08:00
sunhonglai
0d7a2ad353 修改响应结果空指针问题 2025-03-21 15:48:40 +08:00
670788339
9d9d663591 课程详情显示供应商 2025-03-19 13:31:50 +08:00
joshen
90b53a54b2 Merge branch '250220-pre-126-177-gx' into master_1202 2025-03-13 17:38:58 +08:00
gengxin
b89a8266e5 考试完成判断3 2025-03-13 17:37:33 +08:00
joshen
49137387f6 Merge branch '250220-pre-126-177-gx' into master_1202 2025-03-13 17:16:32 +08:00
gengxin
812c09d65c 考试完成判断 2025-03-13 16:38:33 +08:00
gengxin
57a87db016 考试状态修改 2025-03-13 16:32:02 +08:00
gengxin
2536fca503 考试及格线 2025-03-13 16:22:07 +08:00
joshen
047b1f9bae Merge branch '250220-pre-126-177-gx' into master_1202 2025-03-12 17:33:00 +08:00
gengxin
7162d329ee 考试及格线 2025-03-12 17:00:25 +08:00
gengxin
d7d80e8244 考试及格线获取 2025-03-12 16:27:44 +08:00
joshen
af208d0a6d 部署生产1 2025-03-11 17:15:07 +08:00
joshen
713f258c14 Merge branch '250220-pre-126-177-gx' into master_1202
# Conflicts:
#	src/views/portal/case/Index.vue
#	src/views/study/Courses.vue
2025-03-11 17:12:30 +08:00
gengxin
def3892edc 退出全屏 ,需要水印12 2025-03-11 11:51:51 +08:00
gengxin
51a8da4bdb 退出全屏 ,需要水印11 2025-03-11 11:45:34 +08:00
gengxin
6536e36857 退出全屏 ,需要水印10 2025-03-11 11:29:14 +08:00
gengxin
1bf9398177 退出全屏 ,需要水印9 2025-03-11 11:26:11 +08:00
gengxin
a5333952c9 退出全屏 ,需要水印8 2025-03-11 11:15:18 +08:00
gengxin
127f191776 退出全屏 ,需要水印7 2025-03-11 11:09:19 +08:00
gengxin
e9b9a14b69 退出全屏 ,需要水印6 2025-03-11 11:04:53 +08:00
gengxin
11a1065df9 退出全屏 ,需要水印5 2025-03-11 11:00:45 +08:00
gengxin
001452fa2e 退出全屏 ,需要水印4 2025-03-11 10:52:46 +08:00
gengxin
988a7a1e17 退出全屏 ,需要水印3 2025-03-11 10:43:00 +08:00
gengxin
7fed1a3991 退出全屏 ,需要水印3 2025-03-11 10:41:44 +08:00
gengxin
5448a9724d 退出全屏 ,需要水印2 2025-03-11 10:24:53 +08:00
gengxin
23928a0865 退出全屏 ,需要水印1 2025-03-11 10:01:54 +08:00
gengxin
797c08d8fc 退出全屏 ,需要水印 2025-03-10 14:54:46 +08:00
gengxin
a8d44d0169 播放加上水印 2025-03-10 14:46:03 +08:00
joshen
567bed812b 退出全屏 esc 触发监听4 2025-03-10 14:21:58 +08:00
joshen
2e0fe475f2 退出全屏 esc 触发监听3 2025-03-10 14:17:17 +08:00
joshen
04ecf6a971 退出全屏 esc 触发监听2 2025-03-10 14:08:08 +08:00
joshen
5381416a05 退出全屏 esc 触发监听 2025-03-10 11:46:02 +08:00
joshen
216141dbd3 退出全屏 esc 触发监听 2025-03-10 11:30:53 +08:00
joshen
1dad88c59b 退出全屏 2025-03-10 11:18:16 +08:00
joshen
0f3c53cb31 点击 exit 退出 执行效果 2025-03-10 11:11:58 +08:00
joshen
1f4416e9b1 水印的生效 2025-03-10 10:39:01 +08:00
joshen
2ffe9c1872 去除水印 2025-03-08 11:06:12 +08:00
gengxin
e74f14baa0 弹出框处理 2 2025-03-05 08:03:00 +08:00
gengxin
aa0fcf6ff0 弹出框处理 2025-03-05 07:55:39 +08:00
gengxin
9fba452d2d 弹出框处理 2025-03-05 07:46:22 +08:00
joshen
d33e27b3d0 Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into master_1202 2025-02-27 21:07:42 +08:00
Pengxiansen
3d3f2ebc6e 提交 2025-02-27 21:02:06 +08:00
joshen
b679502553 Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into master_1202 2025-02-27 14:21:43 +08:00
Pengxiansen
d027a4c56a 提交 2025-02-27 13:45:29 +08:00
Pengxiansen
c6b2c46311 提交 2025-02-27 09:20:42 +08:00
joshen
181892de67 Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into master_1202 2025-02-27 00:59:20 +08:00
Pengxiansen
a2809f5347 提交 2025-02-27 00:51:33 +08:00
joshen
609902aeb8 Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-26 21:45:32 +08:00
Pengxiansen
6399aa3352 提交 2025-02-26 21:42:14 +08:00
joshen
23ffd002a7 Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-26 21:08:27 +08:00
Pengxiansen
96e0c0fb70 提交 2025-02-26 20:57:48 +08:00
Pengxiansen
6382f6b04d 提交 2025-02-26 19:49:51 +08:00
joshen
4d783ce94d Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-26 18:21:49 +08:00
Pengxiansen
b686a9e2a2 提交 2025-02-26 18:16:03 +08:00
joshen
b52ad264da Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-26 13:06:13 +08:00
Pengxiansen
511b3179f6 提交 2025-02-26 13:04:45 +08:00
joshen
cc48f52fea Merge remote-tracking branch 'yx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-26 10:49:42 +08:00
Pengxiansen
5f230a6543 提交 2025-02-26 10:34:28 +08:00
joshen
a78f6a0152 Merge remote-tracking branch 'zshyx/250207-growth-prod-master_1202-zp' into test20250220 2025-02-20 19:17:59 +08:00
Pengxiansen
7c20a11687 提交 2025-02-20 19:15:55 +08:00
gengxin
5b5d73c905 云效地址更爱 2025-02-20 13:42:44 +08:00
Pengxiansen
09f06b1679 提交 2025-02-19 16:11:07 +08:00
Pengxiansen
c0562c41c0 提交 2025-02-19 15:45:58 +08:00
Pengxiansen
24a34fa779 提交 2025-02-19 14:06:25 +08:00
Pengxiansen
987f69dc64 提交 2025-02-17 16:37:06 +08:00
Pengxiansen
15e15ae5d2 提交 2025-02-16 23:22:01 +08:00
Pengxiansen
5126636193 提交 2025-02-16 23:02:58 +08:00
liu
4dd02dcbc6 修挂样式 2025-02-16 20:09:10 +08:00
Pengxiansen
2456cf72ed 提交 2025-02-16 17:35:05 +08:00
Pengxiansen
315b01999d 提交 2025-02-14 14:41:48 +08:00
Pengxiansen
2b45e63c54 提交 2025-02-13 17:41:27 +08:00
Pengxiansen
1927b5b67f 提交 2025-02-13 17:36:21 +08:00
zhangsir
00d272c3d0 数据导致的报错 2025-01-10 14:29:54 +08:00
zhangsir
398bbc675b 数据导致的报错 2025-01-10 09:49:25 +08:00
zhangsir
d0a6807d2c 数据导致的报错 2025-01-09 17:30:40 +08:00
joshen
f01283f530 10.251.132.75 2025-01-09 11:49:44 +08:00
joshen
b9ec8ed534 Merge branch 'master_1202' into master-video-0930
# Conflicts:
#	src/components/VideoPlayer/index.vue
#	src/components/VideoPlayer/progress-bar.vue
#	src/views/study/coursenew.vue
2025-01-08 18:38:07 +08:00
joshen
6b6e8a4096 pc视频播放器禁止拖动 2024-10-10 23:07:46 +08:00
78 changed files with 13806 additions and 2589 deletions

View File

@@ -41,6 +41,7 @@ VUE_APP_PUBLIC_PATH='/pc'
VUE_APP_MANAGER_PATH='/manage'
VUE_APP_STUDENT_PATH='/fe-student'
VUE_APP_MANAGER_API_PATH='/manageApi'
VUE_APP_GROWTH_API_PATH=''
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -37,6 +37,7 @@ VUE_APP_PUBLIC_PATH='/pc-release'
VUE_APP_MANAGER_PATH='/manage-release'
VUE_APP_STUDENT_PATH='/fe-student-release'
VUE_APP_MANAGER_API_PATH='/manageApi-release'
VUE_APP_GROWTH_API_PATH='/growth'
# 登录地址
VUE_APP_LOGIN_URL='https://u.boe.com/web-release/'

View File

@@ -37,6 +37,7 @@ VUE_APP_PUBLIC_PATH='/pc'
VUE_APP_MANAGER_PATH='/manage'
VUE_APP_STUDENT_PATH='/fe-student'
VUE_APP_MANAGER_API_PATH='/manageApi'
VUE_APP_GROWTH_API_PATH='/growth'
# 登录地址
VUE_APP_LOGIN_URL='https://u.boe.com/web/'

View File

@@ -36,6 +36,7 @@ VUE_APP_PUBLIC_PATH='/pc'
VUE_APP_MANAGER_PATH='/manage'
VUE_APP_STUDENT_PATH='/fe-student'
VUE_APP_MANAGER_API_PATH='/manageApi'
VUE_APP_GROWTH_API_PATH='/growth'
# 登录地址
VUE_APP_LOGIN_URL='https://u-pre.boe.com/web/'

5677
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@mermaid-js/parser": "^0.6.3",
"axios": "^0.21.4",
"core-js": "^3.6.5",
"driver.js": "^0.9.8",
@@ -23,9 +24,15 @@
"element-ui": "^2.15.7",
"file-saver": "^2.0.5",
"fuse.js": "^6.4.6",
"highlight.js": "^11.11.1",
"image-conversion": "^2.1.1",
"jsencrypt": "^3.2.1",
"json-bigint": "^1.0.0",
"katex": "^0.16.25",
"markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.2.0",
"markdown-it-mermaid": "^0.2.5",
"mermaid": "^8.13.10",
"mockjs": "^1.1.0",
"moment": "^2.29.1",
"nprogress": "^0.2.0",
@@ -43,6 +50,7 @@
"vue": "^2.6.11",
"vue-awesome-swiper": "^3.1.3",
"vue-cookies": "^1.7.4",
"vue-katex": "^0.5.0",
"vue-pdf": "^4.2.0",
"vue-quill-editor": "^3.0.6",
"vue-router": "^3.5.2",
@@ -60,6 +68,7 @@
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.1",
"less-loader": "^6.2.0",
"null-loader": "^4.0.1",
"sass": "^1.32.13",
"sass-loader": "^10.1.0",
"vue-template-compiler": "^2.6.11"

BIN
public/images/case-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
public/images/qualityBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,25 +1,74 @@
<template>
<div id="app">
<div id="app" style="width: 100vw">
<keep-alive :include="['case']">
<router-view />
12312
</keep-alive>
<!-- 添加AI Call组件 -->
<AICall
:dialogVisible="showAICall"
@close="onCloseAICall"
@restore="onRestoreAICall"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { mapGetters, mapState } from 'vuex';
import AICall from '@/views/portal/case/AICall.vue';
export default{
name: 'App',
computed: {
...mapGetters(['userInfo'])
components: {
AICall
},
mounted() {
computed: {
...mapGetters(['userInfo']),
...mapState('app', ['showAICall', 'showAICallMinimized'])
},
methods: {
onCloseAICall() {
// 通过Vuex关闭AI Call组件
this.$store.dispatch('app/setShowAICall', false);
},
onRestoreAICall() {
// 通过Vuex显示AI Call组件
this.$store.dispatch('app/setShowAICall', true);
},
// 检查当前路由是否应该显示AI弹窗
checkRouteForAICall() {
const currentRoute = this.$route.name;
// 只在case或caseDetail路由显示弹窗
if (currentRoute === 'case' || currentRoute === 'caseDetail') {
// 设置最小化窗口显示状态为true
this.$store.dispatch('app/setShowAICallMinimized', true);
// 注意这里不再强制设置showAICall为true保留用户之前的操作状态
} else {
// 其他路由关闭弹窗
this.$store.dispatch('app/setShowAICall', false);
// 设置最小化窗口显示状态为false
this.$store.dispatch('app/setShowAICallMinimized', false);
}
}
},
mounted() {
//从状态值中取,因为登录处理所以移动watch中
// console.log(this.userInfo);
// if(this.userInfo && this.userInfo.name!=''){
// this.$watermark.set(this.userInfo.name+this.userInfo.loginName);
// }
// 初始化检查路由
this.checkRouteForAICall();
},
watch: {
// 监听路由变化
$route(to, from) {
this.checkRouteForAICall();
}
}
// watch:{
// userInfo(newVal,oldVal){
// if(newVal && newVal.name!=''){
@@ -39,4 +88,3 @@
box-shadow: 0px 1px 5px 1px rgba(92,98,111,.3);
}
</style>

30
src/api/boe/aiChat.js Normal file
View File

@@ -0,0 +1,30 @@
import ajax from '@/utils/xajax.js'
/**
* AI聊天对话接口
* @param {Object} data - 请求参数
* @param {string} data.conversationId - 会话ID如果为空则创建新会话
* @param {string} data.query - 用户提问内容
* @returns {Promise} - 返回SSE流
*/
export function aiChat(data) {
return ajax.postJson('http://192.168.3.178/xboe/m/boe/case/ai/chat', data)
}
/**
* 查询会话消息记录接口
* @param {string} conversationId - 会话ID
* @returns {Promise} - 返回会话历史记录
*/
export function getChatMessages(conversationId) {
return ajax.get('/xboe/m/boe/case/ai/messages?conversationId=' + conversationId)
}
/**
* 案例专家功能入口显示权限判断接口
* 判断当前登录用户是否显示"案例专家"功能入口
* @returns {Promise} - 返回是否显示功能入口的布尔值
*/
export function showCaseAiEntrance() {
return ajax.get('/xboe/m/boe/case/ai/show-entrance')
}

View File

@@ -62,15 +62,62 @@ const courseRecordList = function(data) {
return ajax.postJson('/b1/system/teacher/teacher-course-list',data);
}
const getListByToken = function(data) {
return ajax2.get('/manageApi',`/admin/teacherRecord/getListByToken?courseName=${data.courseName}&page=${data.page}&pageSize=${data.pageSize}`);
return ajax2.get('/manageApi',`/admin/teacherRecord/getListByToken?courseName=${data.courseName}&pageNo=${data.pageNo}&pageSize=${data.pageSize}`);
}
/**
* 导出授课记录
*
* @param {Object} data
*/
const courseRecordExport2 = function(data) {
return axios.request({
baseURL: "/manageApi",
url: '/admin/export/exportInTeacherRecord',
method: 'get',
data:data,
headers:{'Content-Type':'application/json;charset=utf-8'},
responseType: 'blob'
})
}
/**
* 导出课程下的学员信息
*
*/
const exportStudentOfCourse2 = function(courseId,courseType) {
return axios.request({
baseURL: "/manageApi",
url: '/admin/export/exportTeacherRecordStudentInfo?courseId='+courseId+'&courseType='+courseType,
method: 'get'
// headers:{'Content-Type':'application/json;charset=utf-8'},
// responseType: 'blob'
})
}
/**
* 导出教师下的所有授课记录下的学员信息
*
*/
const exportStudentOfTearcher2 = function(userId) {
return axios.request({
baseURL: "/manageApi",
url: '/b1/system/teacher/teacher-course-student-export?userId='+userId,
method: 'post',
headers:{'Content-Type':'application/json;charset=utf-8'},
responseType: 'blob'
})
}
export default {
courseRecordExport,
courseRecordExport2,
courseRecordList,
getListByToken,
exportStudentOfCourse,
exportStudentOfTearcher
exportStudentOfCourse2,
exportStudentOfTearcher,
exportStudentOfTearcher2,
}

View File

@@ -0,0 +1,8 @@
// import ajax from '@/utils/xajax.js'
import ajax from '../ajax';
// const baseURL = process.env.VUE_APP_GROWTH_API_PATH;
const baseURL = '/growth';
// 查询专业力必修列表
export const listData = (obj) => ajax.get(baseURL, '/professional/student/studentGrowthList', { params: obj })
// 查询专业力统计
export const getTotalGrowthTaskDetail = (obj) => ajax.get(baseURL, '/professional/student/getTotalGrowthTaskDetail', { params: obj })

208
src/api/httpAjax.js Normal file
View File

@@ -0,0 +1,208 @@
import axios from 'axios'
import qs from 'qs'
import {Notification, MessageBox, Message} from 'element-ui'
import store from '@/store'
import {getToken} from '@/utils/token'
import errorCode from '@/utils/errorCode'
/**
*request请求 axios.request(config)
*requestJson请求 axios.request(config)
*get请求 axios.get(url[, config])
*post请求 axios.post(url[, data[, config]])
*postJson请求 axios.post(url[, data[, config]])
*put请求 axios.put(url[, data[, config]])
*putJson请求 axios.put(url[, data[, config]])
*patch请求 axios.patch(url[, data[, config]])
*patchJson请求 axios.patch(url[, data[, config]])
*delete请求 axios.delete(url[, config])
*/
// const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
const TokenName = 'token';
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
//只是用于发送json对象数据时使用post,put,patch
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
//只是用于发送json对象数据时使用post,put,patch
//用于普通的发送请求
const formRequest = axios.create({
//headers:{'Content-Type':'application/x-www-form-urlencoded'},
// axios中请求配置有baseURL选项表示请求URL公共部分
// baseURL: process.env.VUE_APP_CESOURCE_BASE_API,
//超时
timeout: 10000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
let curToken = getToken();
//curToken='eyJ0eXBlIjoidG9rZW4iLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC91LmJvZS5jb20iLCJpYXQiOjE2NzIzMTE2MTIsImV4cCI6MTY3MjMxODgxMiwiR2l2ZW5OYW1lIjoiYm9ldSIsInVzZXJJZCI6IjZCMDQ5RkFGLUMzMTQtN0NDRi0wRDI4LTBEMjNGNEM0MjUzMSIsInVJZCI6Ijk2NTM0MjAyNzQ5NzYwNzE2OCIsInBlcm1pc3Npb24iOiIifQ==.a4f41376e994c5fcd3ab537ce17572ef4c633863f87785cf7b6ffa353e2ed51c';
if (curToken && !isToken) {
config.headers[TokenName] = curToken // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
//console.log(res);
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
// store.dispatch('LogOut').then(() => {
// location.href = this.webBaseUrl + ReLoginUrl;
// })
console.error('', res.data);
return Promise.reject(new Error('接口返回未登录'))
} else if (code === 403) {
var msg = '当前操作没有权限';
Message({message: msg, type: 'error'});
return Promise.reject(new Error(msg))
} else {
//Message({message: res.data.message, type: 'error'});
//console.log('err' + res.data.error);
return res.data
}
}
},
error => {
console.log('err', error)
let {message} = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
} else if (message.includes("timeout")) {
message = "网络异常或接口错误,请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
/**
* request请求,可以自定义参数
*/
const request = formRequest.request;
/**
* get请求 ,只有url
*/
const get = function (baseURL, url) {
return request({
baseURL,
url: url,
method: 'get',
headers: {'Content-Type': 'application/json'}
})
}
/**
* post请求
* @param {Object} url
* @param {Object} postData
*/
const post = function (baseURL, url, postData) {
// if (postData) {
// postData = qs.stringify(postData);
// }
return request({
baseURL,
url: url,
method: 'post',
data: postData,
headers: {'Content-Type': 'application/json'}
})
}
//post请求
const postForm = function (baseURL, url, data) {
return request({
baseURL,
url,
data,
method: 'post',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
}
// const postJson=jsonRequest.post;
const postJson = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
method: 'post',
data: postData,
headers: {'Content-Type': 'application/json;charset=utf-8'},
})
}
// 导出文件请求定义
const postJsonToFile = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
method: 'post',
data: postData,
headers: {'Content-Type': 'application/json;charset=utf-8'},
responseType: 'blob'
})
}
const getJsonToFile = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
method: 'get',
data: postData,
headers: {'Content-Type': 'application/json;charset=utf-8'},
responseType: 'blob'
})
}
/**
* put请求
*/
const put = function (baseURL, url, data) {
if (data) {
data = qs.stringify(data);
}
return request({
baseURL,
url: url,
method: 'put',
data: data,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
}
const putJson = function (baseURL, url, data) {
return request({
baseURL,
url: url,
method: 'put',
data: data,
headers: {'Content-Type': 'application/json;charset=utf-8'},
})
}
export default {
tokenName: TokenName,
request,
get,
post,
postJson,
postJsonToFile,
put,
putJson,
getJsonToFile
}

View File

@@ -1,7 +1,7 @@
/**
* 课程的操作,课程的添加,修改,列表查询,课程的审核发布等操作。
* 针对于管理员,教师的功能
*
*
**/
import ajax from '@/utils/xajax.js'
@@ -170,7 +170,9 @@ const updateContentOrders = function(cid,items) {
const detail = function(id) {
return ajax.get('/xboe/m/course/manage/detail?id=' + id);
}
const getDictIds = function(pid,type) {
return ajax.get(`/xboe/m/course/manage/getDictIds?pid=${pid}&type=${type}`);
}
/**
* 更新内容的名称
* @param {Object} data
@@ -274,7 +276,7 @@ const countWaitAudit = function() {
}
/**
* [已用courseAudit中的hrbpAuditList替换]
* [已用courseAudit中的hrbpAuditList替换]
* 当前用户需要审核的课程列表
* @param {Object} query 同pageList
*/
@@ -283,9 +285,9 @@ const auditList = function(query) {
}
/**
* 【已移到courseAudit中】
* 教师需要审核的课程列表
/**
* 【已移到courseAudit中】
* 教师需要审核的课程列表
*/
const teacherAuditList = function(query) {
return ajax.post('/xboe/m/course/audit/teacher-course', query);
@@ -446,6 +448,7 @@ export default {
findUpdateLogs,
getUpdateLog,
detail,
getDictIds,
saveContent,
pageList,
setEnabled,

View File

@@ -114,6 +114,10 @@ const studyVideoTime = function(data) {
return ajax.post('/xboe/school/study/course/study-video-time',data);
}
const updateStudyVideoTime = function(data) {
return ajax.post('/xboe/school/study/course/updateStudyVideoTime',data);
}
/**
* 获取上一次的学习内容
* @returns
@@ -408,6 +412,7 @@ export default {
studyIndex,
studyInfo,
studyVideoTime,
updateStudyVideoTime,
lastStudy,
studyContent,
appendStudyTime,

View File

@@ -38,7 +38,7 @@ const detailTeacher=function (id){
* tlevelId级别
* salaryId发薪地
* tsystemId
* } query
* @returns
*/

View File

@@ -1,5 +1,7 @@
import ajax from '@/utils/xajax.js'
import http from '../unionAjax'
import httpAjax from '../httpAjax'
const baseURL = process.env.VUE_APP_MANAGER_API_PATH;
@@ -47,6 +49,18 @@ const articlelist=function (type){
const courselist=function (data){
return ajax.post('/xboe/portal/index/courselist',data);
}
// 精品课信息列表
const qualitylist=function (data){
return httpAjax.post(baseURL,'/quality/home/qualityItem',data);
}
// 精品课分页查询
export const qualityPageList=function (data){
return httpAjax.post(baseURL,'/quality/home/qualityPages',data);
}
// 课程精品课标记时间年份列表
export const qualityCourseTimeMark=function (){
return httpAjax.post(baseURL,'/quality/manage/qualityYearList',{});
}
/**
* 首页新课程推荐列表
*/
@@ -61,5 +75,7 @@ export default {
articlelist,
courselist,
newCases,
getRecommendList
getRecommendList,
qualitylist,
qualityPageList
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
src/assets/images/jindu.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -141,6 +141,7 @@
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="1">PC端可见</el-radio>
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="2">移动端可见</el-radio>
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="3">多端可见</el-radio>
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" v-if="isPermission" :label="4">仅内网访问</el-radio>
</el-form-item>
<el-form-item v-if="!weike.onlyRequired" label="课程来源">
<el-radio-group v-model="courseInfo.source">
@@ -305,6 +306,7 @@
<el-radio v-model="courseInfo.device" :label="1">PC端可见</el-radio>
<el-radio v-model="courseInfo.device" :label="2">移动端可见</el-radio>
<el-radio v-model="courseInfo.device" :label="3">多端可见</el-radio>
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" v-if="isPermission" :label="4">仅内网访问</el-radio>
</el-col>
<el-col :span="10">
<el-form-item label="课程来源">
@@ -488,6 +490,8 @@ export default {
refType:''
},
visibleShow:false,
isPermission:false,
dicts:[],
extendRefId:'',
extendRefType:'',
courseTeachers: [], //课程的老师
@@ -527,7 +531,11 @@ export default {
dlgShow: false
},
rightTypeId: {},
catalogSortDialogShow: false
catalogSortDialogShow: false,
selectedOrg: {
orgId: null,
name: ''
}
};
},
created() {
@@ -552,14 +560,18 @@ export default {
},
watch: {
courseInfo: {
handler(newVal) {
//需要保存
handler(newVal, oldVal) {
// 需要保存
this.requireSaveCourse = true;
console.log("--- watch比较 = ", oldVal.orgId, newVal.orgId);
this.checkOrgPermission(newVal.orgId);
},
deep: true
}
},
mounted() {
this.getDictIds();
let extendFlag=this.$route.query.f; //是否是管理端过来的
this.extendRefId=this.$route.query.refId;
this.extendRefType=this.$route.query.refType;
@@ -581,6 +593,19 @@ export default {
this.loadUserGroup();
},
methods: {
// 检查机构权限
checkOrgPermission(orgId) {
console.log("--- 监测组织id orgId = ",orgId)
console.log("--- this.isPermission = ",this.isPermission)
console.log("--- device = ",this.courseInfo.device)
if (!orgId) {
this.isPermission = false;
return;
}
console.log("--- this.dicts = ",this.dicts)
this.isPermission = this.dicts.includes(orgId);
console.log("--- 监听结束 this.isPermission = ",this.isPermission)
},
// 关键字的更改
changeKeywords(option){
if(option.target.value){
@@ -885,11 +910,27 @@ export default {
this.courseCoverurl = '';
this.courseInfo.coverImg = '';
},
//获取字典信息
async getDictIds() {
console.log("--- 获取字典信息 1 = ", this.dicts);
try {
const response = await apiCourse.getDictIds(637, 1); // 确保返回 Promise
console.log("--- 获取字典信息 2 result= ", response);
if (response.status === 200) {
this.dicts = response.result.dicts; // 正确提取 dicts
console.log("--- 获取字典信息 3 = ", this.dicts);
}
} catch (error) {
console.error("获取字典信息失败:", error);
}
},
//获取课程信息
async getDetail(id) {
this.curCourseId = id;
this.orgName='';
let $this = this;
this.isPermission = false;
let $this = this;
try {
const { result, status } = await apiCourse.detail(id);
if (status === 200) {
@@ -906,7 +947,10 @@ export default {
this.contentInfo.list = result.contents;
this.sectionInfo.list = result.sections;
this.courseTeachers = result.teachers; //课程的老师信息
this.isPermission = result.isPermission; //课程的老师信息
this.dicts = result.dicts; //课程的老师信息
console.log("--- 编辑查看 this.isPermission = ",this.isPermission)
console.log("--- 编辑查看 this.dicts = ",this.dicts)
if(!this.courseInfo.orgId){
//根据课程创建者获取机构id
apiUser.getOrgSimpleByUserId(result.course.sysCreateAid).then(ors=>{

View File

@@ -329,6 +329,8 @@ export default {
apiExamPaper.newPaperContent(this.info.id).then(res=>{
if(!res.error&&res.result != ''){
if(this.info.paperType==2){
//TODO1
console.log("调用开始考试 if paperType == 2")
this.examPaper.json=res.result;
let qitems=this.convertToItems(this.examPaper.json);
this.paper ={items:qitems};
@@ -336,9 +338,11 @@ export default {
this.curItem=qitems[this.curIndex];
this.startTime=new Date();//记录开始时间
this.timerValue=this.info.testDuration;
this.content.status=1;
this.timer=setInterval(this.changeTimer,60000);
this.testStart=true;
}else{
console.log("调用开始考试 else paperType != 2")
let paper= {items:res.result};
paper.items.forEach(item=>{
if(item.type==101){
@@ -582,8 +586,15 @@ export default {
apiStudy.saveExam(postData).then(res=>{
this.thisTrue = true
if(res.status == 200) {
console.log("获取数据结果 考试 res ==>",res)
this.records.push(res.result);
this.content.status=9;//表已学习完,判断上级的章是否已完成
//TODO 评分更改 最终完成
if(res.result.score > res.result.passLine){
this.content.status=9;//表已学习完,判断上级的章是否已完成
}else{
this.content.status=1;
}
this.studyItemId=res.result.studyItemId;//第一次保存时是没有的,所以这里要赋值
this.$alert('您本次考试得分:'+this.lastScore, '考试成绩', {
confirmButtonText: '确定',

View File

@@ -109,7 +109,7 @@ export default {
if(res.status==200){
this.info=res.result;
//检查是否过期
if(res.result.deadTime!=''){
if(res.result.deadTime!='' && res.result.deadTime != null){
var d = new Date(res.result.deadTime);
var now=new Date();
if(now.getTime() > d.getTime()){

View File

@@ -44,7 +44,7 @@
<!-- <svg-icon v-else style="margin-right: 0;" :style="{'font-size':(size+2)+'px'}" :icon-class="isFavorite?'scactive2':'xihuan'"></svg-icon> -->
<div v-else class="is_favorite" :class="isFavorite?'is_favorite_a':'is_favorite'"></div>
</el-tooltip>
<span v-if="!courseExclusive" class="interact-bar-value"> {{ data.favorites? data.favorites:0}}</span>
<span v-if="!courseExclusive" class="interact-bar-value"> {{ data.favorites? data.favorites:data.hasCollect?number(data.hasCollect):1}}</span>
</div>
<div v-if="shares" @click="addShare()" :style="`min-width: ${nodeWidth};`" class="interact-bar-btn" :class="{cursor:!readonly}">
<el-tooltip effect="light" content="分享" placement="top" :visible-arrow="false" popper-class="text-tooltip">
@@ -114,6 +114,8 @@ export default {
shares:0,
praises:0,
views:0,
courseId:'',
courseName:''
}
}
},
@@ -234,7 +236,7 @@ export default {
created(){
},
mounted() {
if(this.data && this.data.id && !this.readonly){
if(this.data && (this.data.id||this.data.courseId) && !this.readonly){
this.checkHas();
}
@@ -308,7 +310,7 @@ export default {
}
let msgPageParams=this.pageParams;
if(!msgPageParams){
msgPageParams=this.data.id;
msgPageParams=this.data.id ?this.data.id: this.data.courseId;
}
let message={
content,
@@ -331,9 +333,9 @@ export default {
})
},
checkHas(){
if(this.type>0 && !this.readonly && this.data.id){
if(this.type>0 && !this.readonly && (this.data.id||this.data.courseId)){
if(this.favorites){
apiFavorites.has(this.type,this.data.id).then(rs=>{
apiFavorites.has(this.type,(this.data.id || this.data.courseId)).then(rs=>{
if(rs.status==200 && rs.result){
this.isFavorite=true;
}else{
@@ -506,9 +508,11 @@ export default {
return;
}
//需要判断是否已点赞,已点赞的不再加
console.log(this.data,'---------------');
let postData={
objType:this.type,
objId:this.data.id,
objId:this.data.id ?this.data.id: this.data.courseId,
title:'',
}
if(this.loading) {
@@ -516,7 +520,7 @@ export default {
}
this.loading=true;
if(this.type==1){
postData.title=this.data.name;
postData.title=this.data.name?this.data.name:this.data.courseName;
}else if(this.type==60){
postData.title=this.data.content;
} else if(this.type==5){
@@ -525,7 +529,7 @@ export default {
postData.title=this.data.title;
}
if(this.isFavorite) {// 已经收藏,再次点击取消收藏
apiFavorites.remove(this.type,this.data.id).then(res=>{
apiFavorites.remove(this.type,this.data.id ?this.data.id: this.data.courseId).then(res=>{
this.loading=false;
if(res.status==200){
this.isFavorite=false;
@@ -554,7 +558,7 @@ export default {
this.$store.dispatch("unicomFavorites",true)
}
//if(this.type===2||this.type===4){
this.messageSave(this.data.id,this.data.title,this.userInfo.name,this.data.sysCreateBy,this.data.sysCreateAid,'收藏了我发布的');
this.messageSave(this.data.id ?this.data.id: this.data.courseId,this.data.title,this.userInfo.name,this.data.sysCreateBy,this.data.sysCreateAid,'收藏了我发布的');
//}
this.$message({message:'已加入收藏',type:'success'});
//this.$emit('addFavorite',res.result);//添加收藏,如果是true代表添加成功false代表已存在

View File

@@ -13,9 +13,9 @@
</router-link>
</div>
<div class="top-nav" :style="{color:textColor}" :class="current == 'course' ? activeNav : ''">
<router-link to="/course">课程
<a @click="handleChangeCourse">课程
<div :class="current == 'course' ? 'nav-bottbor' : ''"></div>
</router-link>
</a>
</div>
<div class="top-nav" :style="{color:textColor}" :class="current == 'case' ? activeNav : ''">
<router-link to="/case">案例
@@ -214,6 +214,12 @@ export default {
//this.loadPopupConfig();
},
methods: {
handleChangeCourse() {
const paths = ["/course","/qualityCourse"]
// 如果是 课程 和 精品课程, 那么就不再重定向
const needReload = paths.findIndex(e=> e === this.$route.path) === -1
if (needReload) this.$router.push({path: paths[0]})
},
setCurIdentity(iden){
this.$store.dispatch('SetCurIdentity',iden);

View File

@@ -13,7 +13,7 @@
</div>
<!-- <div style="color: red;">下面的表格标题及内容需要调整完善</div> -->
<div>
<el-tabs v-model="tabName" @tab-click="handleTabClick">
<el-tabs style="width: 100%;" v-model="tabName" @tab-click="handleTabClick">
<el-tab-pane label="报名管理" name="second">
<!-- <div>已报名{{study.list.length}}共有0人报名未成功共有{{study.list.length}}人通过审核</div> -->
<el-row style="margin: 20px 0;" :gutter="20">
@@ -863,4 +863,19 @@ export default {
height: 400px;
overflow: auto;
}
.el-tabs__nav-wrap::after {
display: none; /* 隐藏滚动条轨道 */
}
.el-tabs__nav-scroll {
overflow: hidden; /* 隐藏滚动条滑块 */
}
/* 隐藏垂直滚动条 */
.el-table .el-table__body-wrapper::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera*/
}
</style>

View File

@@ -14,7 +14,7 @@
:default-active="activeMenu"
router
active-text-color="#3379FB"
:default-openeds="['manageTodo','manageFinish','teacherTodo','teacherFinish','lecturer','myqa','notice','mystudy', 'course', 'exam', 'u001', 'u002', 'u003', 'u004','mylecnotes','mycaseRecord']"
:default-openeds="['manageTodo','manageFinish','teacherTodo','teacherFinish','lecturer','myqa','notice','mystudy','myGrowth', 'course', 'exam', 'u001', 'u002', 'u003', 'u004','mylecnotes','mycaseRecord']"
class="el-menu-vertical"
@open="handleOpen"
@close="handleClose"
@@ -242,6 +242,20 @@
<svg-icon :icon-class="activeMenu == '/user/medal'?'myxunzhang-active':'myxunzhang'"></svg-icon>
<span slot="title">我的勋章</span>
</el-menu-item>
<template v-if="showGrowthNav">
<el-submenu index="myGrowth" v-show="curIdentity == 1">
<template slot="title">
<!-- <i class="el-icon-s-grid"></i> -->
<img src="../../assets/images/myGrowth.jpg" style="width: 1em;height: 1em;margin-right: 8px;"></img>
<span>我的必修</span>
</template>
<el-menu-item index="/growth/growthPage">
<!-- <i class="el-icon-menu"></i> -->
<span slot="title" class="study textl">专业力必修</span>
</el-menu-item>
</el-submenu>
</template>
<el-submenu index="mystudy" v-show="curIdentity == 1">
<template slot="title">
<!-- <i class="el-icon-s-grid"></i> -->
@@ -385,6 +399,7 @@ import { mapGetters } from 'vuex';
import apicourseStudy from "../../api/modules/courseStudy.js"
import {pageList} from "@/api/modules/lecturer"
import courseImage from "@/components/Course/courseImage.vue"
import { listData } from "@/api/growth/growthpath"
import testUser from '@/utils/testUsers.js'
export default {
name: 'UcMenu',
@@ -413,7 +428,8 @@ export default {
isCollapse: false,
lastStudy:{},
overlayShow: false,
instructor:0
instructor:0,
showGrowthNav: false
};
},
created() {
@@ -422,6 +438,11 @@ export default {
this.getLastStudy();
//获取讲师认证中条数
this.instructorCertification()
console.log(this.userInfo,'userInfouserInfo')
//如果专业力必修没有学习任务则隐藏“我的必修按钮”
listData().then(res => {
this.showGrowthNav = !!res.data.length
})
},
mounted() {
//console.log(this.userInfo.loginName,'this.userInfo.loginName');

View File

@@ -18,7 +18,6 @@
@keydown.right.prevent="forwardCurrentTime"
@keydown.up.prevent="increaseVolume"
@keydown.down.prevent="lowerVolume"
@keydown.esc.prevent="toggleFullScreen"
tabindex="0"
width="100%"
height="100%"
@@ -318,7 +317,7 @@ export default {
}
setInterval(() => {
//console.log('this.currentProgress::',this.currentProgress,this.isDrag,this.videoDom.currentTime , this.videoDom.duration)
console.log('当前状态:',this.currentProgress,this.isDrag,this.videoDom.currentTime , this.videoDom.duration)
// 视频播放时本地记录视频实时播放时长,视频设置了禁止拖动时执行
if(!this.isDrag){
var time = localStorage.getItem('videoProgressData')
@@ -365,6 +364,11 @@ export default {
}
// 根据视频的readyState判断下一帧是否已加载并控制loading的显示
this.isShowLoading = this.videoDom.readyState < 3;
console.log("当前缓存:"+this.videoDom.readyState)
if (this.videoDom.readyState < 3){
console.log("详细信息",this.videoDom)
console.log("卡了",this.videoDom.readyState)
}
//if()
//console.log(this.videoDom.readyState,'this.videoDom.readyState');
}, 1000);
@@ -385,9 +389,26 @@ export default {
console.log('\x1b[31m%s\x1b[0m','*****************加载失败**********')
})
// 监听全屏事件的变化,保存数据
window.addEventListener("fullscreenchange", () => {
window.addEventListener("F", () => {
this.isFullscreen = this.isFullScreen();
});
document.addEventListener('fullscreenchange',this.handleFullscreenChange);
document.addEventListener('webkitfullscreenchange',this.handleFullscreenChange );
// this.videoDom.addEventListener('keydown', function(e){
// //console.log('\x1b[31m%s\x1b[0m','*****************加载失败**********')
// console.log("video is exit");
// if(e.key === "Escape"){
// console.log("按键 退出全屏 执行")
// }
// })
// this.videoDom.addEventListener("keydown", (e) => {
// console.log("是否监听到");
// if(e.keyCode == 27){
// console.log("是否监听到 esc");
// this.toggleFullScreen();
// }
// });
},
methods: {
//当视频由于需要缓冲下一帧而停止,解决一直计时的问题
@@ -490,6 +511,14 @@ export default {
updateProgressBySetTime(newCurrentTime) {
this.currentProgress = newCurrentTime / this.videoDom.duration;
},
/* 点击进度条更新视频播放进度2
*/
updateProgressByClickBar2(newCurrentTime,progressVideo) {
this.currentProgress = progressVideo;
this.barrageTimelineStart = newCurrentTime;
this.videoDom.currentTime = newCurrentTime;
this.updateProgressText();
},
/* 提高视频音量
*/
increaseVolume() {
@@ -540,6 +569,7 @@ export default {
/* 切换“全屏”和“非全屏”模式
*/
toggleFullScreen() {
console.log("toggleFullScreen 执行 ",this.isFullScreen())
let element = this.$refs.area;
if (!this.isFullScreen()) {
if (element.requestFullscreen) {
@@ -596,6 +626,24 @@ export default {
const currentTime = this.$refs.video.currentTime;
this.$emit('onTimeUpdate', currentTime);
},
/**
* 监听全屏变化
*/
handleFullscreenChange() {
console.log("handleFullscreenChange gx this.isFullScreen()",this.isFullScreen())
console.log("handleFullscreenChange gx this.isFullscreen",this.isFullscreen)
if(this.isFullscreen){
this.isFullscreen = !!document.fullscreenElement;
if (!this.isFullscreen) {
// 退出全屏后的逻辑(如暂停视频、更新 UI 等)
console.log('ESC 已退出全屏 handleFullscreenChange',this.isFullscreen);
this.$emit('onFullscreen',false);//全屏
}
}
}
},
watch: {
currentVolume: function () {

View File

@@ -37,7 +37,6 @@ export default {
},
isDrag:{
type: Boolean,
default: true,
},
blobId:{
type: String,
@@ -92,7 +91,7 @@ export default {
this.$emit("updateProgress", current);
// }
},
move(e) {
if (this.is_mousedown_progress) {
@@ -124,7 +123,7 @@ export default {
// 禁止拖动
if(!this.isDrag && time && arr[this.blobId] < current) return;
this.$emit("updateProgress", current);
}
},
},
@@ -176,4 +175,4 @@ export default {
.progress-bar:hover .progress-current::after {
opacity: 1;
}
</style>
</style>

View File

@@ -82,6 +82,9 @@ export const pages=[
{title:'学习路径图',path:'path',component:'study/StudyPath',hidden:false},
{title:'学习项目',path:'project',component:'study/StudyProject',hidden:false}
]},
{title:'我的必修',path:'/growth',hidden:false,children:[
{title:'专业力必修',path:'growthPage',component:'growth/index',hidden:false},
]},
{title:'个人中心',path:'/uc',hidden:false,children:[
{title:'首页',path:'index',component:'StudyIndex',hidden:false}
]},

View File

@@ -6,7 +6,10 @@
<!-- <div style="height: 20px;"></div> -->
</div>
<!-- width: 990px; -->
<div class="main-body" style="padding: 30px 20px;flex:1; background-color: #fff;margin-left: 20px;">
<div class="main-body" :style="{
backgroundColor: bgColor,
padding
}" style="flex:1; margin-left: 20px;">
<!-- <div style="margin-bottom: 10px;padding:10px; border-bottom: 1px solid #dbdbdb;"> -->
<!-- <el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/uc/index' }"><i class="el-icon-arrow-right"></i>个人中心</el-breadcrumb-item>
@@ -15,10 +18,12 @@
<!-- </div> -->
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<div style="background-color: #FFFFFF;min-height: 600px;padding-bottom: 50px;">
<div :style="{
backgroundColor: bgColor
}" style="min-height: 600px;padding-bottom: 50px;">
<router-view :key="key" />
</div>
</keep-alive>
</keep-alive>
</transition>
</div>
</div>
@@ -39,7 +44,14 @@ export default {
return this.$route.meta.title;
},
key() {
console.log(this.$route)
return this.$route.path
},
bgColor() {
return this.$route.path === "/growth/growthPage" ? "rgba(0,0,0,0)" : "#fff";
},
padding(){
return this.$route.path === "/growth/growthPage" ? "0" : "30px 20px";
}
}
}

View File

@@ -3,6 +3,22 @@ import App from './App.vue'
import router from './router'
import store from './store'
import vueKatexEs from "vue-katex";
import "katex/dist/katex.min.css"
Vue.use(vueKatexEs,{
globalOptions:{
delimiters:[
{left:"$$",right:"$$",display:true},
{left:"$",right:"$",display:false},
{left:"\\[",right:"\\]",display:true},
{left:"\\(",right:"\\)",display:false}
],
throwOnError:true
}
})
//import './mock/index'
import xpage from '@/utils/xpage'

View File

@@ -2,7 +2,6 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
/* Layout */
import Layout from '@/layout/index'
import LayoutPortal from '@/layout/portal'
import Grateful from '@/views/grateful'
Vue.use(VueRouter)
@@ -341,6 +340,13 @@ export const constantRoutes = [{
name: 'hotforum',
meta: {title: '热点论坛', icon: 'dashboard', noCache: true, affix: true},
},
{
path: '/lexiang',
hidden: true,
component: (resolve) => require(['@/views/sso/lexiang'], resolve),
name: 'lexiang',
meta: {title: '单点登录', icon: 'dashboard', noCache: true, affix: true},
},
{
path: '/404',
component: (resolve) => require(['@/views/error/404'], resolve),
@@ -355,7 +361,15 @@ export const constantRoutes = [{
path: '/500',
component: (resolve) => require(['@/views/error/500'], resolve),
hidden: true
},
{
path: '/qualityCourse',
hidden: true,
component: (resolve) => require(['@/views/portal/course/qualityCourse'], resolve),
name: 'qualityCourse',
meta: {title: '精品课课程', keepAlive: true, icon: 'dashboard', noCache: true, affix: false},
}
]
const router = new VueRouter({

View File

@@ -7,7 +7,11 @@ const state = {
withoutAnimation: false
},
device: 'desktop',//默认是桌面以后会有android,ios,minapp
size: Cookies.get('size') || 'medium' //字段大小
size: Cookies.get('size') || 'medium', //字段大小
// 添加AI Call组件显示控制状态
showAICall: false,
// 控制AI Call最小化窗口在特定路由下显示的状态
showAICallMinimized: false
}
const mutations = {
@@ -34,6 +38,14 @@ const mutations = {
SET_SIZE: (state, size) => {
state.size = size
Cookies.set('size', size)
},
// 添加控制AI Call组件显示的mutation
SET_SHOW_AI_CALL: (state, show) => {
state.showAICall = show
},
// 控制AI Call最小化窗口显示的mutation
SET_SHOW_AI_CALL_MINIMIZED: (state, show) => {
state.showAICallMinimized = show
}
}
@@ -49,6 +61,14 @@ const actions = {
},
setSize({ commit }, size) {
commit('SET_SIZE', size)
},
// 添加控制AI Call组件显示的action
setShowAICall({ commit }, show) {
commit('SET_SHOW_AI_CALL', show)
},
// 控制AI Call最小化窗口显示的action
setShowAICallMinimized({ commit }, show) {
commit('SET_SHOW_AI_CALL_MINIMIZED', show)
}
}
@@ -57,4 +77,4 @@ export default {
state,
mutations,
actions
}
}

83
src/utils/sseHelper.js Normal file
View File

@@ -0,0 +1,83 @@
/**
* SSE流式数据处理工具
*/
/**
* 处理SSE响应数据
* @param {String} data - SSE响应数据
* @param {Function} onMessage - 处理消息的回调函数
* @param {Function} onComplete - 完成时的回调函数
* @param {Function} onError - 错误处理回调函数
*/
export function processSSEData(data, onMessage, onComplete, onError) {
try {
const lines = data.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonData = JSON.parse(line.substring(6))
onMessage(jsonData)
// 如果状态为4表示对话结束
if (jsonData.data && jsonData.data.status === 4) {
onComplete()
}
}
}
} catch (error) {
console.error('处理SSE数据时出错:', error)
if (onError) {
onError(error)
}
}
}
/**
* 创建SSE连接
* @param {String} url - 请求地址
* @param {Object} data - 请求数据
* @param {Function} onMessage - 消息处理回调
* @param {Function} onComplete - 完成回调
* @param {Function} onError - 错误回调
* @returns {Promise} - 返回fetch Promise
*/
export function createSSEConnection(url, data, onMessage, onComplete, onError) {
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
function read() {
reader.read().then(({ done, value }) => {
if (done) {
if (onComplete) onComplete()
return
}
buffer += decoder.decode(value, { stream: true })
processSSEData(buffer, onMessage, onComplete, onError)
// 继续读取
read()
}).catch(error => {
console.error('SSE读取错误:', error)
if (onError) onError(error)
})
}
// 开始读取数据
read()
}).catch(error => {
console.error('SSE连接错误:', error)
if (onError) onError(error)
})
}

View File

@@ -2,7 +2,7 @@
<div class="box" :class="{ incl: url.includes('projectdetails') }">
<portal-header style="background: #387DF7;" :hideSearch="true" textColor="#ffffff"></portal-header>
<iframe :src="url" style="width: 100%;height: 100%;" frameborder="0" ref="iframe"></iframe>
<portal-footer></portal-footer>
<portal-footer v-if="isShowFooter"></portal-footer>
</div>
</template>
<script>
@@ -16,7 +16,8 @@ import portalFooter from "@/components/PortalFooter.vue";
data(){
return {
url: "",
boeUrl:process.env.VUE_APP_BOE_WEB_URL
boeUrl:process.env.VUE_APP_BOE_WEB_URL,
isShowFooter:true
}
},
mounted() {
@@ -24,6 +25,10 @@ import portalFooter from "@/components/PortalFooter.vue";
let to = this.$route.query.to;//跳转的地址,以 /开头的地址
let urlPre=window.location.protocol+'//'+window.location.host;
this.url=urlPre+to
if(to.includes('growthList')){
this.isShowFooter = false
}
if(params){
this.url=this.url+'?'+params;
}

View File

@@ -153,9 +153,85 @@
</div>
</div>
<div class="xindex-content">
<!-- 推荐课程 -->
<div class="modules xcontent2">
<!-- <div class="xcontent2-main"> -->
<!--内容块-->
<!-- </div> -->
<!-- 精品课模块 -->
<div class="xcontent2-main">
<div class="modules-title xindex-main" v-if="this.qusisityList.list.length > 0">
<!-- <span class="modules-text" style="color: #3D86F4;">精品课</span> -->
<span class="quyer-tag" style="margin-left: 0px;">
<!-- <img src="../assets/images/tutoring1.pn" alt=""> -->
<img class="modules-text" style="height: 28px;" src="../assets/images/course/courseTitle.png" alt="">
</span>
<span class="more">
<router-link to="/qualityCourse">查看更多>></router-link>
</span>
</div>
<div
v-for="(course, eIndex) in exquisiteList"
:key="'cc' + eIndex"
class="xindex-course courseBg"
style="position: relative;margin-top: 20px;"
>
<div style="position: absolute; right: 25px; bottom: 72px">
<interactBar
nodeWidth="20px"
:courseExclusive="true"
:type="1"
:data="course"
:comments="false"
:praises="false"
:shares="false"
:views="false"
>
</interactBar>
<!-- <svg-icon style="font-size: 32px;margin-top: -5px;" icon-class="collectedCourse"></svg-icon> -->
</div>
<a @click="toCourseDetail(course)">
<div class="xindex-course-image">
<course-image :course="course"></course-image>
<!-- <span v-if="course.type == 20 || 10" class="course-type"
>录播课</span
> -->
<img v-if="course.type == 20 || 10" src="../assets/images/course/courseTag.png" class="course-type" style="background: none;" alt="">
</div>
<div
style="width: 80%"
:title="course.courseName"
class="course-title portal-title-tow two-line-ellipsis"
>
{{ course.courseName }}
</div>
<div class="course-author">
<div class="course-author-left">
{{ course.authorInfo.name }}
<span class="study-num"
>{{ formatNum(course.studyNum) }}人学习</span
>
</div>
<div style="display: flex">
<div v-if="course.courseScore">
<span class="course-score-value" style="margin-left: 10px"
>{{ toScore(course.courseScore) }}</span
>
</div>
<div v-else class="course-score-no">未评分</div>
</div>
</div>
</a>
</div>
<!--内容块-->
<div class="modules-title xindex-main">
<span class="modules-text">推荐课程</span>
@@ -1137,6 +1213,9 @@ export default {
orderType: 2,
list: [],
},
qusisityList: {
list: [],
},
// 推荐课程
recommendedList:{
list: [],
@@ -1164,6 +1243,7 @@ export default {
},
mounted() {
this.getCourseData(1);
this.getEsqusiteList();
this.getRecommendList();
this.getPositive()
this.getCaseData();
@@ -1227,6 +1307,10 @@ export default {
courseComputedTwoList(){
return this.courseList.list.slice(3)
},
// 精品课展示
exquisiteList() {
return this.qusisityList.list.slice(0,3)
},
},
methods: {
getPositive() {
@@ -1421,21 +1505,27 @@ export default {
//二期调整,直接改成一个地址
//return this.webBaseUrl + '/course/detail?id=' + item.id;
let $this = this;
let cId = "";
if (item.id) {
cId = item.id;
} else {
cId = item.courseId;
}
if (item.type == 10) {
//return this.webBaseUrl + "/course/studyindex?id=" + item.id;
//console.log("直接进入学习页面");
this.$router.push("/course/studyindex?id=" + item.id);
this.$router.push("/course/studyindex?id=" + cId);
} else if (item.type == 20) {
apiCourseStudy.hasSignup(item.id).then((rs) => {
apiCourseStudy.hasSignup(cId).then((rs) => {
if (rs.status == 200) {
//return $this.webBaseUrl + "/course/studyindex?id=" + item.id;
this.$router.push("/course/studyindex?id=" + item.id);
//return $this.webBaseUrl + "/course/studyindex?id=" + cId;
this.$router.push("/course/studyindex?id=" + cId);
} else {
//return $this.webBaseUrl + "/course/detail?id=" + item.id;
this.$router.push("/course/detail?id=" + item.id);
//return $this.webBaseUrl + "/course/detail?id=" + cId;
this.$router.push("/course/detail?id=" + cId);
}
});
//return $this.webBaseUrl + "/course/detail?id=" + item.id;
//return $this.webBaseUrl + "/course/detail?id=" + cId;
}
},
orderTypeFilter(val) {
@@ -1472,6 +1562,29 @@ export default {
}
})
},
//精品课展示
getEsqusiteList(){
let course = {
aid: this.userInfo.aid,
}
apiIndex.qualitylist(course).then((res) => {
let courseIds = [];
res.data.result.forEach((item) => {
item.authorInfo = {
aid: "",
name: "",
orgInfo: "",
avatar: "",
code: "",
sex: null,
};
courseIds.push(item.courseId);
});
this.loadCouserTeacher(res.data.result, courseIds);
console.log(res.data.result,'--------------------------');
this.qusisityList.list = res.data.result;
})
},
getCourseData(pageIndex) {
this.isNext = false;
let { orderType, num } = this.courseList;
@@ -1671,7 +1784,7 @@ export default {
let userIds = [];
list.forEach((item, index) => {
cres.result.some((courseTeahcer) => {
if (courseTeahcer.courseId == item.id) {
if (courseTeahcer.courseId == (item.id||item.courseId)) {
if (courseTeahcer.teacherIds) {
userIds.push(courseTeahcer.teacherIds[0]);
item.authorInfo.aid = courseTeahcer.teacherIds[0];
@@ -2661,6 +2774,7 @@ export default {
// padding-bottom: 10px;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
.course-author-left {
font-size: 14px;
@@ -3009,4 +3123,17 @@ export default {
}
}
}
.courseBg{
// width: 332px;
// height: 323px;
// background: url("../assets/images/course/courseBackground.png") no-repeat;
// background-size: 100% 100%; /* 或 use 'contain' */
// background-position: center;
// border: none;
border-radius: 12px;
background: linear-gradient(135deg, #e6f7ff, #f0f8ff, #ffffff);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #d9edf7;
//overflow: hidden;
}
</style>

View File

@@ -1,3 +1,4 @@
<!--
<template>
<div class="login" :style="`background: url(${webBaseUrl}/temp/background.jpg) no-repeat`">
<el-form :model="form" ref="form" :rules="rules">
@@ -24,7 +25,7 @@
<el-form-item>
<section>
<el-checkbox v-model="rememberMe">记住密码</el-checkbox>
<!-- <span>忘记密码</span> -->
&lt;!&ndash; <span>忘记密码</span> &ndash;&gt;
</section>
</el-form-item>
<el-form-item><el-button type="primary" @click="loginSubmit">点击登录</el-button></el-form-item>
@@ -194,3 +195,4 @@
}
}
</style>
-->

View File

@@ -195,7 +195,15 @@
</template>
</el-dialog>
<!--课程管理-->
<el-dialog custom-class="g-dialog" title="课程学习管理" width="900px" :visible.sync="manageStudy.dlgShow" :close-on-click-modal="false">
<el-dialog
custom-class="g-dialog"
title="课程学习管理"
width="900px"
height="900px"
:visible.sync="manageStudy.dlgShow"
:close-on-click-modal="false"
>
<manager :manageStudyData="manageStudyData" :isShowDialog="manageStudy.dlgShow"></manager>
<template #footer>
<el-button @click="manageStudy.dlgShow = false">关闭</el-button>
@@ -668,8 +676,10 @@ export default {
}
apiCourse.pageList(this.params).then(rs=>{
if(rs.status==200){
this.pageData = rs.result.list;
this.pageData = rs.result.list ? rs.result.list : [];
// this.pageData = rs.result.list;
this.page.count = rs.result.count;
this.page.pageSize = rs.result.pageSize;
}else{
return this.$message.error(rs.message);
}
@@ -939,4 +949,8 @@ export default {
}
}
}
.el-dialog__body {
overflow: hidden;
}
</style>

View File

@@ -1,206 +1,276 @@
<template>
<div style="padding: 12px 32px 10px 12px;">
<div style="display: flex;justify-content: flex-start;">
<div style="display: flex;justify-content: flex-start;">
<div><el-input v-model="keyword" placeholder="输入课程名称搜索" clearable></el-input></div>
<div style="padding-left: 10px;"><el-button @click="recordList()" type="primary" icon="el-icon-search">搜索</el-button></div>
<div style="padding: 0px 5px;"><el-button icon="el-icon-refresh-right" @click="keyword = ''" type="primary" >重置</el-button></div>
<div style="padding: 0px 5px;"><el-button type="primary" @click="exportFile()" icon="el-icon-search" size="small" round>导出</el-button></div>
<div style="padding: 0px 5px;"><el-button type="primary" @click="exportStudentOfTearcher()" icon="el-icon-search" size="small" round>导出学员信息</el-button></div>
</div>
<div class="Export">
<!-- <div style="padding-left: 10px;"><el-button type="primary" @click="exportFile()" icon="el-icon-search" size="small" round>导出</el-button></div> -->
<!-- <div style="padding: 0px 5px;"><el-button icon="el-icon-refresh-right" type="primary" size="small" round>导出学员信息</el-button></div> -->
</div>
<div style="margin-top:20px;">
<el-table :data="pageData" border stripe>
<el-table-column
label="课程时间"
prop="courseTime"
width="200px">
</el-table-column>
<el-table-column
label="课程名称"
prop="courseName"
width="200px">
</el-table-column>
<el-table-column
label="开课场地"
prop="address"
width="240px">
</el-table-column>
<el-table-column
label="所属课程"
prop="parentCourse"
width="120px">
</el-table-column>
<el-table-column
label="所属项目"
prop="parentProject"
></el-table-column>
<el-table-column
label="所属路径"
prop="parentRoute"
width="200px"
></el-table-column>
<el-table-column
label="学习总人数"
prop="studentNumber"
width="100px"
></el-table-column>
<el-table-column
label="时长(分钟)"
width="100px"
prop="period"
></el-table-column>
<el-table-column
label="评分"
width="100px"
prop="score"
></el-table-column>
<el-table-column
label="操作"
align="center"
width="150px"
prop="cz"
fixed="right"
>
<div>
<el-input v-model="keyword" placeholder="输入课程名称搜索" clearable></el-input>
</div>
<div style="padding-left: 10px;">
<el-button @click="recordList()" type="primary" icon="el-icon-search">搜索</el-button>
</div>
<div style="padding: 0px 5px;">
<el-button icon="el-icon-refresh-right" @click="keyword = ''" type="primary">重置</el-button>
</div>
<div style="padding: 0px 5px;">
<el-button type="primary" @click="exportFile()" icon="el-icon-search" size="small" round>导出</el-button>
</div>
<!-- <div style="padding: 0px 5px;">-->
<!-- <el-button type="primary" @click="exportStudentOfTearcher()" icon="el-icon-search" size="small" round>-->
<!-- 导出学员信息-->
<!-- </el-button>-->
<!-- </div>-->
</div>
<div class="Export">
<!-- <div style="padding-left: 10px;"><el-button type="primary" @click="exportFile()" icon="el-icon-search" size="small" round>导出</el-button></div> -->
<!-- <div style="padding: 0px 5px;"><el-button icon="el-icon-refresh-right" type="primary" size="small" round>导出学员信息</el-button></div> -->
</div>
<div style="margin-top:20px;">
<el-table :data="pageData" border stripe>
<el-table-column
label="序号"
prop="index"
width="100px">
<template v-slot="scope">
<el-button type="text" @click="exportStudentOfCourse(scope.row.courseId)" size="small">导出学员信息</el-button>
</template>
</el-table-column>
</el-table>
</div>
<span>{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column
label="讲师工号"
prop="userNo"
width="100px">
</el-table-column>
<el-table-column
label="讲师姓名"
prop="teacherName"
width="200px">
</el-table-column>
<el-table-column
label="课程名称"
prop="courseName"
width="200px">
</el-table-column>
<el-table-column
label="培训发生组织"
prop="trainOrgName"
width="200px">
</el-table-column>
<el-table-column label="课程类型" prop="type" width="120px">
<template v-slot="scope">
<span>{{
{
"0": "在线课",
"1": "面授课",
"2": "课程开发",
"3": "作业员入模培训",
"4": "其他",
}[scope.row.type + '']
}}</span>
</template>
</el-table-column>
<el-table-column
label="授课/课程日期"
prop="teachingDate"
width="200px">
</el-table-column>
<div style="text-align: center; margin-top:57px">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageInfo.page"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageInfo.total"
></el-pagination>
</div>
<el-table-column
label="内容分类"
prop="courseTypeName"
width="200px">
</el-table-column>
<el-table-column label="开课状态" width="100px" prop="courseStatus">
<template v-slot="scope">
<span>{{
{
"0": "未开课",
"1": "已开课",
}[scope.row.courseStatus + '']
}}</span>
</template>
</el-table-column>
<el-table-column
label="授课时长(分钟)"
prop="teaching"
width="120px"
></el-table-column>
<el-table-column
label="参训人数"
prop="studys"
width="120px"
></el-table-column>
<el-table-column
label="评分"
prop="score"
width="100px"
></el-table-column>
<el-table-column
label="是否生成讲师费"
prop="createdFee"
width="100px"
>
<template v-slot="scope">
<span>{{
{
"0": "否",
"1": "是",
}[scope.row.createdFee + '']
}}</span>
</template>
</el-table-column>
<el-table-column
label="备注"
width="200px"
prop="remark"
></el-table-column>
<el-table-column
label="操作"
align="center"
width="150px"
prop="cz"
fixed="right"
>
<template v-slot="scope">
<el-button type="text" @click="exportStudentOfCourse(scope.row.courseId, scope.row.type)"
size="small">导出学员信息
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div style="text-align: center; margin-top:57px">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageInfo.page"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageInfo.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageInfo.total"
></el-pagination>
</div>
</div>
</template>
<script>
import apiCourse from '../../api/boe/courseRecord.js';
import { mapGetters } from 'vuex';
import {mapGetters} from 'vuex';
export default {
data(){
return {
pageData:[],
keyword:'',
pageInfo:{},
}
},
data() {
return {
pageData: [],
keyword: '',
pageInfo: {
pageIndex:1,
pageSize:10,
},
}
},
mounted() {
this.recordList();
},
computed: {
...mapGetters(['userInfo'])
},
methods: {
// 导出所有记录
exportFile() {
// let req = {
// }
// apiCourse.courseRecordExport2(req).then(res => {
// const link = document.createElement('a');// 创建a标签
// let blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}); // 设置文件类型
// link.style.display = "none";
// link.href = URL.createObjectURL(blob); // 创建URL
// link.setAttribute("download", "授课记录.xls");
// document.body.appendChild(link);
// link.click();
// document.body.removeChild(link);
// })
// 取pageData记录第一条
if (this.pageData.length > 0) {
let teacherId = this.pageData[0].teacherId;
window.open(
`/manageApi/admin/export/exportInTeacherRecord?teacherId=${teacherId}`
);
}
},
mounted() {
this.recordList(1);
// 导出课程下的学员信息
exportStudentOfCourse(courseId, courseType) {
// apiCourse.exportStudentOfCourse2(courseId, courseType).then(res => {
// const link = document.createElement('a');// 创建a标签
// let blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}); // 设置文件类型
// link.style.display = "none";
// link.href = URL.createObjectURL(blob); // 创建URL
// link.setAttribute("download", "授课记录.xls");
// document.body.appendChild(link);
// link.click();
// document.body.removeChild(link);
// })
},
computed: {
...mapGetters(['userInfo'])
},
window.open(
`/manageApi/admin/export/exportTeacherRecordStudentInfo?courseId=${courseId}&courseType=${courseType}`
);
},
methods: {
// 导出教师下的所有授课记录下的学员信息
exportStudentOfTearcher() {
let userId = this.userInfo.sysId;
apiCourse.exportStudentOfTearcher2(userId).then(res => {
const link = document.createElement('a');// 创建a标签
let blob = new Blob([res.data], {type: 'application/vnd.ms-excel'}); // 设置文件类型
link.style.display = "none";
link.href = URL.createObjectURL(blob); // 创建URL
link.setAttribute("download", "授课记录.xls");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
},
// 导出所有记录
exportFile(){
let req = {
userId: this.userInfo.sysId
// userId:"70F80F4E-34BA-10AB-894A-8FA812B19637"
}
apiCourse.courseRecordExport(req).then(res=>{
const link = document.createElement('a');// 创建a标签
let blob = new Blob([res.data],{type: 'application/vnd.ms-excel'}); // 设置文件类型
link.style.display = "none";
link.href = URL.createObjectURL(blob); // 创建URL
link.setAttribute("download", "授课记录.xls");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
},
// 导出课程下的学员信息
exportStudentOfCourse(courseId){
let userId = this.userInfo.sysId;
apiCourse.exportStudentOfCourse(userId,courseId).then(res=>{
const link = document.createElement('a');// 创建a标签
let blob = new Blob([res.data],{type: 'application/vnd.ms-excel'}); // 设置文件类型
link.style.display = "none";
link.href = URL.createObjectURL(blob); // 创建URL
link.setAttribute("download", "授课记录.xls");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
},
// 导出教师下的所有授课记录下的学员信息
exportStudentOfTearcher(){
let userId = this.userInfo.sysId;
// let userId = '5D36C207-64F4-C512-312D-C9598257695C';
apiCourse.exportStudentOfTearcher(userId).then(res=>{
const link = document.createElement('a');// 创建a标签
let blob = new Blob([res.data],{type: 'application/vnd.ms-excel'}); // 设置文件类型
link.style.display = "none";
link.href = URL.createObjectURL(blob); // 创建URL
link.setAttribute("download", "授课记录.xls");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
},
recordList(pageIndex){
let req = {
//     userId:"6B049FAF-C314-7CCF-0D28-0D23F4C42531",
userId: this.userInfo.sysId,
keyword:this.keyword,
    page:pageIndex,
    pageSize:10
}
apiCourse.courseRecordList(req).then(res=>{
if(res.status == 200) {
this.pageData = res.result.list;
this.pageInfo = res.result.pageInfo;
} else {
recordList() {
let req = {
courseName: this.keyword,
pageNo: this.pageInfo.pageIndex,
pageSize: this.pageInfo.pageSize
}
apiCourse.getListByToken(req).then(res => {
if (res.status == 200) {
this.pageData = res.data.records;
this.pageInfo.pageSize = Number(res.data.size);
this.pageInfo.total = Number(res.data.total);
this.pageInfo.page = Number(res.data.current);
} else {
this.$message({
type: 'error',
message: res.message
});
}
})
},
// 每页显示的条数事件
handleSizeChange(val){
this.pageInfo.pageSize = val;
this.recordList(1);
},
// 显示制定页的数据
handleCurrentChange(val){
this.pageInfo.pageIndex = val;
this.recordList(this.pageInfo.pageIndex);
},
},
type: 'error',
message: res.message
});
}
})
},
// 每页显示的条数事件
handleSizeChange(val) {
this.pageInfo.pageIndex = 1;
this.pageInfo.pageSize = val;
this.recordList();
},
// 显示制定页的数据
handleCurrentChange(val) {
this.pageInfo.pageIndex = val;
this.recordList(this.pageInfo.pageIndex);
},
},
}
</script>
<style lang="scss" scoped>
.Export{
display: flex;
padding-top: 10px;
.Export {
display: flex;
padding-top: 10px;
}
</style>

View File

@@ -954,7 +954,7 @@ export default {
},
findPapers() {
let params={
pageSize:200,
pageSize:10000,
name:''
}
apiPaper.querypaper(params).then((res) => {

View File

@@ -5,8 +5,8 @@
<!-- 当轮播图等于一张时 -->
<swiper :options="swiperOptiontwo">
<swiper-slide style="margin: 0 auto" v-for="(item, idx) in resonimg" :key="'a' + idx"
class="swiper-slide games pointer">
<div class="bannbox" :style="{
class="swiper-slide games pointer" >
<div class="bannbox" @click="handleCarouselClick(item)" :style="{
background: `url(${fileBaseUrl + item.image
}) center center no-repeat`,
}"></div>
@@ -17,8 +17,8 @@
<div id="container" style="z-index: 99" v-else>
<swiper :options="swiperOption" ref="mySwiper" v-if="resonimg.length > 1">
<swiper-slide style="margin: 0 auto" v-for="(item, idx) in resonimg" :key="'b' + idx"
class="swiper-slide games pointer">
<div class="bannbox" :style="{
class="swiper-slide games pointer" >
<div class="bannbox" @click="handleCarouselClick(item)" :style="{
background: `url(${fileBaseUrl + item.image
}) center center no-repeat`,
}"></div>
@@ -220,7 +220,7 @@ export default {
autoplay: false,
// noSwiping: true,
},
resonimg: [],
// resonimg: [],
swiperOption: {
autoplay: {
delay: 2000,
@@ -249,6 +249,13 @@ export default {
this.getToolData()
},
methods: {
// 添加点击轮播图跳转的方法
handleCarouselClick(item) {
if (item.url) {
window.open(item.url, '_blank');
}
},
downTool(toolInfo) {
console.log(toolInfo);
window.open(`/activityApi/xboe/m/boe/tools/url/download?urlStr=${process.env.VUE_APP_BOE_WEB_URL}/upload${toolInfo.filePath}&fileName=${toolInfo.name}`)

View File

@@ -8,7 +8,7 @@
<div class="navTop">
<div>
<router-link to="/grateful" class="nav">首页</router-link>&nbsp;>&nbsp;
<span style="cursor: pointer;" class="nav">认证讲师库2023</span>
<span style="cursor: pointer;" class="nav">认证讲师库</span>
</div>
<div style="position: relative;">
<el-input class="portal-input" placeholder="请输入课程名称" style="border-radius: 20px !important; "

View File

@@ -5,7 +5,7 @@
<!-- </div> -->
<!-- 底部 -->
<div class="grateful">
<img src="../../assets/images/grateful/gratefulFooter.png" alt="" />
<img src="../../assets/images/grateful/gratefulFooter20250613.png" alt="" />
</div>
</div>
</template>

390
src/views/growth/index.vue Normal file
View File

@@ -0,0 +1,390 @@
<template>
<div class="growth" v-loading="loading" id="scrollContainer">
<div class="growth-left">
<div class="navbar">
<div style="display: flex; flex: 1">
<div class="nav-title">我的专业力必修</div>
</div>
</div>
<div class="growth-item" v-for="item of list" :id="item.id">
<div class="growth-nav">
<div class="growth-name">{{ item.growthName }}</div>
<div class="f-a-c">
<div
class="growth-state"
:style="{
color:
item.learningState === 0
? '#999999'
: item.learningState === 1
? '#6ba158'
: '#F2903D',
}"
>
{{
item.learningState === 0
? "未开始"
: item.learningState === 1
? "已完成"
: "进行中"
}}
</div>
<div class="growth-btn" @click="jumpRouter(item)">去学习</div>
</div>
</div>
<template v-if="item.description">
<div class="tip-title">说明</div>
<div class="tip-content">
{{ item.description || "暂无说明" }}
</div>
</template>
<div class="progress-title">学习进度</div>
<div class="progress-content">
<div class="f-j-b" style="margin-bottom: 10px">
<div class="f-a-c">
<div style="font-size: 16px; color: #333333">必修进度</div>
<div style="font-size: 18px; color: #999999; margin-left: 12px">
{{ item.requiredTaskCompletedNum }}/{{ item.requiredTaskNum }}
</div>
</div>
<div style="font-size: 18px; color: #0077ec">
{{ requiredTaskCompletionRate(item) }}%
</div>
</div>
<el-progress
:stroke-width="6"
:show-text="false"
:percentage="requiredTaskCompletionRate(item)"
color="#0077EC"
></el-progress>
</div>
<template v-if="item.electiveTaskNum !== 0">
<div class="progress-content">
<div class="f-j-b" style="margin-bottom: 10px">
<div class="f-a-c">
<div style="font-size: 16px; color: #333333">选修进度</div>
<div style="font-size: 18px; color: #999999; margin-left: 12px">
{{ item.electiveTaskCompletedNum }}/{{ item.electiveTaskNum }}
</div>
<el-tooltip
class="item"
effect="light"
:content="`还需完成${item.electivePoorNum}个选修任务`"
placement="top-start"
>
<i class="el-icon-question"></i>
</el-tooltip>
</div>
<div style="font-size: 18px; color: #0077ec">
{{ electiveTaskCompletionRate(item) }}%
</div>
</div>
<el-progress
:stroke-width="6"
:show-text="false"
color="#0077EC"
:percentage="electiveTaskCompletionRate(item)"
></el-progress>
</div>
</template>
<div class="progress-content">
<div class="f-j-b" style="margin-bottom: 10px">
<div class="f-a-c">
<div style="font-size: 16px; color: #333333">总进度</div>
<div style="font-size: 18px; color: #999999; margin-left: 12px">
{{ item.totalTaskCompletedNum }}/{{ item.totalTaskNum }}
</div>
</div>
<div style="font-size: 18px; color: #0077ec">
{{ totalTaskCompletionRate(item) }}%
</div>
</div>
<el-progress
:stroke-width="6"
:show-text="false"
color="#0077EC"
:percentage="totalTaskCompletionRate(item)"
></el-progress>
</div>
<div class="growth-bg-icon">
<img src="@/assets/images/growth/growth-bg-icon.png" />
</div>
</div>
<template v-if="!list.length">
<div class="empty">
<el-empty :image-size="200"></el-empty>
</div>
</template>
</div>
<!-- <template v-if="!list || list.length < 2">
<div class="growth-right">
<div class="title">专业力必修</div>
<div
:key="item.id"
class="item-growth"
:class="activeItem.id == item.id ? 'active' : ''"
v-for="item of list"
>
<div class="icon"></div>
<div @click="handlerAnchor(item)">
{{ item.growthName }}
</div>
</div>
</div>
</template> -->
</div>
</template>
<script>
import { listData, getTotalGrowthTaskDetail } from "@/api/growth/growthpath";
export default {
data() {
return {
list: [],
loading: false,
totalData: {},
activeItem: {},
};
},
computed: {
// 完成进度
totalTaskCompletionRate() {
return (item) => {
if (item.totalTaskCompletedNum && item.totalTaskNum) {
let num = item.totalTaskCompletedNum / item.totalTaskNum;
num = num * 100;
num = num.toFixed(0);
return Number(num);
} else {
return 0;
}
};
},
// 选修进度
electiveTaskCompletionRate() {
return (item) => {
if (item.electiveTaskCompletedNum && item.electiveTaskNum) {
let num = item.electiveTaskCompletedNum / item.electiveTaskNum;
num = num * 100;
num = num.toFixed(0);
return Number(num);
} else if (item.electiveTaskNum == 0) {
return 100;
} else {
return 0;
}
};
},
// 必修进度
requiredTaskCompletionRate() {
return (item) => {
if (item.requiredTaskCompletedNum && item.requiredTaskNum) {
let num = item.requiredTaskCompletedNum / item.requiredTaskNum;
num = num * 100;
num = num.toFixed(0);
return Number(num);
} else {
return 0;
}
};
},
},
methods: {
select(item) {},
jumpRouter(item) {
let studentPath = process.env.VUE_APP_STUDENT_PATH;
let params = encodeURIComponent("routerId=" + item.id);
this.$router.push(
"/forward?to=" + studentPath + "/growthList&params=" + params
);
},
handlerAnchor(item, index) {
this.activeItem = item;
let dom = document.getElementById(`${item.id}`);
dom.classList.add("handler-add-padding");
dom.scrollIntoView({ behavior: "smooth" });
},
},
created() {
this.loading = true;
listData().then((res) => {
this.activeItem = res.data[0];
this.list = res.data;
this.loading = false;
});
getTotalGrowthTaskDetail().then((res) => {
this.totalData = res.data;
});
// 处理滚动事件的代码
let that = this;
window.addEventListener("scroll", function () {
let dom = document.getElementById(`${that.activeItem.id}`);
if (dom) {
dom.classList.remove("handler-add-padding");
}
});
},
};
</script>
<style lang="less" scoped>
.f-a-c {
display: flex;
align-items: center;
}
.f-j-b {
display: flex;
justify-content: space-between;
}
.f-j-c {
display: flex;
justify-content: center;
}
.growth {
.empty {
height: 600px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
}
.el-icon-question {
color: #387df7;
font-size: 16px;
margin-left: 5px;
}
.handler-add-padding {
padding: 100px 38px 66px 40px !important;
margin-top: -80px;
}
display: flex;
.growth-left {
margin-right: 10px;
flex: 1;
.growth-item {
background: #ffffff;
padding: 30px 38px 66px 40px;
margin-bottom: 35px;
position: relative;
.growth-bg-icon {
position: absolute;
right: 92px;
bottom: 0;
}
.progress-title {
// font-weight: 700;
font-size: 18px;
color: #000000;
margin: 28px 0 20px 0;
}
.progress-content {
margin-bottom: 28px;
}
.tip-title {
// font-weight: 700;
font-size: 18px;
color: #000000;
margin: 30px 0 10px 0;
}
.tip-content {
font-size: 16px;
color: #333333;
line-height: 30px;
}
.growth-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 28px;
border-bottom: 2px solid #f5f5f5;
}
.growth-name {
font-weight: bold;
font-size: 18px;
color: #0071ed;
}
.growth-state {
font-size: 15px;
margin-right: 18px;
}
.growth-btn {
display: flex;
justify-content: center;
align-items: center;
width: 120px;
height: 36px;
background: #f2903d;
border-radius: 4px;
font-size: 16px;
color: #ffffff;
cursor: pointer;
}
}
.navbar {
display: flex;
align-items: center;
height: 90px;
padding-left: 40px;
margin-bottom: 10px;
color: #ffffff;
background-image: url(../../assets/images/growth/growth-bg.png);
background-size: 100% 100%;
.nav-title {
font-weight: 700;
font-size: 18px;
margin-right: 10%;
}
.nav-total {
// margin-left: 15%;
width: 20%;
}
.total-num {
font-weight: bold;
font-size: 22px;
}
}
}
.growth-right {
width: 30%;
max-width: 464px;
margin-bottom: 35px;
background-color: #ffffff;
padding: 35px 20px 20px 30px;
.title {
font-size: 18px;
color: #000000;
font-weight: 700;
margin-bottom: 30px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.item-growth {
display: flex;
align-items: center;
border-radius: 5px;
font-size: 14px;
color: #666666;
padding: 10px 20px 10px 8px;
cursor: pointer;
}
.icon {
width: 4px;
height: 4px;
background: #4c5564;
border-radius: 50%;
margin-right: 10px;
}
.active {
background: #e3edfe;
color: #000;
.icon {
background: #0077ec;
}
}
}
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<div class="hot">
<div>
<div class="center">
<div class="item" :style="{marginRight:(i%2==0||i==0)?'49px':'0'}" v-for="item,i in imgData" :key="i">
<img class="img" @click="goLearn(item.url)" :src="require(`../../assets/images/hotforum/${item.img}.png`)" alt="">
</div>
</div>
<div style="display: flex;justify-content: center;margin-bottom: 52px;">
<img src="../../assets/images/hotforum/foot.png" alt="">
</div>
</div>
</div>
</template>
<script>
export default {
name: "hotforum",
data() {
return {
imgData:[
{img:'01',url:'1265897142383042560'},
{img:'02',url:'1265697724606210048'},
{img:'003',url:'1280185851054231552'},
{img:'04',url:'1321778585966247936'},
],
}
},
methods: {
goLearn(item){
if(item){
// this.$router.push({path:'/course/detail',query:{id:item}})
window.open(`https://u.boe.com/pc/course/detail?id=${item}`)
}
},
},
}
</script>
<style lang="scss" scoped>
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center{
max-width: 1270px;
max-height: 700px;
margin-bottom: 5%;
margin-top: 22%;
display: flex;
flex-wrap: wrap;
.item{
width: 610px;
height: 330px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 14px;
padding-top: 31px;
margin-bottom: 59px;
cursor: pointer;
.img{
width: 581px;
height: 283px;
}
}
}
/* 当窗口宽度大于3068px时的样式 */
}
@media (min-width: 3000px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 2560px;
max-height: 1300px;
margin-bottom: 5%;
margin-top: 30vh;
display: flex;
flex-wrap: wrap;
.item {
width: 1220px;
height: 660px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 28px;
padding-top: 62px;
margin-bottom: 59px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
@media (min-height: 1500px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 1068px;
max-height: 580px;
margin-bottom: 5%;
margin-top: 30vh;
display: flex;
flex-wrap: wrap;
.item {
width: 500px;
height: 271px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 14px;
padding-top: 26px;
margin-bottom: 30px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
@media (min-width: 1928px) and (max-width: 3000px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 1800px;
max-height: 1100px;
margin-bottom: 5%;
margin-top: 35vh;
display: flex;
flex-wrap: wrap;
.item {
width: 860px;
height: 466px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 28px;
padding-top: 48px;
margin-bottom: 70px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
</style>

View File

@@ -1,7 +1,24 @@
<template>
<div class="hot">
<div class="hot25">
<div>
<div class="center">
<div class="title25" style="">
<img class="img" src="../../assets/images/hotforum/2025.png" alt="">
</div>
<div class="center" style="margin-top: 60px;">
<div class="item" style="margin-right: 49px;">
<img class="img" @click="goLearn('1351506180295131136')" src="../../assets/images/hotforum/2501.jpg" alt="">
</div>
<div class="item">
<img class="img" @click="goLearn('1375146833375027200')" src="../../assets/images/hotforum/2503.png" alt="">
</div>
</div>
<div class="line" style="margin: 0 auto;margin-top: 60px;width: 100%;text-align: center;">
<img class="img" src="../../assets/images/hotforum/line.png" alt="">
</div>
<div class="title24" style="margin: 0 auto;margin-top: 60px;width: 100%;text-align: center;">
<img class="img" src="../../assets/images/hotforum/2024.png" alt="">
</div>
<div class="center" style="margin-top: 60px;">
<div class="item" :style="{marginRight:(i%2==0||i==0)?'49px':'0'}" v-for="item,i in imgData" :key="i">
<img class="img" @click="goLearn(item.url)" :src="require(`../../assets/images/hotforum/${item.img}.png`)" alt="">
</div>
@@ -11,11 +28,12 @@
</div>
</div>
</div>
</template>
<script>
export default {
name: "hotforum",
name: "hotforumNew",
data() {
return {
imgData:[
@@ -24,6 +42,10 @@
{img:'003',url:'1280185851054231552'},
{img:'04',url:'1321778585966247936'},
],
imgData25:[
{img:'2501',url:'1351506180295131136'},
{img:'2503',url:'1375146833375027200'},
],
}
},
methods: {
@@ -38,20 +60,22 @@
</script>
<style lang="scss" scoped>
.hot{
.hot25{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
background: url("../../assets/images/hotforum/back25.png") no-repeat;
background-size: cover;
display: flex;
justify-content: center;
.title25{
margin: 0 auto;margin-top: 100px;width: 100%;text-align: center;
}
.center{
max-width: 1270px;
max-height: 700px;
margin-bottom: 5%;
margin-top: 22%;
display: flex;
flex-wrap: wrap;
.item{
@@ -71,109 +95,109 @@
}
/* 当窗口宽度大于3068px时的样式 */
}
@media (min-width: 3000px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 2560px;
max-height: 1300px;
margin-bottom: 5%;
margin-top: 30vh;
display: flex;
flex-wrap: wrap;
// @media (min-width: 3000px) {
// .hot{
// width: 100%;
// // max-width: 1920px;
// min-height: 100%;
// // min-height: 1373px;
// background: url("../../assets/images/hotforum/back.jpg") no-repeat;
// background-size: 100% 100%;
// display: flex;
// justify-content: center;
// .center {
// max-width: 2560px;
// max-height: 1300px;
// margin-bottom: 5%;
// margin-top: 30vh;
// display: flex;
// flex-wrap: wrap;
.item {
width: 1220px;
height: 660px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 28px;
padding-top: 62px;
margin-bottom: 59px;
// .item {
// width: 1220px;
// height: 660px;
// background: url("../../assets/images/hotforum/border.png") no-repeat;
// background-size: 100%;
// padding: 28px;
// padding-top: 62px;
// margin-bottom: 59px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
@media (min-height: 1500px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 1068px;
max-height: 580px;
margin-bottom: 5%;
margin-top: 30vh;
display: flex;
flex-wrap: wrap;
// .img {
// width: 100%; // 图片宽度占满item宽度
// height: auto; // 自动调整高度
// }
// }
// }
// }
// }
// @media (min-height: 1500px) {
// .hot{
// width: 100%;
// // max-width: 1920px;
// min-height: 100%;
// // min-height: 1373px;
// background: url("../../assets/images/hotforum/back.jpg") no-repeat;
// background-size: 100% 100%;
// display: flex;
// justify-content: center;
// .center {
// max-width: 1068px;
// max-height: 580px;
// margin-bottom: 5%;
// margin-top: 30vh;
// display: flex;
// flex-wrap: wrap;
.item {
width: 500px;
height: 271px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 14px;
padding-top: 26px;
margin-bottom: 30px;
// .item {
// width: 500px;
// height: 271px;
// background: url("../../assets/images/hotforum/border.png") no-repeat;
// background-size: 100%;
// padding: 14px;
// padding-top: 26px;
// margin-bottom: 30px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
@media (min-width: 1928px) and (max-width: 3000px) {
.hot{
width: 100%;
// max-width: 1920px;
min-height: 100%;
// min-height: 1373px;
background: url("../../assets/images/hotforum/back.jpg") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
.center {
max-width: 1800px;
max-height: 1100px;
margin-bottom: 5%;
margin-top: 35vh;
display: flex;
flex-wrap: wrap;
// .img {
// width: 100%; // 图片宽度占满item宽度
// height: auto; // 自动调整高度
// }
// }
// }
// }
// }
// @media (min-width: 1928px) and (max-width: 3000px) {
// .hot{
// width: 100%;
// // max-width: 1920px;
// min-height: 100%;
// // min-height: 1373px;
// background: url("../../assets/images/hotforum/back.jpg") no-repeat;
// background-size: 100% 100%;
// display: flex;
// justify-content: center;
// .center {
// max-width: 1800px;
// max-height: 1100px;
// margin-bottom: 5%;
// margin-top: 35vh;
// display: flex;
// flex-wrap: wrap;
.item {
width: 860px;
height: 466px;
background: url("../../assets/images/hotforum/border.png") no-repeat;
background-size: 100%;
padding: 28px;
padding-top: 48px;
margin-bottom: 70px;
// .item {
// width: 860px;
// height: 466px;
// background: url("../../assets/images/hotforum/border.png") no-repeat;
// background-size: 100%;
// padding: 28px;
// padding-top: 48px;
// margin-bottom: 70px;
.img {
width: 100%; // 图片宽度占满item宽度
height: auto; // 自动调整高度
}
}
}
}
}
// .img {
// width: 100%; // 图片宽度占满item宽度
// height: auto; // 自动调整高度
// }
// }
// }
// }
// }
</style>

View File

@@ -0,0 +1,873 @@
<template>
<div>
<!-- 最大化状态的弹窗 -->
<el-dialog
v-show=" windowState === 'maximized'"
v-if="dialogVisible"
:visible="true"
:close-on-click-modal="false"
:show-close="true"
@close="onClose"
class="case-expert-dialog"
:modal="false"
:append-to-body="true"
:fullscreen="false"
top="10vh"
v-resizeable
v-draggable
>
<!-- 标题 -->
<div slot="title" class="dialog-title">
<span>案例专家</span>
<el-button
style="color:#96999f"
type="text"
class="window-control-btn"
@click="minimizeWindow"
>
<i class="el-icon-minus"></i>
</el-button>
</div>
<!-- 内容区域 -->
<div class="content-wrapper">
<div
class="welcome-message"
ref="messageContainer"
@scroll="handleScroll"
>
<div class="message-text" v-for="(item, index) in messageList" :key="index">
<messages :messageData="item" :suggestions="suggestions" @getMinWindow="minimizeWindow"></messages>
</div>
<div class="message-suggestions" v-if="messageList.length > 0 && messageList[messageList.length-1].textCompleted">
<div class="suggestion-item" v-for="(item, index) in suggestions" :key="index">
<a @click="sendSuggestions(item)"> {{ item }} </a>
</div>
</div>
<div v-if="isLoading" class="loading-message">
<div class="loading-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
<!-- 输入框区域 -->
<send-message
v-model="AIContent"
:message-list="messageList"
:suggestions="suggestions"
@loading="handleLoading"
@update-message="updateMessage"
@update-suggestions="updateSuggestions"
@new-conversation="startNewConversation"
:disabled="isLoading"
class="input-area-wrapper"
ref="sendMessage"
/>
</div>
</el-dialog>
<!-- 最小化状态的弹窗 -->
<div
class="minimized-window"
v-show="windowState === 'minimized' && showMinimizedWindow"
@click="onMinimizedWindowClick"
>
<div class="minimized-content">
<span class="window-title">案例专家</span>
<div style="display: flex;align-items: center">
<el-button
type="text"
class="window-control-btn"
@click.stop="onMinimizedWindowClick"
>
<img :src="openImg" alt="" style="width: 17px">
</el-button>
<el-button
style="margin-left: 1px;color:#96999f"
type="text"
class="window-control-btn"
@click.stop="closeMinimizedWindow"
>
<i class="el-icon-close"></i>
</el-button>
</div>
</div>
<div class="minimized-message">
<div v-if="messageList.length <= 1 && messageList[0].isBot">
当前暂无对话内容去创建对话吧
</div>
<div v-else>
{{ getLastUserMessage() }}
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import messages from './components/messages.vue'
import sendMessage from './components/sendMessage.vue'
import openImg from './components/open.png'
export default {
name: 'CaseExpertDialog',
props: {
dialogVisible: {
type: Boolean,
default: false
}
},
components: {
messages,
sendMessage
},
directives: {
draggable: {
bind(el, binding, vnode) {
vnode.context.$nextTick(() => {
const dialogEl = el.querySelector('.el-dialog');
if (!dialogEl) return;
const headerEl = dialogEl.querySelector('.dialog-title');
if (!headerEl) return;
// 检查是否有保存的位置状态
const savedPosition = sessionStorage.getItem('aiCallDialogPosition');
if (savedPosition) {
const { left, top } = JSON.parse(savedPosition);
dialogEl.style.left = left + 'px';
dialogEl.style.top = top + 'px';
} else {
// 设置初始样式
dialogEl.style.position = 'fixed';
dialogEl.style.top = '100px';
dialogEl.style.left = (window.innerWidth - dialogEl.offsetWidth) / 2 + 'px';
}
dialogEl.style.margin = '0';
let isDragging = false;
let startX = 0;
let startY = 0;
let startLeft = 0;
let startTop = 0;
const startDrag = (event) => {
// 只有在标题栏上按下鼠标才开始拖动
if (event.target.closest('.resize-handle')) {
return; // 如果点击的是resize-handle则不触发拖动
}
isDragging = true;
startX = event.clientX;
startY = event.clientY;
startLeft = parseInt(dialogEl.style.left) || dialogEl.offsetLeft;
startTop = parseInt(dialogEl.style.top) || dialogEl.offsetTop;
event.preventDefault();
event.stopPropagation();
// 添加全局事件监听
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', stopDrag);
};
const handleMouseMove = (event) => {
if (!isDragging) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
dialogEl.style.left = (startLeft + deltaX) + 'px';
dialogEl.style.top = (startTop + deltaY) + 'px';
};
const stopDrag = () => {
isDragging = false;
// 保存当前位置到 sessionStorage
const currentPosition = {
left: parseInt(dialogEl.style.left),
top: parseInt(dialogEl.style.top)
};
sessionStorage.setItem('aiCallDialogPosition', JSON.stringify(currentPosition));
// 移除全局事件监听
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', stopDrag);
};
// 为标题栏绑定拖动事件
headerEl.addEventListener('mousedown', startDrag);
});
}
},
resizeable: {
bind(el, binding, vnode) {
// 确保元素已插入DOM
vnode.context.$nextTick(() => {
const dialogEl = el.querySelector('.el-dialog');
if (!dialogEl) return;
// 检查是否有保存的尺寸状态
const savedSize = sessionStorage.getItem('aiCallDialogSize');
if (savedSize) {
const { width, height, left, top } = JSON.parse(savedSize);
dialogEl.style.width = width + 'px';
dialogEl.style.height = height + 'px';
dialogEl.style.left = left + 'px';
dialogEl.style.top = top + 'px';
} else {
// 设置初始样式
dialogEl.style.position = 'fixed';
dialogEl.style.top = '100px';
dialogEl.style.left = (window.innerWidth - dialogEl.offsetWidth) / 2 + 'px';
}
// 创建拖拽手柄
const createHandle = (direction) => {
const handle = document.createElement('div');
handle.className = `resize-handle ${direction}`;
handle.style.position = 'absolute';
handle.style.zIndex = '10';
switch (direction) {
case 'left':
case 'right':
handle.style.width = '6px';
handle.style.height = '100%';
handle.style.top = '0';
handle.style.cursor = 'ew-resize';
break;
case 'top':
case 'bottom':
handle.style.width = '100%';
handle.style.height = '6px';
handle.style.left = '0';
handle.style.cursor = 'ns-resize';
break;
case 'top-left':
case 'top-right':
case 'bottom-left':
case 'bottom-right':
handle.style.width = '10px';
handle.style.height = '10px';
handle.style.zIndex = '20';
break;
}
switch (direction) {
case 'left':
handle.style.left = '0';
break;
case 'right':
handle.style.right = '0';
break;
case 'top':
handle.style.top = '0';
break;
case 'bottom':
handle.style.bottom = '0';
break;
case 'top-left':
handle.style.top = '0';
handle.style.left = '0';
handle.style.cursor = 'nw-resize';
break;
case 'top-right':
handle.style.top = '0';
handle.style.right = '0';
handle.style.cursor = 'ne-resize';
break;
case 'bottom-left':
handle.style.bottom = '0';
handle.style.left = '0';
handle.style.cursor = 'sw-resize';
break;
case 'bottom-right':
handle.style.bottom = '0';
handle.style.right = '0';
handle.style.cursor = 'se-resize';
break;
}
// 防止拖拽手柄的事件冒泡到标题栏
handle.addEventListener('mousedown', (e) => {
e.stopPropagation();
});
dialogEl.appendChild(handle);
return handle;
};
// 创建8个拖拽手柄
const handles = {
left: createHandle('left'),
right: createHandle('right'),
top: createHandle('top'),
bottom: createHandle('bottom'),
topLeft: createHandle('top-left'),
topRight: createHandle('top-right'),
bottomLeft: createHandle('bottom-left'),
bottomRight: createHandle('bottom-right')
};
// 添加拖拽事件处理
let isResizing = false;
let resizeDirection = '';
let startX = 0;
let startY = 0;
let startWidth = 0;
let startHeight = 0;
let startLeft = 0;
let startTop = 0;
const startResize = (direction, event) => {
event.preventDefault();
event.stopPropagation();
isResizing = true;
resizeDirection = direction;
startX = event.clientX;
startY = event.clientY;
startWidth = dialogEl.offsetWidth;
startHeight = dialogEl.offsetHeight;
// 统一使用计算后的样式值
startLeft = parseInt(dialogEl.style.left) || 0;
startTop = parseInt(dialogEl.style.top) || 0;
// 添加全局事件监听
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', stopResize);
};
const handleMouseMove = (event) => {
if (!isResizing) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
let newWidth, newHeight, newLeft, newTop;
switch (resizeDirection) {
case 'right':
newWidth = Math.max(400, startWidth + deltaX);
dialogEl.style.width = newWidth + 'px';
break;
case 'left':
newWidth = Math.max(400, startWidth - deltaX);
newLeft = startLeft + startWidth - newWidth;
dialogEl.style.width = newWidth + 'px';
dialogEl.style.left = newLeft + 'px';
break;
case 'bottom':
newHeight = Math.max(600, startHeight + deltaY);
dialogEl.style.height = newHeight + 'px';
break;
case 'top':
// 当窗口高度达到最小值时,不再调整高度和位置
if (startHeight - deltaY >= 600) {
newHeight = startHeight - deltaY;
newTop = startTop + deltaY;
dialogEl.style.height = newHeight + 'px';
dialogEl.style.top = newTop + 'px';
}
break;
case 'bottom-right':
newWidth = Math.max(400, startWidth + deltaX);
newHeight = Math.max(600, startHeight + deltaY);
dialogEl.style.width = newWidth + 'px';
dialogEl.style.height = newHeight + 'px';
break;
case 'bottom-left':
newWidth = Math.max(400, startWidth - deltaX);
newHeight = Math.max(600, startHeight + deltaY);
newLeft = startLeft + startWidth - newWidth;
dialogEl.style.width = newWidth + 'px';
dialogEl.style.left = newLeft + 'px';
dialogEl.style.height = newHeight + 'px';
break;
case 'top-right':
// 当窗口高度达到最小值时,不再调整高度和位置
if (startHeight - deltaY >= 600) {
newHeight = startHeight - deltaY;
newTop = startTop + deltaY;
newWidth = Math.max(400, startWidth + deltaX);
dialogEl.style.height = newHeight + 'px';
dialogEl.style.top = newTop + 'px';
dialogEl.style.width = newWidth + 'px';
}
break;
case 'top-left':
// 当窗口高度达到最小值时,不再调整高度和位置
if (startHeight - deltaY >= 600) {
newHeight = startHeight - deltaY;
newTop = startTop + deltaY;
newWidth = Math.max(400, startWidth - deltaX);
newLeft = startLeft + startWidth - newWidth;
dialogEl.style.height = newHeight + 'px';
dialogEl.style.top = newTop + 'px';
dialogEl.style.width = newWidth + 'px';
dialogEl.style.left = newLeft + 'px';
}
break;
}
let doc = document.querySelector('.welcome-message')
let sendBox = document.querySelector('.input-area-wrapper');
// sendBox 的高度
if (doc && sendBox) {
doc.style.height = `calc(${dialogEl.style.height} - ${sendBox.offsetHeight}px - 120px)`;
}
};
const stopResize = () => {
isResizing = false;
resizeDirection = '';
// 保存当前尺寸和位置到 sessionStorage
const currentSize = {
width: parseInt(dialogEl.style.width),
height: parseInt(dialogEl.style.height),
left: parseInt(dialogEl.style.left),
top: parseInt(dialogEl.style.top)
};
sessionStorage.setItem('aiCallDialogSize', JSON.stringify(currentSize));
// 移除全局事件监听
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', stopResize);
};
// 为每个手柄绑定事件
handles.left.addEventListener('mousedown', (e) => startResize('left', e));
handles.right.addEventListener('mousedown', (e) => startResize('right', e));
handles.top.addEventListener('mousedown', (e) => startResize('top', e));
handles.bottom.addEventListener('mousedown', (e) => startResize('bottom', e));
handles.topLeft.addEventListener('mousedown', (e) => startResize('top-left', e));
handles.topRight.addEventListener('mousedown', (e) => startResize('top-right', e));
handles.bottomLeft.addEventListener('mousedown', (e) => startResize('bottom-left', e));
handles.bottomRight.addEventListener('mousedown', (e) => startResize('bottom-right', e));
});
}
}
},
computed: {
...mapState('app', ['showAICallMinimized']),
showMinimizedWindow() {
// 只有在Vuex状态为true时才显示最小化窗口
return this.showAICallMinimized;
}
},
data() {
return {
openImg,
AIContent: '',
isLoading: false,
windowState: 'maximized', // 'maximized' 或 'minimized'
messageList: [
{
typing:true,
isBot: true, // 是否为机器人
text: `<p><b>您好!我是京东方案例智能问答助手,随时为您服务。</b></p>
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
}
],
suggestions:[],
isAutoScroll: true, // 是否自动滚动
// 添加一个标志位,用于标识组件是否已经初始化完成
isComponentReady: false
}
},
mounted() {
// 组件挂载完成后,标记为已准备就绪
this.$nextTick(() => {
this.isComponentReady = true;
});
},
watch: {
dialogVisible: {
handler(newVal) {
if (newVal) {
this.$nextTick(() => {
// 获取对话框元素
const dialogEl = document.querySelector('.case-expert-dialog .el-dialog');
if (dialogEl) {
// 检查是否有保存的尺寸状态
const savedSize = sessionStorage.getItem('aiCallDialogSize');
if (savedSize) {
const { width, height, left, top } = JSON.parse(savedSize);
dialogEl.style.width = width + 'px';
dialogEl.style.height = height + 'px';
dialogEl.style.left = left + 'px';
dialogEl.style.top = top + 'px';
}
// 检查是否有保存的位置状态
const savedPosition = sessionStorage.getItem('aiCallDialogPosition');
if (savedPosition) {
const { left, top } = JSON.parse(savedPosition);
dialogEl.style.left = left + 'px';
dialogEl.style.top = top + 'px';
}
}
let doc = document.querySelector('.welcome-message')
let sendBox = document.querySelector('.input-area-wrapper');
// 只有在没有保存的尺寸状态时才使用默认值
if (doc && sendBox) {
const savedSize = sessionStorage.getItem('aiCallDialogSize');
if (!savedSize) {
doc.style.height = `calc(600px - ${sendBox.offsetHeight}px - 120px)`;
} else {
const { height } = JSON.parse(savedSize);
doc.style.height = `calc(${height}px - ${sendBox.offsetHeight}px - 120px)`;
}
}
});
}
},
immediate: true
},
messageList: {
handler() {
// 只有在组件准备就绪后才执行滚动操作
if (this.isComponentReady) {
this.$nextTick(() => {
this.scrollToBottom();
});
}
},
deep: true
}
},
methods: {
// / 关闭最小化窗口
closeMinimizedWindow() {
this.$store.commit('app/SET_SHOW_AI_CALL_MINIMIZED', false);
this.$store.commit('app/SET_SHOW_AI_CALL', false);
this.windowState = 'maximized';
},
getMinWidow(vis){
// this.showAICallMinimized = vis
this.windowState = 'minimized';
},
onClose() {
console.log('关闭弹窗')
// 清除保存的状态
sessionStorage.removeItem('aiCallDialogSize');
sessionStorage.removeItem('aiCallDialogPosition');
this.$emit('close')
// 可以在这里执行其他逻辑
},
minimizeWindow() {
this.windowState = 'minimized';
this.$store.commit('app/SET_SHOW_AI_CALL_MINIMIZED', true);
},
maximizeWindow() {
this.windowState = 'maximized';
},
getLastUserMessage() {
// 从后往前找用户消息
for (let i = this.messageList.length - 1; i >= 0; i--) {
if (!this.messageList[i].isBot) {
// 移除HTML标签只返回纯文本
const tempDiv = document.createElement('div');
tempDiv.innerHTML = this.messageList[i].text;
return tempDiv.textContent || tempDiv.innerText || '';
}
}
return '';
},
// 处理加载状态
handleLoading(status) {
this.isLoading = status;
},
// 更新消息
updateMessage(message) {
// 由于Vue的响应式系统message对象的更改会自动更新视图
// 这里不需要额外的操作
},
updateSuggestions(arr){
this.suggestions = arr
},
// 处理建议
sendSuggestions(item){
// this.suggestions = []
this.AIContent = item
setTimeout(()=>{
this.$refs.sendMessage.handleSend()
this.AIContent = ''
},500)
},
startNewConversation() {
// 重置对话时,先标记组件为未准备就绪状态
this.isComponentReady = false;
this.messageList = [
{
isBot: true,
text: `<p><b>您好!我是京东方案例智能问答助手,随时为您服务。</b></p>
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
}
];
this.AIContent = '';
this.isLoading = false;
// 在下一个 tick 中重新标记为准备就绪
this.$nextTick(() => {
this.isComponentReady = true;
});
},
// 处理滚动事件
handleScroll(event) {
const element = event.target;
// 判断是否滚动到底部
const isAtBottom = element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
// 如果滚动到底部,则开启自动滚动
// 如果离开底部,则关闭自动滚动
this.isAutoScroll = isAtBottom;
},
// 滚动到底部
scrollToBottom() {
if (this.isAutoScroll && this.$refs.messageContainer) {
this.$refs.messageContainer.scrollTop = this.$refs.messageContainer.scrollHeight;
}
},
// 最小化窗口的点击事件处理方法
onMinimizedWindowClick() {
// 当点击最小化窗口时如果dialogVisible为false则通过事件通知父组件显示对话框
if (!this.dialogVisible) {
this.$emit('restore');
}
// 然后将窗口状态设置为最大化
this.windowState = 'maximized';
}
}
}
</script>
<style scoped lang="scss">
.case-expert-dialog {
::v-deep .el-dialog{
background: url("./components/u762.svg") no-repeat ;
background-size: cover;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
//background-color: rgba(255, 255, 255, 0.8);
}
::v-deep .el-dialog__body{
padding: 10px;
flex:1;
//font-size: 12px;
*{
font-size:unset ;
}
}
.dialog-title {
background: transparent;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: 16px;
font-weight: 600;
color: #333;
padding-right: 20px;
cursor: move; /* 添加拖动样式 */
.icon {
width: 24px;
height: 24px;
}
.window-control-btn {
font-size: 18px;
padding: 5px 10px;
color: #333; /* 黑色图标 */
}
}
.message-suggestions{
display: flex;
flex-direction: column;
.suggestion-item{
cursor: pointer;
float: right;
padding: 5px 15px;
box-sizing: border-box;
background-color: rgba(228, 231, 237, 1);
border-radius: 5px;
margin-bottom: 5px;
}
}
.content-wrapper {
padding: 20px;
background-color: transparent;
border-radius: 8px;
min-height: 500px;
height:100%;
position: relative;
//margin-bottom: 20px;
display: flex;
flex-direction: column;
.welcome-message {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 10px;
height: 400px;
//flex:1;
overflow-y: auto;
.avatar {
margin-right: 12px;
img {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #007aff;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
}
}
.message-text {
width: 100%;
//margin-bottom: 15px;
p {
color: #333;
//font-size: 14px;
line-height: 1.6;
margin: 8px 0;
}
}
.loading-message {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 15px;
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #007aff;
margin-right: 12px;
}
.loading-dots {
display: flex;
align-items: center;
span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #999;
margin-right: 5px;
animation: loading 1.4s infinite ease-in-out both;
&:nth-child(1) {
animation-delay: -0.32s;
}
&:nth-child(2) {
animation-delay: -0.16s;
}
}
}
}
}
.input-area-wrapper {
//position: absolute;
//bottom: 10px;
//width: calc(100% - 40px);
}
@keyframes loading {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
}
}
.minimized-window {
position: fixed;
right: 20px;
bottom: 20px;
width: 300px;
background: url("./components/u762.svg") no-repeat;
background-size: cover;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 2000;
cursor: pointer;
.minimized-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
border-bottom: 1px solid #eee;
.window-title {
font-weight: 600;
color: #333;
}
.window-control-btn {
font-size: 16px;
padding: 3px 8px;
color: #000000; /* 黑色图标 */
}
}
.minimized-message {
padding: 15px;
font-size: 14px;
color: #666;
min-height: 60px;
display: flex;
align-items: center;
}
}
</style>

View File

@@ -1,7 +1,9 @@
<template>
<div id="case-list-content">
<div style="margin-bottom:30px" class="case-banner">
<portal-header current="case" textColor="#fff" :goSearch="2"></portal-header>
<portal-header current="case" textColor="#fff" :goSearch="2">
</portal-header>
</div>
<div class="">
<div class="xcontent2">
@@ -20,7 +22,7 @@
</div>
<div class="label">
<author :aid="caseDetail.sysCreateAid" :onlyAvatar="true" :avatar="authorInfo.avatar"
:sex="authorInfo.sex"></author>
:sex="authorInfo.sex" :name="authorInfo.name"></author>
<span>案主{{ authorInfo.name }} ({{ authorInfo.orgInfo }})</span>
<!-- <span>案主{{ authorInfo.name }}</span>
<span>工号{{ authorInfo.code }}</span>
@@ -109,6 +111,7 @@
</div>
</div>
<div class="xcontent2-minor" :style="{ display: zoomShow ? '' : 'none' }">
<AICaseConsult />
<div id="fixd-box">
<router-link class="the_charts" to="/case/charts">
<div class="text">排行榜</div>
@@ -237,9 +240,10 @@ import { formatDate } from "@/utils/datetime.js"
import { cutFullName } from "@/utils/tools.js";
import apiPlace from "@/api/phase2/place.js"
import portalFloatTools from "@/components/PortalFloatTools.vue";
import AICaseConsult from "@/views/portal/case/components/AICaseConsult.vue";
export default {
name: 'atticle',
components: { portalHeader, portalFloatTools, portalFooter, interactBar, author, comments, pdfPreview },
components: {AICaseConsult, portalHeader, portalFloatTools, portalFooter, interactBar, author, comments, pdfPreview },
computed: {
...mapGetters(['userInfo'])
},
@@ -651,6 +655,9 @@ export default {
});
},
getCaseData() {
// 清空pdf数据
this.pdfPath = '';
let $this = this;
apiCase.details(this.resolveId, true).then(res => {
if (res.status == 200) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
<template>
<div>
<div class="AI-case" style="position: relative; margin-bottom: 10px;" v-if="showAiCase" @click.stop="getAICase()">
<img src="../../../../../public/images/case-logo.png" alt="">
<span @click="getAICase()" style="position: absolute; bottom: 65px;left: 15px;z-index: 1;width: 40%;height: 30px;"></span>
</div>
<!-- 移除直接使用的AICall组件 -->
</div>
</template>
<script>
import { showCaseAiEntrance } from '@/api/boe/aiChat.js'
import { mapState } from 'vuex'
export default {
name: 'AICaseConsult',
data() {
return {
showAiCase: false
}
},
computed: {
// 从Vuex中获取showAICall状态虽然当前组件不使用但保持连接
...mapState('app', ['showAICall'])
},
mounted() {
this.getShowAiCase()
},
methods: {
// 是否展示入口
getShowAiCase() {
showCaseAiEntrance().then(res => {
this.showAiCase = res.result
})
},
// 案例立即咨询
getAICase() {
// 通过Vuex控制AICall组件显示
this.$store.dispatch('app/setShowAICall', true)
}
}
}
</script>
<style scoped lang="scss">
.AI-case {
margin-bottom: 10px;
position: relative;
img {
width: 100%;
}
span {
width: 160px;
height: 40px;
position: absolute;
left: 20px;
top: 105px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759024984858" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4686" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="25"><path d="M451.673935 994.395699C478.883834 1025.019147 524.254807 1024.808979 551.400292 993.928851 553.755808 991.387908 558.821323 985.796762 565.872444 977.84835 577.572838 964.659017 590.597131 949.62432 604.615947 932.998315 644.662065 885.504506 684.708678 834.717818 722.129538 782.646447 759.658524 730.424619 792.492213 679.709274 819.314991 631.458462 868.685946 542.646317 896 465.543426 896 402.285715 896 180.109449 719.301715 0 501.333333 0 283.364952 0 106.666667 180.109449 106.666667 402.285715 106.666667 465.598716 134.05152 542.80573 183.54613 631.762622 210.371803 679.976529 243.193308 730.651876 280.699364 782.833154 318.155192 834.94455 358.239268 885.77421 398.322835 933.311031 412.354743 949.952073 425.391185 965.00073 437.102468 978.202579 444.160087 986.158466 449.230214 991.754921 451.982775 994.736706L451.673935 994.395699ZM486.822684 961.321348C484.281231 958.568254 479.425084 953.207989 472.585916 945.498359 461.135889 932.591017 448.364015 917.847761 434.602351 901.527215 395.275714 854.888073 355.949587 805.019548 319.289224 754.014863 282.808749 703.260452 250.983685 654.123578 225.158316 607.707522 179.388826 525.445805 154.50505 455.290161 154.50505 402.285715 154.50505 207.039905 309.785362 48.761905 501.333333 48.761905 692.881306 48.761905 848.161617 207.039905 848.161617 402.285715 848.161617 455.246022 823.345286 525.298263 777.693969 607.419251 751.873483 653.867066 720.038415 703.039925 683.537446 753.831262 646.912604 804.794967 607.624538 854.619674 568.335977 901.215038 554.587654 917.520243 541.828177 932.24925 530.389289 945.143797 523.556841 952.845711 518.705521 958.200435 516.166694 960.950526 507.543772 970.748911 495.255793 970.80583 487.131524 961.662353L486.822684 961.321348Z" fill="#979797" p-id="4687"></path><path d="M714.955981 467.028806C723.919106 442.627955 728.565658 416.668998 728.565658 390.095238 728.565658 268.908183 632.184774 170.666667 513.29293 170.666667 394.401086 170.666667 298.020202 268.908183 298.020202 390.095238 298.020202 511.282291 394.401086 609.52381 513.29293 609.52381 549.003859 609.52381 583.510052 600.631947 614.373097 583.874409 626.032316 577.543868 630.449257 562.77782 624.238611 550.893519 618.027966 539.009218 603.541579 534.507006 591.882359 540.837549 567.900883 553.858639 541.111735 560.761905 513.29293 560.761905 420.821495 560.761905 345.858586 484.351836 345.858586 390.095238 345.858586 295.838641 420.821495 219.428572 513.29293 219.428572 605.764365 219.428572 680.727273 295.838641 680.727273 390.095238 680.727273 410.807981 677.117041 430.977316 670.154965 449.930592 665.522846 462.540883 671.796821 476.591108 684.168282 481.312651 696.53974 486.034191 710.323861 479.639095 714.955981 467.028806L714.955981 467.028806Z" fill="#979797" p-id="4688"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,423 @@
<template>
<div class="messages">
<!-- 机器人消息 -->
<div v-if="messageData.isBot" class="bot-message">
<!-- 思考中提示 -->
<div v-if="messageData.thinkText" class="bot-think" v-katex:auto v-html="md.render(messageData.thinkText)"></div>
<!-- 主要回复内容 -->
<div
ref="contentContainer"
class="message-content"
v-katex:auto
v-html="md.render(displayText)"
></div>
<!-- 引用案例 -->
<div v-if="messageData.caseRefers && messageData.caseRefers.length > 0 && messageData.textCompleted" class="case-refers">
<div class="case-refers-title">
<span><i class="iconfont icon-think"></i> 引用案例</span>
<span v-if="shouldShowMoreButton" class="more" @click="toggleShowAllCaseRefers">
{{ showAllCaseRefers ? '收起' : '查看更多' }}
</span>
</div>
<div class="case-refers-list">
<div
v-for="item in displayedCaseRefers"
:key="item.caseId"
class="case-refers-item"
>
<div class="case-refers-item-title">
<a @click="toUrl(item)" class="title">{{ item.title }}</a>
<span class="case-refers-item-timer">{{ item.uploadTime }}</span>
</div>
<div class="case-refers-item-author">
<span class="user"></span>
<span>{{ item.authorName }}</span>
<span class="case-inter-orginInfo">{{ item.orgInfo }}</span>
</div>
<div class="case-refers-item-keywords">
<span v-for="keyword in item.keywords" :key="keyword">{{ keyword }}</span>
</div>
<div class="message-content case-content" v-html="md.render(item.content)"></div>
</div>
</div>
</div>
</div>
<!-- 用户消息 -->
<div v-else class="user-message">
<div class="message-text" v-html="messageData.text"></div>
</div>
<!-- 推荐问题 -->
<!-- <div v-if="suggestions && suggestions.length > 0" class="suggestions">-->
<!-- <div class="suggestions-title">💡 推荐问题</div>-->
<!-- <div class="suggestions-list">-->
<!-- <button-->
<!-- v-for="(item, index) in suggestions"-->
<!-- :key="index"-->
<!-- class="suggestions-item"-->
<!-- @click="$emit('suggestion-click', item)"-->
<!-- >-->
<!-- {{ item }}-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
</div>
</template>
<script>
import MarkdownIt from 'markdown-it';
import highlight from 'markdown-it-highlightjs';
import 'highlight.js/styles/a11y-dark.css';
import markdownItMermaid from 'markdown-it-mermaid';
import mermaid from 'mermaid';
// 初始化 Mermaid
mermaid.initialize({ startOnLoad: false });
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
md.use(highlight).use(markdownItMermaid);
export default {
name: 'Message',
props: {
messageData: {
type: Object,
required: true,
default: () => ({}),
},
suggestions: {
type: Array,
default: () => [],
},
},
data() {
return {
md,
displayText: '',
typingTimer: null,
typingSpeed: 30, // 毫秒/字符
showAllCaseRefers: false,
};
},
computed: {
displayedCaseRefers() {
if (this.showAllCaseRefers || !this.messageData.caseRefers) {
return this.messageData.caseRefers || [];
}
return this.messageData.caseRefers.slice(0, 3);
},
shouldShowMoreButton() {
return this.messageData.caseRefers && this.messageData.caseRefers.length > 3;
},
},
watch: {
'messageData.text': {
handler(newVal) {
if (!newVal) {
this.displayText = '';
return;
}
if (this.messageData.isBot && !this.messageData.typing) {
// this.startTyping(newVal); // 启动打字机效果/**/
this.displayText = newVal || ''
} else {
this.displayText = this.md.render(newVal);
this.$nextTick(this.renderMermaid); // 直接渲染 Mermaid
}
},
immediate: true,
},
},
methods: {
toUrl(item) {
this.$router.push({
path: '/case/detail',
query: { id: item.caseId },
});
this.$emit('getMinWindow')
},
// 正确的打字机效果:先整体渲染 Markdown再逐字显示 HTML
startTyping(fullText) {
const renderedText = this.md.render(fullText);
this.displayText = '';
let index = 0;
if (this.typingTimer) {
clearInterval(this.typingTimer);
}
this.typingTimer = setInterval(() => {
if (index < renderedText.length) {
this.displayText += renderedText[index];
index++;
} else {
clearInterval(this.typingTimer);
this.typingTimer = null;
this.$nextTick(this.renderMermaid); // 渲染 Mermaid 图表
}
}, this.typingSpeed);
},
// 触发 Mermaid 渲染
renderMermaid() {
this.$nextTick(() => {
const mermaidEls = this.$el.querySelectorAll('.mermaid');
if (mermaidEls.length > 0) {
try {
// mermaid 8.x 版本使用 init 方法而不是 run
if (typeof mermaid.init === 'function') {
mermaid.init(undefined, '.mermaid');
} else if (mermaid.default && typeof mermaid.default.init === 'function') {
mermaid.default.init(undefined, '.mermaid');
}
} catch (e) {
console.warn('Mermaid 渲染失败:', e);
}
}
});
},
// 切换案例引用显示数量
toggleShowAllCaseRefers() {
this.showAllCaseRefers = !this.showAllCaseRefers;
// 切换后重新渲染 Mermaid如果内容中有图表
this.$nextTick(this.renderMermaid);
},
},
beforeDestroy() {
if (this.typingTimer) {
clearInterval(this.typingTimer);
this.typingTimer = null;
}
},
};
</script>
<style scoped lang="scss">
::v-deep .mermaid{
width: 100%;
height: auto;
}
::v-deep svg[id^="mermaid-"]{
width: 100%;
height: 100%;
}
.messages {
width: 100%;
word-wrap: break-word;
.bot-message {
background-color: #fff;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
.bot-think {
color: #909399;
padding-bottom: 5px;
position: relative;
padding-left: 10px;
&:before {
content: ' ';
border-left: 0.5px solid #909399;
position: absolute;
height: 100%;
left: 0;
top: -3px;
transform: scaleX(0.5);
}
}
.message-content {
font-size: 14px;
line-height: 1.6;
}
.case-content {
font-size: 12px !important;
margin-top: 8px;
padding: 6px 10px;
//background-color: #f9f9f9;
border-radius: 4px;
//border: 1px solid #eee;
}
}
.user-message {
float: right;
padding: 8px 15px;
max-width: 80%;
background-color: #e4e7ed;
border-radius: 5px;
margin-bottom: 10px;
font-size: 14px;
word-break: break-word;
}
/* ========== 案例引用样式 ========== */
.case-refers {
margin-top: 12px;
.case-refers-title {
font-weight: bold;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
color: #333;
.icon-think {
background-image: url('./map.svg');
width: 15px;
height: 13px;
display: inline-block;
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 6px;
}
.more {
font-size: 12px;
padding: 2px 8px;
background-color: #f4f7fd;
border-radius: 5px;
color: #577ee1;
font-weight: normal;
cursor: pointer;
}
}
.case-refers-list {
display: flex;
flex-direction: column;
gap: 8px;
.case-refers-item {
border: 1px solid rgba(144, 147, 153, 0.44);
padding: 8px;
border-radius: 6px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
.case-refers-item-title {
font-size: 14px;
margin-bottom: 6px;
font-weight: 600;
color: #000;
display: flex;
align-items: center;
justify-content: space-between;
.title {
max-width: 70%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #000;
cursor: pointer;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.case-refers-item-timer {
font-size: 11px;
color: #aaa;
font-weight: normal;
}
}
.case-refers-item-author {
display: flex;
align-items: center;
font-size: 12px;
color: #555;
margin-bottom: 5px;
.user {
background-image: url('./user.svg');
width: 15px;
height: 15px;
display: inline-block;
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 6px;
}
.case-inter-orginInfo {
font-size: 11px;
color: #999;
margin-left: 6px;
}
}
.case-refers-item-keywords {
margin-top: 4px;
font-size: 12px;
span {
padding: 2px 6px;
background-color: #f4f7fd;
border-radius: 5px;
font-size: 11px !important;
color: #577ee1;
}
span + span {
margin-left: 8px;
}
}
}
}
}
/* ========== 推荐问题 ========== */
.suggestions {
margin-top: 10px;
font-size: 14px;
.suggestions-title {
font-weight: bold;
margin-bottom: 6px;
color: #333;
}
.suggestions-list {
display: flex;
flex-direction: column;
gap: 6px;
.suggestions-item {
padding: 6px 10px;
background-color: #f0f4fc;
border: none;
border-radius: 6px;
text-align: left;
color: #1a73e8;
cursor: pointer;
font-size: 13px;
transition: background-color 0.2s;
&:hover {
background-color: #e1e8f5;
}
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

View File

@@ -0,0 +1,391 @@
<template>
<div class="input-area">
<el-input
v-model="inputContent"
type="textarea"
class="input-placeholder"
placeholder="有问题,尽管问"
@keyup.enter.native="handleSend"
:disabled="disabled"
:autosize="{ minRows: 2, maxRows: 4}"
resize="none"
></el-input>
<div class="action-buttons">
<el-button type="primary" size="small" class="start-btn" @click="handleNewConversation">
+ 开启新对话
</el-button>
<el-button type="text" class="send-btn" @click="handleSend" :disabled="disabled">
<i class="el-icon-s-promotion"></i>
</el-button>
</div>
</div>
</template>
<script>
import { aiChat } from '@/api/boe/aiChat.js'
export default {
name: 'SendMessage',
props: {
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
messageList: {
type: Array,
default: () => []
},
suggestions: {
type: Array,
default: () => []
}
},
data() {
return {
inputContent: this.value,
conversationId: '' // 会话ID
}
},
watch: {
value(newVal) {
this.inputContent = newVal
}
},
methods: {
handleSend() {
if (!this.inputContent.trim() || this.disabled) return
// 添加用户消息到列表
const userMessage = {
isBot: false,
text: this.inputContent
};
this.messageList.push(userMessage);
// 显示加载状态
this.$emit('loading', true);
// 调用AI聊天接口 (暂时注释掉SSE使用模拟数据)
this.callAIChat(this.inputContent);
// 清空输入框
this.inputContent = ''
},
// 真实的SSE实现暂时注释掉
callAIChat(question) {
// 创建新的AI消息对象
const aiMessage = {
isBot: true,
text: '',
status:null,
thinkText: '',
caseRefers: [], // 添加caseRefers字段
textCompleted: false // 添加文字处理完成状态默认为false
};
this.messageList.push(aiMessage);
// 构造请求参数
const requestData = {
conversationId: this.conversationId,
query: question
};
// 创建POST请求
fetch('/systemapi/xboe/m/boe/case/ai/chat',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
"accept": "text/event-stream",
},
body: JSON.stringify(requestData)
}).then(r=>{
return r
}).then(response => {
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let accumulatedContent = ''; // 累积的内容用于打字机效果
let accumulatedThinkContent = ''; // 累积的思考内容
let inThinkSection = false; // 是否在思考部分
let typingTimer = null; // 打字机定时器
let thinkTypingTimer = null; // 思考内容打字机定时器
// 逐字显示文本的函数
const typeText = (message, fullContent) => {
// 如果已有定时器在运行,先清除它
if (typingTimer) {
clearInterval(typingTimer);
}
// 获取当前已显示的文本长度
const currentLength = message.text.length;
// 获取完整文本
const targetLength = fullContent.length;
// 如果已经显示完整文本,不需要继续
if (currentLength >= targetLength) {
return;
}
const typingSpeed = 30; // 每个字符的间隔时间(毫秒)
typingTimer = setInterval(() => {
// 计算下一个要显示的字符索引
const nextIndex = message.text.length + 1;
if (nextIndex <= targetLength) {
message.text = fullContent.substring(0, nextIndex);
this.$emit('update-message', message);
} else {
clearInterval(typingTimer);
typingTimer = null;
// 当打字机效果完成时检查是否应该设置textCompleted为true
// 这应该在status 4交互完成时才设置
if (message.status === 4) {
if (nextIndex >= targetLength) {
message.textCompleted = true;
}
}
}
}, typingSpeed);
};
// 逐字显示思考内容的函数
const typeThinkText = (message, fullThinkContent) => {
// 如果已有定时器在运行,先清除它
if (thinkTypingTimer) {
clearInterval(thinkTypingTimer);
}
// 获取当前已显示的文本长度
const currentLength = message.thinkText.length;
// 获取完整文本
const targetLength = fullThinkContent.length;
// 如果已经显示完整文本,不需要继续
if (currentLength >= targetLength) {
return;
}
// 从当前显示位置开始继续显示(避免清空重显)
const startIndex = currentLength;
const typingSpeed = 20; // 每个字符的间隔时间(毫秒)
thinkTypingTimer = setInterval(() => {
// 计算下一个要显示的字符索引
const nextIndex = message.thinkText.length + 1;
if (nextIndex <= targetLength) {
message.thinkText = fullThinkContent.substring(0, nextIndex);
this.$emit('update-message', message);
} else {
clearInterval(thinkTypingTimer);
thinkTypingTimer = null;
}
}, typingSpeed);
};
// 添加一个检查是否所有文本都已完成显示的函数
const isTextDisplayCompleted = (message, fullContent) => {
return message.text.length >= fullContent.length;
};
// 读取流数据
const read = () => {
reader.read().then(({ done, value }) => {
if (done) {
// 当流结束时,等待打字机效果完成
const waitForTyping = () => {
if (!typingTimer) {
this.$emit('loading', false);
} else {
setTimeout(waitForTyping, 100);
}
};
waitForTyping();
return;
}
// 解码数据
buffer += decoder.decode(value, { stream: true });
// 按行分割数据
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留不完整的行
for (const line of lines) {
if (line.startsWith('data:')) {
try {
const jsonData = JSON.parse(line.substring(5));
// 根据status处理不同类型的数据
switch (jsonData.status) {
case 0: // 引用文件
// 处理引用文件信息
if (jsonData.fileRefer && jsonData.fileRefer.caseRefers) {
aiMessage.caseRefers = jsonData.fileRefer.caseRefers;
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
}
// 从响应中获取并保存conversationId
if (jsonData.conversationId) {
this.conversationId = jsonData.conversationId;
sessionStorage.setItem('conversationId', jsonData.conversationId);
}
break;
case 1: // 流式对话内容
// 处理
const content = jsonData.content;
aiMessage.hasThink = false;
if (content.startsWith('<think>')) {
aiMessage.hasThink = true
inThinkSection = true;
accumulatedThinkContent = content.replace('<think>', '');
// 使用打字机效果显示think内容
typeThinkText(aiMessage, accumulatedThinkContent);
} else if (content.startsWith('</think>')) {
inThinkSection = false;
accumulatedThinkContent += content.replace('</think>', '');
// 使用打字机效果显示think内容
typeThinkText(aiMessage, accumulatedThinkContent);
} else if (inThinkSection) {
accumulatedThinkContent += content;
// 使用打字机效果显示think内容
typeThinkText(aiMessage, accumulatedThinkContent);
} else {
// 累积内容并使用打字机效果更新显示
accumulatedContent += content;
// 如果thinkText已经显示完整则继续使用打字机效果显示内容
if( aiMessage.hasThink){
if(aiMessage.thinkText.length >=accumulatedThinkContent.length){
typeText(aiMessage, accumulatedContent);
}
} else {
typeText(aiMessage, accumulatedContent);
}
}
// 不在这里直接更新,让打字机效果处理更新
break;
case 2: // 回答完成
// 不再在这里设置textCompleted状态
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
// 从响应中获取并保存conversationId
break;
case 3: // 返回建议
// 这里可以处理建议问题
this.$emit('update-suggestions', jsonData.suggestions);
break;
case 4: // 交互完成
aiMessage.status = 4
// 从响应中获取并保存conversationId
this.$emit('loading', false);
// 检查文本是否已经完全显示如果是则设置textCompleted为true
if (isTextDisplayCompleted(aiMessage, accumulatedContent)) {
// aiMessage.textCompleted = true;
this.$emit('update-message', aiMessage);
}
break;
}
} catch (error) {
console.error('解析SSE数据错误:', error);
}
}
}
// 继续读取
read();
}).catch(error => {
console.error('SSE连接错误:', error);
// 出错时也设置文字处理完成状态
if (typingTimer) {
clearInterval(typingTimer);
typingTimer = null;
}
aiMessage.textCompleted = true;
this.$emit('loading', false);
aiMessage.text = '当前无法获取回答,请稍后重试';
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
});
};
// 开始读取数据
read();
}).catch(error => {
console.error('请求失败:', error);
// 出错时也设置文字处理完成状态
aiMessage.textCompleted = true;
this.$emit('loading', false);
aiMessage.text = '当前无法获取回答,请稍后重试';
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
});
},
handleNewConversation() {
this.conversationId = ''
this.$emit('new-conversation')
}
}
}
</script>
<style scoped lang="scss">
.input-area {
background-color: white;
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 5px 16px 10px 16px;
display: flex;
flex-direction: column;
.input-placeholder {
color: #999;
font-size: 14px;
margin: 0;
border: none;
::v-deep .el-input__inner {
border: none;
padding: 0;
height: 30px;
}
}
.action-buttons {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
.start-btn {
padding: 6px 10px;
font-size: 12px;
border-radius: 4px;
color: #409eff;
background-color: #f5f7fa;
border: 1px solid #dcdfe6;
}
.send-btn {
font-size: 18px;
color: #409eff;
padding: 6px;
&[disabled] {
color: #c0c4cc;
}
}
}
}
</style>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="750px" height="850px" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient cx="362.789473684209" cy="413.96491228069874" r="1153.015055438179" gradientTransform="matrix(0.023310357358899587 0.999728276703125 -0.9997282767031253 0.02331035735889959 768.1851497765263 41.62434690904212 )" gradientUnits="userSpaceOnUse" id="RadialGradient4">
<stop id="Stop5" stop-color="#ffffff" offset="0" />
<stop id="Stop6" stop-color="#d4def7" offset="1" />
</radialGradient>
</defs>
<g>
<path d="M 0 5.000000000000001 A 5 5 0 0 1 4.999999999999999 0 L 745 0 A 5 5 0 0 1 750 5 L 750 845 A 5 5 0 0 1 745 850 L 5 850 A 5 5 0 0 1 0 845 L 0 5 Z " fill-rule="nonzero" fill="url(#RadialGradient4)" stroke="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 858 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759026139840" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5676" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="25"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667zM288 810.666667c0-123.733333 100.266667-224 224-224S736 686.933333 736 810.666667c-61.866667 46.933333-140.8 74.666667-224 74.666666s-162.133333-27.733333-224-74.666666z m128-384c0-53.333333 42.666667-96 96-96s96 42.666667 96 96-42.666667 96-96 96-96-42.666667-96-96z m377.6 328.533333c-19.2-96-85.333333-174.933333-174.933333-211.2 32-29.866667 51.2-70.4 51.2-117.333333 0-87.466667-72.533333-160-160-160s-160 72.533333-160 160c0 46.933333 19.2 87.466667 51.2 117.333333-89.6 36.266667-155.733333 115.2-174.933334 211.2-55.466667-66.133333-91.733333-149.333333-91.733333-243.2 0-204.8 168.533333-373.333333 373.333333-373.333333S885.333333 307.2 885.333333 512c0 93.866667-34.133333 177.066667-91.733333 243.2z" fill="#666666" p-id="5677"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -225,7 +225,7 @@
</el-card>
</el-col>
</el-row>
</div>
<portal-footer></portal-footer>
</div>
@@ -653,7 +653,7 @@ export default {
itemId:this.coursewareInfo.studyItemId,
videoTime:intTime
}
apicourseStudy.studyVideoTime(postData).then(rs=>{
apicourseStudy.updateStudyVideoTime(postData).then(rs=>{
if(rs.status!=200){
console.log('记录播放时间错误');
}
@@ -693,7 +693,7 @@ export default {
itemId:this.coursewareInfo.studyItemId,
videoTime:intTime
}
apicourseStudy.studyVideoTime(postData).then(rs=>{
apicourseStudy.updateStudyVideoTime(postData).then(rs=>{
if(rs.status!=200){
console.log('记录播放时间错误');
}

File diff suppressed because it is too large Load Diff

53
src/views/sso/lexiang.vue Normal file
View File

@@ -0,0 +1,53 @@
<template>
<div>
<p v-if="loading">正在跳转请稍候...</p>
<p v-if="error" class="error">{{ errorMessage }}</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
loading: true,
error: false,
errorMessage: ''
}
},
created() {
this.fetchCode()
},
methods: {
async fetchCode() {
try {
// 替换为你的实际API地址
const response = await axios.post('/userbasic/sso/getCode', {})
const code = response.data.result
if (code) {
// 替换为你的目标外部链接,并确保参数名称正确
const redirectUrl = `https://lexiangla.com?company_from=d1f3b156e9ed11ef9dc9720f77c5afa9&login_way=sso&sso_auth_code=${encodeURIComponent(code)}`
window.location.href = redirectUrl
} else {
this.handleError('未获取到有效code')
}
} catch (error) {
this.handleError(`请求失败: ${error.message}`)
}
},
handleError(message) {
this.loading = false
this.error = true
this.errorMessage = message
}
}
}
</script>
<style>
.error {
color: red;
font-weight: bold;
}
</style>

View File

@@ -254,7 +254,7 @@ export default {
let localCourseIds=[];//本地在线课程的id集合
res.result.list.forEach(item=>{
if(item.courseImage?.startsWith('/upload')){
item.courseImage=$this.fileUrl+item.courseImage.substring(7)
item.courseImage=$this.fileUrl+item.courseImage?.substring(7)
}
if(item.source==2){
localCourseIds.push(item.id);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -380,15 +380,19 @@ export default {
getExperience() {//经验值
let data = {
aid:this.userInfo.aid,// #用户id
statType:21, // #统计类型 10学习时长 11 学习天数 20表经验值 30表u币 40表获取天数
statType:20, // #统计类型 10学习时长 11 学习天数 20表经验值 30表u币 40表获取天数
field:this.experience.field,// #统计周期 todays-当天,weeks-周,months-月,years-年 total-总计
num:10,// #显示的条数
}
if(data.field == 'now'){
data.statType = 20
data.field = 'years'
this.experience.field = 'years'
this.isNow = true
}else{
data.statType = 20
data.field = 'total'
this.experience.field = 'total'
this.isNow = true
}
apiStat.getRanking(data).then(res=>{
if(res.status==200){
@@ -402,12 +406,15 @@ export default {
}
this.getUserData([res.result.currentUserRankingData.aid],[res.result.currentUserRankingData])
this.experience.data = res.result.currentUserRankingData;
if(this.experience.field == 'total') {
this.currentUserRankingTotalData.rankNo = res.result.currentUserRankingData.rankingNo;
this.currentUserRankingTotalData.endValue = this.current.endValue;
if(data.field == 'total') {
this.current = this.experienceValue(res.result.currentUserRankingData.total);
this.currentUserRankingTotalData.rankNo = res.result.currentUserRankingData.rankingNo;
this.currentUserRankingTotalData.rankValue = res.result.currentUserRankingData.total;
this.currentUserRankingTotalData.endValue = this.current.endValue;
}else if(data.field == 'years'){
this.currentUserRankingTotalData.rankValue = res.result.currentUserRankingData.years;
}
}
this.current.total=res.result.currentUserRankingData.total;//当前用户的经验值是固定的
const ids= [];
@@ -434,9 +441,15 @@ export default {
}
});
});
this.experience.list = listData.sort((a,b)=>{
return b.total-a.total
}).slice(0,5);
if(data.field == 'total') {
this.experience.list = listData.sort((a,b)=>{
return b.total-a.total
}).slice(0,5);
}else if(data.field == 'years'){
this.experience.list = listData.sort((a,b)=>{
return b.years-a.years
}).slice(0,5);
}
} else {
this.$message.error(res.message);
}
@@ -447,15 +460,19 @@ export default {
getDuration() {//学习时长
let data = {
aid:this.userInfo.aid,// #用户id
statType:12, // #统计类型 10学习时长 11 学习天数 20表经验值 30表u币 40表获取天数
statType:10, // #统计类型 10学习时长 11 学习天数 20表经验值 30表u币 40表获取天数
field:this.learningDuration.field,// #统计周期 todays-当天,weeks-周,months-月,years-年 total-总计
num:10,// #显示的条数
}
if(data.field == 'now'){
data.statType = 10
data.field = 'total'
this.learningDuration.field = 'total'
this.isStudyTime = true
data.statType = 10;
data.field = 'years';
this.learningDuration.field = 'years';
this.isStudyTime = true;
}else{
data.statType = 10;
data.field = 'total';
this.learningDuration.field = 'total';
}
apiStat.getRanking(data).then(res=>{
if(res.status==200){
@@ -469,9 +486,11 @@ export default {
}
this.getUserData([res.result.currentUserRankingData.aid],[res.result.currentUserRankingData])
this.learningDuration.data = res.result.currentUserRankingData;
this.learningDurationTotalData.rankNo = res.result.currentUserRankingData.rankingNo;
if(data.field == 'total'){
this.learningDurationTotalData.rankNo = res.result.currentUserRankingData.rankingNo;
this.learningDurationTotalData.rankValue = res.result.currentUserRankingData.total;
} else if(data.field == 'years'){
this.learningDurationTotalData.rankValue = res.result.currentUserRankingData.years;
}
}
@@ -500,9 +519,16 @@ export default {
}
});
});
this.learningDuration.list = listData.sort((a,b)=>{
return b.total-a.total
}).slice(0,5);
if(data.field == 'total'){
this.learningDuration.list = listData.sort((a,b)=>{
return b.total-a.total
}).slice(0,5)
} else if(data.field == 'years'){
this.learningDuration.list = listData.sort((a,b)=>{
return b.years-a.years
}).slice(0,5)
}
;
} else {
this.$message.error(res.message);
}

View File

@@ -57,6 +57,13 @@ module.exports = {
// set svg-sprite-loader
config.plugins.delete('preload')
config.plugins.delete('prefetch')
// 添加对 mathxyjax3 的处理规则
config.module
.rule('mathxyjax3')
.test(/node_modules[\/\\]mathxyjax3[\/\\].*\.js$/)
.use('null-loader')
.loader('null-loader')
.end()
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
@@ -85,28 +92,28 @@ module.exports = {
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
}
})
})
config.optimization.runtimeChunk('single'), {
from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
to: './', //到根目录下
@@ -124,6 +131,17 @@ module.exports = {
// 114.115.162.187 测试环境
// 192.168.0.107 晋宇
proxy: {
// "/professional": {
// // target: 'https://u-pre.boe.com',
// // target: 'http://192.168.86.195:32002',
// target: 'http://192.168.68.211:32002',
// changeOrigin: true,
// },
"/growth": {
target: 'https://u-pre.boe.com',
// target: 'http://192.168.86.195:32002',
changeOrigin: true,
},
'/manageApi': {
// 目标代理服务器地址
target: 'https://u-pre.boe.com',