Compare commits

..

154 Commits

Author SHA1 Message Date
joshen
738add6f18 Merge branch 'master' into ebiz-uat-2025-11-06 2025-12-04 19:30:29 +08:00
joshen
a7763057c4 Merge remote-tracking branch 'aliyun/20251203-fix-1320' 2025-12-04 17:18:25 +08:00
joshen
421e2b2c51 Merge remote-tracking branch 'aliyun/ebiz-uat-2025-11-06' into ebiz-uat-2025-11-06 2025-12-04 11:07:03 +08:00
zxj
12e91854fe fix:恢复保留其余选择条件的逻辑 2025-12-03 17:46:13 +08:00
zxj
3852a92ab3 fix:返回列表页时先触发校验再执行接口 2025-12-03 17:40:44 +08:00
zxj
56103bbdf6 fix:案例详情页点击搜索返回列表页时触发接口 2025-12-03 17:21:04 +08:00
zxj
d2f3b2d79c fix:解决案例详情页搜索不生效问题 2025-12-03 16:33:23 +08:00
bjzhoujian
4e1940b36f 设置ai弹窗滑动 2025-12-02 10:57:24 +08:00
joshen
3e344a8374 Merge branch 'master' into ebiz-uat-2025-11-06 2025-11-27 17:58:33 +08:00
joshen
82598dd5e0 Merge branch '20251124-fix-1302' 2025-11-27 17:10:43 +08:00
zxj
da72c156e9 fix:修复案例详情校验携带缓存 2025-11-27 12:19:47 +08:00
joshen
f731bb425f Merge branch '20251124-fix-1302' 2025-11-25 19:09:03 +08:00
zxj
8c2f128578 fix:回滚案例详情校验调整 2025-11-25 18:53:35 +08:00
joshen
f16c6eb157 Merge branch '20251124-fix-1302' 2025-11-25 17:44:58 +08:00
bjzhoujian
6016e00ae8 top设为0 2025-11-25 16:42:48 +08:00
bjzhoujian
7155976f31 样式修改 2025-11-25 16:05:02 +08:00
bjzhoujian
4ca01ba233 阻止冒泡 2025-11-25 15:07:09 +08:00
bjzhoujian
7368fa7a8c 调试回车导致弹窗闪动 2025-11-25 11:57:07 +08:00
joshen
d09cbfac5f Merge remote-tracking branch 'aliyun/ebiz-uat-2025-11-06' into ebiz-uat-2025-11-06 2025-11-25 11:03:24 +08:00
bjzhoujian
fd903d0974 调试回车导致弹窗闪动 2025-11-25 10:06:21 +08:00
dong.ai
42885e0d61 修改标题展示 2025-11-24 18:35:56 +08:00
zxj
0b3b9ad082 fix:请求uapi.boe.com.cn禁用缓存 2025-11-24 17:44:06 +08:00
zxj
38fe538e4e fix:案例详情校验禁用缓存 2025-11-24 16:30:47 +08:00
joshen
052ab0be6f Merge branch '20251121-fix-1298' 2025-11-22 17:51:01 +08:00
zxj
4c453e3974 fix:跳转登录判断是否被嵌入 2025-11-22 14:28:58 +08:00
zxj
47dde458de fix:修复被嵌页面token过期不一致问题 2025-11-21 10:33:35 +08:00
670788339
3701605f7a 标签-精品课程库 课程名称与标签间距调整 2025-11-19 17:59:40 +08:00
670788339
b021be2f6f Merge remote-tracking branch '104/master' into 104-master 2025-11-19 13:37:35 +08:00
670788339
11e34ca335 标签-课程库 课程名称与标签间距调整 2025-11-19 13:37:14 +08:00
joshen
bf20eced9b Merge branch 'master' into ebiz-uat-2025-11-06 2025-11-17 18:58:49 +08:00
670788339
8f2da1c736 取消processStatus setup写法 2025-11-17 18:25:29 +08:00
670788339
322172edec Merge branch 'master-20251023-tag' into merge-20251113-tag
# Conflicts:
#	src/components/Course/courseTag.vue
2025-11-17 13:28:18 +08:00
670788339
c801dc8a3d 精品课导航样式 2025-11-14 10:32:42 +08:00
670788339
838e704ab0 精品课导航样式 2025-11-14 08:58:42 +08:00
670788339
d3e891e5cc 精品课导航样式 2025-11-14 08:45:27 +08:00
670788339
40ac85f1fe 在线标签提示改版 2025-11-13 18:43:33 +08:00
670788339
6ee8eaca00 课程库样式调整 2025-11-13 14:29:23 +08:00
670788339
d78cc1f97c 标签选择后直接关闭下拉框 2025-11-13 09:00:48 +08:00
670788339
2576174e95 当输入文字与标签匹配时 选择下拉框匹配标签 手动输入文字未直接消失 2025-11-13 08:51:51 +08:00
670788339
7316215809 当输入文字与标签匹配时 选择下拉框匹配标签 手动输入文字未直接消失 2025-11-13 08:47:31 +08:00
670788339
c5e794ef45 Merge branch 'master-20251023-tag' into merge-20251113-tag 2025-11-12 17:16:17 +08:00
670788339
720cff1d1e 提示修改 2025-11-12 14:54:24 +08:00
hz
f3cc59d313 feat: 修复 setup 异常 2025-11-12 12:41:34 +08:00
670788339
dc57becb0d 精品课点击全部未显示选中 2025-11-12 11:20:11 +08:00
670788339
a94d101853 还原 2025-11-12 10:55:35 +08:00
670788339
426ed75bc3 精品课全部切换 2025-11-12 10:51:14 +08:00
670788339
7e8b807825 标签提示样式调整 2025-11-12 10:19:26 +08:00
670788339
bf13c953be 标签提示 2025-11-12 09:17:39 +08:00
670788339
8d07122420 标签提示 2025-11-12 09:05:16 +08:00
670788339
471a790010 课程详情页 标签与x人学习 留点间距 2025-11-11 18:13:01 +08:00
670788339
d39e1e98ef 提示文案更改 2025-11-11 18:06:29 +08:00
670788339
a82a65da8e 课程库样式调整 2025-11-11 16:58:59 +08:00
670788339
2070466786 在线课标签样式 2025-11-11 16:38:51 +08:00
670788339
57d9f9b483 课程库标签高亮显示 2025-11-11 15:42:36 +08:00
670788339
1710e34f89 全部选中样式 2025-11-11 14:26:57 +08:00
670788339
e292a57b20 课程详情页标签样式 2025-11-11 10:44:24 +08:00
670788339
88c83af460 1111修改课程库内容导航底色样式 2025-11-11 09:10:04 +08:00
670788339
a78bac9368 1110修改样式 2025-11-10 19:26:10 +08:00
670788339
f121a2aaf9 1110修改样式 2025-11-10 19:01:19 +08:00
670788339
8228b33cb0 提示 2025-11-10 18:49:54 +08:00
670788339
702255d9d0 精品课标签导航调整样式 2025-11-10 10:29:56 +08:00
670788339
df3e246d25 精品课标签导航取消加粗 2025-11-10 10:24:02 +08:00
670788339
1d20f11861 精品类型加标签 2025-11-10 08:54:17 +08:00
670788339
d5ec4c1833 开发 2025-11-09 16:25:03 +08:00
670788339
89a9be76d4 开发 2025-11-09 15:25:13 +08:00
670788339
73026b0ab5 在线标签无数据显示调整 2025-11-08 14:46:53 +08:00
670788339
9b11cc3f92 课程详情页标签调整 2025-11-08 11:42:43 +08:00
670788339
372a7c22ed 搜索条件取消红色标记 2025-11-08 09:29:40 +08:00
670788339
2678d22302 修改导航区域结构 2025-11-08 09:06:44 +08:00
hz
914b80c374 Merge branch '20250922-cyd' into ebiz-uat-2025-11-06
# Conflicts:
#	src/views/portal/case/Index.vue
#	src/views/portal/case/components/messages.vue
#	src/views/portal/case/components/sendMessage.vue
2025-11-06 09:45:06 +08:00
陈昱达
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
hz
58f517d2fb Merge remote-tracking branch 'origin/20250922-da' into ebiz-uat-2025-11-06 2025-11-06 09:40:28 +08:00
670788339
ef9e4a0f68 检查标签是否在下拉框中已存在 2025-11-05 14:38:00 +08:00
670788339
a2640771fb 课程库标签样式 2025-11-04 18:50:08 +08:00
670788339
25cb97f462 课程库标签样式 2025-11-04 18:44:09 +08:00
670788339
51c3d29854 样式调整 2025-11-04 17:48:59 +08:00
670788339
c49d69dede 样式调整 2025-11-04 17:35:06 +08:00
670788339
6a764dd698 样式调整 2025-11-04 17:29:39 +08:00
670788339
af10b1fa32 样式调整 2025-11-04 17:16:41 +08:00
陈昱达
1a475c8612 feat(ai-call):优化对话框拖拽与缩放功能- 增强对话框拖拽逻辑,防止事件冒泡
- 完善对话框缩放功能,动态调整欢迎消息区域高度
- 修正机器人欢迎文本中的错别字
- 调整消息列表区域样式,优化滚动条显示
- 监听对话框可见性变化,确保内容正确渲染
2025-11-04 17:14:38 +08:00
Caojr
782bcc31e5 szx-1282 超时清空展示文件 2025-11-04 15:54:23 +08:00
670788339
7be0bdee6c 样式调整 2025-11-04 15:26:21 +08:00
670788339
f88a3a0b53 在线标签交互 2025-11-04 14:58:31 +08:00
Caojr
1a95852912 szx-1282 超时清空展示文件 2025-11-04 14:55:16 +08:00
陈昱达
01e4c676fc feat(portal/case): 增强AI对话窗口交互功能
-为sendMessage组件添加textarea输入框,支持多行输入
- 为AI对话窗口添加拖拽和调整大小功能
- 在最小化窗口中添加关闭按钮- 优化窗口样式和布局,提升用户体验
- 添加拖拽手柄和窗口控制按钮
- 实现窗口位置和大小的动态调整
- 引入open.png图标用于最小化窗口操作
2025-11-04 14:54:51 +08:00
670788339
48ec56dcbc 创建调整 2025-11-04 13:51:02 +08:00
Caojr
f5d865ccc3 szx-1282 增加超时配置 2025-11-04 13:47:02 +08:00
670788339
25c2e673dc 调整计数样式 2025-11-03 19:20:20 +08:00
670788339
78cc822464 调整 2025-11-03 18:37:28 +08:00
670788339
366f1dc45b 标签清除 2025-11-02 18:08:19 +08:00
670788339
4ee6697166 默认提示 2025-11-02 15:28:16 +08:00
670788339
a54c642f4b 标签调整 2025-11-02 10:22:23 +08:00
670788339
6a77bd9dc4 在线新增 2025-11-01 15:04:02 +08:00
670788339
8e51663b86 详情页标签调整 2025-10-31 20:19:35 +08:00
670788339
cbe7981abd 调试 2025-10-31 19:08:54 +08:00
670788339
1ad2816622 调试 2025-10-31 18:21:08 +08:00
670788339
03b3c61c6b 调试 2025-10-31 18:10:32 +08:00
670788339
b3756280cf 调试 2025-10-31 16:58:56 +08:00
670788339
f34d2a6e94 热点标签+保存标签调试 2025-10-31 16:36:07 +08:00
Caojr
65673561d8 szx-1280 编辑时长无效问题修复 2025-10-31 16:16:29 +08:00
670788339
4d5b462b61 热点标签调试 2025-10-31 15:55:13 +08:00
670788339
055476c583 热点标签 2025-10-31 14:54:05 +08:00
670788339
78f681b4cb 热点标签 2025-10-31 13:33:41 +08:00
670788339
520fb4ee5e 取消无分类时限制标签 2025-10-31 11:13:16 +08:00
670788339
ba7bfe5f11 课程库标签 2025-10-31 11:09:09 +08:00
670788339
86bcf06d87 标签输入框下拉调整 日志 2025-10-30 20:08:16 +08:00
670788339
3e1b545d2a 标签输入框下拉调整 2025-10-30 19:42:31 +08:00
670788339
3f028e5cd8 标签输入框调整 2025-10-30 19:10:17 +08:00
670788339
d94bcf96a1 还原释在线保存前创建标签 2025-10-30 15:39:17 +08:00
670788339
36b739d139 注释在线保存前创建标签 2025-10-30 14:10:54 +08:00
Caojr
2cbb379fa6 样式修改 2025-10-29 18:10:07 +08:00
670788339
8bf7a8e8e7 注释在线保存前创建标签 2025-10-29 15:27:43 +08:00
670788339
4b92308d1d 注释在线保存前创建标签 2025-10-29 14:59:23 +08:00
670788339
a25ea0c4ba 注释在线保存前创建标签 2025-10-29 14:46:25 +08:00
670788339
e55aa09409 日志 2025-10-28 16:42:07 +08:00
670788339
5a3b57bd1c Merge branch '104-master' into master-20251023-tag 2025-10-28 15:13:59 +08:00
670788339
6d4af3aa2d 还原视频状态 调试 2025-10-24 13:29:09 +08:00
670788339
b8ba52731f Merge remote-tracking branch 'nyx/20250912-tag-add' into master-20251023-tag 2025-10-24 09:28:26 +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
joshen
5ebee96ce4 Merge remote-tracking branch 'yx/202599-da' 2025-09-29 17:42:32 +08:00
408d6a1612 修复视频 status 状态 2025-09-29 17:26:53 +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
王卓煜
581be5567f 标签管理未选择分类给个提示语 2025-09-28 11:59:09 +08:00
王卓煜
c9465492f4 标签管理未选择分类给个提示语 2025-09-28 11:46:33 +08:00
陈昱达
3cef730e61 feat(ai-chat): 实现案例专家功能入口权限控制及消息展示优化- 修改AI聊天接口地址为本地开发环境地址
- 新增showCaseAiEntrance接口用于控制案例专家功能入口显示
- 优化消息组件中的案例引用展示逻辑,支持展开/收起功能
- 增加案例引用的上传时间、作者机构等信息展示
- 实现打字机效果的文本逐字显示功能
- 优化AI消息响应处理逻辑,支持think标签内容解析
2025-09-28 11:28:36 +08:00
王卓煜
b18500bad7 标签管理查询条件 缺少重置按钮 2025-09-28 11:12:06 +08:00
王卓煜
5536fc06e1 标签管理修改无数据也可以回车创建 2025-09-28 11:07:08 +08:00
王卓煜
18f3804536 标签管理修改提示语 2025-09-28 11:03:09 +08:00
王卓煜
7230bd18e8 标签管理无目录课程添加标签 2025-09-26 19:03:56 +08:00
b1508ad226 更新精品课图片 2025-09-26 09:18:51 +08:00
a9764bf2f8 更新精品课图片 2025-09-26 09:15:55 +08:00
王卓煜
fd10d99454 标签管理去除无效功能 2025-09-22 10:17:37 +08:00
王卓煜
a51d87fbe8 标签关联课程下面展示标签 2025-09-19 17:12:24 +08:00
王卓煜
2aa36c82ab 标签管理搜索关键字和搜索条件区别显示 2025-09-19 11:12:58 +08:00
王卓煜
0bba87cb3d 标签管理前端友好提示 2025-09-18 09:45:33 +08:00
王卓煜
98ba239494 标签管理添加图片(社招新员工的样式影响了所以添加一下) 2025-09-12 16:14:43 +08:00
王卓煜
df3b1d7162 标签管理 2025-09-12 15:47:26 +08:00
39 changed files with 15031 additions and 21959 deletions

22046
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"

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!=''){
@@ -38,5 +87,16 @@
border: 1px solid #e7e7e7 !important;
box-shadow: 0px 1px 5px 1px rgba(92,98,111,.3);
}
</style>
#app {
pointer-events: none;
}
#app > *:not(.case-expert-dialog) {
pointer-events: auto;
}
.case-expert-dialog {
pointer-events: auto;
}
</style>

View File

@@ -19,88 +19,93 @@ import errorCode from '@/utils/errorCode'
// const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
const TokenName='XBOE-Access-Token';
const TokenName = 'XBOE-Access-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: 60000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
return res.data
}else{
if(code === 401){
//Message({message: msg, type: 'error'});
store.dispatch('LogOut').then(() => {
location.href = this.webBaseUrl + ReLoginUrl;
})
}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 => {
const formRequest = axios.create({
// headers:{'Content-Type':'application/x-www-form-urlencoded'},
// axios中请求配置有baseURL选项表示请求URL公共部分
// baseURL: process.env.VUE_APP_CESOURCE_BASE_API,
//超时
timeout: 60000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
//Message({message: msg, type: 'error'});
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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 = "网络异常,请稍后重试";
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "网络异常或接口错误,请求超时";
message = "网络异常或接口错误,请求超时";
}
else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({
message: message,
type: 'error',
duration: 5 * 1000
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
}
)
/**
* request请求,可以自定义参数
*/
const request=formRequest.request;
const request = formRequest.request;
/**
* get请求 ,只有url
*/
const get = function(baseURL,url){
return request({
baseURL,
url: url,
method: 'get',
headers:{'Content-Type':'application/x-www-form-urlencoded'}
})
const get = function (baseURL, url) {
return request({
baseURL,
url: url,
method: 'get',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
/**
@@ -108,61 +113,61 @@ const get = function(baseURL,url){
* @param {Object} url
* @param {Object} postData
*/
const post=function(baseURL,url,postData){
if(postData){
postData=qs.stringify(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/x-www-form-urlencoded'}
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
//post请求
const postForm=function(baseURL,url,data){
return request({
baseURL,
url,
data,
method: 'post',
headers:{'Content-Type':'application/x-www-form-urlencoded'}
});
}
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){
const postJson = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
method: 'post',
data:postData,
headers:{'Content-Type':'application/json;charset=utf-8'},
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
})
}
const postPdf=function(baseURL,url,postData){
const postPdf = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
responseType: 'blob',
method: 'post',
data:postData,
headers:{'Content-Type':'application/pdf'},
baseURL,
url: url,
responseType: 'blob',
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/pdf' },
})
}
// 导出文件请求定义
const postJsonToFile=function(baseURL,url,postData){
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'
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
responseType: 'blob'
})
}
@@ -170,39 +175,39 @@ const postJsonToFile=function(baseURL,url,postData){
/**
* put请求
*/
const put=function(baseURL,url,data){
if(data){
data=qs.stringify(data);
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'}
baseURL,
url: url,
method: 'put',
data: data,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
const putJson=function(baseURL,url,data){
const putJson = function (baseURL, url, data) {
return request({
baseURL,
url: url,
method: 'put',
data:data,
headers:{'Content-Type':'application/json;charset=utf-8'},
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,
postPdf,
tokenName: TokenName,
request,
get,
post,
postJson,
postJsonToFile,
put,
putJson,
postPdf,
}

View File

@@ -19,90 +19,95 @@ import errorCode from '@/utils/errorCode'
// const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
const TokenName='token';
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;
})
}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)
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(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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 = "网络异常,请稍后重试";
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "网络异常或接口错误,请求超时";
message = "网络异常或接口错误,请求超时";
}
else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({
message: message,
type: 'error',
duration: 5 * 1000
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
}
)
/**
* request请求,可以自定义参数
*/
const request=formRequest.request;
const request = formRequest.request;
/**
* get请求 ,只有url
*/
const get = function(baseURL,url){
return request({
baseURL,
url: url,
method: 'get',
headers:{'Content-Type':'application/x-www-form-urlencoded'}
})
const get = function (baseURL, url) {
return request({
baseURL,
url: url,
method: 'get',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
/**
@@ -110,49 +115,49 @@ const get = function(baseURL,url){
* @param {Object} url
* @param {Object} postData
*/
const post=function(baseURL,url,postData){
if(postData){
postData=qs.stringify(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/x-www-form-urlencoded'}
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
//post请求
const postForm=function(baseURL,url,data){
return request({
baseURL,
url,
data,
method: 'post',
headers:{'Content-Type':'application/x-www-form-urlencoded'}
});
}
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){
const postJson = function (baseURL, url, postData) {
return request({
baseURL,
url: url,
method: 'post',
data:postData,
headers:{'Content-Type':'application/json'},
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/json' },
})
}
// 导出文件请求定义
const postJsonToFile=function(baseURL,url,postData){
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'
baseURL,
url: url,
method: 'post',
data: postData,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
responseType: 'blob'
})
}
@@ -160,38 +165,38 @@ const postJsonToFile=function(baseURL,url,postData){
/**
* put请求
*/
const put=function(baseURL,url,data){
if(data){
data=qs.stringify(data);
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'}
baseURL,
url: url,
method: 'put',
data: data,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
const putJson=function(baseURL,url,data){
const putJson = function (baseURL, url, data) {
return request({
baseURL,
url: url,
method: 'put',
data:data,
headers:{'Content-Type':'application/json;charset=utf-8'},
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,
tokenName: TokenName,
request,
get,
post,
postJson,
postJsonToFile,
put,
putJson,
}

View File

@@ -18,191 +18,201 @@ import errorCode from '@/utils/errorCode'
*delete请求 axios.delete(url[, config])
*/
const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
const TokenName='token';
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
const jsonRequest=axios.create({
headers:{'Content-Type':'application/json;charset=utf-8'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BOE_BASE_API,
//超时
timeout: 10000,
const jsonRequest = axios.create({
headers: { 'Content-Type': 'application/json;charset=utf-8' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BOE_BASE_API,
//超时
timeout: 10000,
});
//发送json对象的拦截器
jsonRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
})
// 响应拦截器
jsonRequest.interceptors.response.use(res => {
const code1 = res.data.status || 200;
const code=parseInt(code1);
if(code===200){
return res.data
}else{
if(code == 6001){ //对方是字符串,所以这里不要使用三个等号
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code===403){
var msg='当前操作没有权限';
Message({message: msg, type: 'error'});
return Promise.reject(new Error(msg))
//return res.data;
}else{
//Message({message: res.data.message, type: 'error'});
//console.log('err:' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
const code1 = res.data.status || 200;
const code = parseInt(code1);
if (code === 200) {
return res.data
} else {
if (code == 6001) { //对方是字符串,所以这里不要使用三个等号
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 403) {
var msg = '当前操作没有权限';
Message({ message: msg, type: 'error' });
return Promise.reject(new Error(msg))
//return res.data;
} else {
//Message({message: res.data.message, type: 'error'});
//console.log('err:' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
)
//用于普通的发送请求
const formRequest=axios.create({
headers:{'Content-Type':'application/x-www-form-urlencoded'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BOE_BASE_API,
//超时
timeout: 10000,
const formRequest = axios.create({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BOE_BASE_API,
//超时
timeout: 10000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
return res.data
}else{
if(code == 6001){ //对方是字符串,所以这里不要使用三个等号
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}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 Promise.reject(new Error(res.data.message))
return res.data;//返回给用户做业务处理
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code == 6001) { //对方是字符串,所以这里不要使用三个等号
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} 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 Promise.reject(new Error(res.data.message))
return res.data;//返回给用户做业务处理
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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=function(cfg){
if(cfg.data){
cfg.data=qs.stringify(cfg.data);
const request = function (cfg) {
if (cfg.data) {
cfg.data = qs.stringify(cfg.data);
}
};
//requestJson请求
const requestJson=jsonRequest.request;
const requestJson = jsonRequest.request;
//get请求
const get=formRequest.request;
const get = formRequest.request;
//post请求
const post=function(url,data,config){
if(data){
data=qs.stringify(data);
const post = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.post(url,data,config);
return formRequest.post(url, data, config);
}
//postJson请求
const postJson=jsonRequest.post;
const postJson = jsonRequest.post;
//put请求
const put=function(url,data,config){
if(data){
data=qs.stringify(data);
const put = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.put(url,data,config);
return formRequest.put(url, data, config);
}
//putJson请求
const putJson=jsonRequest.put;
const putJson = jsonRequest.put;
//patch请求
const patch=function(url,data,config){
if(data){
data=qs.stringify(data);
const patch = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.patch(url,data,config);
return formRequest.patch(url, data, config);
}
//patchJson请求
const patchJson=jsonRequest.patch;
const patchJson = jsonRequest.patch;
//delete请求
const del=formRequest.delete;
const del = formRequest.delete;
export default {
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
}

View File

@@ -16,186 +16,196 @@ import errorCode from '@/utils/errorCode'
*patchJson请求 axios.patch(url[, data[, config]])
*delete请求 axios.delete(url[, config])
*/
const ReLoginUrl="/login";
const TokenName='XBOE-Access-Token';
const ReLoginUrl = "/login";
const TokenName = 'XBOE-Access-Token';
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
//只是用于发送json对象数据时使用post,put,patch
const jsonRequest=axios.create({
headers:{'Content-Type':'application/json;charset=utf-8'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_CESOURCE_BASE_API,
//超时
timeout: 10000,
const jsonRequest = axios.create({
headers: { 'Content-Type': 'application/json;charset=utf-8' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_CESOURCE_BASE_API,
//超时
timeout: 10000,
});
//发送json对象的拦截器
jsonRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
})
// 响应拦截器
jsonRequest.interceptors.response.use(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;
})
}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;
//return Promise.reject(new Error(res.data.message))
}
}
},
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)
}
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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;
//return Promise.reject(new Error(res.data.message))
}
}
},
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)
}
)
//用于普通的发送请求
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,
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
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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
}else{
if(code === 401){
store.dispatch('LogOut').then(() => {
location.href = this.webBaseUrl + ReLoginUrl;
})
}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)
}
}
}
},
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=function(cfg){
if(cfg.data){
cfg.data=qs.stringify(cfg.data);
const request = function (cfg) {
if (cfg.data) {
cfg.data = qs.stringify(cfg.data);
}
};
//requestJson请求
const requestJson=jsonRequest.request;
const requestJson = jsonRequest.request;
//get请求
const get=formRequest.request;
const get = formRequest.request;
//post请求
const post=function(url,data,config){
if(data){
data=qs.stringify(data);
const post = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.post(url,data,config);
return formRequest.post(url, data, config);
}
//postJson请求
const postJson=jsonRequest.post;
const postJson = jsonRequest.post;
//put请求
const put=function(url,data,config){
if(data){
data=qs.stringify(data);
const put = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.put(url,data,config);
return formRequest.put(url, data, config);
}
//putJson请求
const putJson=jsonRequest.put;
const putJson = jsonRequest.put;
//patch请求
const patch=function(url,data,config){
if(data){
data=qs.stringify(data);
const patch = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.patch(url,data,config);
return formRequest.patch(url, data, config);
}
//patchJson请求
const patchJson=jsonRequest.patch;
const patchJson = jsonRequest.patch;
//delete请求
const del=formRequest.delete;
const del = formRequest.delete;
export default {
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
}

View File

@@ -17,185 +17,195 @@ import errorCode from '@/utils/errorCode'
*delete请求 axios.delete(url[, config])
*/
const ReLoginUrl="/login";
const TokenName='XBOE-Access-Token';
const ReLoginUrl = "/login";
const TokenName = 'XBOE-Access-Token';
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
//只是用于发送json对象数据时使用post,put,patch
const jsonRequest=axios.create({
headers:{'Content-Type':'application/json;charset=utf-8'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_STAT_BASE_API,
//超时
timeout: 10000,
const jsonRequest = axios.create({
headers: { 'Content-Type': 'application/json;charset=utf-8' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_STAT_BASE_API,
//超时
timeout: 10000,
});
//发送json对象的拦截器
jsonRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
})
// 响应拦截器
jsonRequest.interceptors.response.use(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;
})
}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;
//return Promise.reject(new Error(res.data.message))
}
}
},
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)
}
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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;
//return Promise.reject(new Error(res.data.message))
}
}
},
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)
}
)
//用于普通的发送请求
const formRequest=axios.create({
headers:{'Content-Type':'application/x-www-form-urlencoded'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_STAT_BASE_API,
//超时
timeout: 10000,
const formRequest = axios.create({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_STAT_BASE_API,
//超时
timeout: 10000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = this.webBaseUrl + ReLoginUrl;
} else {
window.location.href = this.webBaseUrl + ReLoginUrl;
}
// location.href = this.webBaseUrl + ReLoginUrl;
})
} 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
}else{
if(code === 401){
store.dispatch('LogOut').then(() => {
location.href = this.webBaseUrl + ReLoginUrl;
})
}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)
}
}
}
},
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=function(cfg){
if(cfg.data){
cfg.data=qs.stringify(cfg.data);
const request = function (cfg) {
if (cfg.data) {
cfg.data = qs.stringify(cfg.data);
}
};
//requestJson请求
const requestJson=jsonRequest.request;
const requestJson = jsonRequest.request;
//get请求
const get=formRequest.request;
const get = formRequest.request;
//post请求
const post=function(url,data,config){
if(data){
data=qs.stringify(data);
const post = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.post(url,data,config);
return formRequest.post(url, data, config);
}
//postJson请求
const postJson=jsonRequest.post;
const postJson = jsonRequest.post;
//put请求
const put=function(url,data,config){
if(data){
data=qs.stringify(data);
const put = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.put(url,data,config);
return formRequest.put(url, data, config);
}
//putJson请求
const putJson=jsonRequest.put;
const putJson = jsonRequest.put;
//patch请求
const patch=function(url,data,config){
if(data){
data=qs.stringify(data);
const patch = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.patch(url,data,config);
return formRequest.patch(url, data, config);
}
//patchJson请求
const patchJson=jsonRequest.patch;
const patchJson = jsonRequest.patch;
//delete请求
const del=formRequest.delete;
const del = formRequest.delete;
export default {
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del
}

View File

@@ -440,6 +440,12 @@ const queryCrowd=function(query){
const ids=function (data){
return ajax.postJson('/xboe/m/course/manage/ids',data);
}
const saveTip = function() {
return ajax.postJson('/xboe/m/course/manage/saveTip');
}
export default {
saveBase,
submitCourse,
@@ -482,6 +488,7 @@ export default {
exportCourseAudit,
exportCourse,
queryCrowd,
ids
ids,
saveTip
}

View File

@@ -22,7 +22,7 @@ const pageList = function(data) {
/**
* 选择课件的查询,这里也是分页查询,只是返回的内容,字段会很少,用于课件制作那选择已有课件内容。
*
*
* @param {Object} data
* 查询参数如上面pageList方法
*/
@@ -47,7 +47,9 @@ const findList = function(data) {
}
*/
const saveUpload = function(data) {
return ajax.post('/xboe/m/course/file/upload/save', data);
return ajax.post('/xboe/m/course/file/upload/save', data, {
timeout: 60000
});
}
/**
@@ -88,4 +90,4 @@ export default {
batchUpdate,
detail,
delFile
}
}

View File

@@ -0,0 +1,64 @@
/**课程标签模块的相关处理*/
import ajax from '@/utils/xajax.js'
/**
* 分页查询:标签列表
* @param {Object} query
*/
const portalPageList = function(query) {
return ajax.post('/xboe/m/coursetag/page', query);
}
//改变标签的公共属性
const changeTagPublic = function (row){
// 返回 Promise 的 API 调用
return ajax.post('/xboe/m/coursetag/changePublicStatus', {
id: row.id,
isPublic: row.isPublic
});
}
//改变标签的热点属性
const changeTagHot = function (row){
// 返回 Promise 的 API 调用
return ajax.post('/xboe/m/coursetag/changeHotStatus', {
id: row.id,
isHot: row.isHot
});
}
//查询指定id的标签关联的所有课程
const showCourseByTag = function (query){
return ajax.post('/xboe/m/coursetag/showCourseByTag', query);
}
//解除指定id的课程和某个标签之间的关联关系
const unbindCourseTagRelation = function (params){
return ajax.post('/xboe/m/coursetag/unbind', params);
}
//编辑课程:标签模糊查询
const searchTags = function (params){
return ajax.post('/xboe/m/coursetag/searchTags', params);
}
//编辑课程:创建标签(与当前课程关联)
const createTag = function (params){
return ajax.post('/xboe/m/coursetag/createTag', params);
}
//获取最新前10个热点标签
const getHotTagList = function (params){
return ajax.post('/xboe/m/coursetag/getHotTagList', params);
}
export default {
portalPageList,
changeTagPublic,
changeTagHot,
showCourseByTag,
unbindCourseTagRelation,
searchTags,
createTag,
getHotTagList
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -50,7 +50,7 @@
<el-input-number v-model="duration" size="mini" :min="1" :max="100"></el-input-number>
</span>
</div>
<el-upload class="upload-demo" :headers="headers" :data="data" drag :action="uploadFileUrl" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload">
<el-upload ref="uploadRef" class="upload-demo" :headers="headers" :data="data" drag :action="uploadFileUrl" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">文件大小限制{{curComType.maxSizeName}},支持的文件类型{{curComType.fileTypes.join(',')}}</div>
@@ -195,6 +195,7 @@
// this.cware.content.content=result.filePath;
}else{
this.$message.error(rs.message);
this.$refs.uploadRef.clearFiles();
}
});
}else{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,400 @@
<template>
<div class="tag-container" @click="handleContainerClick">
<el-select style="width: 100%;"
v-model="selectedTags"
multiple
filterable
value-key="id"
remote
reserve-keyword
:remote-method="debouncedSearch"
:loading="loading"
:placeholder="'回车创建新标签'"
:no-data-text="'无此标签,按回车键创建'"
@remove-tag="handleTagRemove"
@change="handleSelectionChange"
@keyup.enter.native="handleEnterKey"
@keyup.delete.native="handleDeleteKey"
@focus="handleFocus"
ref="tagSelect"
>
<el-option
v-for="item in searchResults"
:key="item.id"
:label="item.tagName"
:value="item"
:disabled="isTagDisabled(item)"
/>
</el-select>
<!-- 添加标签计数显示 -->
<div class="tag-count">
{{ selectedTags.length }}/5
</div>
</div>
</template>
<script>
import { debounce } from 'lodash'
import apiCourseTag from '@/api/modules/courseTag.js'
import { mapGetters } from 'vuex';
export default {
props: {
courseId:{
type:String,
require:true,
},
sysTypeList:{
type:Array,
require:true,
default: []
},
maxTags: {
type: Number,
default: 5
},
// 添加接收初始标签数据的props
initialTags: {
type: Array,
default: () => []
}
},
data() {
return {
selectedTags: [],
searchResults: [],
loading: false,
tagMap: new Map(),
inputBuffer: '',
params: {},
tag: {},
// 添加临时存储用于回滚
previousTags: []
}
},
computed: {
...mapGetters(['userInfo']),
displayTags() {
return this.selectedTags.map(tag =>
typeof tag === 'object' ? tag : this.tagMap.get(tag)
).filter(Boolean)
}
},
created() {
this.debouncedSearch = debounce(this.doSearch, 500)
console.log("----------sysTypeList.length---------->"+this.sysTypeList.length)
console.log("----------sysTypeList.length---------->"+(this.sysTypeList.length===0))
},
// 添加:挂载时初始化标签数据
mounted() {
if (this.initialTags && this.initialTags.length > 0) {
this.selectedTags = this.initialTags;
this.searchResults = this.initialTags;
// 将初始标签添加到tagMap中确保删除功能正常
this.initialTags.forEach(tag => {
if (tag.id) {
this.tagMap.set(tag.id, tag);
}
});
}
},
watch: {
// 监听课程ID变化重置所有状态
courseId(newVal) {
this.resetTagState();
},
// 监听初始标签变化,重新加载
initialTags(newVal) {
this.selectedTags = newVal || [];
this.searchResults = newVal || [];
this.tagMap.clear(); // 清空旧缓存
newVal.forEach(tag => {
if (tag.id) this.tagMap.set(tag.id, tag);
});
},
// 监听分类变化,重新加载搜索结果
sysTypeList: {
handler() {
// 只有在已选择分类且有焦点时才重新加载
if (this.sysTypeList.length > 0 && this.$refs.tagSelect && this.$refs.tagSelect.visible) {
this.doSearch('');
}
},
deep: true
}
},
methods: {
// 新增:检查标签是否应该被禁用
isTagDisabled(tag) {
// 如果标签已经被选中,不应该禁用(允许取消选择)
const isSelected = this.selectedTags.some(selectedTag => selectedTag.id === tag.id);
if (isSelected) {
return false;
}
// 如果标签未被选中且已达到最大数量,则禁用
return this.selectedTags.length >= this.maxTags;
},
// 新增:处理输入框获得焦点事件
async handleFocus() {
this.previousTags = [...this.selectedTags];
// 当输入框获得焦点时,加载默认的搜索结果
if (this.sysTypeList.length > 0) {
await this.doSearch('');
}
this.$emit('focus');
},
handleContainerClick() {
// 容器点击时也触发焦点事件
this.$emit('focus');
},
// 新增:重置标签状态的方法
resetTagState() {
this.selectedTags = [];
this.searchResults = [];
this.tagMap.clear();
this.loading = false;
this.params = {};
},
handleTagRemove(tagId) {
this.selectedTags = this.selectedTags.filter(id => id !== tagId)
this.$emit('change', this.displayTags)
this.clearInput();
},
removeTag(tagId) {
this.handleTagRemove(tagId)
},
// 新增:处理删除键事件
handleDeleteKey(event) {
// 如果输入框内容为空,不执行任何搜索
if (!event.target.value.trim()) {
this.searchResults = []
}
},
//按回车键,创建新标签
handleEnterKey(event) {
const inputVal = event.target.value?.trim()
if (!inputVal) return;
// 检查是否与已选择的标签重复
const isDuplicate = this.selectedTags.some(tag => tag.tagName === inputVal);
if (isDuplicate) {
this.$message.warning('该标签已存在,无需重复创建');
event.target.value = '';
return;
}
if (!isDuplicate && inputVal && this.selectedTags.length < this.maxTags) {
this.createNewTag(event.target.value.trim())
this.clearInput();
} else if (this.selectedTags.length >= this.maxTags) {
this.$message.warning('最多只能添加5个标签')
this.clearInput();
} else {
this.clearInput();
}
},
// 新增:处理选择变化事件
handleSelectionChange(newValues) {
// 检查每个标签对象是否完整
newValues.forEach((tag, index) => {
if (!tag.tagName) {
console.error(`${index}个标签缺少tagName:`, tag);
}
});
// 检查数量限制
if (newValues.length > this.maxTags) {
this.$message.warning(`最多只能选择${this.maxTags}个标签`);
// 回滚到之前的状态
this.selectedTags = [...this.previousTags];
return;
}
// 更新前保存当前状态
this.previousTags = [...newValues];
this.$emit('change', this.displayTags);
this.clearInput();
this.$nextTick(() => {
this.$refs.tagSelect.visible = false;
});
},
clearInput() {
if (this.$refs.tagSelect) {
const input = this.$refs.tagSelect.$refs.input;
if (input) {
input.value = '';
}
}
},
//创建新标签
async createNewTag(tagName) {
// 标签不能超过八个字
if (tagName.length > 8) {
this.$message.error('标签不能超过8个字')
return;
}
// 检查标签是否在下拉框中已存在
const isExistInSearch = this.searchResults.some(tag => tag.tagName === tagName);
if (isExistInSearch) {
this.$message.warning('已存在此标签,请选择');
return;
}
// 首先检查是否与已选择的标签重复
const isDuplicate = this.selectedTags.some(tag => tag.tagName === tagName);
if (isDuplicate) {
this.$message.warning('该标签已存在,无需重复创建');
return;
}
// 标签格式验证:仅支持中文、英文、数字、下划线、中横线
const tagPattern = /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/;
if (!tagPattern.test(tagName)) {
this.$message.error('标签名称仅支持中文、英文、数字、下划线(_)和中横线(-),不支持空格、点和特殊字符');
return;
}
// 添加标签数量限制检查
if (this.selectedTags.length >= this.maxTags) {
this.$message.warning('最多只能添加5个标签')
return;
}
this.loading = true
try {
this.params.courseId = this.courseId;
this.params.tagName = tagName;
// 分类
if (this.sysTypeList.length > 0) {
this.params.sysType1 = this.sysTypeList[0]; //一级的id
}
if (this.sysTypeList.length > 1) {
this.params.sysType2 = this.sysTypeList[1]; //二级的id
}
if (this.sysTypeList.length > 2) {
this.params.sysType3 = this.sysTypeList[2]; //三级的id
}
const {result:newTag} = await apiCourseTag.createTag(this.params)
this.$message.success('标签创建成功',newTag);
this.selectedTags = [...this.selectedTags, newTag];
// 更新搜索结果的逻辑保持不变
this.searchResults = [newTag, ...this.searchResults];
this.tagMap.set(newTag.id, newTag)
this.$emit('change', this.displayTags)
this.$nextTick(() => {
// 强制重新设置selectedTags来触发更新
const tempTags = [...this.selectedTags];
this.selectedTags = [];
this.$nextTick(() => {
this.selectedTags = tempTags;
});
this.$refs.tagSelect.visible = false;
});
} finally {
this.loading = false
}
},
// 修改doSearch方法添加搜索结果为空时的提示
async doSearch(query) {
// 不再在空查询时清空搜索结果
// if (!query.trim()) {
// this.searchResults = []
// return
// }
console.log("---- doSearch ------ query = " + query )
this.loading = true
try {
// 获取 typeId取 sysTypeList 最后一个有效的值
const typeId = this.sysTypeList.length > 2 ? this.sysTypeList[2] :
this.sysTypeList.length > 1 ? this.sysTypeList[1] :
this.sysTypeList.length > 0 ? this.sysTypeList[0] : null;
console.log("---- doSearch searchTags ------ query = " + query + " , typeId = " + typeId )
const {result:tags} = await apiCourseTag.searchTags({tagName:query,typeId:typeId})
console.log("-- searchTags 查询结果 tags = " + tags )
tags.forEach(item => {
this.tagMap.set(item.id, item)
})
this.searchResults = tags
// 当搜索结果为空时,提示用户可以按回车键创建标签
if (tags.length === 0) {
// this.$message.info('无此标签,按回车键创建')
}
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.tag-container {
position: relative;
}
.tag-preview {
margin-top: 8px;
}
.el-tag {
margin-right: 6px;
margin-bottom: 6px;
}
/* 添加标签计数样式 */
.tag-count {
position: absolute;
right: 10px;
top: 47%;
transform: translateY(-40%);
font-size: 12px;
color: #999;
background: white;
padding: 0 5px;
pointer-events: none;
/* 添加高度限制 */
height: 25px;
line-height: 25px; /* 垂直居中文字 */
box-sizing: border-box; /* 确保padding包含在height内 */
}
::v-deep(.el-select__tags) {
display: flex;
flex-wrap: wrap;
align-items: center;
}
/*
::v-deep(.el-tag) {
flex: 0 0 calc(50% - 8px);
max-width: calc(50% - 8px);
box-sizing: border-box;
margin-right: 8px;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}*/
::v-deep(.el-tag) {
flex: 1 1 auto; /* 自动调整宽度 */
min-width: 30%; /* 设置最小宽度 */
max-width: 48%; /* 设置最大宽度,留出边距 */
box-sizing: border-box;
margin-right: 8px;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
justify-content: center;
text-align: center;
}
::v-deep(.el-select__input) {
flex: 1;
min-width: 60px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
<script setup>
<script>
import {getCertificationProcess} from "@/api/modules/lecturer";
export default {

View File

@@ -128,7 +128,8 @@ export const iframes=[
{title:'查看受众', path:'/iframe/ugroup/view',hidden:false,component:'manage/AudienceView'},
{title:'问答管理', path:'/iframe/qa/manages',hidden:false,component:'qa/ManageList'},
{title:'待审核课程', path:'/iframe/course/noapproved',hidden:false,component:'examine/NotApproved'},
{title:'已审核课程', path:'/iframe/course/reviewed',hidden:false,component:'examine/Reviewed'}
{title:'已审核课程', path:'/iframe/course/reviewed',hidden:false,component:'examine/Reviewed'},
{title:'标签管理', path:'/iframe/tag/manages',hidden:false,component:'tag/TagManageList'},
]

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

@@ -11,16 +11,16 @@ import xpage from '@/utils/xpage'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login','/logout','/loading','/pc/loading','/500','/auth-redirect','/forget','/reset/password']
const whiteList = ['/login', '/logout', '/loading', '/pc/loading', '/500', '/auth-redirect', '/forget', '/reset/password']
router.beforeEach((to, from, next) => {
watermark.set("");
//动态计算文件的路径
let configPath=process.env.VUE_APP_FILE_RELATIVE_PATH;
if(configPath.startsWith('http')){
xpage.constants.fileBaseUrl=configPath;
}else{
xpage.constants.fileBaseUrl=window.location.protocol+'//'+window.location.host+configPath;
let configPath = process.env.VUE_APP_FILE_RELATIVE_PATH;
if (configPath.startsWith('http')) {
xpage.constants.fileBaseUrl = configPath;
} else {
xpage.constants.fileBaseUrl = window.location.protocol + '//' + window.location.host + configPath;
}
NProgress.start();
@@ -28,66 +28,71 @@ router.beforeEach((to, from, next) => {
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
}else{
if(getToken()){
if(to.path === '/login'){
} else {
if (getToken()) {
if (to.path === '/login') {
// 如果是外部用户,把配置的路由跳转到个人中心
if(store.getters.userInfo.role === 2){
next({ path: process.env.VUE_APP_PUBLIC_PATH+'/uc/study/courses' })
}else{
next({ path: process.env.VUE_APP_PUBLIC_PATH+'/index' })
if (store.getters.userInfo.role === 2) {
next({ path: process.env.VUE_APP_PUBLIC_PATH + '/uc/study/courses' })
} else {
next({ path: process.env.VUE_APP_PUBLIC_PATH + '/index' })
}
NProgress.done();
} else {
//console.log('store.getters.userInfo:',store.getters.userInfo.role)
// 如果是外部用户,把配置的路由跳转到个人中心
if(store.getters.userInfo.role === 2){
if(to.path === '/index' || to.path === '/course' || to.path === '/case' || to.path === '/article' ) location.href = '/pc/uc/study/task'
if (store.getters.userInfo.role === 2) {
if (to.path === '/index' || to.path === '/course' || to.path === '/case' || to.path === '/article') location.href = '/pc/uc/study/task'
}
//后续这里需要增加一定的控制
if (!store.getters.init) {
sessionStorage.removeItem(xpage.constants.localCaseFiltersKey);
sessionStorage.removeItem(xpage.constants.localCourseFiltersKey);
// 判断当前控制台是否已拉取完数据
store.dispatch('InitData').then(res => {
//加载白名单信息
//testUser.init();
//加载信息资源归属,系统分类信息
store.dispatch('resOwner/loadResOwners');
store.dispatch('sysType/loadSysTypes');
if (!store.getters.init) {
sessionStorage.removeItem(xpage.constants.localCaseFiltersKey);
sessionStorage.removeItem(xpage.constants.localCourseFiltersKey);
// 判断当前控制台是否已拉取完数据
store.dispatch('InitData').then(res => {
//加载白名单信息
//testUser.init();
//加载信息资源归属,系统分类信息
store.dispatch('resOwner/loadResOwners');
store.dispatch('sysType/loadSysTypes');
store.commit('app/SET_INITDATA',true);
//routers数据先使用固定的以后在初始化接口中返回
let myRouters=routers();
store.dispatch('GenerateRoutes',{routers:myRouters}).then(accessRoutes=>{
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
});
store.commit('app/SET_INITDATA', true);
//routers数据先使用固定的以后在初始化接口中返回
let myRouters = routers();
store.dispatch('GenerateRoutes', { routers: myRouters }).then(accessRoutes => {
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
});
}).catch(err => {
console.log(err);
store.commit('app/SET_INITDATA',false);
//如果初始化错误,就不再执行了,不然会一直循环下去
next({ path: '/500' })
//NProgress.done();
})
} else {
to.meta.keepAlive = true
next();
}
}).catch(err => {
console.log(err);
store.commit('app/SET_INITDATA', false);
//如果初始化错误,就不再执行了,不然会一直循环下去
next({ path: '/500' })
//NProgress.done();
})
} else {
to.meta.keepAlive = true
next();
}
}
//next();
}else{
} else {
//next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
//设置之前的路径
//store.commit('portal/SetBackUrl',location.href);
//console.log(location.href,'location.href');
//let urlPre=window.location.protocol+'//'+window.location.host;
//let backUrl=location.href.substring(urlPre.length); encodeURIComponent()
location.href=process.env.VUE_APP_LOGIN_URL+"?returnUrl="+encodeURIComponent(location.href);
NProgress.done()
//next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
//设置之前的路径
//store.commit('portal/SetBackUrl',location.href);
//console.log(location.href,'location.href');
//let urlPre=window.location.protocol+'//'+window.location.host;
//let backUrl=location.href.substring(urlPre.length); encodeURIComponent()
if (top !== window) { // 判断当前是否在iframe内
top.location.href = process.env.VUE_APP_LOGIN_URL + "?returnUrl=" + encodeURIComponent(location.href);
} else {
window.location.href = process.env.VUE_APP_LOGIN_URL + "?returnUrl=" + encodeURIComponent(location.href);
}
// location.href=process.env.VUE_APP_LOGIN_URL+"?returnUrl="+encodeURIComponent(location.href);
NProgress.done()
}
}

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

View File

@@ -19,215 +19,255 @@ import errorCode from '@/utils/errorCode'
//const ReLoginUrl="/login";
const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
const ReLoginUrl = process.env.VUE_APP_LOGIN_URL;
const TokenName='XBOE-Access-Token';
const TokenName = 'XBOE-Access-Token';
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
//只是用于发送json对象数据时使用post,put,patch
const jsonRequest=axios.create({
headers:{'Content-Type':'application/json;charset=utf-8'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
//超时
timeout: 60000,
const jsonRequest = axios.create({
headers: { 'Content-Type': 'application/json;charset=utf-8' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
//超时
timeout: 60000,
});
//发送json对象的拦截器
jsonRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
})
// 响应拦截器
jsonRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
return res.data
}else{
if(code == 6001){ //针对于老系统的处理
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code === 401){
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code === 402){
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code===403){
var msg='当前操作没有权限';
Message({message: msg, type: 'error'});
return Promise.reject(new Error(msg))
//return res.data;
}else if(code===302){
location.href = ReLoginUrl;
}else{
//Message({message: res.data.message, type: 'error'});
//console.log('err:' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code == 6001) { //针对于老系统的处理
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 402) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 403) {
var msg = '当前操作没有权限';
Message({ message: msg, type: 'error' });
return Promise.reject(new Error(msg))
//return res.data;
} else if (code === 302) {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
// location.href = ReLoginUrl;
} else {
//Message({message: res.data.message, type: 'error'});
//console.log('err:' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
)
//用于普通的发送请求
const formRequest=axios.create({
headers:{'Content-Type':'application/x-www-form-urlencoded'},
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
//超时
timeout: 10000,
const formRequest = axios.create({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
//超时
timeout: 10000,
})
//发送json对象的拦截器
formRequest.interceptors.request.use(config => {
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
//是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (getToken() && !isToken) {
config.headers[TokenName] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
console.log(error)
Promise.reject(error)
});
formRequest.interceptors.response.use(res => {
const code = res.data.status || 200;
if(code===200){
return res.data
}else{
if(code == 6001){ //针对于老系统的处理,因为老系统是字符串,所以这里不使用三等于号
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code === 401){
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code === 402){
store.dispatch('LogOut').then(() => {
location.href = ReLoginUrl;
})
}else if(code===403){
var msg='当前操作没有权限';
Message({message: msg, type: 'error'});
return Promise.reject(new Error(msg))
}else if(code===302){
location.href = ReLoginUrl;
}else{
//Message({message: res.data.message, type: 'error'});
//console.log('err' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;//返回给用户做业务处理
const code = res.data.status || 200;
if (code === 200) {
return res.data
} else {
if (code == 6001) { //针对于老系统的处理,因为老系统是字符串,所以这里不使用三等于号
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 401) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 402) {
store.dispatch('LogOut').then(() => {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
// location.href = ReLoginUrl;
})
} else if (code === 403) {
var msg = '当前操作没有权限';
Message({ message: msg, type: 'error' });
return Promise.reject(new Error(msg))
} else if (code === 302) {
if (top !== window) { // 判断当前是否在iframe内
top.location.href = ReLoginUrl;
} else {
window.location.href = ReLoginUrl;
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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)
}
// location.href = ReLoginUrl;
} else {
//Message({message: res.data.message, type: 'error'});
//console.log('err' + res.data.error);
//return Promise.reject(new Error(res.data.message))
return res.data;//返回给用户做业务处理
}
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "网络异常,请稍后重试";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
//location.href = this.webBaseUrl + ReLoginUrl;
}
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=function(cfg){
if(cfg.data){
cfg.data=qs.stringify(cfg.data);
const request = function (cfg) {
if (cfg.data) {
cfg.data = qs.stringify(cfg.data);
}
};
//requestJson请求
const requestJson=jsonRequest.request;
const requestJson = jsonRequest.request;
//get请求
const get=formRequest.request;
const get = formRequest.request;
//post请求
const post=function(url,data,config){
if(data){
data=qs.stringify(data);
const post = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.post(url,data,config);
return formRequest.post(url, data, config);
}
//post请求
const postForm=function(url,data,config){
return formRequest.post(url,data,config);
const postForm = function (url, data, config) {
return formRequest.post(url, data, config);
}
//postJson请求
const postJson=jsonRequest.post;
const postJson = jsonRequest.post;
//put请求
const put=function(url,data,config){
if(data){
data=qs.stringify(data);
const put = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.put(url,data,config);
return formRequest.put(url, data, config);
}
//putJson请求
const putJson=jsonRequest.put;
const putJson = jsonRequest.put;
//patch请求
const patch=function(url,data,config){
if(data){
data=qs.stringify(data);
const patch = function (url, data, config) {
if (data) {
data = qs.stringify(data);
}
return formRequest.patch(url,data,config);
return formRequest.patch(url, data, config);
}
//patchJson请求
const patchJson=jsonRequest.patch;
const patchJson = jsonRequest.patch;
//delete请求
const del=formRequest.delete;
const del = formRequest.delete;
export default {
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del,
postForm
request,
requestJson,
get,
post,
postJson,
put,
putJson,
patch,
patchJson,
del,
postForm
}

View File

@@ -153,28 +153,29 @@
</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="jin-text">精品课</span> -->
<div class="jin-zhe"></div>
<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"
@@ -231,7 +232,7 @@
</div>
<!--内容块-->
<div class="modules-title xindex-main">
<span class="modules-text">推荐课程</span>
@@ -1308,7 +1309,7 @@ export default {
return this.courseList.list.slice(3)
},
// 精品课展示
exquisiteList() {
exquisiteList() {
return this.qusisityList.list.slice(0,3)
},
},
@@ -1567,7 +1568,7 @@ export default {
let course = {
aid: this.userInfo.aid,
}
apiIndex.qualitylist(course).then((res) => {
apiIndex.qualitylist(course).then((res) => {
let courseIds = [];
res.data.result.forEach((item) => {
item.authorInfo = {
@@ -2826,6 +2827,15 @@ export default {
display: flex;
.modules-title {
position: relative;
.jin-zhe{
width: 410px;
height: 30px;
background: #f7f7f9;
position: absolute;
left: 86px;
}
.modules-text {
height: 28px;
font-size: 20px;
@@ -3136,4 +3146,13 @@ export default {
border: 1px solid #d9edf7;
//overflow: hidden;
}
.jin-text{
font-family: "Source Han Sans CN", "SourceHanSansCN", sans-serif !important;
font-weight: bold;
font-size: 20px;
color: #3E87F5;
// line-height: 29px;/
text-align: justify;
font-style: normal;
}
</style>

View File

@@ -5,56 +5,61 @@
</template>
<script>
import Cookies from 'vue-cookies'
import apiLogin from '@/api/login.js'
import { getToken,setToken } from '@/utils/token'
export default{
mounted(){
this.toUrl=this.$route.query.returnUrl;
let token=getToken();
let $this=this;
if(!token){
//console.log(token,'未获取token');
setTimeout(function(){
$this.curToken=getToken();
if(!$this.curToken){
//console.log(token,'第二次未获取token');
location.href = process.env.VUE_APP_LOGIN_URL;
}else{
$this.boeLogin();
import Cookies from "vue-cookies";
import apiLogin from "@/api/login.js";
import { getToken, setToken } from "@/utils/token";
export default {
mounted() {
this.toUrl = this.$route.query.returnUrl;
let token = getToken();
let $this = this;
if (!token) {
//console.log(token,'未获取token');
setTimeout(function () {
$this.curToken = getToken();
if (!$this.curToken) {
//console.log(token,'第二次未获取token');
if (top !== window) {
// 判断当前是否在iframe内
top.location.href = process.env.VUE_APP_LOGIN_URL;
} else {
window.location.href = process.env.VUE_APP_LOGIN_URL;
}
},500);
}else{
this.curToken=token;
this.boeLogin();
}
},
data(){
return {
curToken:'',
toUrl:''
}
},
methods:{
boeLogin(){
apiLogin.boeLogin(this.curToken).then(rs=>{
if(rs.status==200){
//setToken(rs.result.access_token);
localStorage.setItem(this.$xpage.constants.newLoginKey,1);
if(this.toUrl){
location.href=this.toUrl;
}else{
this.$router.push({ path: "/index" })
}
//this.$router.push({ path: "/index" })
}else{
this.$message.error("登录失败:"+rs.message);
}
})
}
// location.href = process.env.VUE_APP_LOGIN_URL;
} else {
$this.boeLogin();
}
}, 500);
} else {
this.curToken = token;
this.boeLogin();
}
}
},
data() {
return {
curToken: "",
toUrl: "",
};
},
methods: {
boeLogin() {
apiLogin.boeLogin(this.curToken).then((rs) => {
if (rs.status == 200) {
//setToken(rs.result.access_token);
localStorage.setItem(this.$xpage.constants.newLoginKey, 1);
if (this.toUrl) {
location.href = this.toUrl;
} else {
this.$router.push({ path: "/index" });
}
//this.$router.push({ path: "/index" })
} else {
this.$message.error("登录失败:" + rs.message);
}
});
},
},
};
</script>
<style>

View File

@@ -483,8 +483,11 @@ export default {
} else if (this.form.device2 === true) {
this.form.device = 2;
}
//时长,秒与分钟的转化
//if(this.form.)
// 时长,秒与分钟的转化
if (this.form.minute) {
this.form.duration = this.form.minute * 60;
}
try {
const { status,message} = await coueseFile.batchUpdate([this.form]);
if (status === 200) {

View File

@@ -1,66 +1,117 @@
<template>
<el-dialog
:visible="dialogVisible"
width="600px"
:close-on-click-modal="false"
:show-close="true"
@close="onClose"
class="case-expert-dialog"
>
<!-- 标题 -->
<div slot="title" class="dialog-title">
<!-- <img src="@/assets/images/case-expert-icon.png" alt="案例专家" class="icon" /> -->
<span>案例专家</span>
</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"></messages>
</div>
<div class="message-suggestions" v-if="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>
<!-- 最大化状态的弹窗 -->
<el-dialog
v-if="dialogVisible && windowState === 'maximized'"
:visible="true"
:close-on-click-modal="false"
:show-close="true"
@close="onClose"
class="case-expert-dialog"
:modal="false"
:fullscreen="false"
top="0"
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>
<!-- 输入框区域 -->
<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>
<!-- 内容区域 -->
<div class="content-wrapper">
<div
class="welcome-message"
ref="messageContainer"
@scroll="handleScroll"
@wheel="handleWheel"
>
<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>
<!-- 关闭按钮在右上角 el-dialog 自动处理 -->
</el-dialog>
<!-- 输入框区域 -->
<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: {
@@ -73,43 +124,480 @@ export default {
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>
text: `<p><b>您好!我是京东方案智能问答助手,随时为您服务。</b></p>
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
}
],
suggestions:[],
isAutoScroll: true // 是否自动滚动
isAutoScroll: true, // 是否自动滚动
// 添加一个标志位,用于标识组件是否已经初始化完成
isComponentReady: false
}
},
mounted() {
// 组件挂载完成后,标记为已准备就绪
this.$nextTick(() => {
this.isComponentReady = true;
});
},
watch: {
dialogVisible: {
handler(newVal) {
console.log('dialogVisible发生变化');
console.log(newVal);
console.log(this.windowState);
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() {
this.$nextTick(() => {
this.scrollToBottom();
});
// 只有在组件准备就绪后才执行滚动操作
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) {
console.log('handleLoading---'+status);
this.isLoading = status;
},
@@ -131,10 +619,13 @@ export default {
},500)
},
startNewConversation() {
// 重置对话时,先标记组件为未准备就绪状态
this.isComponentReady = false;
this.messageList = [
{
isBot: true,
text: `<p><b>您好!我是京东方案智能问答助手,随时为您服务。</b></p>
text: `<p><b>您好!我是京东方案智能问答助手,随时为您服务。</b></p>
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
@@ -142,6 +633,11 @@ export default {
];
this.AIContent = '';
this.isLoading = false;
// 在下一个 tick 中重新标记为准备就绪
this.$nextTick(() => {
this.isComponentReady = true;
});
},
// 处理滚动事件
@@ -160,21 +656,66 @@ export default {
if (this.isAutoScroll && this.$refs.messageContainer) {
this.$refs.messageContainer.scrollTop = this.$refs.messageContainer.scrollHeight;
}
},
// 处理鼠标滚轮事件
handleWheel(event) {
const element = this.$refs.messageContainer;
if (!element) return;
// 阻止事件冒泡,防止滚动底层页面
event.stopPropagation();
// 计算滚动方向和距离
const delta = event.deltaY || event.detail || event.wheelDelta;
// 检查是否可以继续滚动
if (delta < 0 && element.scrollTop === 0) {
// 向上滚动且已在顶部,阻止默认行为
event.preventDefault();
} else if (delta > 0 && element.scrollHeight - element.scrollTop <= element.clientHeight) {
// 向下滚动且已在底部,阻止默认行为
event.preventDefault();
} else {
// 允许在容器内滚动
element.scrollTop += delta;
event.preventDefault();
}
},
// 最小化窗口的点击事件处理方法
onMinimizedWindowClick() {
// 当点击最小化窗口时如果dialogVisible为false则通过事件通知父组件显示对话框
if (!this.dialogVisible) {
this.$emit('restore');
}
// 然后将窗口状态设置为最大化
this.windowState = 'maximized';
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-dialog__wrapper{
position: unset!important;
}
.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;
pointer-events: auto;
z-index: 2000;
//background-color: rgba(255, 255, 255, 0.8);
}
::v-deep .el-dialog__body{
padding: 10px;
flex:1;
//font-size: 12px;
*{
font-size:unset ;
@@ -185,15 +726,24 @@ export default {
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; /* 黑色图标 */
}
}
@@ -214,7 +764,8 @@ export default {
padding: 20px;
background-color: transparent;
border-radius: 8px;
height: 550px;
min-height: 500px;
height:100%;
position: relative;
//margin-bottom: 20px;
display: flex;
@@ -225,7 +776,8 @@ export default {
flex-direction: column;
align-items: flex-start;
margin-bottom: 10px;
flex:1;
height: 400px;
//flex:1;
overflow-y: auto;
.avatar {
@@ -310,4 +862,45 @@ export default {
}
}
}
.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>

File diff suppressed because it is too large Load Diff

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

@@ -1,160 +1,223 @@
<!--消息渲染-->
<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",
name: 'Message',
props: {
messageData: {
type: Object,
default: function () {
return {}
}
required: true,
default: () => ({}),
},
suggestions: {
type: Array,
default: () => []
}
default: () => [],
},
},
data() {
return {
md,
displayText: '',
typingTimer: null,
typingSpeed: 30, // 打字机速度(毫秒/字符
showAllCaseRefers: false // 控制是否显示所有案例引用
}
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.messageData.isBot && !this.messageData.typing) {
// this.startTyping(newVal)
if (!newVal) {
this.displayText = '';
return;
}
if (this.messageData.isBot && !this.messageData.typing) {
// this.startTyping(newVal); // 启动打字机效果/**/
this.displayText = newVal || ''
} else {
this.displayText = newVal || ''
this.displayText = this.md.render(newVal);
this.$nextTick(this.renderMermaid); // 直接渲染 Mermaid
}
},
immediate: true
}
immediate: true,
},
},
methods: {
startTyping(text) {
// 清除之前的定时器
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 = null
clearInterval(this.typingTimer);
}
// 初始化
// this.displayText = ''
let index = 0
// 开始打字机效果
this.typingTimer = setInterval(() => {
if (index < text.length) {
this.displayText += text.charAt(index)
index++
if (index < renderedText.length) {
this.displayText += renderedText[index];
index++;
} else {
// 打字完成,清除定时器
clearInterval(this.typingTimer)
this.typingTimer = null
clearInterval(this.typingTimer);
this.typingTimer = null;
this.$nextTick(this.renderMermaid); // 渲染 Mermaid 图表
}
}, this.typingSpeed)
}, 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
clearInterval(this.typingTimer);
this.typingTimer = null;
}
}
}
},
};
</script>
<template>
<div class="messages">
{{messageData}}
<!-- 机器人消息-->
<div v-if="messageData.isBot" class="bot-message">
<div class="bot-think" v-if="messageData.thinkText" v-html="messageData.thinkText"></div>
<div v-html="displayText" ></div>
<div v-if="messageData.caseRefers && messageData.caseRefers.length > 0 && messageData.textCompleted">
<div 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 class="case-refers-item" v-for="item in displayedCaseRefers" :key="item.caseId">
<div class="case-refers-item-title">
<a :href="'#case-' + item.caseId" 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">{{item.content}}</div>
</div>
</div>
</div>
</div>
</div>
<!-- 非机器人消息-->
<div v-else class="user-message">
<div class="message-text">
<div v-html="messageData.text"></div>
</div>
</div>
<!-- 推荐问题-->
<!-- <div v-if="suggestions && suggestions.length > 0">-->
<!-- <div class="suggestions">-->
<!-- <div class="suggestions-title">-->
<!-- <span>推荐问题</span>-->
<!-- </div>-->
<!-- <div class="suggestions-list">-->
<!-- <div class="suggestions-item" v-for="item in suggestions">-->
<!-- <div class="suggestions-item-title">-->
<!-- {{item}}-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</template>
<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;
@@ -176,125 +239,185 @@ export default {
left: 0;
top: -3px;
transform: scaleX(0.5);
margin-right: 5px;
}
}
.case-refers {
margin-top: 10px;
.message-content {
font-size: 14px;
line-height: 1.6;
}
.case-refers-title {
font-weight: bold;
margin-bottom: 5px;
display: flex;
align-items: center;
justify-content: space-between;
.icon-think {
background-image: url("./map.svg") ;
width: 15px;
height: 13px;
display: inline-block;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.more{
font-size: 10px;
padding: 2px 6px;
background-color: #F4F7FD;
border-radius: 5px;
color:#577EE1;font-weight: unset;
cursor: pointer;
}
}
.case-refers-list {
display: flex;
flex-direction: column;
.case-refers-item {
//margin-right: 10px;
margin-bottom: 5px;
border: 1px solid rgba(144, 147, 153, 0.44);
padding: 5px;
border-radius: 5px;
.case-refers-item-title {
font-size: 14px;
margin-bottom: 5px;
font-weight: 600;
color: #000;
display: flex;
align-items: flex-end;
justify-content: space-between;
.title{
max-width: 70% ;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.case-refers-item-timer{
font-size: 10px;
margin-right: 20px;
color:#cecece;
font-weight: unset!important;
}
}
.case-refers-item-author{
display: flex;
align-items: center;
.user{
background-image: url("./user.svg");
width: 15px;
height: 15px;
display: inline-block;
background-repeat: no-repeat;
background-size: 100% 100%;
margin-right: 5px;
}
.case-inter-orginInfo{
font-size: 10px;
color: rgba(144, 147, 153, 0.44);margin-left: 5px;
}
}
.case-refers-item-author,
.case-refers-item-keywords {
font-size: 12px;
font-weight: 600;
color: #000;
}
}
}
.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: 5px 15px;
box-sizing: border-box;
background-color: rgba(228, 231, 237, 1);
padding: 8px 15px;
max-width: 80%;
background-color: #e4e7ed;
border-radius: 5px;
margin-bottom: 10px;
font-size: 14px;
word-break: break-word;
}
.case-refers-item-keywords{
margin-top: 5px;
span{
padding: 1px 4px;
background-color: #F4F7FD;
border-radius: 5px;
font-size: 10px!important;
color:#577EE1
/* ========== 案例引用样式 ========== */
.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;
}
}
span + span{
margin-left: 8px;
.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;
}
}
}
}
}
.message-content{
font-size: 12px!important;
margin-top: 5px;
/* ========== 推荐问题 ========== */
.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

@@ -2,10 +2,13 @@
<div class="input-area">
<el-input
v-model="inputContent"
type="textarea"
class="input-placeholder"
placeholder="有问题,尽管问"
@keyup.enter.native="handleSend"
@keyup.enter.native.prevent="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">
@@ -53,7 +56,14 @@ export default {
}
},
methods: {
handleSend() {
handleSend(event) {
// 阻止事件的默认行为和冒泡
if (event) {
event.preventDefault();
event.stopPropagation();
console.log('preventDefault');
}
console.log('handleSend');
if (!this.inputContent.trim() || this.disabled) return
// 添加用户消息到列表
const userMessage = {
@@ -91,12 +101,12 @@ export default {
conversationId: this.conversationId,
query: question
};
// 创建POST请求
fetch('/systemapi/xboe/m/boe/case/ai/chat',{
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
"accept": "text/event-stream",
},
body: JSON.stringify(requestData)
}).then(r=>{
@@ -129,7 +139,7 @@ export default {
return;
}
const typingSpeed = 50; // 每个字符的间隔时间(毫秒)
const typingSpeed = 30; // 每个字符的间隔时间(毫秒)
typingTimer = setInterval(() => {
// 计算下一个要显示的字符索引
@@ -230,6 +240,7 @@ export default {
// 从响应中获取并保存conversationId
if (jsonData.conversationId) {
this.conversationId = jsonData.conversationId;
sessionStorage.setItem('conversationId', jsonData.conversationId);
}
break;
@@ -310,7 +321,7 @@ export default {
}
aiMessage.textCompleted = true;
this.$emit('loading', false);
aiMessage.text = '抱歉,网络连接出现问题,请稍后重试';
aiMessage.text = '当前无法获取回答,请稍后重试';
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
});
@@ -323,12 +334,13 @@ export default {
// 出错时也设置文字处理完成状态
aiMessage.textCompleted = true;
this.$emit('loading', false);
aiMessage.text = '抱歉,网络连接出现问题,请稍后重试';
aiMessage.text = '当前无法获取回答,请稍后重试';
// 更新父组件的messageList
this.$emit('update-message', aiMessage);
});
},
handleNewConversation() {
this.conversationId = ''
this.$emit('new-conversation')
}
}

View File

@@ -30,13 +30,19 @@
<!-- <div class="course-title-right"> -->
<!-- <interactBar :readonly="!stuStusts || stuStusts==0" :type="1" :data="courseInfo" :comments="false" :views="false"></interactBar> -->
<!-- </div> -->
<!-- <div class="label-div">
<el-tag class="label-item" effect="plain" v-for="(item,tagIdx) in tagArray" :key="tagIdx">{{item}}</el-tag>
</div>-->
<div class="label-div">
<div v-for="(item, tagIdx) in tagArray" :key="tagIdx" class="keyword-tag">
{{ item }}
</div>
</div>
<div>
<div class="study-count">{{courseInfo.studys}}人学习</div>
<!-- <div><span style="font-size:20px;color:#ff8e00">{{courseInfo.score ? courseInfo.score.toFixed(1) : 0}}</span><span style="font-size:12px;color:#ff8e00"></span></div> -->
</div>
<div class="label-div">
<el-tag class="label-item" effect="plain" v-for="(item,tagIdx) in tagArray" :key="tagIdx">{{item}}</el-tag>
</div>
<!-- <div style="width:160px;height:50px"> -->
<!-- </div> -->
<!-- <div class="label-div">
@@ -419,7 +425,7 @@ export default {
.course-title{
position: relative;
height: 90px;
height: auto;
display: flex;
justify-content: space-between;
.title {
@@ -452,18 +458,43 @@ export default {
padding: 24px 24px 5px 24px;
// margin-right: 361px;
.study-count {
margin-top: 10px;
margin-top: 30px;
font-size: 16px;
color: #444444;
}
.label-div {
/*.label-div {
margin: 5px 0;
min-height: 20px;
.label-item {
padding: 0 7px;
padding: 0px 8px;
margin-top: 5px;
float: left;
line-height: 24px;
font-size: 12px;
border-radius: 2px;
margin-right: 8px;
margin-bottom: 0px;
color: #2C68FF;
height: 24px;
background: rgba(44, 104, 255, 0.06);
border: none; // 或者使用 border-color: transparent;
}
}*/
.label-div {
margin: 5px 0;
min-height: 20px;
.keyword-tag {
padding: 0px 10px;
margin-top: 7px;
float: left;
line-height: 24px;
font-size: 12px;
border-radius: 2px;
margin-right: 10px;
color: #2C68FF;
height: 24px;
background: rgba(44, 104, 255, 0.06);
}
}
::v-deep .el-rate__icon {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,389 @@
<template>
<div class="u-page" style="padding-right:32px">
<div style="width: 100%; margin-left: 12px;padding: 2px 0px 10px 12px;background-color: white">
<el-form :inline="true" style="margin-left: 12px;" :model="pageData" class="demo-form-inline">
<el-form-item label="标签ID:" label-width="60px">
<el-input id="tag-id" placeholder="请输入标签ID" v-model="pageData.id" clearable />
</el-form-item>
<el-form-item label="标签名称:" label-width="80px">
<el-input id="tag-id" placeholder="请输入标签名称" v-model="pageData.tagName" clearable />
</el-form-item>
<el-form-item label="热点标签:" label-width="80px">
<el-select v-model="pageData.isHot" style="width: 120px;" clearable placeholder="请选择状态">
<el-option label="开启" value="true"></el-option>
<el-option label="关闭" value="false"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="getsearch" icon="el-icon-search" type="primary">查询</el-button>
<!-- 添加重置按钮 -->
<el-button @click="resetSearch" icon="el-icon-refresh">重置</el-button>
</el-form-item>
</el-form>
</div>
<div style="padding: 5px 0px 2px 12px;">
<!-- <el-checkbox label="前台公共显示"></el-checkbox>-->
<!-- <el-checkbox label="热点标签展示"></el-checkbox>-->
</div>
<div style="width: 100%; margin-left: 12px;padding: 2px 0px 10px 12px;background-color: white">
<el-table style="width: 96%; margin:2px 32px 10px 12px;" :data="pageData.list" border stripe
:header-cell-style="{ background: '#E9F0FF' }"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange">
<el-table-column type="selection" width="80px"></el-table-column>
<el-table-column label="标签ID" width="200px" prop="id"></el-table-column>
<el-table-column label="标签名称" width="235px" prop="tagName"></el-table-column>
<el-table-column label="已关联课程" width="220px"
prop="useCount"
sortable="custom"
:sort-orders="['descending', 'ascending']"
>
<template #default="scope">
<a v-if="scope.row.useCount > 0"
@click="showCourseByTag(`${scope.row.id}`)"
style="font-weight:bold; color: #409EFF; text-decoration: underline;">
{{ scope.row.useCount }}
</a>
<span style="font-weight:bold; color: #409EFF; text-decoration: underline;" v-else>0</span>
</template>
</el-table-column>
<el-table-column label="前台公共显示" width="220px" prop="isPublic">
<template #default="scope"><!-- 开关状态会直接修改 pageData.list 中的数据 -->
<el-switch
v-model="scope.row.isPublic"
:disabled="scope.row.isHot==1?true:false"
@change="handlePublicChange(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="热点标签展示" width="220px" prop="isHot">
<template #default="scope">
<el-switch
v-model="scope.row.isHot"
:disabled="scope.row.isPublic==0?true:false"
@change="handleHotChange(scope.row)"
>
</el-switch>
</template>
</el-table-column>
</el-table>
<div v-if="pageData.list.length > 0" style="text-align: center;margin-top: 50px;">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageData.pageIndex"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageData.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</div>
<!-- 标签关联课程弹窗 -->
<el-dialog custom-class="g-dialog" title="关联课程"
width="850px" top="20px"
:visible.sync="dialogVisible"
:modal-append-to-body="true"
:append-to-body="true">
<div class="dialog-content-container">
<el-table
:data="pageData.list2"
border stripe style="width: 100%"
:header-cell-style="{ background: '#E9F0FF' }"
@sort-change="handleSortChange2">
<el-table-column label="序号" width="60px" align="center">
<template #default="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="关联课程名称" width="200px" prop="courseName"></el-table-column>
<el-table-column label="关联课程ID" width="100px" prop="courseId"></el-table-column>
<el-table-column label="关联人" width="80px" prop="sysCreateBy"></el-table-column>
<el-table-column label="关联时间" width="110px" prop="sysCreateTime"
:formatter="dateFormat" sortable="custom"
:sort-orders="['descending', 'ascending']"></el-table-column>
<el-table-column label="本课程绑定的其他标签" width="200px" prop="otherTags"></el-table-column>
<el-table-column label="操作" width="60px">
<template #default="scope">
<a @click="unbindCurrentTag(scope.row)"
style="font-weight:bold; color: #409EFF;">
解绑
</a>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div v-if="pageData.list2.length > 0" class="pagination-container">
<el-pagination
background
@size-change="handleSizeChange2"
@current-change="handleCurrentChange2"
:current-page="pageData.pageIndex2"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageData.pageSize2"
layout="total, sizes, prev, pager, next, jumper"
:total="total2">
</el-pagination>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import moment from 'moment';
import apiCourseTag from '@/api/modules/courseTag.js'
import { mapGetters } from 'vuex';
export default {
name: 'courseTagItems',
computed: {
...mapGetters(['userInfo'])
},
data() {
return {
pageData: {
pageIndex: 1,
pageIndex2: 1,
pageSize: 10,
pageSize2: 10,
list:[],
list2:[],
orderField: null,
orderAsc: null,
orderField2: null,
orderAsc2: null,
},
total: 0,
total2: 0,
dialogVisible: false,
tagId: null,
}
},
created() {
this.getCourseTagList()
},
methods: {
//重置搜索条件
resetSearch() {
this.pageData.id = '';
this.pageData.tagName = '';
this.pageData.isHot = '';
this.pageData.pageIndex = 1;
this.getCourseTagList(); // 重置后重新加载列表
},
//初始化:课程标签列表
getsearch(){
this.pageData.pageIndex = 1;
this.getCourseTagList()
},
//课程标签列表:排序
handleSortChange({ prop, order }) {
this.pageData.orderField = prop; // 当前排序字段
this.pageData.orderAsc = order === 'ascending'; // 排序方向
this.getCourseTagList(); // 重新获取数据
},
//TODO:课程标签列表:监听选中项变化(批量的设置标签公共显示|热点标签)
handleSelectionChange(selection) {
this.selectedRows = selection; // 更新选中的行数据
},
//课程标签列表:获取课程标签列表数据
getCourseTagList() {
const { pageIndex, pageSize, orderField, orderAsc } = this.pageData
let query = { pageIndex, pageSize, orderField, orderAsc}
//拼接查询条件
if (this.pageData.id) {
const { id } = this.pageData
query.id = id
}
if (this.pageData.tagName) {
const { tagName } = this.pageData
query.tagName = tagName
}
if (this.pageData.isHot) {
const { isHot } = this.pageData
query.isHot = isHot
}
apiCourseTag.portalPageList(query).then((res) => {
if (res.status == 200) {
this.total = res.result.count
this.pageData.list = res.result.list
}
})
.catch((err) => {
this.$message.error('获取数据失败')
})
},
//课程标签列表:改变标签的公共属性
async handlePublicChange(row) {
// 保存原始状态用于回滚
const originalStatus = row.isPublic;
try {
// 调用 API 更新状态
await apiCourseTag.changeTagPublic(row);
this.$message.success('更新成功');
} catch (error) {
// 发生错误时回滚状态
row.isPublic = originalStatus;
this.$message.error('更新失败:' + error.message);
}
},
//课程标签列表:改变标签的热点属性
async handleHotChange(row) {
const isPublic=row.isPublic;
// 保存原始状态用于回滚
const originalStatus = row.isHot;
try {
// 调用 API 更新状态
await apiCourseTag.changeTagHot(row).then((res)=>{
if (res.status == 200){
this.$message.success(res.message);
}else {
row.isHot=false;
this.$message.warning(res.message);
}
});
} catch (error) {
// 发生错误时回滚状态
row.isHot = originalStatus;
this.$message.error('更新失败:' + error.message);
}
},
//课程标签列表:改变条数的回调
handleSizeChange(value) {
this.pageData.pageIndex = 1;
this.pageData.pageSize = value;
this.getCourseTagList();
},
//课程标签列表:改变页数的回调
handleCurrentChange(value) {
this.pageData.pageIndex = value;
this.getCourseTagList();
},
//标签关联的所有课程弹出框显示指定标签id关联的课程列表
showCourseByTag(tagId) {
this.tagId=tagId;
this.getCourseOfTagList(tagId);
this.dialogVisible=true;
},
//分页查询指定标签关联的所有课程
getCourseOfTagList(){
const { pageIndex2:pageIndex, pageSize2:pageSize, orderField2:orderField, orderAsc2:orderAsc } = this.pageData
let query = { pageIndex, pageSize, orderField, orderAsc }
//拼接查询条件
if (this.tagId) {
query.id = this.tagId
apiCourseTag.showCourseByTag(query).then((res) => {
if (res.status == 200) {
this.total2 = res.result.count
this.pageData.list2 = res.result.list
if (this.total2==0){
this.dialogVisible=false
this.getCourseTagList(); // 重新获取课程标签列表数据
}
}
})
.catch((err) => {
this.$message.error('获取数据失败')
});
}
},
//标签关联课程列表:排序
handleSortChange2({ prop, order }) {
this.pageData.orderField2 = prop; // 当前排序字段
this.pageData.orderAsc2 = order === 'ascending'; // 排序方向
this.getCourseOfTagList(); // 重新获取数据
},
//标签关联的所有课程列表:改变条数的回调
handleSizeChange2(value) {
this.pageData.pageIndex2= 1;
this.pageData.pageSize2 = value;
this.getCourseOfTagList();
},
//标签关联的所有课程列表:改变页数的回调
handleCurrentChange2(value) {
this.pageData.pageIndex2 = value;
this.getCourseOfTagList();
},
//关联时间格式化
dateFormat(row, column) {
return row[column.property] ?
moment(row[column.property]).format('YYYY-MM-DD') : '';
},
//解除指定课程和当前标签的关联关系
unbindCurrentTag (row) {
let id = row.id;
let tagId = this.tagId;
let courseId = row.courseId;
//拼接查询条件
if (tagId && courseId) {
let params = { id, tagId, courseId }
apiCourseTag.unbindCourseTagRelation(params).then((res) => {
if (res.status == 200) {
//刷新列表
this.getCourseOfTagList(this.tagId);
}
})
.catch((err) => {
this.$message.error('解绑失败!')
});
}
}
}
}
</script>
<style>
.demo-form-inline {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px; /* 间距 */
}
.demo-form-inline .el-form-item {
margin-bottom: 0; /* 消除默认底部间距 */
}
.dialog-content-container {
padding: 10px;
border: 1px solid #d9d9d9;
}
.pagination-container {
margin-top: 20px;
text-align: center;
}
.g-dialog .el-dialog__header {
background-color: #409EFF;
padding: 15px 20px;
}
.g-dialog .el-dialog__title {
color: white;
font-weight: bold;
}
.g-dialog .el-dialog__headerbtn .el-dialog__close {
color: white;
}
</style>

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'))