Compare commits
253 Commits
20250918-c
...
test1024
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bbc3904e0 | ||
|
|
3d7ca1cc20 | ||
|
|
b529695751 | ||
|
|
ebae7f6c0b | ||
|
|
16355a1e5e | ||
|
|
91f06d4ed6 | ||
|
|
4ee084cff2 | ||
|
|
9b3b3b94ef | ||
|
|
427ff20531 | ||
|
|
43aaeabf99 | ||
|
|
6c5e189448 | ||
|
|
38fe26c146 | ||
|
|
ef9e4a0f68 | ||
|
|
27e3f37033 | ||
|
|
f5ac82769f | ||
|
|
ce4be998a4 | ||
|
|
a2640771fb | ||
|
|
2f034447b6 | ||
|
|
25cb97f462 | ||
|
|
8524f7ec6f | ||
|
|
3f108ab9b7 | ||
|
|
9c3b1f8857 | ||
|
|
57c01d2519 | ||
|
|
e9f872dd07 | ||
|
|
3a5462465b | ||
|
|
51c3d29854 | ||
|
|
fb2e07cea3 | ||
|
|
c49d69dede | ||
|
|
6a764dd698 | ||
|
|
24de64d8e5 | ||
|
|
86ec8138dd | ||
|
|
bc15ec9e21 | ||
|
|
af10b1fa32 | ||
|
|
79040580a1 | ||
|
|
4b2285a6f9 | ||
|
|
782bcc31e5 | ||
|
|
a15c8510c2 | ||
|
|
7be0bdee6c | ||
|
|
109122f1c1 | ||
|
|
6d6d0e4539 | ||
|
|
f88a3a0b53 | ||
|
|
5e561930e1 | ||
|
|
1a95852912 | ||
|
|
556eaea825 | ||
|
|
2fd3ac0de2 | ||
|
|
bbafde31b5 | ||
|
|
3eae32b617 | ||
|
|
48ec56dcbc | ||
|
|
dfdba51202 | ||
|
|
0b5d0e3180 | ||
|
|
9bc6af207b | ||
|
|
f5d865ccc3 | ||
|
|
8711bbaabb | ||
|
|
a25198f80f | ||
|
|
cc7801d42d | ||
|
|
ef868f8165 | ||
|
|
417d911f5d | ||
|
|
aa1301575e | ||
|
|
2a633e25e5 | ||
|
|
81b8ef168c | ||
|
|
7f587138ff | ||
|
|
973e3f947d | ||
|
|
6afc19d21c | ||
|
|
305a5481c8 | ||
|
|
01bbe6c5c4 | ||
|
|
be10b56885 | ||
|
|
b2458b6b92 | ||
|
|
f7d247dca2 | ||
|
|
560d180583 | ||
|
|
e9953f2ac6 | ||
|
|
b2908ba75e | ||
|
|
d087082613 | ||
|
|
f8c6f55e73 | ||
|
|
25c2e673dc | ||
|
|
f0ea689166 | ||
|
|
5b2a6d304c | ||
|
|
44ccf8db6d | ||
|
|
5f42a95f47 | ||
|
|
b0bc517936 | ||
|
|
897684b4f2 | ||
|
|
c9185722f8 | ||
|
|
f43a03b661 | ||
|
|
69294e0d49 | ||
|
|
78cc822464 | ||
|
|
b00e2c80b0 | ||
|
|
a901e488b4 | ||
|
|
512144d7d7 | ||
|
|
fd85b8b5d0 | ||
|
|
d1d65d41ca | ||
|
|
526e5e0e48 | ||
|
|
366f1dc45b | ||
|
|
cee45f82cb | ||
|
|
4b668cf186 | ||
|
|
a656c27e24 | ||
|
|
743478655e | ||
|
|
f07ef38cd5 | ||
|
|
47ab19db7e | ||
|
|
4ee6697166 | ||
|
|
254344a528 | ||
|
|
a54c642f4b | ||
|
|
55b8ee7840 | ||
|
|
109d154387 | ||
|
|
6a77bd9dc4 | ||
|
|
e48cb36917 | ||
|
|
8e51663b86 | ||
|
|
79cd4d2e03 | ||
|
|
cbe7981abd | ||
|
|
cc4909897b | ||
|
|
2e7fa1cc2e | ||
|
|
dfc22479bb | ||
|
|
1ad2816622 | ||
|
|
ac788d59f3 | ||
|
|
03b3c61c6b | ||
|
|
4f74216c6e | ||
|
|
a5279210c7 | ||
|
|
e0db15c85d | ||
|
|
b3756280cf | ||
|
|
b38a4768a6 | ||
|
|
63439cb57f | ||
|
|
f34d2a6e94 | ||
|
|
478890ff09 | ||
|
|
65673561d8 | ||
|
|
b18f9d132b | ||
|
|
4d5b462b61 | ||
|
|
a1047420fb | ||
|
|
7b02644cde | ||
|
|
055476c583 | ||
|
|
2f6e38430e | ||
|
|
78f681b4cb | ||
|
|
6a875182d7 | ||
|
|
520fb4ee5e | ||
|
|
ba7bfe5f11 | ||
|
|
a7ccf26932 | ||
|
|
a2760f2ec6 | ||
|
|
86bcf06d87 | ||
|
|
5d8f9874c8 | ||
|
|
3e1b545d2a | ||
|
|
fbf3b19e40 | ||
|
|
3f028e5cd8 | ||
|
|
1d2d20acb0 | ||
|
|
d94bcf96a1 | ||
|
|
3cc0f690a4 | ||
|
|
36b739d139 | ||
|
|
2cbb379fa6 | ||
|
|
0d01f7da86 | ||
|
|
8bf7a8e8e7 | ||
|
|
803d9c2dbb | ||
|
|
4b92308d1d | ||
|
|
101ebdbbad | ||
|
|
a25ea0c4ba | ||
|
|
1b47071a99 | ||
|
|
e55aa09409 | ||
|
|
8a39d424a2 | ||
|
|
43a8ae3606 | ||
|
|
5a3b57bd1c | ||
|
|
6d4af3aa2d | ||
|
|
ebf99e8a7f | ||
|
|
6d37310bc4 | ||
|
|
f9675b1ab7 | ||
|
|
ed6fb14367 | ||
|
|
ee43ea7b0c | ||
|
|
83bda2e8a5 | ||
|
|
90622d6ebc | ||
|
|
b7273410a0 | ||
|
|
9580dd9735 | ||
|
|
b8ba52731f | ||
|
|
86e25f69f9 | ||
|
|
b8daef0983 | ||
|
|
df45c9d896 | ||
|
|
b9caf2c4ad | ||
|
|
0afd733f47 | ||
|
|
3720b5667d | ||
|
|
72472979bd | ||
| 70000e2e10 | |||
|
|
5ebee96ce4 | ||
| 408d6a1612 | |||
|
|
969c9f6797 | ||
|
|
33406f6964 | ||
|
|
e1f2e91648 | ||
|
|
8c023d459f | ||
|
|
47c1d29ef2 | ||
|
|
a3dab45af0 | ||
|
|
e3422d15ee | ||
|
|
581be5567f | ||
|
|
c9465492f4 | ||
|
|
3cef730e61 | ||
|
|
b18500bad7 | ||
|
|
5536fc06e1 | ||
|
|
18f3804536 | ||
|
|
7230bd18e8 | ||
| b1508ad226 | |||
| a9764bf2f8 | |||
|
|
483b57f667 | ||
|
|
be411ec72d | ||
|
|
d7e425ce9d | ||
|
|
8b68489b25 | ||
|
|
fd10d99454 | ||
|
|
c3f53515b9 | ||
|
|
a51d87fbe8 | ||
| 087be5dd38 | |||
|
|
2aa36c82ab | ||
|
|
1772c972b9 | ||
|
|
0bba87cb3d | ||
|
|
91bafcb5b9 | ||
|
|
8c533c5f3a | ||
|
|
bb17784501 | ||
|
|
69530fe6ad | ||
|
|
b1cd8e2f63 | ||
|
|
7335dd4eba | ||
| 3860087fac | |||
| 046509f70d | |||
| bf0ae91184 | |||
| f61742a0b9 | |||
|
|
c886a80193 | ||
| 05ad90b025 | |||
| f3833a23fa | |||
| d957a8d44b | |||
|
|
89ed79828f | ||
|
|
b926590edc | ||
| 19bc757dc2 | |||
| f3a1036b64 | |||
| 3c1650b751 | |||
| ff2bdb4ae5 | |||
|
|
14fb53ccd3 | ||
|
|
72f9661150 | ||
|
|
7806afc5f7 | ||
| e1af233c5f | |||
| 2738493030 | |||
| 480a4f5564 | |||
|
|
205bf5469f | ||
|
|
30897a1fa5 | ||
|
|
1c59cffd3e | ||
|
|
6e9f93d6c9 | ||
|
|
3ee4a2fd6e | ||
|
|
1b442ef040 | ||
|
|
4693cb0db1 | ||
|
|
92fecbec80 | ||
|
|
be63f5a1aa | ||
|
|
c9899eda6b | ||
|
|
0f52a69beb | ||
|
|
3410afedcf | ||
|
|
33866c0f49 | ||
|
|
c9e51fc21f | ||
|
|
a42668c929 | ||
|
|
01d4bc0536 | ||
|
|
d52e8b389b | ||
|
|
e9a86d0364 | ||
|
|
0e43ca5e82 | ||
|
|
0771460f60 | ||
|
|
1a2829d70a | ||
|
|
68eda7efcc | ||
|
|
98ba239494 | ||
|
|
df3b1d7162 |
22046
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mermaid-js/parser": "^0.6.3",
|
||||
"axios": "^0.21.4",
|
||||
"core-js": "^3.6.5",
|
||||
"driver.js": "^0.9.8",
|
||||
@@ -23,9 +24,15 @@
|
||||
"element-ui": "^2.15.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"fuse.js": "^6.4.6",
|
||||
"highlight.js": "^11.11.1",
|
||||
"image-conversion": "^2.1.1",
|
||||
"jsencrypt": "^3.2.1",
|
||||
"json-bigint": "^1.0.0",
|
||||
"katex": "^0.16.25",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-highlightjs": "^4.2.0",
|
||||
"markdown-it-mermaid": "^0.2.5",
|
||||
"mermaid": "^8.13.10",
|
||||
"mockjs": "^1.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"nprogress": "^0.2.0",
|
||||
@@ -43,6 +50,7 @@
|
||||
"vue": "^2.6.11",
|
||||
"vue-awesome-swiper": "^3.1.3",
|
||||
"vue-cookies": "^1.7.4",
|
||||
"vue-katex": "^0.5.0",
|
||||
"vue-pdf": "^4.2.0",
|
||||
"vue-quill-editor": "^3.0.6",
|
||||
"vue-router": "^3.5.2",
|
||||
@@ -60,6 +68,7 @@
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"less": "^4.1.1",
|
||||
"less-loader": "^6.2.0",
|
||||
"null-loader": "^4.0.1",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^10.1.0",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
|
||||
BIN
public/images/case-logo.png
Normal file
|
After Width: | Height: | Size: 438 KiB |
BIN
public/images/qualityBg.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
60
src/App.vue
@@ -2,24 +2,73 @@
|
||||
<div id="app">
|
||||
<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,4 @@
|
||||
border: 1px solid #e7e7e7 !important;
|
||||
box-shadow: 0px 1px 5px 1px rgba(92,98,111,.3);
|
||||
}
|
||||
</style>
|
||||
|
||||
</style>
|
||||
30
src/api/boe/aiChat.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import ajax from '@/utils/xajax.js'
|
||||
|
||||
/**
|
||||
* AI聊天对话接口
|
||||
* @param {Object} data - 请求参数
|
||||
* @param {string} data.conversationId - 会话ID,如果为空则创建新会话
|
||||
* @param {string} data.query - 用户提问内容
|
||||
* @returns {Promise} - 返回SSE流
|
||||
*/
|
||||
export function aiChat(data) {
|
||||
return ajax.postJson('http://192.168.3.178/xboe/m/boe/case/ai/chat', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会话消息记录接口
|
||||
* @param {string} conversationId - 会话ID
|
||||
* @returns {Promise} - 返回会话历史记录
|
||||
*/
|
||||
export function getChatMessages(conversationId) {
|
||||
return ajax.get('/xboe/m/boe/case/ai/messages?conversationId=' + conversationId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 案例专家功能入口显示权限判断接口
|
||||
* 判断当前登录用户是否显示"案例专家"功能入口
|
||||
* @returns {Promise} - 返回是否显示功能入口的布尔值
|
||||
*/
|
||||
export function showCaseAiEntrance() {
|
||||
return ajax.get('/xboe/m/boe/case/ai/show-entrance')
|
||||
}
|
||||
208
src/api/httpAjax.js
Normal file
@@ -0,0 +1,208 @@
|
||||
import axios from 'axios'
|
||||
import qs from 'qs'
|
||||
import {Notification, MessageBox, Message} from 'element-ui'
|
||||
import store from '@/store'
|
||||
import {getToken} from '@/utils/token'
|
||||
import errorCode from '@/utils/errorCode'
|
||||
|
||||
/**
|
||||
*request请求 axios.request(config)
|
||||
*requestJson请求 axios.request(config)
|
||||
*get请求 axios.get(url[, config])
|
||||
*post请求 axios.post(url[, data[, config]])
|
||||
*postJson请求 axios.post(url[, data[, config]])
|
||||
*put请求 axios.put(url[, data[, config]])
|
||||
*putJson请求 axios.put(url[, data[, config]])
|
||||
*patch请求 axios.patch(url[, data[, config]])
|
||||
*patchJson请求 axios.patch(url[, data[, config]])
|
||||
*delete请求 axios.delete(url[, config])
|
||||
*/
|
||||
|
||||
|
||||
// const ReLoginUrl=process.env.VUE_APP_LOGIN_URL;
|
||||
const TokenName = 'token';
|
||||
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
|
||||
//只是用于发送json对象数据时使用post,put,patch
|
||||
/**axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'**/
|
||||
//只是用于发送json对象数据时使用post,put,patch
|
||||
//用于普通的发送请求
|
||||
const formRequest = axios.create({
|
||||
//headers:{'Content-Type':'application/x-www-form-urlencoded'},
|
||||
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||
// baseURL: process.env.VUE_APP_CESOURCE_BASE_API,
|
||||
//超时
|
||||
timeout: 10000,
|
||||
})
|
||||
//发送json对象的拦截器
|
||||
formRequest.interceptors.request.use(config => {
|
||||
//是否需要设置 token
|
||||
const isToken = (config.headers || {}).isToken === false
|
||||
let curToken = getToken();
|
||||
//curToken='eyJ0eXBlIjoidG9rZW4iLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC91LmJvZS5jb20iLCJpYXQiOjE2NzIzMTE2MTIsImV4cCI6MTY3MjMxODgxMiwiR2l2ZW5OYW1lIjoiYm9ldSIsInVzZXJJZCI6IjZCMDQ5RkFGLUMzMTQtN0NDRi0wRDI4LTBEMjNGNEM0MjUzMSIsInVJZCI6Ijk2NTM0MjAyNzQ5NzYwNzE2OCIsInBlcm1pc3Npb24iOiIifQ==.a4f41376e994c5fcd3ab537ce17572ef4c633863f87785cf7b6ffa353e2ed51c';
|
||||
if (curToken && !isToken) {
|
||||
config.headers[TokenName] = curToken // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
return config
|
||||
}, error => {
|
||||
console.log(error)
|
||||
Promise.reject(error)
|
||||
});
|
||||
formRequest.interceptors.response.use(res => {
|
||||
//console.log(res);
|
||||
const code = res.data.status || 200;
|
||||
if (code === 200) {
|
||||
return res.data
|
||||
} else {
|
||||
if (code === 401) {
|
||||
// store.dispatch('LogOut').then(() => {
|
||||
// location.href = this.webBaseUrl + ReLoginUrl;
|
||||
// })
|
||||
console.error('', res.data);
|
||||
return Promise.reject(new Error('接口返回未登录'))
|
||||
} else if (code === 403) {
|
||||
var msg = '当前操作没有权限';
|
||||
Message({message: msg, type: 'error'});
|
||||
return Promise.reject(new Error(msg))
|
||||
} else {
|
||||
//Message({message: res.data.message, type: 'error'});
|
||||
//console.log('err' + res.data.error);
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log('err', error)
|
||||
let {message} = error;
|
||||
if (message == "Network Error") {
|
||||
message = "网络异常,请稍后重试";
|
||||
} else if (message.includes("timeout")) {
|
||||
message = "网络异常或接口错误,请求超时";
|
||||
} else if (message.includes("Request failed with status code")) {
|
||||
message = "系统接口" + message.substr(message.length - 3) + "异常";
|
||||
}
|
||||
Message({
|
||||
message: message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* request请求,可以自定义参数
|
||||
*/
|
||||
const request = formRequest.request;
|
||||
|
||||
/**
|
||||
* get请求 ,只有url
|
||||
*/
|
||||
const get = function (baseURL, url) {
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'get',
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* post请求
|
||||
* @param {Object} url
|
||||
* @param {Object} postData
|
||||
*/
|
||||
const post = function (baseURL, url, postData) {
|
||||
// if (postData) {
|
||||
// postData = qs.stringify(postData);
|
||||
// }
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'post',
|
||||
data: postData,
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
})
|
||||
}
|
||||
//post请求
|
||||
const postForm = function (baseURL, url, data) {
|
||||
return request({
|
||||
baseURL,
|
||||
url,
|
||||
data,
|
||||
method: 'post',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
});
|
||||
}
|
||||
// const postJson=jsonRequest.post;
|
||||
|
||||
const postJson = function (baseURL, url, postData) {
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'post',
|
||||
data: postData,
|
||||
headers: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
})
|
||||
}
|
||||
|
||||
// 导出文件请求定义
|
||||
const postJsonToFile = function (baseURL, url, postData) {
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'post',
|
||||
data: postData,
|
||||
headers: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
const getJsonToFile = function (baseURL, url, postData) {
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'get',
|
||||
data: postData,
|
||||
headers: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* put请求
|
||||
*/
|
||||
const put = function (baseURL, url, data) {
|
||||
if (data) {
|
||||
data = qs.stringify(data);
|
||||
}
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'put',
|
||||
data: data,
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
})
|
||||
}
|
||||
|
||||
const putJson = function (baseURL, url, data) {
|
||||
return request({
|
||||
baseURL,
|
||||
url: url,
|
||||
method: 'put',
|
||||
data: data,
|
||||
headers: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
tokenName: TokenName,
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
postJson,
|
||||
postJsonToFile,
|
||||
put,
|
||||
putJson,
|
||||
getJsonToFile
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
64
src/api/modules/courseTag.js
Normal 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
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import ajax from '@/utils/xajax.js'
|
||||
import http from '../unionAjax'
|
||||
import httpAjax from '../httpAjax'
|
||||
|
||||
const baseURL = process.env.VUE_APP_MANAGER_API_PATH;
|
||||
|
||||
|
||||
@@ -47,6 +49,18 @@ const articlelist=function (type){
|
||||
const courselist=function (data){
|
||||
return ajax.post('/xboe/portal/index/courselist',data);
|
||||
}
|
||||
// 精品课信息列表
|
||||
const qualitylist=function (data){
|
||||
return httpAjax.post(baseURL,'/quality/home/qualityItem',data);
|
||||
}
|
||||
// 精品课分页查询
|
||||
export const qualityPageList=function (data){
|
||||
return httpAjax.post(baseURL,'/quality/home/qualityPages',data);
|
||||
}
|
||||
// 课程精品课标记时间年份列表
|
||||
export const qualityCourseTimeMark=function (){
|
||||
return httpAjax.post(baseURL,'/quality/manage/qualityYearList',{});
|
||||
}
|
||||
/**
|
||||
* 首页新课程推荐列表
|
||||
*/
|
||||
@@ -61,5 +75,7 @@ export default {
|
||||
articlelist,
|
||||
courselist,
|
||||
newCases,
|
||||
getRecommendList
|
||||
getRecommendList,
|
||||
qualitylist,
|
||||
qualityPageList
|
||||
}
|
||||
|
||||
BIN
src/assets/images/course/courseBackground.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
src/assets/images/course/courseTag.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/images/course/courseTitle.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/assets/images/project/title-bg.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
@@ -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{
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
<el-form label-width="120px" label-position="left" >
|
||||
<el-form-item label="课程名称" required><el-input maxlength="90" v-model="courseInfo.name" show-word-limit placeholder="课程名称(限90字以内)"></el-input></el-form-item>
|
||||
<el-form-item label="是否设置目录" required>
|
||||
<div>
|
||||
<el-radio v-model="courseInfo.type" :label="20">是</el-radio>
|
||||
<el-radio v-model="courseInfo.type" :label="10">否</el-radio>
|
||||
</div>
|
||||
<div>
|
||||
<el-radio v-model="courseInfo.type" :label="20">是</el-radio>
|
||||
<el-radio v-model="courseInfo.type" :label="10">否</el-radio>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- <div style="margin-top:40px">适合您<span style="color:#e83d3a;font-size: 16px;">快速</span>分享<span style="color:#e83d3a;font-size: 16px;">单一</span>的视频、音频或者文档制作说明</div> -->
|
||||
<!-- <div v-show="courseExample == 1">适合您<span class="tip">快速</span>分享<span class="tip">单一</span>的视频、音频或者文档制作说明</div>
|
||||
<div v-show="courseExample == 2">适合您分享<span class="tip">体系化</span>、<span class="tip">结构化</span>的课程内容制作说明</div>
|
||||
-->
|
||||
<!-- <div style="margin-top:40px">适合您<span style="color:#e83d3a;font-size: 16px;">快速</span>分享<span style="color:#e83d3a;font-size: 16px;">单一</span>的视频、音频或者文档制作说明</div> -->
|
||||
<!-- <div v-show="courseExample == 1">适合您<span class="tip">快速</span>分享<span class="tip">单一</span>的视频、音频或者文档制作说明</div>
|
||||
<div v-show="courseExample == 2">适合您分享<span class="tip">体系化</span>、<span class="tip">结构化</span>的课程内容制作说明</div>
|
||||
-->
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- <div style="height:100px"></div> -->
|
||||
@@ -73,10 +73,13 @@
|
||||
:props="{ value: 'id', label: 'name' }"
|
||||
:options="sysTypeListMap"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" required>
|
||||
<courseTag ref="courseTag" :courseId="curCourseId" :sysTypeList="sysTypeList" :initialTags="courseTags" @change="handleTagsChange"></courseTag>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源归属" required>
|
||||
<el-input placeholder="请选择" v-model="orgName" >
|
||||
<el-button v-if="identity==3 || identity==5" @click="showChooseOrg()" slot="append" icon="el-icon-search">选择</el-button>
|
||||
</el-input>
|
||||
<el-input placeholder="请选择" v-model="orgName" >
|
||||
<el-button v-if="identity==3 || identity==5" @click="showChooseOrg()" slot="append" icon="el-icon-search">选择</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="场景" v-show="!weike.onlyRequired">
|
||||
<el-col :span="18">
|
||||
@@ -109,11 +112,11 @@
|
||||
<el-input maxlength="50" v-model="courseInfo.forUsers" show-word-limit placeholder="目标人群(限50字以内)"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="visibleShow" label="学员可见">
|
||||
<el-checkbox v-model="courseInfo.visible"></el-checkbox>
|
||||
<el-checkbox v-model="courseInfo.visible"></el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="受众" v-if="!weike.onlyRequired">
|
||||
<el-select value-key="id" style="width: 100%;" v-model="courseCrowds" filterable multiple :clearable="false" @remove-tag="removeCrowd" placeholder="请选择">
|
||||
<el-option v-for="item in userGroupList" :key="item.id" :disabled="item.disabled" :label="item.name" :value="item"></el-option>
|
||||
<el-option v-for="item in userGroupList" :key="item.id" :disabled="item.disabled" :label="item.name" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="课程价值" v-if="!weike.onlyRequired">
|
||||
@@ -141,7 +144,6 @@
|
||||
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="1">PC端可见</el-radio>
|
||||
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="2">移动端可见</el-radio>
|
||||
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" :label="3">多端可见</el-radio>
|
||||
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" v-if="isPermission" :label="4">仅内网访问</el-radio>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!weike.onlyRequired" label="课程来源">
|
||||
<el-radio-group v-model="courseInfo.source">
|
||||
@@ -151,11 +153,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!weike.onlyRequired" label="课程简介">
|
||||
<el-input type="textarea"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
:rows="3"
|
||||
v-model="courseInfo.summary"
|
||||
placeholder="请尽量填写课程简介,用于列表中显示,可以让用户更容易了解课程信息">
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
:rows="3"
|
||||
v-model="courseInfo.summary"
|
||||
placeholder="请尽量填写课程简介,用于列表中显示,可以让用户更容易了解课程信息">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -254,18 +256,21 @@
|
||||
</el-select> -->
|
||||
<choice :teacherValue="teacherValues" @getTeacherList="getTeacherList"></choice>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" required>
|
||||
<courseTag ref="courseTag" :courseId="curCourseId" :sysTypeList="sysTypeList" :initialTags="courseTags" @change="handleTagsChange"></courseTag>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键字">
|
||||
<el-input v-model.trim="keywords" maxlength="100" @keyup.enter.native="changeKeywords" placeholder="请输入关键字"></el-input>
|
||||
<el-tag v-for="(tag,index) in tips" size="small" :key="index" closable type="info" @close="closeKeywordsTag(tag,index)">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-tag v-for="(tag,index) in tips" size="small" :key="index" closable type="info" @close="closeKeywordsTag(tag,index)">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源归属" required>
|
||||
<el-input placeholder="请选择" v-model="orgName" >
|
||||
<el-button v-if="identity==3 || identity==5" @click="showChooseOrg()" slot="append" icon="el-icon-search">选择</el-button>
|
||||
</el-input>
|
||||
<el-input placeholder="请选择" v-model="orgName" >
|
||||
<el-button v-if="identity==3 || identity==5" @click="showChooseOrg()" slot="append" icon="el-icon-search">选择</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标人群" required>
|
||||
<el-col :span="14">
|
||||
@@ -273,13 +278,13 @@
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item v-if="visibleShow" label="学员可见">
|
||||
<el-checkbox v-model="courseInfo.visible"></el-checkbox>
|
||||
<el-checkbox v-model="courseInfo.visible"></el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="受众"><!--:disabled="item.disabled"-->
|
||||
<el-select value-key="id" style="width: 100%;" v-model="courseCrowds" filterable multiple :clearable="false" @remove-tag="removeCrowd" placeholder="请选择">
|
||||
<el-option v-for="item in userGroupList" :key="item.id" :label="item.name" :value="item"></el-option>
|
||||
<el-option v-for="item in userGroupList" :key="item.id" :label="item.name" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="课程价值"><el-input maxlength="200" v-model="courseInfo.value" show-word-limit placeholder="课程价值(限200字以内)"></el-input></el-form-item>
|
||||
@@ -306,7 +311,6 @@
|
||||
<el-radio v-model="courseInfo.device" :label="1">PC端可见</el-radio>
|
||||
<el-radio v-model="courseInfo.device" :label="2">移动端可见</el-radio>
|
||||
<el-radio v-model="courseInfo.device" :label="3">多端可见</el-radio>
|
||||
<el-radio style="margin-right: 10px;" v-model="courseInfo.device" v-if="isPermission" :label="4">仅内网访问</el-radio>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="课程来源">
|
||||
@@ -393,8 +397,8 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog class="checked-show" :visible.sync="courseInfoFormCheckedShow" top="14vh" width="800px" :show-close="false" :modal="false">
|
||||
<agreement></agreement>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<agreement></agreement>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button style="margin-right:10px" type="primary" @click="courseInfoFormCheckedShow = false">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
@@ -404,6 +408,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import courseTag from "@/components/Course/courseTag.vue";
|
||||
import choice from '@/components/Course/choice.vue';
|
||||
import agreement from '@/components/Portal/agreement.vue';
|
||||
import weikeContent from '@/components/Course/weikeContent.vue';
|
||||
@@ -420,6 +425,7 @@ import apiCourse from '../../api/modules/course.js';
|
||||
import apiCourseAudit from '../../api/modules/courseAudit.js';
|
||||
import apiOrg from '../../api/system/organiza.js';
|
||||
import apiUser from '../../api/system/user.js';
|
||||
import apiCourseTag from '../../api/modules/courseTag.js';
|
||||
import WxEditor from '@/components/Editor/index.vue';
|
||||
import catalogSort from '@/components/Course/catalogSort.vue';
|
||||
import { courseType, getType } from '../../utils/tools.js';
|
||||
@@ -428,7 +434,7 @@ import filecloud from '@/components/FileCloud/index.vue';
|
||||
import chooseOrg from '@/components/System/chooseOrg.vue';
|
||||
export default {
|
||||
props: {},
|
||||
components: { weikeContent, catalogCourseware, imageUpload, WxEditor, catalogSort,agreement,filecloud,choice,chooseOrg},
|
||||
components: { courseTag, weikeContent, catalogCourseware, imageUpload, WxEditor, catalogSort,agreement,filecloud,choice,chooseOrg},
|
||||
data() {
|
||||
return {
|
||||
keywords:'',//关键字的定义
|
||||
@@ -468,6 +474,7 @@ export default {
|
||||
orgName:'',
|
||||
orgNamePath:'',
|
||||
orgKid:'',
|
||||
courseTags:[],
|
||||
courseInfo: {
|
||||
id: '',
|
||||
name: '',
|
||||
@@ -490,8 +497,6 @@ export default {
|
||||
refType:''
|
||||
},
|
||||
visibleShow:false,
|
||||
isPermission:false,
|
||||
dicts:[],
|
||||
extendRefId:'',
|
||||
extendRefType:'',
|
||||
courseTeachers: [], //课程的老师
|
||||
@@ -531,11 +536,7 @@ export default {
|
||||
dlgShow: false
|
||||
},
|
||||
rightTypeId: {},
|
||||
catalogSortDialogShow: false,
|
||||
selectedOrg: {
|
||||
orgId: null,
|
||||
name: ''
|
||||
}
|
||||
catalogSortDialogShow: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -560,18 +561,14 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
courseInfo: {
|
||||
handler(newVal, oldVal) {
|
||||
// 需要保存
|
||||
handler(newVal) {
|
||||
//需要保存
|
||||
this.requireSaveCourse = true;
|
||||
|
||||
console.log("--- watch比较 = ", oldVal.orgId, newVal.orgId);
|
||||
this.checkOrgPermission(newVal.orgId);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDictIds();
|
||||
let extendFlag=this.$route.query.f; //是否是管理端过来的
|
||||
this.extendRefId=this.$route.query.refId;
|
||||
this.extendRefType=this.$route.query.refType;
|
||||
@@ -593,19 +590,6 @@ export default {
|
||||
this.loadUserGroup();
|
||||
},
|
||||
methods: {
|
||||
// 检查机构权限
|
||||
checkOrgPermission(orgId) {
|
||||
console.log("--- 监测组织id orgId = ",orgId)
|
||||
console.log("--- this.isPermission = ",this.isPermission)
|
||||
console.log("--- device = ",this.courseInfo.device)
|
||||
if (!orgId) {
|
||||
this.isPermission = false;
|
||||
return;
|
||||
}
|
||||
console.log("--- this.dicts = ",this.dicts)
|
||||
this.isPermission = this.dicts.includes(orgId);
|
||||
console.log("--- 监听结束 this.isPermission = ",this.isPermission)
|
||||
},
|
||||
// 关键字的更改
|
||||
changeKeywords(option){
|
||||
if(option.target.value){
|
||||
@@ -617,15 +601,33 @@ export default {
|
||||
closeKeywordsTag(item,index){
|
||||
this.tips.splice(index, 1);
|
||||
},
|
||||
// 处理标签变化事件
|
||||
handleTagsChange(tags) {
|
||||
console.log("父组件:",tags)
|
||||
// 限制最多5个标签
|
||||
if (tags.length > 5) {
|
||||
this.$message.warning('最多只能选择5个标签')
|
||||
// 强制限制为5个
|
||||
tags = tags.slice(0, 5);
|
||||
return
|
||||
}
|
||||
let ids = "";
|
||||
tags.forEach(tag=>{
|
||||
console.log("父组件name : ",tag.tagName)
|
||||
ids += tag.id + ',';
|
||||
})
|
||||
this.courseInfo.tags = ids;
|
||||
this.$emit('change', tags.slice(0, 5)); // 确保传出数据也不超过5个
|
||||
},
|
||||
showChooseOrg(){
|
||||
this.$refs.refChooseOrg.dlgShow = true;
|
||||
this.$refs.refChooseOrg.dlgShow = true;
|
||||
},
|
||||
removeCrowd(e){
|
||||
//console.log(e);
|
||||
if(e.disabled){
|
||||
this.$message.error("您不能移除创建人加的受众");
|
||||
this.courseCrowds.push(e);
|
||||
return false;
|
||||
this.$message.error("您不能移除创建人加的受众");
|
||||
this.courseCrowds.push(e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
confirmChooseOrg(orgInfo,parentInfo){
|
||||
@@ -694,17 +696,17 @@ export default {
|
||||
// }
|
||||
// });
|
||||
apiUserBasic.getUserAudiences(params).then(rs=>{
|
||||
if(rs.status==200){
|
||||
let crowdList=[];
|
||||
rs.result.list.forEach(item=>{
|
||||
crowdList.push({
|
||||
id:item.id,
|
||||
name:item.audienceName,
|
||||
disabled:false
|
||||
})
|
||||
});
|
||||
this.userGroupList=crowdList;
|
||||
}
|
||||
if(rs.status==200){
|
||||
let crowdList=[];
|
||||
rs.result.list.forEach(item=>{
|
||||
crowdList.push({
|
||||
id:item.id,
|
||||
name:item.audienceName,
|
||||
disabled:false
|
||||
})
|
||||
});
|
||||
this.userGroupList=crowdList;
|
||||
}
|
||||
})
|
||||
},
|
||||
resOwnerName(code) {
|
||||
@@ -747,6 +749,7 @@ export default {
|
||||
this.$emit('close');
|
||||
},
|
||||
initShow(editData) {
|
||||
console.log('初始化显示内容============', editData)
|
||||
//console.log(this.$refs.weikePanel,'this.$refs.weikePanel');
|
||||
//this.$refs.weikePanel.init();
|
||||
//this.$refs.onlineCourse.resetData();
|
||||
@@ -794,9 +797,11 @@ export default {
|
||||
this.tips=[];
|
||||
|
||||
if (!editData) {
|
||||
//console.log("新建课程?");
|
||||
//以下为了保证初始化处理
|
||||
this.weikeReset = Math.round(Math.random()) + '';
|
||||
this.tips=[];
|
||||
this.courseTags=[],
|
||||
//console.log("新建课程?");
|
||||
//以下为了保证初始化处理
|
||||
this.weikeReset = Math.round(Math.random()) + '';
|
||||
this.onlineReset = Math.round(Math.random()) + '';
|
||||
this.courseChooseShow = true;
|
||||
this.biaoke.tabIndex = 'base';
|
||||
@@ -817,11 +822,11 @@ export default {
|
||||
// });
|
||||
apiUserBasic.getOrgInfo(this.courseInfo.orgId).then(rs=>{
|
||||
if(rs.status==200){
|
||||
this.orgName=rs.result.name;
|
||||
this.courseInfo.orgName=rs.result.name;
|
||||
//this.orgKid=rs.result.kid;
|
||||
this.orgNamePath=rs.result.namePath;
|
||||
}
|
||||
this.orgName=rs.result.name;
|
||||
this.courseInfo.orgName=rs.result.name;
|
||||
//this.orgKid=rs.result.kid;
|
||||
this.orgNamePath=rs.result.namePath;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -890,16 +895,30 @@ export default {
|
||||
if (rs.status == 200) {
|
||||
this.courseChooseShow = false;
|
||||
this.courseInfo = rs.result;
|
||||
this.curCourseId = this.courseInfo.id
|
||||
console.log('保存课程成功',this.curCourseId)
|
||||
if (this.courseChooseId == 1) {
|
||||
this.weike.dlgShow = true;
|
||||
} else {
|
||||
this.biaoke.dlgShow = true;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.initTagComponent();
|
||||
});
|
||||
} else {
|
||||
this.$message.error(rs.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 新增初始化标签方法
|
||||
initTagComponent() {
|
||||
if (this.$refs.courseTag) {
|
||||
// 确保组件已渲染后再调用搜索
|
||||
setTimeout(() => {
|
||||
this.$refs.courseTag.debouncedSearch('');
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
//上传课程图片处理
|
||||
uploadCoverImgSuccess(res) {
|
||||
//console.log(res,'res');
|
||||
@@ -910,30 +929,16 @@ export default {
|
||||
this.courseCoverurl = '';
|
||||
this.courseInfo.coverImg = '';
|
||||
},
|
||||
//获取字典信息
|
||||
async getDictIds() {
|
||||
console.log("--- 获取字典信息 1 = ", this.dicts);
|
||||
try {
|
||||
const response = await apiCourse.getDictIds(637, 1); // 确保返回 Promise
|
||||
console.log("--- 获取字典信息 2 result= ", response);
|
||||
|
||||
if (response.status === 200) {
|
||||
this.dicts = response.result.dicts; // 正确提取 dicts
|
||||
console.log("--- 获取字典信息 3 = ", this.dicts);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取字典信息失败:", error);
|
||||
}
|
||||
},
|
||||
//获取课程信息
|
||||
async getDetail(id) {
|
||||
this.curCourseId = id;
|
||||
this.orgName='';
|
||||
this.isPermission = false;
|
||||
let $this = this;
|
||||
let $this = this;
|
||||
try {
|
||||
const { result, status } = await apiCourse.detail(id);
|
||||
if (status === 200) {
|
||||
this.courseTags = result.tagList;
|
||||
console.log('获取课程信息成功', this.courseTags);
|
||||
//把数据附给三个对象
|
||||
if(result.course.visible==''){
|
||||
result.course.visible=false;
|
||||
@@ -947,36 +952,33 @@ export default {
|
||||
this.contentInfo.list = result.contents;
|
||||
this.sectionInfo.list = result.sections;
|
||||
this.courseTeachers = result.teachers; //课程的老师信息
|
||||
this.isPermission = result.isPermission; //课程的老师信息
|
||||
this.dicts = result.dicts; //课程的老师信息
|
||||
console.log("--- 编辑查看 this.isPermission = ",this.isPermission)
|
||||
console.log("--- 编辑查看 this.dicts = ",this.dicts)
|
||||
|
||||
if(!this.courseInfo.orgId){
|
||||
//根据课程创建者获取机构id
|
||||
apiUser.getOrgSimpleByUserId(result.course.sysCreateAid).then(ors=>{
|
||||
if(ors.status==200){
|
||||
$this.courseInfo.orgId=ors.result.id;
|
||||
// apiOrg.getSimple(ors.result.id).then(rrs=>{
|
||||
// if(rrs.status==200){
|
||||
// $this.orgName=rrs.result.name;
|
||||
// $this.orgKid=rrs.result.kid;
|
||||
// $this.orgNamePath=rrs.result.namePath;
|
||||
// }
|
||||
// })
|
||||
apiUserBasic.getOrgInfo(ors.result.id).then(rrs=>{
|
||||
if(rrs.status==200){
|
||||
$this.orgName=rrs.result.name;
|
||||
this.courseInfo.orgName=rrs.result.name;
|
||||
//$this.orgKid=rrs.result.kid;
|
||||
$this.orgNamePath=rrs.result.namePath;
|
||||
}else{
|
||||
this.courseInfo.orgId='';
|
||||
//this.$message.error('资源归属已变更,请重新选择');
|
||||
}
|
||||
})
|
||||
}else{
|
||||
//this.$message.error('无机构关联,不需要提示');
|
||||
}
|
||||
if(ors.status==200){
|
||||
$this.courseInfo.orgId=ors.result.id;
|
||||
// apiOrg.getSimple(ors.result.id).then(rrs=>{
|
||||
// if(rrs.status==200){
|
||||
// $this.orgName=rrs.result.name;
|
||||
// $this.orgKid=rrs.result.kid;
|
||||
// $this.orgNamePath=rrs.result.namePath;
|
||||
// }
|
||||
// })
|
||||
apiUserBasic.getOrgInfo(ors.result.id).then(rrs=>{
|
||||
if(rrs.status==200){
|
||||
$this.orgName=rrs.result.name;
|
||||
this.courseInfo.orgName=rrs.result.name;
|
||||
//$this.orgKid=rrs.result.kid;
|
||||
$this.orgNamePath=rrs.result.namePath;
|
||||
}else{
|
||||
this.courseInfo.orgId='';
|
||||
//this.$message.error('资源归属已变更,请重新选择');
|
||||
}
|
||||
})
|
||||
}else{
|
||||
//this.$message.error('无机构关联,不需要提示');
|
||||
}
|
||||
})
|
||||
|
||||
}else{
|
||||
@@ -992,17 +994,16 @@ export default {
|
||||
// });
|
||||
apiUserBasic.getOrgInfo(this.courseInfo.orgId).then(rs=>{
|
||||
if(rs.status==200){
|
||||
$this.orgName=rs.result.name;
|
||||
$this.courseInfo.orgName=rs.result.name;
|
||||
//$this.orgKid=rs.result.kid;
|
||||
$this.orgNamePath=rs.result.namePath;
|
||||
}else{
|
||||
$this.courseInfo.orgId='';
|
||||
$this.$message.error('资源归属已变更,请重新选择');
|
||||
}
|
||||
$this.orgName=rs.result.name;
|
||||
$this.courseInfo.orgName=rs.result.name;
|
||||
//$this.orgKid=rs.result.kid;
|
||||
$this.orgNamePath=rs.result.namePath;
|
||||
}else{
|
||||
$this.courseInfo.orgId='';
|
||||
$this.$message.error('资源归属已变更,请重新选择');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.resOwnerArray=[];
|
||||
if (result.course.resOwner1 == '') {
|
||||
this.resOwnerArray.push(result.course.resOwner1);
|
||||
@@ -1015,33 +1016,33 @@ export default {
|
||||
}
|
||||
this.sysTypeList=[];
|
||||
if(result.course.sysType1!='' && result.course.sysType1!='0'){
|
||||
this.sysTypeList.push(result.course.sysType1);
|
||||
this.sysTypeList.push(result.course.sysType1);
|
||||
}
|
||||
if(result.course.sysType2!='' && result.course.sysType2!='0'){
|
||||
this.sysTypeList.push(result.course.sysType2);
|
||||
this.sysTypeList.push(result.course.sysType2);
|
||||
}
|
||||
if(result.course.sysType3!='' && result.course.sysType3!='0'){
|
||||
this.sysTypeList.push(result.course.sysType3);
|
||||
this.sysTypeList.push(result.course.sysType3);
|
||||
}
|
||||
//console.log(this.sysTypeList,'this.sysTypeList');
|
||||
//受众处理,crowds
|
||||
let crowdList=[];
|
||||
if(result.crowds && result.crowds.length>0){
|
||||
result.crowds.forEach(crowd=>{
|
||||
let newCrowd={
|
||||
id:crowd.groupId,
|
||||
name:crowd.groupName,
|
||||
disabled:false
|
||||
}
|
||||
crowdList.push(newCrowd);
|
||||
let hasUG=$this.userGroupList.some(ug=>{
|
||||
return ug.id==crowd.groupId;
|
||||
});
|
||||
if(!hasUG){
|
||||
newCrowd.disabled=true;
|
||||
$this.userGroupList.push(newCrowd);
|
||||
}
|
||||
result.crowds.forEach(crowd=>{
|
||||
let newCrowd={
|
||||
id:crowd.groupId,
|
||||
name:crowd.groupName,
|
||||
disabled:false
|
||||
}
|
||||
crowdList.push(newCrowd);
|
||||
let hasUG=$this.userGroupList.some(ug=>{
|
||||
return ug.id==crowd.groupId;
|
||||
});
|
||||
if(!hasUG){
|
||||
newCrowd.disabled=true;
|
||||
$this.userGroupList.push(newCrowd);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.courseCrowds=crowdList;
|
||||
//反向看userGroupList是否有
|
||||
@@ -1109,7 +1110,7 @@ export default {
|
||||
inputValue: sec.name,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern:/^(a-z|A-Z|0-9)*[^$%^&*;:,<>?()\""\']{1,20}$/,
|
||||
inputPattern:/^(a-z|A-Z|0-9)*[^$%^&*;:,<>?()\""\']{1,20}$/,
|
||||
inputErrorMessage:'不要超过20个字'
|
||||
})
|
||||
.then(({ value }) => {
|
||||
@@ -1170,7 +1171,7 @@ export default {
|
||||
let order = [];
|
||||
if(cha.contents.length > 0) {
|
||||
cha.contents.forEach(con=>{
|
||||
order.push(con.sortIndex);
|
||||
order.push(con.sortIndex);
|
||||
})
|
||||
let addOrder = order.sort(function(a,b) {return b-a;})
|
||||
this.addOrder = addOrder[0] + 1;
|
||||
@@ -1222,12 +1223,17 @@ export default {
|
||||
},
|
||||
//保存课程信息并进入下一步
|
||||
saveAndNext(btnType) {
|
||||
console.log("courseForm 保存课程信息 btnType = " + btnType);
|
||||
//if(this.courseInfo.type)
|
||||
//console.log(this.courseCrowds,'courseCrowds');
|
||||
//标签,多个,转化为逗号分隔的
|
||||
if (this.showTags.length > 0) {
|
||||
this.courseInfo.tags = this.showTags.join();
|
||||
}
|
||||
|
||||
console.log("courseForm 保存课程信息 this.showTags = " + this.showTags);
|
||||
console.log("courseForm 保存课程信息 this.courseInfo.tags = " + this.courseInfo.tags);
|
||||
// if (this.showTags.length > 0) {
|
||||
// this.courseInfo.tags = this.courseInfo.tags.join();
|
||||
// }
|
||||
// console.log("courseForm 保存课程信息 this.courseInfo.tags = " + this.courseInfo.tags);
|
||||
this.courseInfo.keywords = this.tips.join(',') || ''
|
||||
//检查输入是否合法
|
||||
//if(!this.requireSaveCourse){
|
||||
@@ -1287,7 +1293,7 @@ export default {
|
||||
teachers: saveTeachers,
|
||||
crowds:crowds
|
||||
};
|
||||
//console.log(postData);
|
||||
console.log(postData);
|
||||
//this.btnLoading=false;
|
||||
apiCourse
|
||||
.saveBase(postData)
|
||||
@@ -1333,9 +1339,9 @@ export default {
|
||||
|
||||
if(this.curContent.id == '') {// 新增
|
||||
if(this.curContent.contentType == 60) { // 判断作业是否保存
|
||||
if(courseware.homework.content || courseware.homework.name || courseware.homework.deadTime){
|
||||
pass = false;
|
||||
}
|
||||
if(courseware.homework.content || courseware.homework.name || courseware.homework.deadTime){
|
||||
pass = false;
|
||||
}
|
||||
} else if(this.curContent.contentType == 61) { //考试
|
||||
|
||||
// if(courseware.exam.paperContent != '') {
|
||||
@@ -1346,11 +1352,11 @@ export default {
|
||||
// }
|
||||
}else if(this.curContent.contentType == 41) { //图文
|
||||
if(courseware.htmlContent.length > 7){
|
||||
pass = false;
|
||||
pass = false;
|
||||
}
|
||||
}else if(this.curContent.contentType == 52) { //外部链接
|
||||
if(courseware.linkInfo.url != '') {
|
||||
pass = false;
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
} else {// 编辑
|
||||
@@ -1365,15 +1371,15 @@ export default {
|
||||
// }
|
||||
} else if(this.curContent.contentType == 41) { //图文
|
||||
if(this.curContent.content !== courseware.htmlContent) {
|
||||
pass = false;
|
||||
pass = false;
|
||||
}
|
||||
} else if(this.curContent.contentType == 52) { //外部链接
|
||||
if(this.curContent.content !== JSON.stringify(courseware.linkInfo)) {
|
||||
pass = false;
|
||||
pass = false;
|
||||
}
|
||||
} else if(this.curContent.contentType == 10 || this.curContent.contentType == 20) {// 视频
|
||||
if(this.curContent.content !== JSON.stringify(courseware.curriculumData)) {
|
||||
pass = false;
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1410,6 +1416,9 @@ export default {
|
||||
return true;
|
||||
},
|
||||
submitCourse() {
|
||||
console.log("courseForm 课程提交审核 this.showTags = " + this.showTags);
|
||||
console.log("courseForm 课程提交审核 this.courseInfo.tags = " + this.courseInfo.tags);
|
||||
|
||||
if(this.biaoke.dlgShow && !this.unsavedContent()){
|
||||
this.$message.error('您有未保存的内容,请先保存');
|
||||
return;
|
||||
@@ -1447,7 +1456,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (this.showTags.length > 0) {
|
||||
this.courseInfo.tags = this.showTags.join();
|
||||
// this.courseInfo.tags = this.showTags.join();
|
||||
}
|
||||
if (this.sysTypeList.length < 1) {
|
||||
this.$message.error('请选择内容分类');
|
||||
@@ -1532,61 +1541,61 @@ export default {
|
||||
//2023-1-5 对于默认管理员,不需要提交hrbp。直接提交并发布
|
||||
let adminType=this.userInfo.adminType;
|
||||
if(adminType==1){ //默认管理员,直接审核通过
|
||||
apiCourseAudit.submitAndPublish(postData).then(res=>{
|
||||
setTimeout(function() {
|
||||
$this.btnLoading = false;
|
||||
}, 1000);
|
||||
if (res.status === 200) {
|
||||
//提交成功,直接关闭当前窗口
|
||||
this.$message.success('提交成功!!!');
|
||||
} else {
|
||||
this.$message.error(res.message);
|
||||
}
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
this.$emit('submitSuccess');
|
||||
});
|
||||
apiCourseAudit.submitAndPublish(postData).then(res=>{
|
||||
setTimeout(function() {
|
||||
$this.btnLoading = false;
|
||||
}, 1000);
|
||||
if (res.status === 200) {
|
||||
//提交成功,直接关闭当前窗口
|
||||
this.$message.success('提交成功!!!');
|
||||
} else {
|
||||
this.$message.error(res.message);
|
||||
}
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
this.$emit('submitSuccess');
|
||||
});
|
||||
}else{
|
||||
//先获取HRBP审核 人员信息,姓名,机构路径,工号,用于邮件中的信息
|
||||
apiUserBasic.getOrgHrbpInfo(this.courseInfo.orgId).then(rs=>{
|
||||
if(rs.status==200 && rs.result){
|
||||
postData.auditUser={
|
||||
email:rs.result.email,
|
||||
code:rs.result.userNo,
|
||||
name:rs.result.name,
|
||||
aid:rs.result.id,
|
||||
orgId:rs.result.orgId
|
||||
}
|
||||
//下面的机构名称,路径不对,应该取课程的资源归属(机构)的路径
|
||||
postData.course.orgName=rs.result.orgNamePath+'/'+rs.result.orgName;
|
||||
apiCourse.submitCourse(postData).then(res => {
|
||||
//this.btnLoading=false;
|
||||
setTimeout(function() {
|
||||
$this.btnLoading = false;
|
||||
}, 1000);
|
||||
if (res.status === 200) {
|
||||
//提交成功,直接关闭当前窗口
|
||||
this.$message.success('提交成功!!!');
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
//提交成功回调处理
|
||||
this.$emit('submitSuccess');
|
||||
} else {
|
||||
this.$message.error(res.message);
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
this.$emit('submitSuccess');
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$this.btnLoading = false;
|
||||
this.$message.error('获取审核HRBP失败:'+rs.message);
|
||||
//先获取HRBP审核 人员信息,姓名,机构路径,工号,用于邮件中的信息
|
||||
apiUserBasic.getOrgHrbpInfo(this.courseInfo.orgId).then(rs=>{
|
||||
if(rs.status==200 && rs.result){
|
||||
postData.auditUser={
|
||||
email:rs.result.email,
|
||||
code:rs.result.userNo,
|
||||
name:rs.result.name,
|
||||
aid:rs.result.id,
|
||||
orgId:rs.result.orgId
|
||||
}
|
||||
}).catch(err=>{
|
||||
//this.$message.error('获取审核HRBP失败:'+err);
|
||||
this.$message.error('获取审核HRBP失败,请检查资源归属下是否有HRBP审核人员');
|
||||
$this.btnLoading = false;
|
||||
})
|
||||
//下面的机构名称,路径不对,应该取课程的资源归属(机构)的路径
|
||||
postData.course.orgName=rs.result.orgNamePath+'/'+rs.result.orgName;
|
||||
apiCourse.submitCourse(postData).then(res => {
|
||||
//this.btnLoading=false;
|
||||
setTimeout(function() {
|
||||
$this.btnLoading = false;
|
||||
}, 1000);
|
||||
if (res.status === 200) {
|
||||
//提交成功,直接关闭当前窗口
|
||||
this.$message.success('提交成功!!!');
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
//提交成功回调处理
|
||||
this.$emit('submitSuccess');
|
||||
} else {
|
||||
this.$message.error(res.message);
|
||||
this.biaoke.dlgShow = false;
|
||||
this.weike.dlgShow = false;
|
||||
this.$emit('submitSuccess');
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$this.btnLoading = false;
|
||||
this.$message.error('获取审核HRBP失败:'+rs.message);
|
||||
}
|
||||
}).catch(err=>{
|
||||
//this.$message.error('获取审核HRBP失败:'+err);
|
||||
this.$message.error('获取审核HRBP失败,请检查资源归属下是否有HRBP审核人员');
|
||||
$this.btnLoading = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
// 教师列标,远程查询
|
||||
@@ -1695,19 +1704,19 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.red-tip{
|
||||
margin-top: 8px;
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
}
|
||||
.red-tip{
|
||||
margin-top: 8px;
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
}
|
||||
::v-deep .wei-from{
|
||||
.el-form-item{
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-textarea__inner{
|
||||
padding-right: 40px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
::v-deep .checked-show{
|
||||
@@ -1716,9 +1725,9 @@ export default {
|
||||
}
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 10px 10px;
|
||||
// overflow-y: auto;
|
||||
max-height: calc(70vh - 140px);
|
||||
padding: 10px 10px;
|
||||
// overflow-y: auto;
|
||||
max-height: calc(70vh - 140px);
|
||||
}
|
||||
.example {
|
||||
height: 100%;
|
||||
@@ -1771,8 +1780,8 @@ export default {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.tip{
|
||||
font-size: 16px;
|
||||
color:#ffb30f;
|
||||
font-size: 16px;
|
||||
color:#ffb30f;
|
||||
}
|
||||
.cctree {
|
||||
.cctree-chapter {
|
||||
@@ -1798,13 +1807,13 @@ export default {
|
||||
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.cusprompt{
|
||||
padding: 20px 30px;
|
||||
.el-message-box__content{
|
||||
margin-top: 30px;
|
||||
.el-message-box__input{
|
||||
.cusprompt{
|
||||
padding: 20px 30px;
|
||||
.el-message-box__content{
|
||||
margin-top: 30px;
|
||||
.el-message-box__input{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
377
src/components/Course/courseTag.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
|
||||
<template>
|
||||
<div class="tag-container">
|
||||
<el-select style="width: 100%;"
|
||||
v-model="selectedTags"
|
||||
multiple
|
||||
filterable
|
||||
value-key="id"
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="debouncedSearch"
|
||||
:loading="loading"
|
||||
:placeholder="'回车创建新标签'"
|
||||
@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('');
|
||||
}
|
||||
},
|
||||
// 新增:重置标签状态的方法
|
||||
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();
|
||||
},
|
||||
|
||||
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) {
|
||||
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-select__input) {
|
||||
flex: 1;
|
||||
min-width: 60px;
|
||||
}
|
||||
</style>
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- <svg-icon v-else style="margin-right: 0;" :style="{'font-size':(size+2)+'px'}" :icon-class="isFavorite?'scactive2':'xihuan'"></svg-icon> -->
|
||||
<div v-else class="is_favorite" :class="isFavorite?'is_favorite_a':'is_favorite'"></div>
|
||||
</el-tooltip>
|
||||
<span v-if="!courseExclusive" class="interact-bar-value"> {{ data.favorites? data.favorites:0}}</span>
|
||||
<span v-if="!courseExclusive" class="interact-bar-value"> {{ data.favorites? data.favorites:data.hasCollect?number(data.hasCollect):1}}</span>
|
||||
</div>
|
||||
<div v-if="shares" @click="addShare()" :style="`min-width: ${nodeWidth};`" class="interact-bar-btn" :class="{cursor:!readonly}">
|
||||
<el-tooltip effect="light" content="分享" placement="top" :visible-arrow="false" popper-class="text-tooltip">
|
||||
@@ -114,6 +114,8 @@ export default {
|
||||
shares:0,
|
||||
praises:0,
|
||||
views:0,
|
||||
courseId:'',
|
||||
courseName:''
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -234,7 +236,7 @@ export default {
|
||||
created(){
|
||||
},
|
||||
mounted() {
|
||||
if(this.data && this.data.id && !this.readonly){
|
||||
if(this.data && (this.data.id||this.data.courseId) && !this.readonly){
|
||||
this.checkHas();
|
||||
}
|
||||
|
||||
@@ -308,7 +310,7 @@ export default {
|
||||
}
|
||||
let msgPageParams=this.pageParams;
|
||||
if(!msgPageParams){
|
||||
msgPageParams=this.data.id;
|
||||
msgPageParams=this.data.id ?this.data.id: this.data.courseId;
|
||||
}
|
||||
let message={
|
||||
content,
|
||||
@@ -331,9 +333,9 @@ export default {
|
||||
})
|
||||
},
|
||||
checkHas(){
|
||||
if(this.type>0 && !this.readonly && this.data.id){
|
||||
if(this.type>0 && !this.readonly && (this.data.id||this.data.courseId)){
|
||||
if(this.favorites){
|
||||
apiFavorites.has(this.type,this.data.id).then(rs=>{
|
||||
apiFavorites.has(this.type,(this.data.id || this.data.courseId)).then(rs=>{
|
||||
if(rs.status==200 && rs.result){
|
||||
this.isFavorite=true;
|
||||
}else{
|
||||
@@ -506,9 +508,11 @@ export default {
|
||||
return;
|
||||
}
|
||||
//需要判断是否已点赞,已点赞的不再加
|
||||
console.log(this.data,'---------------');
|
||||
|
||||
let postData={
|
||||
objType:this.type,
|
||||
objId:this.data.id,
|
||||
objId:this.data.id ?this.data.id: this.data.courseId,
|
||||
title:'',
|
||||
}
|
||||
if(this.loading) {
|
||||
@@ -516,7 +520,7 @@ export default {
|
||||
}
|
||||
this.loading=true;
|
||||
if(this.type==1){
|
||||
postData.title=this.data.name;
|
||||
postData.title=this.data.name?this.data.name:this.data.courseName;
|
||||
}else if(this.type==60){
|
||||
postData.title=this.data.content;
|
||||
} else if(this.type==5){
|
||||
@@ -525,7 +529,7 @@ export default {
|
||||
postData.title=this.data.title;
|
||||
}
|
||||
if(this.isFavorite) {// 已经收藏,再次点击取消收藏
|
||||
apiFavorites.remove(this.type,this.data.id).then(res=>{
|
||||
apiFavorites.remove(this.type,this.data.id ?this.data.id: this.data.courseId).then(res=>{
|
||||
this.loading=false;
|
||||
if(res.status==200){
|
||||
this.isFavorite=false;
|
||||
@@ -554,7 +558,7 @@ export default {
|
||||
this.$store.dispatch("unicomFavorites",true)
|
||||
}
|
||||
//if(this.type===2||this.type===4){
|
||||
this.messageSave(this.data.id,this.data.title,this.userInfo.name,this.data.sysCreateBy,this.data.sysCreateAid,'收藏了我发布的');
|
||||
this.messageSave(this.data.id ?this.data.id: this.data.courseId,this.data.title,this.userInfo.name,this.data.sysCreateBy,this.data.sysCreateAid,'收藏了我发布的');
|
||||
//}
|
||||
this.$message({message:'已加入收藏',type:'success'});
|
||||
//this.$emit('addFavorite',res.result);//添加收藏,如果是true代表添加成功,false代表已存在
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="top-nav" :style="{color:textColor}" :class="current == 'course' ? activeNav : ''">
|
||||
<router-link to="/course">课程
|
||||
<a @click="handleChangeCourse">课程
|
||||
<div :class="current == 'course' ? 'nav-bottbor' : ''"></div>
|
||||
</router-link>
|
||||
</a>
|
||||
</div>
|
||||
<div class="top-nav" :style="{color:textColor}" :class="current == 'case' ? activeNav : ''">
|
||||
<router-link to="/case">案例
|
||||
@@ -214,6 +214,12 @@ export default {
|
||||
//this.loadPopupConfig();
|
||||
},
|
||||
methods: {
|
||||
handleChangeCourse() {
|
||||
const paths = ["/course","/qualityCourse"]
|
||||
// 如果是 课程 和 精品课程, 那么就不再重定向
|
||||
const needReload = paths.findIndex(e=> e === this.$route.path) === -1
|
||||
if (needReload) this.$router.push({path: paths[0]})
|
||||
},
|
||||
|
||||
setCurIdentity(iden){
|
||||
this.$store.dispatch('SetCurIdentity',iden);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script>
|
||||
import {getCertificationProcess} from "@/api/modules/lecturer";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -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'},
|
||||
|
||||
]
|
||||
|
||||
|
||||
16
src/main.js
@@ -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'
|
||||
|
||||
@@ -2,7 +2,6 @@ import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
/* Layout */
|
||||
import Layout from '@/layout/index'
|
||||
import LayoutPortal from '@/layout/portal'
|
||||
import Grateful from '@/views/grateful'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
@@ -362,7 +361,15 @@ export const constantRoutes = [{
|
||||
path: '/500',
|
||||
component: (resolve) => require(['@/views/error/500'], resolve),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/qualityCourse',
|
||||
hidden: true,
|
||||
component: (resolve) => require(['@/views/portal/course/qualityCourse'], resolve),
|
||||
name: 'qualityCourse',
|
||||
meta: {title: '精品课课程', keepAlive: true, icon: 'dashboard', noCache: true, affix: false},
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
|
||||
@@ -7,7 +7,11 @@ const state = {
|
||||
withoutAnimation: false
|
||||
},
|
||||
device: 'desktop',//默认是桌面,以后会有android,ios,minapp
|
||||
size: Cookies.get('size') || 'medium' //字段大小
|
||||
size: Cookies.get('size') || 'medium', //字段大小
|
||||
// 添加AI Call组件显示控制状态
|
||||
showAICall: false,
|
||||
// 控制AI Call最小化窗口在特定路由下显示的状态
|
||||
showAICallMinimized: false
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
@@ -34,6 +38,14 @@ const mutations = {
|
||||
SET_SIZE: (state, size) => {
|
||||
state.size = size
|
||||
Cookies.set('size', size)
|
||||
},
|
||||
// 添加控制AI Call组件显示的mutation
|
||||
SET_SHOW_AI_CALL: (state, show) => {
|
||||
state.showAICall = show
|
||||
},
|
||||
// 控制AI Call最小化窗口显示的mutation
|
||||
SET_SHOW_AI_CALL_MINIMIZED: (state, show) => {
|
||||
state.showAICallMinimized = show
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +61,14 @@ const actions = {
|
||||
},
|
||||
setSize({ commit }, size) {
|
||||
commit('SET_SIZE', size)
|
||||
},
|
||||
// 添加控制AI Call组件显示的action
|
||||
setShowAICall({ commit }, show) {
|
||||
commit('SET_SHOW_AI_CALL', show)
|
||||
},
|
||||
// 控制AI Call最小化窗口显示的action
|
||||
setShowAICallMinimized({ commit }, show) {
|
||||
commit('SET_SHOW_AI_CALL_MINIMIZED', show)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +77,4 @@ export default {
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
}
|
||||
83
src/utils/sseHelper.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* SSE流式数据处理工具
|
||||
*/
|
||||
|
||||
/**
|
||||
* 处理SSE响应数据
|
||||
* @param {String} data - SSE响应数据
|
||||
* @param {Function} onMessage - 处理消息的回调函数
|
||||
* @param {Function} onComplete - 完成时的回调函数
|
||||
* @param {Function} onError - 错误处理回调函数
|
||||
*/
|
||||
export function processSSEData(data, onMessage, onComplete, onError) {
|
||||
try {
|
||||
const lines = data.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const jsonData = JSON.parse(line.substring(6))
|
||||
onMessage(jsonData)
|
||||
|
||||
// 如果状态为4,表示对话结束
|
||||
if (jsonData.data && jsonData.data.status === 4) {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理SSE数据时出错:', error)
|
||||
if (onError) {
|
||||
onError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SSE连接
|
||||
* @param {String} url - 请求地址
|
||||
* @param {Object} data - 请求数据
|
||||
* @param {Function} onMessage - 消息处理回调
|
||||
* @param {Function} onComplete - 完成回调
|
||||
* @param {Function} onError - 错误回调
|
||||
* @returns {Promise} - 返回fetch Promise
|
||||
*/
|
||||
export function createSSEConnection(url, data, onMessage, onComplete, onError) {
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
let buffer = ''
|
||||
|
||||
function read() {
|
||||
reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
if (onComplete) onComplete()
|
||||
return
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
processSSEData(buffer, onMessage, onComplete, onError)
|
||||
|
||||
// 继续读取
|
||||
read()
|
||||
}).catch(error => {
|
||||
console.error('SSE读取错误:', error)
|
||||
if (onError) onError(error)
|
||||
})
|
||||
}
|
||||
|
||||
// 开始读取数据
|
||||
read()
|
||||
}).catch(error => {
|
||||
console.error('SSE连接错误:', error)
|
||||
if (onError) onError(error)
|
||||
})
|
||||
}
|
||||
@@ -153,9 +153,85 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="xindex-content">
|
||||
|
||||
<!-- 推荐课程 -->
|
||||
<div class="modules xcontent2">
|
||||
<!-- <div class="xcontent2-main"> -->
|
||||
<!--内容块-->
|
||||
|
||||
<!-- </div> -->
|
||||
<!-- 精品课模块 -->
|
||||
<div class="xcontent2-main">
|
||||
<div class="modules-title xindex-main" v-if="this.qusisityList.list.length > 0">
|
||||
<!-- <span class="modules-text" style="color: #3D86F4;">精品课</span> -->
|
||||
<span class="quyer-tag" style="margin-left: 0px;">
|
||||
<!-- <img src="../assets/images/tutoring1.pn" alt=""> -->
|
||||
<img class="modules-text" style="height: 28px;" src="../assets/images/course/courseTitle.png" alt="">
|
||||
</span>
|
||||
<span class="more">
|
||||
<router-link to="/qualityCourse">查看更多>></router-link>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div
|
||||
v-for="(course, eIndex) in exquisiteList"
|
||||
:key="'cc' + eIndex"
|
||||
class="xindex-course courseBg"
|
||||
style="position: relative;margin-top: 20px;"
|
||||
>
|
||||
<div style="position: absolute; right: 25px; bottom: 72px">
|
||||
<interactBar
|
||||
nodeWidth="20px"
|
||||
:courseExclusive="true"
|
||||
:type="1"
|
||||
:data="course"
|
||||
:comments="false"
|
||||
:praises="false"
|
||||
:shares="false"
|
||||
:views="false"
|
||||
>
|
||||
</interactBar>
|
||||
<!-- <svg-icon style="font-size: 32px;margin-top: -5px;" icon-class="collectedCourse"></svg-icon> -->
|
||||
</div>
|
||||
<a @click="toCourseDetail(course)">
|
||||
<div class="xindex-course-image">
|
||||
<course-image :course="course"></course-image>
|
||||
<!-- <span v-if="course.type == 20 || 10" class="course-type"
|
||||
>录播课</span
|
||||
> -->
|
||||
<img v-if="course.type == 20 || 10" src="../assets/images/course/courseTag.png" class="course-type" style="background: none;" alt="">
|
||||
</div>
|
||||
<div
|
||||
style="width: 80%"
|
||||
:title="course.courseName"
|
||||
class="course-title portal-title-tow two-line-ellipsis"
|
||||
>
|
||||
{{ course.courseName }}
|
||||
</div>
|
||||
<div class="course-author">
|
||||
<div class="course-author-left">
|
||||
{{ course.authorInfo.name }}
|
||||
|
||||
<span class="study-num"
|
||||
>{{ formatNum(course.studyNum) }}人学习</span
|
||||
>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<div v-if="course.courseScore">
|
||||
<span class="course-score-value" style="margin-left: 10px"
|
||||
>{{ toScore(course.courseScore) }}分</span
|
||||
>
|
||||
</div>
|
||||
<div v-else class="course-score-no">未评分</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--内容块-->
|
||||
<div class="modules-title xindex-main">
|
||||
<span class="modules-text">推荐课程</span>
|
||||
@@ -1137,6 +1213,9 @@ export default {
|
||||
orderType: 2,
|
||||
list: [],
|
||||
},
|
||||
qusisityList: {
|
||||
list: [],
|
||||
},
|
||||
// 推荐课程
|
||||
recommendedList:{
|
||||
list: [],
|
||||
@@ -1164,6 +1243,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.getCourseData(1);
|
||||
this.getEsqusiteList();
|
||||
this.getRecommendList();
|
||||
this.getPositive()
|
||||
this.getCaseData();
|
||||
@@ -1227,6 +1307,10 @@ export default {
|
||||
courseComputedTwoList(){
|
||||
return this.courseList.list.slice(3)
|
||||
},
|
||||
// 精品课展示
|
||||
exquisiteList() {
|
||||
return this.qusisityList.list.slice(0,3)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPositive() {
|
||||
@@ -1421,21 +1505,27 @@ export default {
|
||||
//二期调整,直接改成一个地址
|
||||
//return this.webBaseUrl + '/course/detail?id=' + item.id;
|
||||
let $this = this;
|
||||
let cId = "";
|
||||
if (item.id) {
|
||||
cId = item.id;
|
||||
} else {
|
||||
cId = item.courseId;
|
||||
}
|
||||
if (item.type == 10) {
|
||||
//return this.webBaseUrl + "/course/studyindex?id=" + item.id;
|
||||
//console.log("直接进入学习页面");
|
||||
this.$router.push("/course/studyindex?id=" + item.id);
|
||||
this.$router.push("/course/studyindex?id=" + cId);
|
||||
} else if (item.type == 20) {
|
||||
apiCourseStudy.hasSignup(item.id).then((rs) => {
|
||||
apiCourseStudy.hasSignup(cId).then((rs) => {
|
||||
if (rs.status == 200) {
|
||||
//return $this.webBaseUrl + "/course/studyindex?id=" + item.id;
|
||||
this.$router.push("/course/studyindex?id=" + item.id);
|
||||
//return $this.webBaseUrl + "/course/studyindex?id=" + cId;
|
||||
this.$router.push("/course/studyindex?id=" + cId);
|
||||
} else {
|
||||
//return $this.webBaseUrl + "/course/detail?id=" + item.id;
|
||||
this.$router.push("/course/detail?id=" + item.id);
|
||||
//return $this.webBaseUrl + "/course/detail?id=" + cId;
|
||||
this.$router.push("/course/detail?id=" + cId);
|
||||
}
|
||||
});
|
||||
//return $this.webBaseUrl + "/course/detail?id=" + item.id;
|
||||
//return $this.webBaseUrl + "/course/detail?id=" + cId;
|
||||
}
|
||||
},
|
||||
orderTypeFilter(val) {
|
||||
@@ -1472,6 +1562,29 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
//精品课展示
|
||||
getEsqusiteList(){
|
||||
let course = {
|
||||
aid: this.userInfo.aid,
|
||||
}
|
||||
apiIndex.qualitylist(course).then((res) => {
|
||||
let courseIds = [];
|
||||
res.data.result.forEach((item) => {
|
||||
item.authorInfo = {
|
||||
aid: "",
|
||||
name: "",
|
||||
orgInfo: "",
|
||||
avatar: "",
|
||||
code: "",
|
||||
sex: null,
|
||||
};
|
||||
courseIds.push(item.courseId);
|
||||
});
|
||||
this.loadCouserTeacher(res.data.result, courseIds);
|
||||
console.log(res.data.result,'--------------------------');
|
||||
this.qusisityList.list = res.data.result;
|
||||
})
|
||||
},
|
||||
getCourseData(pageIndex) {
|
||||
this.isNext = false;
|
||||
let { orderType, num } = this.courseList;
|
||||
@@ -1671,7 +1784,7 @@ export default {
|
||||
let userIds = [];
|
||||
list.forEach((item, index) => {
|
||||
cres.result.some((courseTeahcer) => {
|
||||
if (courseTeahcer.courseId == item.id) {
|
||||
if (courseTeahcer.courseId == (item.id||item.courseId)) {
|
||||
if (courseTeahcer.teacherIds) {
|
||||
userIds.push(courseTeahcer.teacherIds[0]);
|
||||
item.authorInfo.aid = courseTeahcer.teacherIds[0];
|
||||
@@ -2661,6 +2774,7 @@ export default {
|
||||
// padding-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.course-author-left {
|
||||
font-size: 14px;
|
||||
@@ -3009,4 +3123,17 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.courseBg{
|
||||
// width: 332px;
|
||||
// height: 323px;
|
||||
// background: url("../assets/images/course/courseBackground.png") no-repeat;
|
||||
// background-size: 100% 100%; /* 或 use 'contain' */
|
||||
// background-position: center;
|
||||
// border: none;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #e6f7ff, #f0f8ff, #ffffff);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #d9edf7;
|
||||
//overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -123,6 +123,16 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" prop="sysCreateBy"></el-table-column>
|
||||
<el-table-column label="创建时间" prop="sysCreateTime" width="230px" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="审核人" prop="auditUser" width="130px" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.auditUser || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核时间" prop="auditTime" width="230px" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.auditTime || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否停用" width="130px">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.enabled == true ? '启用' : '停用' }}
|
||||
|
||||
771
src/views/portal/case/AICall.vue
Normal file
@@ -0,0 +1,771 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 最大化状态的弹窗 -->
|
||||
<el-dialog
|
||||
v-if="dialogVisible"
|
||||
:visible="true"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="true"
|
||||
@close="onClose"
|
||||
class="case-expert-dialog"
|
||||
:modal="false"
|
||||
:append-to-body="true"
|
||||
:fullscreen="false"
|
||||
top="10vh"
|
||||
v-show="windowState === 'maximized'"
|
||||
v-resizeable
|
||||
v-draggable
|
||||
>
|
||||
<!-- 标题 -->
|
||||
<div slot="title" class="dialog-title">
|
||||
<span>案例专家</span>
|
||||
<el-button
|
||||
style="color:#96999f"
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click="minimizeWindow"
|
||||
>
|
||||
<i class="el-icon-minus"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<div
|
||||
class="welcome-message"
|
||||
ref="messageContainer"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div class="message-text" v-for="(item, index) in messageList" :key="index">
|
||||
<messages :messageData="item" :suggestions="suggestions" @getMinWindow="minimizeWindow"></messages>
|
||||
</div>
|
||||
<div class="message-suggestions" v-if="messageList.length > 0 && messageList[messageList.length-1].textCompleted">
|
||||
<div class="suggestion-item" v-for="(item, index) in suggestions" :key="index">
|
||||
<a @click="sendSuggestions(item)"> {{ item }} →</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isLoading" class="loading-message">
|
||||
<div class="loading-dots">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入框区域 -->
|
||||
<send-message
|
||||
v-model="AIContent"
|
||||
:message-list="messageList"
|
||||
:suggestions="suggestions"
|
||||
@loading="handleLoading"
|
||||
@update-message="updateMessage"
|
||||
@update-suggestions="updateSuggestions"
|
||||
@new-conversation="startNewConversation"
|
||||
:disabled="isLoading"
|
||||
class="input-area-wrapper"
|
||||
ref="sendMessage"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 最小化状态的弹窗 -->
|
||||
<div
|
||||
class="minimized-window"
|
||||
v-show="windowState === 'minimized' && showMinimizedWindow"
|
||||
@click="onMinimizedWindowClick"
|
||||
>
|
||||
<div class="minimized-content">
|
||||
<span class="window-title">案例专家</span>
|
||||
<div style="display: flex;align-items: center">
|
||||
<el-button
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click.stop="onMinimizedWindowClick"
|
||||
>
|
||||
<img :src="openImg" alt="" style="width: 17px">
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
style="margin-left: 1px;color:#96999f"
|
||||
type="text"
|
||||
class="window-control-btn"
|
||||
@click.stop="closeMinimizedWindow"
|
||||
>
|
||||
<i class="el-icon-close"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="minimized-message">
|
||||
<div v-if="messageList.length <= 1 && messageList[0].isBot">
|
||||
当前暂无对话内容,去创建对话吧
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ getLastUserMessage() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import messages from './components/messages.vue'
|
||||
import sendMessage from './components/sendMessage.vue'
|
||||
import openImg from './components/open.png'
|
||||
export default {
|
||||
name: 'CaseExpertDialog',
|
||||
props: {
|
||||
dialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
messages,
|
||||
sendMessage
|
||||
},
|
||||
directives: {
|
||||
draggable: {
|
||||
bind(el, binding, vnode) {
|
||||
vnode.context.$nextTick(() => {
|
||||
const dialogEl = el.querySelector('.el-dialog');
|
||||
if (!dialogEl) return;
|
||||
|
||||
const headerEl = dialogEl.querySelector('.dialog-title');
|
||||
if (!headerEl) return;
|
||||
|
||||
// 设置初始样式
|
||||
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;
|
||||
|
||||
// 移除全局事件监听
|
||||
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;
|
||||
|
||||
// 设置初始样式
|
||||
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;
|
||||
|
||||
switch (resizeDirection) {
|
||||
case 'right':
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
break;
|
||||
case 'left':
|
||||
const newWidth = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidth + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidth) + 'px';
|
||||
break;
|
||||
case 'bottom':
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'top':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
break;
|
||||
case 'bottom-right':
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'bottom-left':
|
||||
const newWidthBL = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidthBL + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidthBL) + 'px';
|
||||
dialogEl.style.height = Math.max(600, startHeight + deltaY) + 'px';
|
||||
break;
|
||||
case 'top-right':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
dialogEl.style.width = Math.max(400, startWidth + deltaX) + 'px';
|
||||
break;
|
||||
case 'top-left':
|
||||
// 当窗口高度达到最小值时,不再调整高度和位置
|
||||
if (startHeight - deltaY >= 600) {
|
||||
dialogEl.style.height = (startHeight - deltaY) + 'px';
|
||||
dialogEl.style.top = (startTop + deltaY) + 'px';
|
||||
}
|
||||
const newWidthTL = Math.max(400, startWidth - deltaX);
|
||||
dialogEl.style.width = newWidthTL + 'px';
|
||||
dialogEl.style.left = (startLeft + startWidth - newWidthTL) + 'px';
|
||||
break;
|
||||
}
|
||||
|
||||
let doc = document.querySelector('.welcome-message')
|
||||
let sendBox = document.querySelector('.input-area-wrapper');
|
||||
// sendBox 的高度
|
||||
doc.style.height = `calc(${dialogEl.style.height} - ${sendBox.offsetHeight}px - 120px)`;
|
||||
};
|
||||
|
||||
const stopResize = () => {
|
||||
isResizing = false;
|
||||
resizeDirection = '';
|
||||
|
||||
// 移除全局事件监听
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
// 为每个手柄绑定事件
|
||||
handles.left.addEventListener('mousedown', (e) => startResize('left', e));
|
||||
handles.right.addEventListener('mousedown', (e) => startResize('right', e));
|
||||
handles.top.addEventListener('mousedown', (e) => startResize('top', e));
|
||||
handles.bottom.addEventListener('mousedown', (e) => startResize('bottom', e));
|
||||
handles.topLeft.addEventListener('mousedown', (e) => startResize('top-left', e));
|
||||
handles.topRight.addEventListener('mousedown', (e) => startResize('top-right', e));
|
||||
handles.bottomLeft.addEventListener('mousedown', (e) => startResize('bottom-left', e));
|
||||
handles.bottomRight.addEventListener('mousedown', (e) => startResize('bottom-right', e));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['showAICallMinimized']),
|
||||
showMinimizedWindow() {
|
||||
// 只有在Vuex状态为true时才显示最小化窗口
|
||||
return this.showAICallMinimized;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openImg,
|
||||
AIContent: '',
|
||||
isLoading: false,
|
||||
windowState: 'maximized', // 'maximized' 或 'minimized'
|
||||
messageList: [
|
||||
{
|
||||
typing:true,
|
||||
isBot: true, // 是否为机器人
|
||||
text: `<p><b>您好!我是京东方案例智能问答助手,随时为您服务。</b></p>
|
||||
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
|
||||
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
|
||||
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
|
||||
}
|
||||
],
|
||||
suggestions:[],
|
||||
isAutoScroll: true // 是否自动滚动
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dialogVisible(newVal) {
|
||||
if(newVal){
|
||||
this.$nextTick(() => {
|
||||
let doc = document.querySelector('.welcome-message')
|
||||
let sendBox = document.querySelector('.input-area-wrapper');
|
||||
doc.style.height = `calc(600px - ${sendBox.offsetHeight}px - 120px)`;
|
||||
});
|
||||
}
|
||||
|
||||
// 移除之前的逻辑,因为现在通过事件机制处理状态恢复
|
||||
},
|
||||
messageList: {
|
||||
handler() {
|
||||
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('关闭弹窗')
|
||||
this.$emit('close')
|
||||
// 可以在这里执行其他逻辑
|
||||
},
|
||||
|
||||
minimizeWindow() {
|
||||
this.windowState = 'minimized';
|
||||
this.$store.commit('app/SET_SHOW_AI_CALL_MINIMIZED', true);
|
||||
},
|
||||
|
||||
maximizeWindow() {
|
||||
this.windowState = 'maximized';
|
||||
},
|
||||
|
||||
getLastUserMessage() {
|
||||
// 从后往前找用户消息
|
||||
for (let i = this.messageList.length - 1; i >= 0; i--) {
|
||||
if (!this.messageList[i].isBot) {
|
||||
// 移除HTML标签只返回纯文本
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = this.messageList[i].text;
|
||||
return tempDiv.textContent || tempDiv.innerText || '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
// 处理加载状态
|
||||
handleLoading(status) {
|
||||
this.isLoading = status;
|
||||
},
|
||||
|
||||
// 更新消息
|
||||
updateMessage(message) {
|
||||
// 由于Vue的响应式系统,message对象的更改会自动更新视图
|
||||
// 这里不需要额外的操作
|
||||
},
|
||||
updateSuggestions(arr){
|
||||
this.suggestions = arr
|
||||
},
|
||||
// 处理建议
|
||||
sendSuggestions(item){
|
||||
// this.suggestions = []
|
||||
this.AIContent = item
|
||||
setTimeout(()=>{
|
||||
this.$refs.sendMessage.handleSend()
|
||||
this.AIContent = ''
|
||||
},500)
|
||||
},
|
||||
startNewConversation() {
|
||||
this.messageList = [
|
||||
{
|
||||
isBot: true,
|
||||
text: `<p><b>您好!我是京东方案例智能问答助手,随时为您服务。</b></p>
|
||||
<p>我可以帮您快速查找和解读平台内的各类案例内容。只需输入您想了解的问题或关键词,我会从案例库中精准匹配相关信息,并提供清晰的解答。每条回答都会附上来源链接,方便您随时查阅原始案例全文。</p>
|
||||
<p>我还会根据您的提问,智能推荐相关延伸问题,助您更高效地探索知识、解决问题。</p>
|
||||
<p>现在,欢迎随时向我提问,开启高效的知识查询体验吧!</p>`
|
||||
}
|
||||
];
|
||||
this.AIContent = '';
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
// 处理滚动事件
|
||||
handleScroll(event) {
|
||||
const element = event.target;
|
||||
// 判断是否滚动到底部
|
||||
const isAtBottom = element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
|
||||
|
||||
// 如果滚动到底部,则开启自动滚动
|
||||
// 如果离开底部,则关闭自动滚动
|
||||
this.isAutoScroll = isAtBottom;
|
||||
},
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom() {
|
||||
if (this.isAutoScroll && this.$refs.messageContainer) {
|
||||
this.$refs.messageContainer.scrollTop = this.$refs.messageContainer.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
// 最小化窗口的点击事件处理方法
|
||||
onMinimizedWindowClick() {
|
||||
// 当点击最小化窗口时,如果dialogVisible为false,则通过事件通知父组件显示对话框
|
||||
if (!this.dialogVisible) {
|
||||
this.$emit('restore');
|
||||
}
|
||||
// 然后将窗口状态设置为最大化
|
||||
this.windowState = 'maximized';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.case-expert-dialog {
|
||||
::v-deep .el-dialog{
|
||||
background: url("./components/u762.svg") no-repeat ;
|
||||
background-size: cover;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
//background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
::v-deep .el-dialog__body{
|
||||
padding: 10px;
|
||||
flex:1;
|
||||
//font-size: 12px;
|
||||
*{
|
||||
font-size:unset ;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding-right: 20px;
|
||||
cursor: move; /* 添加拖动样式 */
|
||||
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.window-control-btn {
|
||||
font-size: 18px;
|
||||
padding: 5px 10px;
|
||||
color: #333; /* 黑色图标 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.message-suggestions{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.suggestion-item{
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
padding: 5px 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(228, 231, 237, 1);
|
||||
border-radius: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
.content-wrapper {
|
||||
padding: 20px;
|
||||
background-color: transparent;
|
||||
border-radius: 8px;
|
||||
min-height: 500px;
|
||||
height:100%;
|
||||
position: relative;
|
||||
//margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.welcome-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
//height: 200px;
|
||||
//flex:1;
|
||||
overflow-y: auto;
|
||||
|
||||
.avatar {
|
||||
margin-right: 12px;
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #007aff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-text {
|
||||
width: 100%;
|
||||
//margin-bottom: 15px;
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
//font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #007aff;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #999;
|
||||
margin-right: 5px;
|
||||
animation: loading 1.4s infinite ease-in-out both;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-area-wrapper {
|
||||
//position: absolute;
|
||||
//bottom: 10px;
|
||||
//width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.minimized-window {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
width: 300px;
|
||||
background: url("./components/u762.svg") no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 2000;
|
||||
cursor: pointer;
|
||||
|
||||
.minimized-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.window-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.window-control-btn {
|
||||
font-size: 16px;
|
||||
padding: 3px 8px;
|
||||
color: #000000; /* 黑色图标 */
|
||||
}
|
||||
}
|
||||
|
||||
.minimized-message {
|
||||
padding: 15px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div id="case-list-content">
|
||||
<div style="margin-bottom:30px" class="case-banner">
|
||||
<portal-header current="case" textColor="#fff" :goSearch="2"></portal-header>
|
||||
<portal-header current="case" textColor="#fff" :goSearch="2">
|
||||
|
||||
</portal-header>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="xcontent2">
|
||||
@@ -109,6 +111,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="xcontent2-minor" :style="{ display: zoomShow ? '' : 'none' }">
|
||||
<AICaseConsult />
|
||||
<div id="fixd-box">
|
||||
<router-link class="the_charts" to="/case/charts">
|
||||
<div class="text">排行榜</div>
|
||||
@@ -237,9 +240,10 @@ import { formatDate } from "@/utils/datetime.js"
|
||||
import { cutFullName } from "@/utils/tools.js";
|
||||
import apiPlace from "@/api/phase2/place.js"
|
||||
import portalFloatTools from "@/components/PortalFloatTools.vue";
|
||||
import AICaseConsult from "@/views/portal/case/components/AICaseConsult.vue";
|
||||
export default {
|
||||
name: 'atticle',
|
||||
components: { portalHeader, portalFloatTools, portalFooter, interactBar, author, comments, pdfPreview },
|
||||
components: {AICaseConsult, portalHeader, portalFloatTools, portalFooter, interactBar, author, comments, pdfPreview },
|
||||
computed: {
|
||||
...mapGetters(['userInfo'])
|
||||
},
|
||||
|
||||
63
src/views/portal/case/components/AICaseConsult.vue
Normal 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>
|
||||
1
src/views/portal/case/components/map.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759024984858" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4686" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="25"><path d="M451.673935 994.395699C478.883834 1025.019147 524.254807 1024.808979 551.400292 993.928851 553.755808 991.387908 558.821323 985.796762 565.872444 977.84835 577.572838 964.659017 590.597131 949.62432 604.615947 932.998315 644.662065 885.504506 684.708678 834.717818 722.129538 782.646447 759.658524 730.424619 792.492213 679.709274 819.314991 631.458462 868.685946 542.646317 896 465.543426 896 402.285715 896 180.109449 719.301715 0 501.333333 0 283.364952 0 106.666667 180.109449 106.666667 402.285715 106.666667 465.598716 134.05152 542.80573 183.54613 631.762622 210.371803 679.976529 243.193308 730.651876 280.699364 782.833154 318.155192 834.94455 358.239268 885.77421 398.322835 933.311031 412.354743 949.952073 425.391185 965.00073 437.102468 978.202579 444.160087 986.158466 449.230214 991.754921 451.982775 994.736706L451.673935 994.395699ZM486.822684 961.321348C484.281231 958.568254 479.425084 953.207989 472.585916 945.498359 461.135889 932.591017 448.364015 917.847761 434.602351 901.527215 395.275714 854.888073 355.949587 805.019548 319.289224 754.014863 282.808749 703.260452 250.983685 654.123578 225.158316 607.707522 179.388826 525.445805 154.50505 455.290161 154.50505 402.285715 154.50505 207.039905 309.785362 48.761905 501.333333 48.761905 692.881306 48.761905 848.161617 207.039905 848.161617 402.285715 848.161617 455.246022 823.345286 525.298263 777.693969 607.419251 751.873483 653.867066 720.038415 703.039925 683.537446 753.831262 646.912604 804.794967 607.624538 854.619674 568.335977 901.215038 554.587654 917.520243 541.828177 932.24925 530.389289 945.143797 523.556841 952.845711 518.705521 958.200435 516.166694 960.950526 507.543772 970.748911 495.255793 970.80583 487.131524 961.662353L486.822684 961.321348Z" fill="#979797" p-id="4687"></path><path d="M714.955981 467.028806C723.919106 442.627955 728.565658 416.668998 728.565658 390.095238 728.565658 268.908183 632.184774 170.666667 513.29293 170.666667 394.401086 170.666667 298.020202 268.908183 298.020202 390.095238 298.020202 511.282291 394.401086 609.52381 513.29293 609.52381 549.003859 609.52381 583.510052 600.631947 614.373097 583.874409 626.032316 577.543868 630.449257 562.77782 624.238611 550.893519 618.027966 539.009218 603.541579 534.507006 591.882359 540.837549 567.900883 553.858639 541.111735 560.761905 513.29293 560.761905 420.821495 560.761905 345.858586 484.351836 345.858586 390.095238 345.858586 295.838641 420.821495 219.428572 513.29293 219.428572 605.764365 219.428572 680.727273 295.838641 680.727273 390.095238 680.727273 410.807981 677.117041 430.977316 670.154965 449.930592 665.522846 462.540883 671.796821 476.591108 684.168282 481.312651 696.53974 486.034191 710.323861 479.639095 714.955981 467.028806L714.955981 467.028806Z" fill="#979797" p-id="4688"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
423
src/views/portal/case/components/messages.vue
Normal file
@@ -0,0 +1,423 @@
|
||||
<template>
|
||||
<div class="messages">
|
||||
<!-- 机器人消息 -->
|
||||
<div v-if="messageData.isBot" class="bot-message">
|
||||
<!-- 思考中提示 -->
|
||||
<div v-if="messageData.thinkText" class="bot-think" v-katex:auto v-html="md.render(messageData.thinkText)"></div>
|
||||
|
||||
<!-- 主要回复内容 -->
|
||||
<div
|
||||
ref="contentContainer"
|
||||
class="message-content"
|
||||
v-katex:auto
|
||||
v-html="md.render(displayText)"
|
||||
></div>
|
||||
|
||||
<!-- 引用案例 -->
|
||||
<div v-if="messageData.caseRefers && messageData.caseRefers.length > 0 && messageData.textCompleted" class="case-refers">
|
||||
<div class="case-refers-title">
|
||||
<span><i class="iconfont icon-think"></i> 引用案例</span>
|
||||
<span v-if="shouldShowMoreButton" class="more" @click="toggleShowAllCaseRefers">
|
||||
{{ showAllCaseRefers ? '收起' : '查看更多' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="case-refers-list">
|
||||
<div
|
||||
v-for="item in displayedCaseRefers"
|
||||
:key="item.caseId"
|
||||
class="case-refers-item"
|
||||
>
|
||||
<div class="case-refers-item-title">
|
||||
<a @click="toUrl(item)" class="title">{{ item.title }}</a>
|
||||
<span class="case-refers-item-timer">{{ item.uploadTime }}</span>
|
||||
</div>
|
||||
<div class="case-refers-item-author">
|
||||
<span class="user"></span>
|
||||
<span>{{ item.authorName }}</span>
|
||||
<span class="case-inter-orginInfo">{{ item.orgInfo }}</span>
|
||||
</div>
|
||||
<div class="case-refers-item-keywords">
|
||||
<span v-for="keyword in item.keywords" :key="keyword">{{ keyword }}</span>
|
||||
</div>
|
||||
<div class="message-content case-content" v-html="md.render(item.content)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户消息 -->
|
||||
<div v-else class="user-message">
|
||||
<div class="message-text" v-html="messageData.text"></div>
|
||||
</div>
|
||||
|
||||
<!-- 推荐问题 -->
|
||||
<!-- <div v-if="suggestions && suggestions.length > 0" class="suggestions">-->
|
||||
<!-- <div class="suggestions-title">💡 推荐问题</div>-->
|
||||
<!-- <div class="suggestions-list">-->
|
||||
<!-- <button-->
|
||||
<!-- v-for="(item, index) in suggestions"-->
|
||||
<!-- :key="index"-->
|
||||
<!-- class="suggestions-item"-->
|
||||
<!-- @click="$emit('suggestion-click', item)"-->
|
||||
<!-- >-->
|
||||
<!-- {{ item }}-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import highlight from 'markdown-it-highlightjs';
|
||||
import 'highlight.js/styles/a11y-dark.css';
|
||||
import markdownItMermaid from 'markdown-it-mermaid';
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
// 初始化 Mermaid
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
});
|
||||
|
||||
md.use(highlight).use(markdownItMermaid);
|
||||
|
||||
export default {
|
||||
name: 'Message',
|
||||
props: {
|
||||
messageData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
suggestions: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
md,
|
||||
displayText: '',
|
||||
typingTimer: null,
|
||||
typingSpeed: 30, // 毫秒/字符
|
||||
showAllCaseRefers: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayedCaseRefers() {
|
||||
if (this.showAllCaseRefers || !this.messageData.caseRefers) {
|
||||
return this.messageData.caseRefers || [];
|
||||
}
|
||||
return this.messageData.caseRefers.slice(0, 3);
|
||||
},
|
||||
shouldShowMoreButton() {
|
||||
return this.messageData.caseRefers && this.messageData.caseRefers.length > 3;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'messageData.text': {
|
||||
handler(newVal) {
|
||||
if (!newVal) {
|
||||
this.displayText = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.messageData.isBot && !this.messageData.typing) {
|
||||
// this.startTyping(newVal); // 启动打字机效果/**/
|
||||
|
||||
this.displayText = newVal || ''
|
||||
} else {
|
||||
this.displayText = this.md.render(newVal);
|
||||
this.$nextTick(this.renderMermaid); // 直接渲染 Mermaid
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toUrl(item) {
|
||||
this.$router.push({
|
||||
path: '/case/detail',
|
||||
query: { id: item.caseId },
|
||||
});
|
||||
|
||||
|
||||
this.$emit('getMinWindow')
|
||||
},
|
||||
|
||||
// 正确的打字机效果:先整体渲染 Markdown,再逐字显示 HTML
|
||||
startTyping(fullText) {
|
||||
const renderedText = this.md.render(fullText);
|
||||
this.displayText = '';
|
||||
let index = 0;
|
||||
|
||||
if (this.typingTimer) {
|
||||
clearInterval(this.typingTimer);
|
||||
}
|
||||
|
||||
this.typingTimer = setInterval(() => {
|
||||
if (index < renderedText.length) {
|
||||
this.displayText += renderedText[index];
|
||||
index++;
|
||||
} else {
|
||||
clearInterval(this.typingTimer);
|
||||
this.typingTimer = null;
|
||||
this.$nextTick(this.renderMermaid); // 渲染 Mermaid 图表
|
||||
}
|
||||
}, this.typingSpeed);
|
||||
},
|
||||
|
||||
// 触发 Mermaid 渲染
|
||||
renderMermaid() {
|
||||
this.$nextTick(() => {
|
||||
const mermaidEls = this.$el.querySelectorAll('.mermaid');
|
||||
if (mermaidEls.length > 0) {
|
||||
try {
|
||||
// mermaid 8.x 版本使用 init 方法而不是 run
|
||||
if (typeof mermaid.init === 'function') {
|
||||
mermaid.init(undefined, '.mermaid');
|
||||
} else if (mermaid.default && typeof mermaid.default.init === 'function') {
|
||||
mermaid.default.init(undefined, '.mermaid');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Mermaid 渲染失败:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 切换案例引用显示数量
|
||||
toggleShowAllCaseRefers() {
|
||||
this.showAllCaseRefers = !this.showAllCaseRefers;
|
||||
// 切换后重新渲染 Mermaid(如果内容中有图表)
|
||||
this.$nextTick(this.renderMermaid);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.typingTimer) {
|
||||
clearInterval(this.typingTimer);
|
||||
this.typingTimer = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .mermaid{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
::v-deep svg[id^="mermaid-"]{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.messages {
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
|
||||
.bot-message {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.bot-think {
|
||||
color: #909399;
|
||||
padding-bottom: 5px;
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
|
||||
&:before {
|
||||
content: ' ';
|
||||
border-left: 0.5px solid #909399;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: -3px;
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.case-content {
|
||||
font-size: 12px !important;
|
||||
margin-top: 8px;
|
||||
padding: 6px 10px;
|
||||
//background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
//border: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.user-message {
|
||||
float: right;
|
||||
padding: 8px 15px;
|
||||
max-width: 80%;
|
||||
background-color: #e4e7ed;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* ========== 案例引用样式 ========== */
|
||||
.case-refers {
|
||||
margin-top: 12px;
|
||||
|
||||
.case-refers-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
|
||||
.icon-think {
|
||||
background-image: url('./map.svg');
|
||||
width: 15px;
|
||||
height: 13px;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.more {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
background-color: #f4f7fd;
|
||||
border-radius: 5px;
|
||||
color: #577ee1;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.case-refers-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.case-refers-item {
|
||||
border: 1px solid rgba(144, 147, 153, 0.44);
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
|
||||
.case-refers-item-title {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
max-width: 70%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.case-refers-item-timer {
|
||||
font-size: 11px;
|
||||
color: #aaa;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.case-refers-item-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.user {
|
||||
background-image: url('./user.svg');
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.case-inter-orginInfo {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.case-refers-item-keywords {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
span {
|
||||
padding: 2px 6px;
|
||||
background-color: #f4f7fd;
|
||||
border-radius: 5px;
|
||||
font-size: 11px !important;
|
||||
color: #577ee1;
|
||||
}
|
||||
|
||||
span + span {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 推荐问题 ========== */
|
||||
.suggestions {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
.suggestions-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.suggestions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.suggestions-item {
|
||||
padding: 6px 10px;
|
||||
background-color: #f0f4fc;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
color: #1a73e8;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #e1e8f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/views/portal/case/components/open.png
Normal file
|
After Width: | Height: | Size: 283 B |
391
src/views/portal/case/components/sendMessage.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<div class="input-area">
|
||||
<el-input
|
||||
v-model="inputContent"
|
||||
type="textarea"
|
||||
class="input-placeholder"
|
||||
placeholder="有问题,尽管问"
|
||||
@keyup.enter.native="handleSend"
|
||||
:disabled="disabled"
|
||||
:autosize="{ minRows: 2, maxRows: 4}"
|
||||
resize="none"
|
||||
></el-input>
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" size="small" class="start-btn" @click="handleNewConversation">
|
||||
+ 开启新对话
|
||||
</el-button>
|
||||
<el-button type="text" class="send-btn" @click="handleSend" :disabled="disabled">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { aiChat } from '@/api/boe/aiChat.js'
|
||||
|
||||
export default {
|
||||
name: 'SendMessage',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
messageList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
suggestions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputContent: this.value,
|
||||
conversationId: '' // 会话ID
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.inputContent = newVal
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSend() {
|
||||
if (!this.inputContent.trim() || this.disabled) return
|
||||
// 添加用户消息到列表
|
||||
const userMessage = {
|
||||
isBot: false,
|
||||
text: this.inputContent
|
||||
};
|
||||
this.messageList.push(userMessage);
|
||||
|
||||
// 显示加载状态
|
||||
this.$emit('loading', true);
|
||||
|
||||
// 调用AI聊天接口 (暂时注释掉SSE,使用模拟数据)
|
||||
this.callAIChat(this.inputContent);
|
||||
|
||||
// 清空输入框
|
||||
this.inputContent = ''
|
||||
},
|
||||
|
||||
|
||||
// 真实的SSE实现(暂时注释掉)
|
||||
callAIChat(question) {
|
||||
// 创建新的AI消息对象
|
||||
const aiMessage = {
|
||||
isBot: true,
|
||||
text: '',
|
||||
status:null,
|
||||
thinkText: '',
|
||||
caseRefers: [], // 添加caseRefers字段
|
||||
textCompleted: false // 添加文字处理完成状态,默认为false
|
||||
};
|
||||
this.messageList.push(aiMessage);
|
||||
|
||||
// 构造请求参数
|
||||
const requestData = {
|
||||
conversationId: this.conversationId,
|
||||
query: question
|
||||
};
|
||||
// 创建POST请求
|
||||
fetch('/systemapi/xboe/m/boe/case/ai/chat',{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"accept": "text/event-stream",
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
}).then(r=>{
|
||||
return r
|
||||
}).then(response => {
|
||||
// 处理流式响应
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let buffer = '';
|
||||
let accumulatedContent = ''; // 累积的内容用于打字机效果
|
||||
let accumulatedThinkContent = ''; // 累积的思考内容
|
||||
let inThinkSection = false; // 是否在思考部分
|
||||
let typingTimer = null; // 打字机定时器
|
||||
let thinkTypingTimer = null; // 思考内容打字机定时器
|
||||
|
||||
// 逐字显示文本的函数
|
||||
const typeText = (message, fullContent) => {
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (typingTimer) {
|
||||
clearInterval(typingTimer);
|
||||
}
|
||||
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.text.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typingSpeed = 30; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
typingTimer = setInterval(() => {
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.text.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.text = fullContent.substring(0, nextIndex);
|
||||
this.$emit('update-message', message);
|
||||
} else {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
// 当打字机效果完成时,检查是否应该设置textCompleted为true
|
||||
// 这应该在status 4(交互完成)时才设置
|
||||
if (message.status === 4) {
|
||||
if (nextIndex >= targetLength) {
|
||||
message.textCompleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 逐字显示思考内容的函数
|
||||
const typeThinkText = (message, fullThinkContent) => {
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (thinkTypingTimer) {
|
||||
clearInterval(thinkTypingTimer);
|
||||
}
|
||||
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.thinkText.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullThinkContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从当前显示位置开始继续显示(避免清空重显)
|
||||
const startIndex = currentLength;
|
||||
|
||||
const typingSpeed = 20; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
thinkTypingTimer = setInterval(() => {
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.thinkText.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.thinkText = fullThinkContent.substring(0, nextIndex);
|
||||
this.$emit('update-message', message);
|
||||
} else {
|
||||
clearInterval(thinkTypingTimer);
|
||||
thinkTypingTimer = null;
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 添加一个检查是否所有文本都已完成显示的函数
|
||||
const isTextDisplayCompleted = (message, fullContent) => {
|
||||
return message.text.length >= fullContent.length;
|
||||
};
|
||||
|
||||
// 读取流数据
|
||||
const read = () => {
|
||||
reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
// 当流结束时,等待打字机效果完成
|
||||
const waitForTyping = () => {
|
||||
if (!typingTimer) {
|
||||
this.$emit('loading', false);
|
||||
} else {
|
||||
setTimeout(waitForTyping, 100);
|
||||
}
|
||||
};
|
||||
waitForTyping();
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码数据
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// 按行分割数据
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop(); // 保留不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
try {
|
||||
const jsonData = JSON.parse(line.substring(5));
|
||||
// 根据status处理不同类型的数据
|
||||
switch (jsonData.status) {
|
||||
case 0: // 引用文件
|
||||
// 处理引用文件信息
|
||||
if (jsonData.fileRefer && jsonData.fileRefer.caseRefers) {
|
||||
aiMessage.caseRefers = jsonData.fileRefer.caseRefers;
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
// 从响应中获取并保存conversationId
|
||||
if (jsonData.conversationId) {
|
||||
this.conversationId = jsonData.conversationId;
|
||||
sessionStorage.setItem('conversationId', jsonData.conversationId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // 流式对话内容
|
||||
// 处理
|
||||
const content = jsonData.content;
|
||||
aiMessage.hasThink = false;
|
||||
if (content.startsWith('<think>')) {
|
||||
aiMessage.hasThink = true
|
||||
inThinkSection = true;
|
||||
accumulatedThinkContent = content.replace('<think>', '');
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (content.startsWith('</think>')) {
|
||||
inThinkSection = false;
|
||||
accumulatedThinkContent += content.replace('</think>', '');
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (inThinkSection) {
|
||||
accumulatedThinkContent += content;
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else {
|
||||
// 累积内容并使用打字机效果更新显示
|
||||
accumulatedContent += content;
|
||||
// 如果thinkText已经显示完整,则继续使用打字机效果显示内容
|
||||
if( aiMessage.hasThink){
|
||||
if(aiMessage.thinkText.length >=accumulatedThinkContent.length){
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
} else {
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
|
||||
}
|
||||
// 不在这里直接更新,让打字机效果处理更新
|
||||
break;
|
||||
|
||||
case 2: // 回答完成
|
||||
// 不再在这里设置textCompleted状态
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
// 从响应中获取并保存conversationId
|
||||
|
||||
break;
|
||||
|
||||
case 3: // 返回建议
|
||||
// 这里可以处理建议问题
|
||||
this.$emit('update-suggestions', jsonData.suggestions);
|
||||
break;
|
||||
|
||||
case 4: // 交互完成
|
||||
aiMessage.status = 4
|
||||
|
||||
// 从响应中获取并保存conversationId
|
||||
this.$emit('loading', false);
|
||||
// 检查文本是否已经完全显示,如果是则设置textCompleted为true
|
||||
if (isTextDisplayCompleted(aiMessage, accumulatedContent)) {
|
||||
// aiMessage.textCompleted = true;
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析SSE数据错误:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 继续读取
|
||||
read();
|
||||
}).catch(error => {
|
||||
console.error('SSE连接错误:', error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
if (typingTimer) {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
}
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit('loading', false);
|
||||
aiMessage.text = '当前无法获取回答,请稍后重试';
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
});
|
||||
};
|
||||
|
||||
// 开始读取数据
|
||||
read();
|
||||
}).catch(error => {
|
||||
console.error('请求失败:', error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit('loading', false);
|
||||
aiMessage.text = '当前无法获取回答,请稍后重试';
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
});
|
||||
},
|
||||
handleNewConversation() {
|
||||
this.conversationId = ''
|
||||
this.$emit('new-conversation')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-area {
|
||||
background-color: white;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
padding: 5px 16px 10px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.input-placeholder {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border: none;
|
||||
padding: 0;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
.start-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
color: #409eff;
|
||||
background-color: #f5f7fa;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
font-size: 18px;
|
||||
color: #409eff;
|
||||
padding: 6px;
|
||||
|
||||
&[disabled] {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
12
src/views/portal/case/components/u762.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="750px" height="850px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient cx="362.789473684209" cy="413.96491228069874" r="1153.015055438179" gradientTransform="matrix(0.023310357358899587 0.999728276703125 -0.9997282767031253 0.02331035735889959 768.1851497765263 41.62434690904212 )" gradientUnits="userSpaceOnUse" id="RadialGradient4">
|
||||
<stop id="Stop5" stop-color="#ffffff" offset="0" />
|
||||
<stop id="Stop6" stop-color="#d4def7" offset="1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M 0 5.000000000000001 A 5 5 0 0 1 4.999999999999999 0 L 745 0 A 5 5 0 0 1 750 5 L 750 845 A 5 5 0 0 1 745 850 L 5 850 A 5 5 0 0 1 0 845 L 0 5 Z " fill-rule="nonzero" fill="url(#RadialGradient4)" stroke="none" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 858 B |
1
src/views/portal/case/components/user.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759026139840" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5676" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="25"><path d="M512 74.666667C270.933333 74.666667 74.666667 270.933333 74.666667 512S270.933333 949.333333 512 949.333333 949.333333 753.066667 949.333333 512 753.066667 74.666667 512 74.666667zM288 810.666667c0-123.733333 100.266667-224 224-224S736 686.933333 736 810.666667c-61.866667 46.933333-140.8 74.666667-224 74.666666s-162.133333-27.733333-224-74.666666z m128-384c0-53.333333 42.666667-96 96-96s96 42.666667 96 96-42.666667 96-96 96-96-42.666667-96-96z m377.6 328.533333c-19.2-96-85.333333-174.933333-174.933333-211.2 32-29.866667 51.2-70.4 51.2-117.333333 0-87.466667-72.533333-160-160-160s-160 72.533333-160 160c0 46.933333 19.2 87.466667 51.2 117.333333-89.6 36.266667-155.733333 115.2-174.933334 211.2-55.466667-66.133333-91.733333-149.333333-91.733333-243.2 0-204.8 168.533333-373.333333 373.333333-373.333333S885.333333 307.2 885.333333 512c0 93.866667-34.133333 177.066667-91.733333 243.2z" fill="#666666" p-id="5677"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -30,13 +30,14 @@
|
||||
<!-- <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>
|
||||
<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 +420,7 @@ export default {
|
||||
|
||||
.course-title{
|
||||
position: relative;
|
||||
height: 90px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.title {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
:disabled="!twoList.children.length" :open-delay="0" :close-delay="0" trigger="hover"
|
||||
:visible-arrow="false" @hide="leaveIndex" @show="changeIndex(twoList.id)" transition="none">
|
||||
<div class="course-two-content" slot="reference">{{
|
||||
twoList.name }}</div>
|
||||
twoList.name }}</div>-
|
||||
<!-- 内容 -->
|
||||
<div class="course-three-box">
|
||||
<div class="course-three-box-title">
|
||||
@@ -284,34 +284,62 @@
|
||||
<!-- 内容导航 -->
|
||||
<div class="topNav" v-if="!newData">
|
||||
<div class="search-div nav" style="height: 100px;flex: 1;">
|
||||
<div @click="handleTypeAllClick(1)" class="option-item" :class="{ 'option-active': ctypeTagAll }">
|
||||
<div @click="handleTypeAllClick(1)" class="option-item" style="font-weight: bold;position: relative;margin-right: 20px;" :class="{ 'option-active': ctypeTagAll }">
|
||||
<a>全部</a>
|
||||
<span :class="ctypeTagAll ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
<div @click="handleTypeClick(ctypeList[0], ctypeList)" class="option-item"
|
||||
<div @click="handleTypeClick(ctypeList[0], ctypeList)" class="option-item" style="font-weight: bold"
|
||||
:class="{ 'option-active': ctypeList[0].checked }">
|
||||
<a>录播课</a>
|
||||
<span :class="ctypeList[0].checked ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
<div @click="handleTypeClick(ctypeList[1], ctypeList)" class="option-item"
|
||||
<div @click="handleTypeClick(ctypeList[1], ctypeList)" class="option-item" style="font-weight: bold"
|
||||
:class="{ 'option-active': ctypeList[1].checked }">
|
||||
<a>线下课</a>
|
||||
<span :class="ctypeList[1].checked ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
<div @click="handleTypeClick(ctypeList[2], ctypeList)" class="option-item"
|
||||
<div @click="handleTypeClick(ctypeList[2], ctypeList)" class="option-item" style="font-weight: bold"
|
||||
:class="{ 'option-active': ctypeList[2].checked }">
|
||||
<a>学习项目</a>
|
||||
<span :class="ctypeList[2].checked ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
<a class="option-item">
|
||||
<span @click="uClassClick" class="Uxtext" style=""> U选小课堂
|
||||
<span @click="uClassClick" class="Uxtext" style="font-weight: bold"> U选小课堂
|
||||
<span class="uxicon">
|
||||
<svg-icon icon-class="hot" style="font-size:22px"></svg-icon>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<!-- 修改热点标签区域 -->
|
||||
<div style="margin-top:10px;flex: 1;">
|
||||
<!-- <div class="search-item-type" style="padding-top: 2px; float: left;">
|
||||
<span class="item-title" style="padding-right: 5px;">热点标签:</span>
|
||||
</div>-->
|
||||
<!-- 修改热点标签容器,支持换行 -->
|
||||
<div class="hot-tags-wrapper">
|
||||
<div
|
||||
class="option-item" style="font-weight: bold; padding-top: 2px;"
|
||||
:class="{ 'option-active': isAllHotTagsSelected }"
|
||||
@click="handleClearHotTags"
|
||||
>
|
||||
<span>全部</span>
|
||||
<span :class="isAllHotTagsSelected ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="option-item" style="font-weight: bold; padding-top: 2px;"
|
||||
v-for="tag in hotTagsList"
|
||||
:key="tag.id"
|
||||
@click="handleTagClick(tag, hotTagsList,1)"
|
||||
:class="{ 'option-active': tag.checked }"
|
||||
>
|
||||
<span>{{tag.tagName}}</span>
|
||||
<span :class="tag.checked ? 'nav-bottbor' : ''"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="fixd-box" class="upload" style="margin-left: 26px;">
|
||||
<!-- <div id="fixd-box" class="upload" style="margin-left: 26px;">
|
||||
<div v-if="identity == 2 || identity == 3 || identity == 5">
|
||||
<div class="portal-model-btn pointer" style="margin-bottom: 0px;height: 100px;line-height: 100px;"
|
||||
@click="toNeedCourse">
|
||||
@@ -319,15 +347,17 @@
|
||||
上传课程
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
<!-- 清除 -->
|
||||
<div v-if="stagList.length > 0 && !newData" class="search-div" style="padding: 0;margin-bottom: 20px;">
|
||||
<div class="searchbar" style="background-color:#f6f7fb;display: flex;justify-content: space-between;">
|
||||
<div style="line-height: 30px;">
|
||||
<span class="item-title"> 搜索条件</span>
|
||||
<el-tag closable v-for="(tag, tagIdx) in stagList" :key="'t' + tagIdx" @close="stagClose(tag, tagIdx)">{{
|
||||
tag.name }}</el-tag>
|
||||
<span class="item-title"> 搜索条件:</span>
|
||||
<el-tag closable v-for="(tag, tagIdx) in stagList" :key="'t' + tagIdx" @close="stagClose(tag, tagIdx)"
|
||||
:style="{ color: tag.type === 0 ? '#ff0000' : '' }">
|
||||
{{ tag.tagName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" size="mini" @click="handleClearTags">清除</el-button>
|
||||
@@ -361,6 +391,18 @@
|
||||
<span v-if="cinfo.type == 40" class="course-type-left">学习项目</span>
|
||||
</div>
|
||||
<div class="course-title two-line-ellipsis" :title="cinfo.title" v-html="cinfo.name"></div>
|
||||
<!-- 添加标签显示区域 -->
|
||||
<div class="course-tags" v-if="cinfo.tagsList && cinfo.tagsList.length > 0">
|
||||
<el-tag
|
||||
v-for="(tag, tagIndex) in cinfo.tagsList"
|
||||
:key="tagIndex"
|
||||
size="mini"
|
||||
type="info" style="margin: 2px 2px; border-radius: 2px;"
|
||||
:style="{ color: isTagMatched(tag) ? '#387DF7' : '#333333' }"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 关键字 -->
|
||||
<div class="keywordInfo-every">
|
||||
<div class="keywordInfo" v-for="(keyword, index) in cinfo.keywordsActive" :key="index">
|
||||
@@ -507,12 +549,14 @@ import apiTeacher from "@/api/modules/teacher.js";
|
||||
import apiUser from "@/api/system/user.js";
|
||||
import scene from "@/api/modules/scene.js";
|
||||
import apiUserbasic from "@/api/boe/userbasic.js";
|
||||
import apiManage from '@/api/manage/manage.js';
|
||||
import interactBar from "@/components/Portal/interactBar.vue";
|
||||
import courseImage from "@/components/Course/courseImage.vue";
|
||||
import { courseType, getType, toScore, formatDate, formatUserNumber, formatDateByFmt } from "@/utils/tools.js";
|
||||
import { deepClone, param } from "../../../utils";
|
||||
import apiSearchterm from "@/api/modules/searchterm.js";
|
||||
import apiPlace from "@/api/phase2/place.js"
|
||||
import apiCourseTag from '@/api/modules/courseTag.js'
|
||||
export default {
|
||||
name: "index",
|
||||
components: {
|
||||
@@ -534,21 +578,43 @@ export default {
|
||||
},
|
||||
stagList() { //计算出选择的内容
|
||||
let list = [];
|
||||
|
||||
// 关键词
|
||||
if (this.keyword) {
|
||||
list.push({
|
||||
type: 0,
|
||||
id: 'keyword',
|
||||
name: this.keyword,
|
||||
tagName: this.keyword,
|
||||
checked: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// 课程类型
|
||||
this.ctypeList.forEach(item => {
|
||||
if (item.checked) {
|
||||
list.push(item);
|
||||
list.push({
|
||||
...item,
|
||||
tagName: item.name
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 热点标签 - 这是关键修复
|
||||
this.hotTagsList.forEach(item => {
|
||||
if (item.checked) {
|
||||
list.push({
|
||||
...item,
|
||||
name: item.tagName || item.name,
|
||||
tagName: item.tagName || item.name,
|
||||
type: 14
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 三级分类
|
||||
this.oneList.forEach(one => {
|
||||
var twoChildChecked = false;//是否有下级
|
||||
var twoChildChecked = false;
|
||||
one.children.forEach(two => {
|
||||
if (two.checked) {
|
||||
twoChildChecked = true;
|
||||
@@ -556,34 +622,28 @@ export default {
|
||||
var threeChildChecked = false;
|
||||
two.children.forEach(three => {
|
||||
if (three.checked) {
|
||||
list.push(three);
|
||||
list.push({
|
||||
...three,
|
||||
tagName: three.name
|
||||
});
|
||||
threeChildChecked = true;
|
||||
}
|
||||
});
|
||||
if (two.checked && !threeChildChecked) {
|
||||
list.push(two);
|
||||
list.push({
|
||||
...two,
|
||||
tagName: two.name
|
||||
});
|
||||
}
|
||||
});
|
||||
if (one.checked && !twoChildChecked) {
|
||||
list.push(one);
|
||||
list.push({
|
||||
...one,
|
||||
tagName: one.name
|
||||
});
|
||||
}
|
||||
})
|
||||
// this.oneList.forEach(item=>{
|
||||
// if(item.checked){
|
||||
// list.push(item);
|
||||
// }
|
||||
// });
|
||||
// this.twoList.forEach(item=>{
|
||||
// if(item.checked){
|
||||
// list.push(item);
|
||||
// }
|
||||
// });
|
||||
// this.threeList.forEach(item=>{
|
||||
// if(item.checked){
|
||||
// list.push(item);
|
||||
// }
|
||||
// });
|
||||
//console.log(list,'list');
|
||||
});
|
||||
|
||||
return list;
|
||||
},
|
||||
ctypeTagAll() {
|
||||
@@ -610,15 +670,22 @@ export default {
|
||||
// });
|
||||
// return !flag;
|
||||
// }
|
||||
|
||||
// 添加计算是否所有热点标签都未选中(即"全部"选中状态)
|
||||
isAllHotTagsSelected() {
|
||||
return !this.hotTagsList.some(tag => tag.checked);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hotTagsList: [],
|
||||
newData: false,//线上品牌系列隐藏
|
||||
navTitle: [],
|
||||
// 设置高亮
|
||||
twoId: '',
|
||||
count: 0,//分页总条条数
|
||||
showUClass: false,
|
||||
projectDialogVisible: false,
|
||||
ctypeList: [
|
||||
{ type: 1, id: 20, name: '录播课', checked: false },
|
||||
{ type: 1, id: 30, name: '线下课', checked: false },
|
||||
@@ -629,7 +696,7 @@ export default {
|
||||
twoList: [], //二级分类{type:12}
|
||||
threeList: [],//三级分类{type:13}
|
||||
searching: false,//是否正在搜索中
|
||||
|
||||
studentInfo: {},
|
||||
resonimg: {},
|
||||
formatDate,
|
||||
formatNum: formatUserNumber,
|
||||
@@ -687,6 +754,17 @@ export default {
|
||||
console.log(rs.message);
|
||||
}
|
||||
})
|
||||
//初始化:获取最新前10个热点标签
|
||||
apiCourseTag.getHotTagList(null).then(rs => {
|
||||
if (rs.status == 200) {
|
||||
this.hotTagsList = rs.result.map(tag => ({
|
||||
...tag,
|
||||
checked: false
|
||||
}));
|
||||
} else {
|
||||
console.log(rs.message);
|
||||
}
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
let screenWidth = window.screen.availWidth;
|
||||
@@ -769,6 +847,27 @@ export default {
|
||||
// window.removeEventListener("scroll", this.handleScroll);
|
||||
},
|
||||
methods: {
|
||||
|
||||
isTagMatched(tag) {
|
||||
// 检查stagList中是否有匹配的标签
|
||||
return this.stagList.some(searchTag =>
|
||||
searchTag.tagName === tag || searchTag.name === tag
|
||||
);
|
||||
},
|
||||
|
||||
// 添加清除热点标签选中的方法
|
||||
handleClearHotTags() {
|
||||
// 清除所有热点标签的选中状态
|
||||
this.hotTagsList.forEach(tag => {
|
||||
tag.checked = false;
|
||||
});
|
||||
// 清空course.tags
|
||||
this.course.tags = '';
|
||||
|
||||
// 触发搜索
|
||||
this.searchData();
|
||||
},
|
||||
|
||||
// 改变分页
|
||||
currentChange(val) {
|
||||
this.course.pageIndex = val
|
||||
@@ -852,10 +951,54 @@ export default {
|
||||
//搜索条件
|
||||
stagClose(tag, tagIndex) {
|
||||
tag.checked = false;
|
||||
|
||||
// 根据标签类型处理不同的清除逻辑
|
||||
if (tag.type == 0) {
|
||||
// 关键词类型
|
||||
this.keyword = '';
|
||||
} else if (tag.type == 1) {
|
||||
// 课程类型(录播课、线下课、学习项目)
|
||||
this.ctypeList.forEach(item => {
|
||||
if (item.id == tag.id) {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
} else if (tag.type == 14) {
|
||||
// 热点标签类型
|
||||
this.hotTagsList.forEach(item => {
|
||||
if (item.id == tag.id) {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新course.tags,移除被删除的热点标签
|
||||
const checkedHotTags = this.hotTagsList.filter(tag => tag.checked);
|
||||
let tagIds = checkedHotTags.map(tag => tag.id).join(',');
|
||||
this.course.tags = tagIds;
|
||||
|
||||
} else if (tag.type == 11 || tag.type == 12 || tag.type == 13) {
|
||||
// 三级分类标签
|
||||
this.oneList.forEach(one => {
|
||||
if (one.id == tag.id) {
|
||||
one.checked = false;
|
||||
}
|
||||
one.children.forEach(two => {
|
||||
if (two.id == tag.id) {
|
||||
two.checked = false;
|
||||
}
|
||||
two.children.forEach(three => {
|
||||
if (three.id == tag.id) {
|
||||
three.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
this.navTitle = []
|
||||
|
||||
// 重置导航标题
|
||||
this.navTitle = [];
|
||||
|
||||
// 触发搜索更新
|
||||
this.searchData();
|
||||
},
|
||||
|
||||
@@ -890,31 +1033,36 @@ export default {
|
||||
ct.checked = false;
|
||||
})
|
||||
}
|
||||
this.searchData();
|
||||
this.searchData(1);
|
||||
},
|
||||
// 清除
|
||||
handleClearTags() {
|
||||
//清空所有的条件
|
||||
this.keyword = '';
|
||||
this.ctypeList.forEach(item => {
|
||||
item.checked = false;
|
||||
handleClearTags() {
|
||||
//清空所有的条件
|
||||
this.keyword = '';
|
||||
this.ctypeList.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
this.hotTagsList.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
this.course.tags = ''; // 清空标签ID
|
||||
|
||||
// 添加清除三级分类的逻辑
|
||||
this.oneList.forEach(one => {
|
||||
one.checked = false;
|
||||
one.children.forEach(two => {
|
||||
two.checked = false;
|
||||
two.children.forEach(three => {
|
||||
three.checked = false;
|
||||
});
|
||||
this.oneList.forEach(one => {
|
||||
one.checked = false;
|
||||
one.children.forEach(two => {
|
||||
two.checked = false;
|
||||
two.children.forEach(three => {
|
||||
three.checked = false;
|
||||
})
|
||||
})
|
||||
});
|
||||
this.twoList = [];
|
||||
this.threeList = [];
|
||||
this.navTitle = [];
|
||||
this.newData = false;
|
||||
sessionStorage.removeItem(this.localSessionKey)
|
||||
this.searchData();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// 清空导航标题
|
||||
this.navTitle = [];
|
||||
|
||||
this.searchData();
|
||||
},
|
||||
// 导航切换(录播课,线下课,学习项目)
|
||||
handleTypeClick(item, list) {
|
||||
item.checked = !item.checked;
|
||||
@@ -924,13 +1072,26 @@ export default {
|
||||
// })
|
||||
// item.checked=true;
|
||||
|
||||
this.searchData();
|
||||
this.searchData(1);
|
||||
},
|
||||
//点击标签
|
||||
handleTagClick(item, list,type) {
|
||||
item.checked = !item.checked;
|
||||
|
||||
// 更新course.tags
|
||||
const checkedTags = this.hotTagsList.filter(tag => tag.checked);
|
||||
let tagIds = checkedTags.map(tag => tag.id).join(',');
|
||||
this.course.tags = tagIds;
|
||||
|
||||
// 强制触发stagList重新计算
|
||||
this.$nextTick(() => {
|
||||
this.searchData(type);
|
||||
});
|
||||
},
|
||||
//三级分类
|
||||
handleOptionClick(item, level, list) {
|
||||
// 线上品牌展示效果
|
||||
this.newData = item.newData;
|
||||
console.log(this.newData);
|
||||
// 单选,排除法
|
||||
this.oneList.forEach(one => {
|
||||
one.checked = false;
|
||||
@@ -1165,13 +1326,13 @@ export default {
|
||||
this.$refs.floatTools.toNeedCourse();
|
||||
|
||||
},
|
||||
searchData() {
|
||||
searchData(type) {
|
||||
this.course.pageIndex = 1;
|
||||
this.noPageList = true; //判断接口是否还有数据
|
||||
this.noDataList = true; //判断接口是否还有数据
|
||||
this.courseList = [];
|
||||
this.totalPages = 4;
|
||||
this.search();
|
||||
this.search(type);
|
||||
},
|
||||
inputOn() {
|
||||
this.$forceUpdate();
|
||||
@@ -1307,12 +1468,18 @@ export default {
|
||||
},
|
||||
getAllChecked() { //获取全部选中的标签
|
||||
let list = [];
|
||||
|
||||
//获取选中的课程类型
|
||||
this.ctypeList.forEach(item => {
|
||||
if (item.checked) {
|
||||
list.push(item);
|
||||
}
|
||||
});
|
||||
//获取选中的热点标签
|
||||
this.hotTagsList.forEach(item => {
|
||||
if (item.checked) {
|
||||
list.push(item);
|
||||
}
|
||||
});
|
||||
this.oneList.forEach(one => {
|
||||
one.children.forEach(two => {
|
||||
two.children.forEach(three => {
|
||||
@@ -1331,7 +1498,7 @@ export default {
|
||||
return list;
|
||||
},
|
||||
// 查询
|
||||
async search() {
|
||||
async search(type) {
|
||||
//
|
||||
if (this.searching) {
|
||||
this.$message.warning("正在搜索中,请待搜索完成后再重新搜索");
|
||||
@@ -1375,7 +1542,20 @@ export default {
|
||||
that.course.sysType3 += item.id;
|
||||
}
|
||||
});
|
||||
|
||||
if (!type && type !== 1){
|
||||
apiCourseTag.getHotTagList(that.course).then(rs => {
|
||||
if (rs.status == 200) {
|
||||
// 保留已选中标签的状态
|
||||
const currentCheckedTags = this.hotTagsList.filter(tag => tag.checked);
|
||||
this.hotTagsList = rs.result.map(tag => ({
|
||||
...tag,
|
||||
checked: currentCheckedTags.some(checkedTag => checkedTag.id === tag.id)
|
||||
}));
|
||||
} else {
|
||||
console.log(rs.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
this.isFind = true;
|
||||
this.course.device = 1;
|
||||
if (this.course.pageIndex == 1) {
|
||||
@@ -1417,7 +1597,7 @@ export default {
|
||||
item.name = item.name;
|
||||
}
|
||||
});
|
||||
console.log(res.result.list,'data')
|
||||
console.log(res.result.list,'data')
|
||||
this.courseList = res?.result?.list ?? []
|
||||
console.log(this.courseList);
|
||||
if (this.newData) {
|
||||
@@ -2421,4 +2601,149 @@ console.log(res.result.list,'data')
|
||||
|
||||
.option-active {
|
||||
color: #387DF7;
|
||||
}</style>
|
||||
}
|
||||
/* 项目简介 方法一:外部 CSS 类 */
|
||||
::v-deep.el-dialog {
|
||||
border-radius: 3% 3% 1% 1%;
|
||||
padding: 0;
|
||||
}
|
||||
::v-deep.custom-class .el-dialog__header {
|
||||
height: 100px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
background-image: url('../../../assets/images/project/title-bg.png');
|
||||
background-size: 100% 100%; /* 完全填充 */
|
||||
display: block; /* 避免行内元素空隙 */
|
||||
}
|
||||
::v-deep.custom-class .el-dialog__header .el-dialog__title {
|
||||
padding: 0 !important;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin: 60px;
|
||||
line-height: 100px;
|
||||
}
|
||||
::v-deep.custom-class .el-dialog__body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
/* ---end--- */
|
||||
/* ---标签管理 added by zhengsongbo on 2025-08-01--- */
|
||||
.search-div.nav {
|
||||
display: block;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
.option-item {
|
||||
margin: 0px 5px;
|
||||
}
|
||||
/* 热点标签:自定义按钮样式 */
|
||||
a.custom {
|
||||
/* 基础样式 */
|
||||
display: inline-block; /* 使内边距生效 */
|
||||
padding: 1px; /* 按钮内边距 */
|
||||
margin: 1px 5px;
|
||||
background-color: #F2F2F2; /* 淡灰色背景 */
|
||||
color: #333; /* 文字颜色 */
|
||||
text-decoration: none; /* 去除下划线 */
|
||||
border-radius: 3px; /* 圆角设计 */
|
||||
font-family: Arial, sans-serif; /* 字体 */
|
||||
font-size: 14px; /* 文字大小 */
|
||||
height: 24px;
|
||||
line-height: 20px;
|
||||
/* 过渡效果,使颜色变化更平滑 */
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* 鼠标悬停效果 */
|
||||
a.custom:hover {
|
||||
background-color: #DDEDFF; /* 浅蓝色背景 */
|
||||
}
|
||||
|
||||
/* 可选:点击时效果 */
|
||||
a.custom:active {
|
||||
background-color: #757575; /* 点击时更深的灰色 */
|
||||
}
|
||||
|
||||
/* 鼠标悬停效果 */
|
||||
a.custom2 {
|
||||
background-color: #DDEDFF; /* 浅蓝色背景 */
|
||||
}
|
||||
|
||||
.hot-tags-container {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.hot-tags-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 添加标签样式 */
|
||||
//.course-tags {
|
||||
// margin: 5px 0;
|
||||
// min-height: 20px;
|
||||
//}
|
||||
//.course-tags ::v-deep .el-tag {
|
||||
// color: #387DF7 !important;
|
||||
// border-color: #387DF7 !important;
|
||||
//}
|
||||
//.course-tags ::v-deep .el-tag .el-tag__close {
|
||||
// color: #387DF7 !important;
|
||||
//}
|
||||
//.course-tags ::v-deep .el-tag .el-tag__close:hover {
|
||||
// background-color: #387DF7 !important;
|
||||
// color: white !important;
|
||||
//}
|
||||
|
||||
.course-tag-item {
|
||||
color: #333333; // 默认深灰色
|
||||
}
|
||||
.course-tag-item[style*="color: #387DF7"] {
|
||||
color: #387DF7 !important; // 匹配时的蓝色
|
||||
}
|
||||
|
||||
/* 添加热点标签容器样式,支持换行 */
|
||||
.hot-tags-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
padding-top: 2px;
|
||||
//margin-left: 90px; /* 为"热点标签:"文本留出空间 */
|
||||
}
|
||||
|
||||
/* 调整option-item样式以适应换行布局 */
|
||||
.option-item {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 保持原有的导航底部横线样式 */
|
||||
.nav-bottbor {
|
||||
position: absolute;
|
||||
top: 130%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: #387DF7;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* 响应式调整,当屏幕较小时减小标签间距 */
|
||||
@media (max-width: 1600px) {
|
||||
.hot-tags-wrapper {
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.hot-tags-wrapper {
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
/* ---end--- */
|
||||
</style>
|
||||
|
||||
2254
src/views/portal/course/qualityCourse.vue
Normal file
@@ -480,6 +480,7 @@
|
||||
defaultMaxTime:1800, //非音频默认最大时间
|
||||
warn:"测试内容",
|
||||
warnTitle:"测试标题",
|
||||
isFinishingStudyItem: false, // 防止重复调用完成状态更新接口
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -1684,12 +1685,17 @@
|
||||
//这种可能没有,不过这里也是为了万中那个1
|
||||
!this.tentative && this.saveStudyInfo();
|
||||
} else {
|
||||
// 如果正在处理完成请求,则直接返回,避免重复调用
|
||||
if (this.isFinishingStudyItem) {
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
itemId: this.contentData.studyItemId,
|
||||
studyId: this.studyId,
|
||||
courseId: this.courseId,
|
||||
cnum: this.totalContent
|
||||
}
|
||||
this.isFinishingStudyItem = true; // 设置标志位
|
||||
apiVideoStudy.finishStudyItem(params).then(res => {
|
||||
if (res.status == 200) {
|
||||
this.contentData.status = 9;
|
||||
@@ -1697,6 +1703,10 @@
|
||||
} else {
|
||||
console.log("记录完成学习失败:" + res.message + "," + res.error);
|
||||
}
|
||||
this.isFinishingStudyItem = false; // 重置标志位
|
||||
}).catch(error => {
|
||||
console.error("记录完成学习出错:", error);
|
||||
this.isFinishingStudyItem = false; // 出错时也重置标志位
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -1960,16 +1970,19 @@
|
||||
}
|
||||
|
||||
.player-box {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 20px auto;
|
||||
height: 187px;
|
||||
background: rgba(74, 74, 74, .5);
|
||||
background: rgba(74, 74, 74, .8);
|
||||
border-radius: 33px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
top: 50%;
|
||||
box-sizing: border-box;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.player-praise {
|
||||
margin-top: 25px;
|
||||
|
||||
389
src/views/tag/TagManageList.vue
Normal 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>
|
||||
@@ -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'))
|
||||
|
||||