Compare commits
11 Commits
main-1.0终版
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
8e9c57a6f9 | |
|
|
3c4aaa0f77 | |
|
|
8f8ace89e1 | |
|
|
11c286c717 | |
|
|
227b836d93 | |
|
|
86b3a51206 | |
|
|
1536637159 | |
|
|
ac49885fad | |
|
|
f738c1db94 | |
|
|
e8009a845c | |
|
|
52a1f58f78 |
2
App.vue
2
App.vue
|
|
@ -109,7 +109,7 @@ export default {
|
||||||
window.location.protocol.indexOf("https") === 0 ? "wss" : "ws";
|
window.location.protocol.indexOf("https") === 0 ? "wss" : "ws";
|
||||||
const Id =
|
const Id =
|
||||||
(this.vuex_user && (this.vuex_user.id || this.vuex_user.Id)) || "";
|
(this.vuex_user && (this.vuex_user.id || this.vuex_user.Id)) || "";
|
||||||
return `${protocol}://120.55.234.65:8082/api/Dialogue/HandleConnection?Id=${Id}`; // &equipmentType=0
|
return `${protocol}://120.55.234.65:8073/api/Dialogue/HandleConnection?Id=${Id}`; // &equipmentType=0
|
||||||
},
|
},
|
||||||
// 初始化原生 WebSocket 连接
|
// 初始化原生 WebSocket 连接
|
||||||
initWebSocket() {
|
initWebSocket() {
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,12 @@ const install = (Vue, vm) => {
|
||||||
// 验证码登录-教师
|
// 验证码登录-教师
|
||||||
let TeacherLoginByCode = (params = {}) =>
|
let TeacherLoginByCode = (params = {}) =>
|
||||||
vm.$u.post("api/Login/PhoneLoginManagementEnd", params);
|
vm.$u.post("api/Login/PhoneLoginManagementEnd", params);
|
||||||
|
// 忘记密码-获取短信验证码
|
||||||
|
let RequestForgotPasswordSMSCode = (params = {}) =>
|
||||||
|
vm.$u.post("api/Login/RequestForgotPasswordSMSCode", params);
|
||||||
|
// 忘记密码-修改密码
|
||||||
|
let ForgotPasswordChangePassword = (params = {}) =>
|
||||||
|
vm.$u.post("api/Login/ForgotPasswordChangePassword", params);
|
||||||
|
|
||||||
/** 用户-个人中心 */
|
/** 用户-个人中心 */
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
|
|
@ -251,6 +257,9 @@ const install = (Vue, vm) => {
|
||||||
// 转人工服务
|
// 转人工服务
|
||||||
let TransferToALiveAgentApi = (params = {}) =>
|
let TransferToALiveAgentApi = (params = {}) =>
|
||||||
vm.$u.post("api/Dialogue/TransferToALiveAgent", params);
|
vm.$u.post("api/Dialogue/TransferToALiveAgent", params);
|
||||||
|
// 结束人工服务
|
||||||
|
let EndLiveAgentApi = (params = {}) =>
|
||||||
|
vm.$u.post("api/Dialogue/EndLiveAgent", params);
|
||||||
|
|
||||||
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
||||||
vm.$u.api = {
|
vm.$u.api = {
|
||||||
|
|
@ -309,6 +318,8 @@ const install = (Vue, vm) => {
|
||||||
GetTeacherVerifyCode,
|
GetTeacherVerifyCode,
|
||||||
TeacherLogin,
|
TeacherLogin,
|
||||||
TeacherLoginByCode,
|
TeacherLoginByCode,
|
||||||
|
RequestForgotPasswordSMSCode,
|
||||||
|
ForgotPasswordChangePassword,
|
||||||
GetUserApi,
|
GetUserApi,
|
||||||
UpdateUserApi,
|
UpdateUserApi,
|
||||||
GetTeacherListApi,
|
GetTeacherListApi,
|
||||||
|
|
@ -322,6 +333,7 @@ const install = (Vue, vm) => {
|
||||||
ReadMessageApi,
|
ReadMessageApi,
|
||||||
DeleteDialogueApi,
|
DeleteDialogueApi,
|
||||||
TransferToALiveAgentApi,
|
TransferToALiveAgentApi,
|
||||||
|
EndLiveAgentApi,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ const install = (Vue, vm) => {
|
||||||
Vue.prototype.$u.http.setConfig({
|
Vue.prototype.$u.http.setConfig({
|
||||||
// baseUrl: 'https://xy.apps.service.zheke.com',
|
// baseUrl: 'https://xy.apps.service.zheke.com',
|
||||||
// imgUrl: 'https://xy.apps.service.zheke.com/',
|
// imgUrl: 'https://xy.apps.service.zheke.com/',
|
||||||
baseUrl: "http://120.55.234.65:8082",
|
baseUrl: "http://120.55.234.65:8073",
|
||||||
imgUrl: "http://120.55.234.65:8082/",
|
imgUrl: "http://120.55.234.65:8073/",
|
||||||
// imgUrl:'http://115.238.47.235:8987/',
|
// imgUrl:'http://115.238.47.235:8987/',
|
||||||
// baseUrl: 'http://115.238.47.235:8993',
|
// baseUrl: 'http://115.238.47.235:8993',
|
||||||
// 如果将此值设置为true,拦截回调中将会返回服务端返回的所有数据response,而不是response.data
|
// 如果将此值设置为true,拦截回调中将会返回服务端返回的所有数据response,而不是response.data
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,31 @@
|
||||||
>
|
>
|
||||||
<view class="phone-popup">
|
<view class="phone-popup">
|
||||||
<view class="phone-title">招生电话</view>
|
<view class="phone-title">招生电话</view>
|
||||||
<view class="phone-content">0790-6764666/6765666</view>
|
|
||||||
|
<view class="phone-card-list">
|
||||||
|
<view class="phone-card" @click="makeCall('0790-6764666')">
|
||||||
|
<view class="icon-wrapper">
|
||||||
|
<u-icon name="phone-fill" color="#ffffff" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="info-wrapper">
|
||||||
|
<text class="phone-number">0790-6764666</text>
|
||||||
|
<text class="phone-desc">拨打招生电话1</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="phone-card" @click="makeCall('0790-6765666')">
|
||||||
|
<view class="icon-wrapper">
|
||||||
|
<u-icon name="phone-fill" color="#ffffff" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="info-wrapper">
|
||||||
|
<text class="phone-number">0790-6765666</text>
|
||||||
|
<text class="phone-desc">拨打招生电话2</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="phone-button">
|
<view class="phone-button">
|
||||||
<u-button class="cancel-button" type="default" @click="closePopup"
|
<view class="cancel-btn" @click="closePopup">取消</view>
|
||||||
>取消</u-button
|
|
||||||
>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
|
@ -48,6 +68,11 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
makeCall(phoneNumber) {
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber,
|
||||||
|
});
|
||||||
|
},
|
||||||
closePopup() {
|
closePopup() {
|
||||||
this.showPopup = false;
|
this.showPopup = false;
|
||||||
this.$emit("update:show", false);
|
this.$emit("update:show", false);
|
||||||
|
|
@ -66,7 +91,6 @@ export default {
|
||||||
background-size: 630rpx 100rpx;
|
background-size: 630rpx 100rpx;
|
||||||
background-position: -20rpx 0;
|
background-position: -20rpx 0;
|
||||||
|
|
||||||
|
|
||||||
.phone-title {
|
.phone-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: DouyinSans;
|
font-family: DouyinSans;
|
||||||
|
|
@ -76,19 +100,72 @@ export default {
|
||||||
margin-bottom: 40rpx;
|
margin-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phone-content {
|
.phone-card-list {
|
||||||
margin-bottom: 120rpx;
|
padding: 0 48rpx;
|
||||||
text-align: center;
|
margin-bottom: 60rpx;
|
||||||
font-family: PingFang SC;
|
|
||||||
font-size: 32rpx;
|
.phone-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
box-shadow: 0px 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
background: #5ac799;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 30rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.phone-number {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.phone-button {
|
.phone-button {
|
||||||
padding: 0 40rpx;
|
padding: 0 48rpx;
|
||||||
|
|
||||||
.cancel-button {
|
.cancel-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
text-align: center;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const TAB_BAR_CONFIG = [
|
export const TAB_BAR_CONFIG = [
|
||||||
{
|
// {
|
||||||
text: "在线咨询",
|
// text: "在线咨询",
|
||||||
icon: "/static/tabbar/tabbar-icon1.png",
|
// icon: "/static/tabbar/tabbar-icon1.png",
|
||||||
activeIcon: "/static/tabbar/tabbar-icon1-active.png",
|
// activeIcon: "/static/tabbar/tabbar-icon1-active.png",
|
||||||
pagePath: "/pages/consultation/index",
|
// pagePath: "/pages/consultation/index",
|
||||||
// 可选配置
|
// // 可选配置
|
||||||
badge: "", // 角标文字
|
// badge: "", // 角标文字
|
||||||
dot: false, // 是否显示小红点
|
// dot: false, // 是否显示小红点
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
text: "人工转接",
|
text: "人工转接",
|
||||||
icon: "/static/tabbar/tabbar-icon4.png",
|
icon: "/static/tabbar/tabbar-icon4.png",
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ const getWebSocketUrl = () => {
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
// H5 开发环境
|
// H5 开发环境
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
return 'ws://localhost:8082/ws/chat'
|
return 'ws://localhost:8073/ws/chat'
|
||||||
}
|
}
|
||||||
// H5 生产环境
|
// H5 生产环境
|
||||||
return 'wss://120.55.234.65:8082/ws/chat'
|
return 'wss://120.55.234.65:8073/ws/chat'
|
||||||
// #endif
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
36
pages.json
36
pages.json
|
|
@ -57,6 +57,13 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/my/change-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改密码",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/home/admissions/index",
|
"path": "pages/home/admissions/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
@ -73,14 +80,14 @@
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
"path": "pages/consultation/index",
|
// "path": "pages/consultation/index",
|
||||||
"style": {
|
// "style": {
|
||||||
"navigationBarTitleText": "在线咨询",
|
// "navigationBarTitleText": "在线咨询",
|
||||||
"enablePullDownRefresh": false,
|
// "enablePullDownRefresh": false,
|
||||||
"navigationStyle": "custom"
|
// "navigationStyle": "custom"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
"path": "pages/transfer/index",
|
"path": "pages/transfer/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|
@ -134,12 +141,13 @@
|
||||||
"borderStyle": "black",
|
"borderStyle": "black",
|
||||||
"backgroundColor": "#ffffff",
|
"backgroundColor": "#ffffff",
|
||||||
"list": [
|
"list": [
|
||||||
{
|
// 暂时不用在线咨询页面,保留配置便于恢复
|
||||||
"pagePath": "pages/consultation/index",
|
// {
|
||||||
"iconPath": "static/tabbar/icon_home.png",
|
// "pagePath": "pages/consultation/index",
|
||||||
"selectedIconPath": "static/tabbar/icon_home_active.png",
|
// "iconPath": "static/tabbar/icon_home.png",
|
||||||
"text": "在线咨询"
|
// "selectedIconPath": "static/tabbar/icon_home_active.png",
|
||||||
},
|
// "text": "在线咨询"
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
"pagePath": "pages/transfer/index",
|
"pagePath": "pages/transfer/index",
|
||||||
"iconPath": "static/tabbar/icon_message.png",
|
"iconPath": "static/tabbar/icon_message.png",
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,26 @@
|
||||||
<view class="home-container">
|
<view class="home-container">
|
||||||
<header-bar
|
<header-bar
|
||||||
title="源小新"
|
title="源小新"
|
||||||
|
:showLeftIcon="false"
|
||||||
leftIcon="list"
|
leftIcon="list"
|
||||||
@leftClick="handleLeftClick"
|
@leftClick="handleLeftClick"
|
||||||
></header-bar>
|
>
|
||||||
|
<image
|
||||||
|
slot="left"
|
||||||
|
class="header-icon"
|
||||||
|
src="/static/common/images/icon-userSetting.png"
|
||||||
|
@click="handleSettingClick"
|
||||||
|
></image>
|
||||||
|
<!-- 记录干掉了 -->
|
||||||
|
<!-- <image
|
||||||
|
slot="right"
|
||||||
|
class="header-icon"
|
||||||
|
src="/static/common/images/icon-userRecord.png"
|
||||||
|
@click="handleRecordClick"
|
||||||
|
></image> -->
|
||||||
|
</header-bar>
|
||||||
|
|
||||||
|
<!-- 这是新版 -->
|
||||||
|
|
||||||
<!-- 首页 -->
|
<!-- 首页 -->
|
||||||
<view class="main-content" v-if="!isChat">
|
<view class="main-content" v-if="!isChat">
|
||||||
|
|
@ -59,10 +76,16 @@
|
||||||
:style="{
|
:style="{
|
||||||
background: item.background,
|
background: item.background,
|
||||||
}"
|
}"
|
||||||
@click="handleFeatureClick(item)"
|
|
||||||
>
|
>
|
||||||
<image :src="item.icon" class="feature-icon"></image>
|
<image :src="item.icon" class="feature-icon"></image>
|
||||||
<text class="feature-text">{{ item.title }}</text>
|
<view class="feature-text-wrapper">
|
||||||
|
<text class="feature-title">{{ item.title }}</text>
|
||||||
|
<text class="feature-tip">{{ item.tip }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="feature-action-btn" @click="handleFeatureClick(item)">
|
||||||
|
<text>去咨询</text>
|
||||||
|
<u-icon name="arrow-right" size="24" color="#ffffff"></u-icon>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -225,6 +248,7 @@
|
||||||
"
|
"
|
||||||
:id="'msg-' + message.id"
|
:id="'msg-' + message.id"
|
||||||
>
|
>
|
||||||
|
<view class="message-content-container-AI">
|
||||||
<image
|
<image
|
||||||
class="ai-avatar"
|
class="ai-avatar"
|
||||||
src="/static/common/images/avatar_ai.png"
|
src="/static/common/images/avatar_ai.png"
|
||||||
|
|
@ -234,6 +258,8 @@
|
||||||
class="message-content"
|
class="message-content"
|
||||||
:class="{
|
:class="{
|
||||||
'message-content-width': !message.isLoading,
|
'message-content-width': !message.isLoading,
|
||||||
|
'message-content-structured':
|
||||||
|
getStructuredMessageBlocks(message).length,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<!-- 加载动画 -->
|
<!-- 加载动画 -->
|
||||||
|
|
@ -242,9 +268,50 @@
|
||||||
<view class="dot"></view>
|
<view class="dot"></view>
|
||||||
<view class="dot"></view>
|
<view class="dot"></view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 正常消息内容 -->
|
<view
|
||||||
|
v-else-if="getStructuredMessageBlocks(message).length"
|
||||||
|
class="structured-message"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(block, blockIndex) in getStructuredMessageBlocks(
|
||||||
|
message,
|
||||||
|
)"
|
||||||
|
:key="`${message.id || index}-block-${blockIndex}`"
|
||||||
|
class="structured-item"
|
||||||
|
:class="`structured-item-${block.type}`"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-if="block.type === 'text'"
|
||||||
|
class="structured-text"
|
||||||
|
>
|
||||||
|
{{ block.value }}
|
||||||
|
</text>
|
||||||
|
<image
|
||||||
|
v-else-if="block.type === 'image'"
|
||||||
|
class="structured-image"
|
||||||
|
:src="block.url"
|
||||||
|
mode="widthFix"
|
||||||
|
@click="previewMessageImage(block.url)"
|
||||||
|
></image>
|
||||||
|
<view
|
||||||
|
v-else-if="block.type === 'file'"
|
||||||
|
class="structured-file"
|
||||||
|
@click="downloadMessageFile(block.url)"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
name="download"
|
||||||
|
size="28"
|
||||||
|
color="#4370fe"
|
||||||
|
></u-icon>
|
||||||
|
<text class="structured-file-name">{{
|
||||||
|
block.name
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<markdown-viewer v-else :content="message.message" />
|
<markdown-viewer v-else :content="message.message" />
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 回答反馈:点赞/点踩 -->
|
<!-- 回答反馈:点赞/点踩 -->
|
||||||
<view v-if="!message.isLoading" class="feedback-container">
|
<view v-if="!message.isLoading" class="feedback-container">
|
||||||
|
|
@ -287,6 +354,7 @@
|
||||||
v-else-if="message.interactMode === 8"
|
v-else-if="message.interactMode === 8"
|
||||||
:id="'msg-' + message.id"
|
:id="'msg-' + message.id"
|
||||||
>
|
>
|
||||||
|
<view class="message-content-container-Teacher">
|
||||||
<image
|
<image
|
||||||
class="ai-avatar"
|
class="ai-avatar"
|
||||||
:src="receiverHeadSculptureUrl"
|
:src="receiverHeadSculptureUrl"
|
||||||
|
|
@ -296,6 +364,8 @@
|
||||||
class="message-content"
|
class="message-content"
|
||||||
:class="{
|
:class="{
|
||||||
'message-content-width': !message.isLoading,
|
'message-content-width': !message.isLoading,
|
||||||
|
'message-content-structured':
|
||||||
|
getStructuredMessageBlocks(message).length,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<view v-if="message.isLoading" class="loading-dots">
|
<view v-if="message.isLoading" class="loading-dots">
|
||||||
|
|
@ -303,9 +373,51 @@
|
||||||
<view class="dot"></view>
|
<view class="dot"></view>
|
||||||
<view class="dot"></view>
|
<view class="dot"></view>
|
||||||
</view>
|
</view>
|
||||||
|
<view
|
||||||
|
v-else-if="getStructuredMessageBlocks(message).length"
|
||||||
|
class="structured-message"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(block, blockIndex) in getStructuredMessageBlocks(
|
||||||
|
message,
|
||||||
|
)"
|
||||||
|
:key="`${message.id || index}-teacher-block-${blockIndex}`"
|
||||||
|
class="structured-item"
|
||||||
|
:class="`structured-item-${block.type}`"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-if="block.type === 'text'"
|
||||||
|
class="structured-text"
|
||||||
|
>
|
||||||
|
{{ block.value }}
|
||||||
|
</text>
|
||||||
|
<image
|
||||||
|
v-else-if="block.type === 'image'"
|
||||||
|
class="structured-image"
|
||||||
|
:src="block.url"
|
||||||
|
mode="widthFix"
|
||||||
|
@click="previewMessageImage(block.url)"
|
||||||
|
></image>
|
||||||
|
<view
|
||||||
|
v-else-if="block.type === 'file'"
|
||||||
|
class="structured-file"
|
||||||
|
@click="downloadMessageFile(block.url)"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
name="download"
|
||||||
|
size="28"
|
||||||
|
color="#4370fe"
|
||||||
|
></u-icon>
|
||||||
|
<text class="structured-file-name">{{
|
||||||
|
block.name
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<markdown-viewer v-else :content="message.message" />
|
<markdown-viewer v-else :content="message.message" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
@ -316,7 +428,7 @@
|
||||||
<view class="floating-tabs">
|
<view class="floating-tabs">
|
||||||
<view
|
<view
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
v-for="(tab, index) in floatingTabs"
|
v-for="(tab, index) in displayFloatingTabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleFeatureClick({ title: tab.title, path: tab.path })"
|
@click="handleFeatureClick({ title: tab.title, path: tab.path })"
|
||||||
>
|
>
|
||||||
|
|
@ -422,16 +534,17 @@ export default {
|
||||||
"我什么时候能推知道自己是否被录取?",
|
"我什么时候能推知道自己是否被录取?",
|
||||||
],
|
],
|
||||||
features: [
|
features: [
|
||||||
{
|
// {
|
||||||
title: "在线咨询",
|
// title: "在线咨询",
|
||||||
icon: "/static/common/images/icon_admissions.png",
|
// icon: "/static/common/images/icon_admissions.png",
|
||||||
path: "/pages/home/admissions/index",
|
// path: "/pages/home/admissions/index",
|
||||||
background: "linear-gradient(0deg, #F4FBFE 0%, #F4FBFE 100%)",
|
// background: "linear-gradient(0deg, #F4FBFE 0%, #F4FBFE 100%)",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "电话咨询",
|
title: "电话咨询",
|
||||||
icon: "/static/common/images/icon_phone.png",
|
tip: "欢迎致电本校招生咨询热线",
|
||||||
background: "linear-gradient(0deg, #F4FBF9 0%, #F4FBF9 100%)",
|
icon: "/static/common/images/icon-phone1.png",
|
||||||
|
background: "#fff",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
floatingTabs: [
|
floatingTabs: [
|
||||||
|
|
@ -439,11 +552,11 @@ export default {
|
||||||
title: "首页",
|
title: "首页",
|
||||||
icon: "/static/common/images/icon_home.png",
|
icon: "/static/common/images/icon_home.png",
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: "招生在线",
|
// title: "招生在线",
|
||||||
icon: "/static/common/images/icon_admissions2.png",
|
// icon: "/static/common/images/icon_admissions2.png",
|
||||||
path: "/pages/home/admissions/index",
|
// path: "/pages/home/admissions/index",
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "转人工",
|
title: "转人工",
|
||||||
icon: "/static/common/images/icon_conversation.png",
|
icon: "/static/common/images/icon_conversation.png",
|
||||||
|
|
@ -516,6 +629,10 @@ export default {
|
||||||
},
|
},
|
||||||
displayFeatures() {
|
displayFeatures() {
|
||||||
const list = Array.isArray(this.features) ? this.features : [];
|
const list = Array.isArray(this.features) ? this.features : [];
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
displayFloatingTabs() {
|
||||||
|
const list = Array.isArray(this.floatingTabs) ? this.floatingTabs : [];
|
||||||
return list.map((item) => {
|
return list.map((item) => {
|
||||||
if (item && item.title === "转人工") {
|
if (item && item.title === "转人工") {
|
||||||
return {
|
return {
|
||||||
|
|
@ -576,6 +693,18 @@ export default {
|
||||||
this.handlePopupShow();
|
this.handlePopupShow();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 处理设置点击
|
||||||
|
handleSettingClick() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/home/userSetting/index",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理记录点击
|
||||||
|
// handleRecordClick() {
|
||||||
|
// console.log("点击了记录");
|
||||||
|
// },
|
||||||
|
|
||||||
// 重置对话状态
|
// 重置对话状态
|
||||||
resetChatState({
|
resetChatState({
|
||||||
conversationId = "",
|
conversationId = "",
|
||||||
|
|
@ -658,38 +787,46 @@ export default {
|
||||||
extraOffset: 200,
|
extraOffset: 200,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 处理功能点击
|
||||||
handleFeatureClick(item) {
|
handleFeatureClick(item) {
|
||||||
if (item.title === "首页") {
|
const actions = {
|
||||||
this.resetChatState({ isChat: false });
|
首页: () => this.resetChatState({ isChat: false }),
|
||||||
return;
|
转人工: () => this.handleTransferEntryClick(),
|
||||||
}
|
结束会话: () => this.handleEndConversation(),
|
||||||
if (item.title === "转人工") {
|
电话咨询: () => {
|
||||||
this.handleTransferEntryClick();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item.title === "结束会话") {
|
|
||||||
this.$store.commit("set_IsTransferChat", false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.title === "电话咨询") {
|
|
||||||
this.advicePhoneShow = true;
|
this.advicePhoneShow = true;
|
||||||
return;
|
},
|
||||||
} else if (item.path) {
|
};
|
||||||
uni.navigateTo({
|
|
||||||
url: item.path,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.$refs.uToast.show({
|
|
||||||
title: "暂未开放",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
const action = actions[item.title];
|
||||||
|
if (action) {
|
||||||
|
action();
|
||||||
|
} else if (item.path) {
|
||||||
|
uni.navigateTo({ url: item.path });
|
||||||
|
} else {
|
||||||
|
this.$refs.uToast.show({ title: "暂未开放", type: "warning" });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 独立出结束会话逻辑
|
||||||
|
handleEndConversation() {
|
||||||
|
this.$u.api
|
||||||
|
.EndLiveAgentApi({
|
||||||
|
dialogueManagementId: this.vuex_msgUser?.dialogueManagementId,
|
||||||
|
teacherManagementId: this.vuex_msgUser?.receiverId,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.succeed) {
|
||||||
|
this.$store.commit("set_IsTransferChat", false);
|
||||||
|
this.$u.toast("会话已结束");
|
||||||
|
} else {
|
||||||
|
this.$u.toast(res.error || "会话结束失败");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建本地用户文本消息
|
||||||
createLocalUserTextMessage(messageText) {
|
createLocalUserTextMessage(messageText) {
|
||||||
return {
|
return {
|
||||||
id: Math.random().toString(36).substring(2, 15),
|
id: Math.random().toString(36).substring(2, 15),
|
||||||
|
|
@ -704,6 +841,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 创建转人工卡片消息
|
||||||
createTransferCardMessage() {
|
createTransferCardMessage() {
|
||||||
return {
|
return {
|
||||||
id: "transfer_" + Math.random().toString(36).substring(2, 15),
|
id: "transfer_" + Math.random().toString(36).substring(2, 15),
|
||||||
|
|
@ -722,6 +860,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 处理转人工点击
|
||||||
handleTransferEntryClick() {
|
handleTransferEntryClick() {
|
||||||
if (!this.isChat) {
|
if (!this.isChat) {
|
||||||
this.resetChatState();
|
this.resetChatState();
|
||||||
|
|
@ -807,22 +946,14 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dialogueManagementId 结束转人工用
|
||||||
const dialogueManagementId =
|
const dialogueManagementId =
|
||||||
this.currentDMid ||
|
(res && res.data && res.data.dialogueManagementId) || "";
|
||||||
(res &&
|
// receiverId 接线老师
|
||||||
res.data &&
|
const receiverId = (res && res.data && res.data.receiverId) || "";
|
||||||
(res.data.dialogueManagementId ||
|
|
||||||
res.data.DialogueManagementId)) ||
|
console.log(dialogueManagementId, "dialogueManagementId");
|
||||||
"";
|
console.log(receiverId, "receiverId");
|
||||||
const receiverId =
|
|
||||||
(res &&
|
|
||||||
res.data &&
|
|
||||||
(res.data.receiverId ||
|
|
||||||
res.data.ReceiverId ||
|
|
||||||
res.data.liveAgentId ||
|
|
||||||
res.data.agentId ||
|
|
||||||
res.data.userId)) ||
|
|
||||||
"";
|
|
||||||
|
|
||||||
if (!this.currentDMid && dialogueManagementId) {
|
if (!this.currentDMid && dialogueManagementId) {
|
||||||
this.currentDMid = dialogueManagementId;
|
this.currentDMid = dialogueManagementId;
|
||||||
|
|
@ -835,12 +966,17 @@ export default {
|
||||||
|
|
||||||
if (dialogueManagementId && receiverId) {
|
if (dialogueManagementId && receiverId) {
|
||||||
this.$store.commit("set_IsTransferChat", true);
|
this.$store.commit("set_IsTransferChat", true);
|
||||||
|
this.$store.commit("update_MsgUser", {
|
||||||
|
dialogueManagementId,
|
||||||
|
receiverId,
|
||||||
|
});
|
||||||
this.$store.dispatch("selectTeacherChatItem", {
|
this.$store.dispatch("selectTeacherChatItem", {
|
||||||
id: dialogueManagementId,
|
id: dialogueManagementId,
|
||||||
receiverId,
|
receiverId,
|
||||||
navigate: false,
|
navigate: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
console.log(this.vuex_msgUser, "vuex_msgUser");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
updateTransferCard({
|
updateTransferCard({
|
||||||
|
|
@ -965,10 +1101,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// 点击热门问题
|
// 点击热门问题
|
||||||
handleQAClick(item) {
|
async handleQAClick(item) {
|
||||||
// 如果不在对话模式,切换到对话模式并初始化
|
// 如果不在对话模式,切换到对话模式并初始化
|
||||||
if (!this.isChat) {
|
if (!this.isChat) {
|
||||||
this.resetChatState();
|
// this.resetChatState();
|
||||||
|
await this.handleStartChat(); // 切换到对话模式并初始化,加载历史数据
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessage = item.content;
|
const sendMessage = item.content;
|
||||||
|
|
@ -1035,12 +1172,20 @@ export default {
|
||||||
(msg) => !msg.isLoading,
|
(msg) => !msg.isLoading,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const answerTypeLabel = data.detailedExplanation; // 文字
|
||||||
|
const imageUrl = data.imageUrl; // 图片
|
||||||
|
const nearbyPaths = data.nearbyPaths; // 文件
|
||||||
|
const message = `{\r\n "answerTypeLabel": ${JSON.stringify(
|
||||||
|
answerTypeLabel || "",
|
||||||
|
)},\r\n "imageUrl": ${JSON.stringify(
|
||||||
|
imageUrl || "",
|
||||||
|
)},\r\n "nearbyPaths": ${JSON.stringify(nearbyPaths || "")}\r\n}`;
|
||||||
|
|
||||||
// 创建AI回复消息对象
|
// 创建AI回复消息对象
|
||||||
const aiMessage = {
|
const aiMessage = {
|
||||||
id:
|
id:
|
||||||
data.conversationId ||
|
this.currentDMid || Math.random().toString(36).substring(2, 15),
|
||||||
Math.random().toString(36).substring(2, 15),
|
message: message,
|
||||||
message: data.detailedExplanation,
|
|
||||||
sendDate: "",
|
sendDate: "",
|
||||||
isSend: true,
|
isSend: true,
|
||||||
isRead: false,
|
isRead: false,
|
||||||
|
|
@ -1108,6 +1253,107 @@ export default {
|
||||||
this.handleGetConversationDetail();
|
this.handleGetConversationDetail();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 解析结构化消息块
|
||||||
|
getStructuredMessageBlocks(message) {
|
||||||
|
const rawMessage =
|
||||||
|
(message && (message.rawMessage || message.message)) || "";
|
||||||
|
if (!rawMessage || typeof rawMessage !== "string") return [];
|
||||||
|
|
||||||
|
let parsed = null;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(rawMessage);
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!parsed || typeof parsed !== "object") return [];
|
||||||
|
|
||||||
|
const answerTypeLabel = (parsed.answerTypeLabel || "").trim();
|
||||||
|
const imageUrl = parsed.imageUrl || "";
|
||||||
|
const nearbyPaths = parsed.nearbyPaths || "";
|
||||||
|
|
||||||
|
if (!answerTypeLabel && !imageUrl && !nearbyPaths) return [];
|
||||||
|
|
||||||
|
const blocks = [];
|
||||||
|
if (answerTypeLabel) {
|
||||||
|
blocks.push({ type: "text", value: answerTypeLabel });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.normalizeResourceList(imageUrl).forEach((item) => {
|
||||||
|
const url = this.formatMessageResourceUrl(item);
|
||||||
|
if (url) blocks.push({ type: "image", url });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.normalizeResourceList(nearbyPaths).forEach((item) => {
|
||||||
|
const url = this.formatMessageResourceUrl(item);
|
||||||
|
if (!url) return;
|
||||||
|
blocks.push({
|
||||||
|
type: "file",
|
||||||
|
url,
|
||||||
|
name: this.getFileNameFromPath(item),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
},
|
||||||
|
normalizeResourceList(value) {
|
||||||
|
if (!value) return [];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
.split(/[\n,|]/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
formatMessageResourceUrl(path) {
|
||||||
|
if (!path) return "";
|
||||||
|
const currentPath = String(path).trim();
|
||||||
|
if (!currentPath) return "";
|
||||||
|
if (
|
||||||
|
currentPath.startsWith("http://") ||
|
||||||
|
currentPath.startsWith("https://") ||
|
||||||
|
currentPath.startsWith("blob:")
|
||||||
|
) {
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
const cleanPath = currentPath.replace(/\\/g, "/");
|
||||||
|
const cleanBaseUrl = (this.baseUrl || "")
|
||||||
|
.replace(/\\/g, "/")
|
||||||
|
.replace(/\/$/, "");
|
||||||
|
if (!cleanBaseUrl) return cleanPath;
|
||||||
|
if (cleanPath.startsWith("/")) return `${cleanBaseUrl}${cleanPath}`;
|
||||||
|
return `${cleanBaseUrl}/${cleanPath}`;
|
||||||
|
},
|
||||||
|
getFileNameFromPath(path) {
|
||||||
|
const cleanPath = String(path || "")
|
||||||
|
.replace(/\\/g, "/")
|
||||||
|
.split("?")[0];
|
||||||
|
const segments = cleanPath.split("/");
|
||||||
|
const fileName = segments[segments.length - 1] || "下载文件";
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(fileName);
|
||||||
|
} catch (error) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
previewMessageImage(url) {
|
||||||
|
if (!url) return;
|
||||||
|
uni.previewImage({
|
||||||
|
current: url,
|
||||||
|
urls: [url],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
downloadMessageFile(url) {
|
||||||
|
if (!url) return;
|
||||||
|
if (typeof window !== "undefined" && window.open) {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.downloadFile({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 处理消息内容,如果是JSON格式则解析并格式化
|
// 处理消息内容,如果是JSON格式则解析并格式化
|
||||||
processMessageContent(message) {
|
processMessageContent(message) {
|
||||||
return processChatMessageContent(message);
|
return processChatMessageContent(message);
|
||||||
|
|
@ -1120,11 +1366,11 @@ export default {
|
||||||
|
|
||||||
// 刷新当前对话的消息详情
|
// 刷新当前对话的消息详情
|
||||||
handleGetConversationDetail() {
|
handleGetConversationDetail() {
|
||||||
if (!this.currentDMid) return;
|
// if (!this.currentDMid) return;
|
||||||
|
|
||||||
this.$u.api
|
return this.$u.api
|
||||||
.GetConversationDetail({
|
.GetConversationDetail({
|
||||||
"Item1.Id": this.currentDMid,
|
// "Item1.Id": "08de2e31-4cdf-4b0c-8b83-d3185f604a5a",
|
||||||
PageIndex: this.pageQuery.PageIndex,
|
PageIndex: this.pageQuery.PageIndex,
|
||||||
PageSize: this.pageQuery.PageSize,
|
PageSize: this.pageQuery.PageSize,
|
||||||
})
|
})
|
||||||
|
|
@ -1145,14 +1391,14 @@ export default {
|
||||||
|
|
||||||
// 刷新当前页数据;若当前页为空且页码>1,则自动回退上一页
|
// 刷新当前页数据;若当前页为空且页码>1,则自动回退上一页
|
||||||
refreshPageWithFallback() {
|
refreshPageWithFallback() {
|
||||||
if (!this.currentDMid) return;
|
// if (!this.currentDMid) return;
|
||||||
|
|
||||||
const currentIndex = this.pageQuery.PageIndex || 1;
|
const currentIndex = this.pageQuery.PageIndex || 1;
|
||||||
const pageSize = this.pageQuery.PageSize;
|
const pageSize = this.pageQuery.PageSize;
|
||||||
|
|
||||||
return this.$u.api
|
return this.$u.api
|
||||||
.GetConversationDetail({
|
.GetConversationDetail({
|
||||||
"Item1.Id": this.currentDMid,
|
// "Item1.Id": this.currentDMid,
|
||||||
PageIndex: currentIndex,
|
PageIndex: currentIndex,
|
||||||
PageSize: pageSize,
|
PageSize: pageSize,
|
||||||
})
|
})
|
||||||
|
|
@ -1162,7 +1408,7 @@ export default {
|
||||||
const prevIndex = currentIndex - 1;
|
const prevIndex = currentIndex - 1;
|
||||||
return this.$u.api
|
return this.$u.api
|
||||||
.GetConversationDetail({
|
.GetConversationDetail({
|
||||||
"Item1.Id": this.currentDMid,
|
// "Item1.Id": this.currentDMid,
|
||||||
PageIndex: prevIndex,
|
PageIndex: prevIndex,
|
||||||
PageSize: pageSize,
|
PageSize: pageSize,
|
||||||
})
|
})
|
||||||
|
|
@ -1193,6 +1439,9 @@ export default {
|
||||||
// 开始新对话
|
// 开始新对话
|
||||||
handleStartChat() {
|
handleStartChat() {
|
||||||
this.resetChatState();
|
this.resetChatState();
|
||||||
|
|
||||||
|
// 获取历史对话
|
||||||
|
return this.handleGetConversationDetail();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 滚动到底部事件处理
|
// 滚动到底部事件处理
|
||||||
|
|
@ -1219,7 +1468,7 @@ export default {
|
||||||
this.pageQuery.PageIndex++;
|
this.pageQuery.PageIndex++;
|
||||||
this.$u.api
|
this.$u.api
|
||||||
.GetConversationDetail({
|
.GetConversationDetail({
|
||||||
"Item1.Id": this.currentDMid,
|
// "Item1.Id": this.currentDMid,
|
||||||
PageIndex: this.pageQuery.PageIndex,
|
PageIndex: this.pageQuery.PageIndex,
|
||||||
PageSize: this.pageQuery.PageSize,
|
PageSize: this.pageQuery.PageSize,
|
||||||
})
|
})
|
||||||
|
|
@ -1252,7 +1501,14 @@ export default {
|
||||||
// 回答反馈:点赞/点踩(统一调用刷新&上一页回退逻辑)
|
// 回答反馈:点赞/点踩(统一调用刷新&上一页回退逻辑)
|
||||||
handleFeedback(message, isHelp) {
|
handleFeedback(message, isHelp) {
|
||||||
this.$u.api.ModifyStatus({ id: message.id, isHelp }).then((res) => {
|
this.$u.api.ModifyStatus({ id: message.id, isHelp }).then((res) => {
|
||||||
if (!res.succeed) return;
|
if (!res.succeed) {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.error || "操作失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$u.toast("操作成功");
|
this.$u.toast("操作成功");
|
||||||
// 刷新当前页;若空则自动回退上一页并刷新
|
// 刷新当前页;若空则自动回退上一页并刷新
|
||||||
this.refreshPageWithFallback();
|
this.refreshPageWithFallback();
|
||||||
|
|
@ -1326,6 +1582,11 @@ export default {
|
||||||
|
|
||||||
/* Header样式移至HeaderBar组件 */
|
/* Header样式移至HeaderBar组件 */
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
padding-top: 60rpx;
|
padding-top: 60rpx;
|
||||||
|
|
@ -1464,29 +1725,59 @@ export default {
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 32rpx;
|
margin-top: 32rpx;
|
||||||
gap: 30rpx;
|
gap: 30rpx;
|
||||||
|
|
||||||
.feature-item {
|
.feature-item {
|
||||||
height: 150rpx;
|
height: 140rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 30rpx;
|
// padding-left: 30rpx;
|
||||||
gap: 20rpx;
|
gap: 30rpx;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.feature-icon {
|
.feature-icon {
|
||||||
width: 80rpx;
|
width: 80rpx;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
margin-top: 16rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-text {
|
.feature-text-wrapper {
|
||||||
font-size: 26rpx;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12rpx;
|
||||||
|
flex: 1;
|
||||||
|
.feature-title {
|
||||||
|
font-size: 28rpx;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.feature-tip {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 14rpx 20rpx 14rpx 24rpx;
|
||||||
|
gap: 10rpx;
|
||||||
|
// height: 56rpx;
|
||||||
|
background: linear-gradient(-33deg, #6079ff 0%, #418ded 100%);
|
||||||
|
box-shadow: 0px 4rpx 16rpx 0rpx rgba(62, 106, 255, 0.6);
|
||||||
|
border-radius: 28rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1604,8 +1895,14 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-left {
|
.message-left {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.message-content-container-AI,
|
||||||
|
.message-content-container-Teacher {
|
||||||
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap; /* 允许反馈区换行到消息下方 */
|
flex-wrap: wrap; /* 允许反馈区换行到消息下方 */
|
||||||
|
}
|
||||||
|
|
||||||
.ai-avatar {
|
.ai-avatar {
|
||||||
width: 64rpx;
|
width: 64rpx;
|
||||||
|
|
@ -1624,6 +1921,12 @@ export default {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
|
&.message-content-structured {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 加载动画样式 */
|
/* 加载动画样式 */
|
||||||
.loading-dots {
|
.loading-dots {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1696,15 +1999,88 @@ export default {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.structured-message {
|
||||||
|
.structured-item + .structured-item {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-item {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-item-text {
|
||||||
|
.structured-text {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
border-radius: 0 16rpx 16rpx 16rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-text {
|
||||||
|
display: inline-block;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-item-image {
|
||||||
|
.structured-image {
|
||||||
|
display: block;
|
||||||
|
max-width: 240rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-image {
|
||||||
|
display: block;
|
||||||
|
max-width: 240rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-item-file {
|
||||||
|
.structured-file {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-file {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structured-file-name {
|
||||||
|
margin-left: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4370fe;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content-width {
|
.message-content-width {
|
||||||
width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
/* 回答反馈容器,跟随左侧消息,右下角对齐 */
|
/* 回答反馈容器,跟随左侧消息,右下角对齐 */
|
||||||
.feedback-container {
|
.feedback-container {
|
||||||
width: 70%;
|
max-width: 70%;
|
||||||
margin-left: 80rpx; /* 头像宽度64rpx + 间距16rpx */
|
margin-left: 80rpx; /* 头像宽度64rpx + 间距16rpx */
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 留言板页面 —— 此页面已废弃 -->
|
||||||
<view class="message-board-page">
|
<view class="message-board-page">
|
||||||
<header-bar title="留言板" @leftClick="handleLeftClick"></header-bar>
|
<header-bar title="留言板" @leftClick="handleLeftClick"></header-bar>
|
||||||
<view class="custom-tabs-box">
|
<view class="custom-tabs-box">
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,7 @@ export default {
|
||||||
// 获取用户信息后再跳转,确保数据加载完成
|
// 获取用户信息后再跳转,确保数据加载完成
|
||||||
const redirect = () => {
|
const redirect = () => {
|
||||||
const url = this.isTeacher
|
const url = this.isTeacher
|
||||||
? "/pages/consultation/index"
|
? "/pages/transfer/index"
|
||||||
: "/pages/home/index/index";
|
: "/pages/home/index/index";
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: url,
|
url: url,
|
||||||
|
|
@ -821,4 +821,3 @@ export default {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
<template>
|
||||||
|
<view class="change-password-page">
|
||||||
|
<PageHeader
|
||||||
|
title="修改密码"
|
||||||
|
:is-back="true"
|
||||||
|
:border-bottom="false"
|
||||||
|
:background="headerBackground"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<view class="content-wrapper">
|
||||||
|
<view class="form-card single-row-card">
|
||||||
|
<view class="form-row">
|
||||||
|
<text class="label">手机号</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
v-model="form.phone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
placeholder-style="color: #c0c0c0;"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-card single-row-card">
|
||||||
|
<view class="form-row captcha-row">
|
||||||
|
<text class="label">图形验证码</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
v-model="form.captcha"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
placeholder-style="color: #c0c0c0;"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
class="captcha-image"
|
||||||
|
:src="captchaUrl"
|
||||||
|
mode="aspectFill"
|
||||||
|
@click="refreshCaptcha"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-card single-row-card">
|
||||||
|
<view class="form-row">
|
||||||
|
<text class="label">验证码</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
v-model="form.code"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
placeholder-style="color: #c0c0c0;"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
class="get-code"
|
||||||
|
:class="{ disabled: codeText !== '获取验证码' }"
|
||||||
|
@click="handleGetCode"
|
||||||
|
>
|
||||||
|
{{ codeText }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-card">
|
||||||
|
<view class="form-row">
|
||||||
|
<text class="label">新密码</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
:password="!showPassword"
|
||||||
|
v-model="form.pwd"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
placeholder-style="color: #c0c0c0;"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="form-row">
|
||||||
|
<text class="label">确认新密码</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
:password="!showPassword"
|
||||||
|
v-model="form.confirmPwd"
|
||||||
|
placeholder="请再次输入新密码"
|
||||||
|
placeholder-style="color: #c0c0c0;"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="submit-button" @click="handleSubmit">确定</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<u-toast ref="uToast" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PageHeader from "@/components/PageHeader.vue";
|
||||||
|
import { generateSign } from "@/utils/signUtil.js";
|
||||||
|
import md5 from "js-md5";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ChangePassword",
|
||||||
|
components: {
|
||||||
|
PageHeader,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
headerBackground: {
|
||||||
|
background: "transparent",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
phone: "",
|
||||||
|
code: "",
|
||||||
|
captcha: "",
|
||||||
|
pwd: "",
|
||||||
|
confirmPwd: "",
|
||||||
|
},
|
||||||
|
showPassword: false,
|
||||||
|
codeText: "获取验证码",
|
||||||
|
countdown: 60,
|
||||||
|
timer: null,
|
||||||
|
captchaId: "",
|
||||||
|
captchaUrl: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const userInfo = this.vuex_user || {};
|
||||||
|
const phone = userInfo.phone || userInfo.Phone || "";
|
||||||
|
if (phone) {
|
||||||
|
this.form.phone = phone;
|
||||||
|
}
|
||||||
|
this.refreshCaptcha();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refreshCaptcha() {
|
||||||
|
this.$u.api.GetCaptcha().then((res) => {
|
||||||
|
this.captchaId = res.captchaId || "";
|
||||||
|
this.captchaUrl = res.imageBase64
|
||||||
|
? `data:image/png;base64,${res.imageBase64}`
|
||||||
|
: "";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleGetCode() {
|
||||||
|
if (this.codeText !== "获取验证码") return;
|
||||||
|
if (!this.validatePhone()) return;
|
||||||
|
if (!this.validateCaptcha()) return;
|
||||||
|
this.requestSmsCode();
|
||||||
|
},
|
||||||
|
requestSmsCode() {
|
||||||
|
const sign = generateSign(this.form.phone);
|
||||||
|
const params = {
|
||||||
|
phone: this.form.phone,
|
||||||
|
sign,
|
||||||
|
captchaId: this.captchaId,
|
||||||
|
captchaCode: this.form.captcha,
|
||||||
|
ip: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$u.api.RequestForgotPasswordSMSCode(params).then((res) => {
|
||||||
|
if (res && res.succeed) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "发送成功",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
this.startCountdown();
|
||||||
|
} else {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: res?.error || "发送失败",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
this.refreshCaptcha();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
startCountdown() {
|
||||||
|
this.countdown = 60;
|
||||||
|
this.codeText = `${this.countdown}秒后重试`;
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.countdown -= 1;
|
||||||
|
this.codeText = `${this.countdown}秒后重试`;
|
||||||
|
if (this.countdown <= 0) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.codeText = "获取验证码";
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
validatePhone() {
|
||||||
|
if (!this.form.phone) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "请输入手机号",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(this.form.phone)) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "手机号格式不正确",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
validateCaptcha() {
|
||||||
|
if (!this.form.captcha) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "请输入图形验证码",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
validateForm() {
|
||||||
|
if (!this.validatePhone()) return false;
|
||||||
|
if (!this.validateCaptcha()) return false;
|
||||||
|
if (!this.form.code) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "请输入验证码",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.form.pwd) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "请输入新密码",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.form.confirmPwd) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "请再次输入新密码",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.form.pwd !== this.form.confirmPwd) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "两次密码输入不一致",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
handleSubmit() {
|
||||||
|
if (!this.validateForm()) return;
|
||||||
|
const params = {
|
||||||
|
phone: this.form.phone,
|
||||||
|
code: this.form.code,
|
||||||
|
pwd: md5(this.form.pwd),
|
||||||
|
};
|
||||||
|
this.$u.api.ForgotPasswordChangePassword(params).then((res) => {
|
||||||
|
if (res && res.succeed) {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: "修改成功",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 800);
|
||||||
|
} else {
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
title: res?.error || "修改失败",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.change-password-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-image: url("@/static/notes/bg.png");
|
||||||
|
background-position: center top;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
padding: 30rpx;
|
||||||
|
padding-top: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 0;
|
||||||
|
/* border-bottom: 1rpx solid #f1f1f1; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row.captcha-row {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 150rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 27rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-code {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4f6aff;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
border-left: 1rpx solid #e5e5e5;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-code.disabled {
|
||||||
|
color: #b8b8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-image {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
margin-top: 30rpx;
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
background-color: #4f6aff;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-row-card .form-row {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-row .input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -123,9 +123,8 @@ export default {
|
||||||
url: '/pages/my/personalInfo'
|
url: '/pages/my/personalInfo'
|
||||||
});
|
});
|
||||||
} else if (route === 'change-password') {
|
} else if (route === 'change-password') {
|
||||||
uni.showToast({
|
uni.navigateTo({
|
||||||
title: '功能开发中',
|
url: '/pages/my/change-password'
|
||||||
icon: 'none'
|
|
||||||
});
|
});
|
||||||
} else if (route === 'logout-records') {
|
} else if (route === 'logout-records') {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,18 @@
|
||||||
<view class="content-wrapper">
|
<view class="content-wrapper">
|
||||||
<view class="form-container">
|
<view class="form-container">
|
||||||
<view class="form-card calendar-card">
|
<view class="form-card calendar-card">
|
||||||
<view class="card-title">{{ monthTitle }}</view>
|
<view class="calendar-header">
|
||||||
|
<text class="month-action" @click="handlePrevMonth">‹</text>
|
||||||
|
<picker
|
||||||
|
mode="date"
|
||||||
|
fields="month"
|
||||||
|
:value="monthValue"
|
||||||
|
@change="handleMonthPickerChange"
|
||||||
|
>
|
||||||
|
<view class="card-title month-title">{{ monthTitle }}</view>
|
||||||
|
</picker>
|
||||||
|
<text class="month-action" @click="handleNextMonth">›</text>
|
||||||
|
</view>
|
||||||
<view class="week-row">
|
<view class="week-row">
|
||||||
<text
|
<text
|
||||||
v-for="(label, index) in weekLabels"
|
v-for="(label, index) in weekLabels"
|
||||||
|
|
@ -82,6 +93,9 @@ export default {
|
||||||
monthTitle() {
|
monthTitle() {
|
||||||
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月`;
|
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月`;
|
||||||
},
|
},
|
||||||
|
monthValue() {
|
||||||
|
return `${this.currentYear}-${String(this.currentMonth).padStart(2, "0")}`;
|
||||||
|
},
|
||||||
selectedDateTitle() {
|
selectedDateTitle() {
|
||||||
const dayText = String(this.selectedDay).padStart(2, "0");
|
const dayText = String(this.selectedDay).padStart(2, "0");
|
||||||
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月${dayText}日`;
|
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月${dayText}日`;
|
||||||
|
|
@ -97,16 +111,21 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchSchedule() {
|
fetchSchedule(
|
||||||
|
year = this.currentYear,
|
||||||
|
month = this.currentMonth,
|
||||||
|
day = this.selectedDay
|
||||||
|
) {
|
||||||
const userInfo = this.vuex_user || {};
|
const userInfo = this.vuex_user || {};
|
||||||
const teacherId = userInfo.id || userInfo.Id;
|
const teacherId = userInfo.id || userInfo.Id;
|
||||||
if (!teacherId) {
|
if (!teacherId) {
|
||||||
this.$u.toast("缺少教师ID");
|
this.$u.toast("缺少教师ID");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const scheduleDate = new Date(year, month - 1, day, 12, 0, 0);
|
||||||
this.$u.api
|
this.$u.api
|
||||||
.getShiftSchedulingData({
|
.getShiftSchedulingData({
|
||||||
SchedulingTime: new Date().toISOString(),
|
SchedulingTime: scheduleDate.toISOString(),
|
||||||
TeacherManagementId: teacherId,
|
TeacherManagementId: teacherId,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
|
@ -177,6 +196,40 @@ export default {
|
||||||
this.scheduleList
|
this.scheduleList
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
handlePrevMonth() {
|
||||||
|
this.changeMonth(-1);
|
||||||
|
},
|
||||||
|
handleNextMonth() {
|
||||||
|
this.changeMonth(1);
|
||||||
|
},
|
||||||
|
handleMonthPickerChange(event) {
|
||||||
|
const value = event?.detail?.value || "";
|
||||||
|
const [yearText, monthText] = value.split("-");
|
||||||
|
const year = Number(yearText);
|
||||||
|
const month = Number(monthText);
|
||||||
|
if (!year || !month) return;
|
||||||
|
this.updateMonth(year, month);
|
||||||
|
},
|
||||||
|
changeMonth(offset) {
|
||||||
|
const date = new Date(this.currentYear, this.currentMonth - 1 + offset, 1);
|
||||||
|
this.updateMonth(date.getFullYear(), date.getMonth() + 1);
|
||||||
|
},
|
||||||
|
updateMonth(year, month) {
|
||||||
|
this.currentYear = year;
|
||||||
|
this.currentMonth = month;
|
||||||
|
const today = new Date();
|
||||||
|
if (today.getFullYear() === year && today.getMonth() + 1 === month) {
|
||||||
|
this.selectedDay = today.getDate();
|
||||||
|
} else {
|
||||||
|
this.selectedDay = 1;
|
||||||
|
}
|
||||||
|
this.calendarDays = this.buildCalendarDays(
|
||||||
|
this.currentYear,
|
||||||
|
this.currentMonth,
|
||||||
|
this.scheduleList
|
||||||
|
);
|
||||||
|
this.fetchSchedule(year, month, this.selectedDay);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -213,11 +266,33 @@ export default {
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
margin-bottom: 20rpx;
|
}
|
||||||
|
|
||||||
|
.month-title {
|
||||||
|
padding: 6rpx 18rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #f5f6ff;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 200rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-action {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #4f6aff;
|
||||||
|
width: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.week-row {
|
.week-row {
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ export default {
|
||||||
|
|
||||||
// 本地服务器测试
|
// 本地服务器测试
|
||||||
testLocalServer() {
|
testLocalServer() {
|
||||||
this.wsUrl = 'ws://localhost:8082/ws/chat?token=test-token'
|
this.wsUrl = 'ws://localhost:8073/ws/chat?token=test-token'
|
||||||
this.messageToSend = JSON.stringify({
|
this.messageToSend = JSON.stringify({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
fromUserId: 'test_user',
|
fromUserId: 'test_user',
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ export default {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
const res = await this.$u.api.GetDialogueListApi({
|
const res = await this.$u.api.GetDialogueListApi({
|
||||||
"Item1.OnlineConsultationType": 2,
|
"Item1.OnlineConsultationType": 1,
|
||||||
});
|
});
|
||||||
const list = (res && res.data && res.data.item1) || [];
|
const list = (res && res.data && res.data.item1) || [];
|
||||||
this.chatList = this.normalizeDialogueList(list);
|
this.chatList = this.normalizeDialogueList(list);
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 872 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -149,6 +149,14 @@ const store = new Vuex.Store({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 更新当前聊天用户的部分属性
|
||||||
|
update_MsgUser(state, payload) {
|
||||||
|
if (state.vuex_msgUser) {
|
||||||
|
state.vuex_msgUser = { ...state.vuex_msgUser, ...payload };
|
||||||
|
} else {
|
||||||
|
state.vuex_msgUser = payload;
|
||||||
|
}
|
||||||
|
},
|
||||||
// 现在没有id,先覆盖整个list
|
// 现在没有id,先覆盖整个list
|
||||||
push_MsgList(state, list) {
|
push_MsgList(state, list) {
|
||||||
state.vuex_msgList = list || [];
|
state.vuex_msgList = list || [];
|
||||||
|
|
@ -173,7 +181,7 @@ const store = new Vuex.Store({
|
||||||
// 注:现在的消息没有id,无法去重,暂时用push_MsgList
|
// 注:现在的消息没有id,无法去重,暂时用push_MsgList
|
||||||
if (!msg || !msg.id) return;
|
if (!msg || !msg.id) return;
|
||||||
const exists = (state.vuex_msgList || []).some(
|
const exists = (state.vuex_msgList || []).some(
|
||||||
(item) => item && item.id === msg.id
|
(item) => item && item.id === msg.id,
|
||||||
);
|
);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
state.vuex_msgList.push(msg);
|
state.vuex_msgList.push(msg);
|
||||||
|
|
@ -198,15 +206,14 @@ const store = new Vuex.Store({
|
||||||
if (!msgId) return;
|
if (!msgId) return;
|
||||||
|
|
||||||
const exists = (state.vuex_aiMessageGroups || []).some(
|
const exists = (state.vuex_aiMessageGroups || []).some(
|
||||||
(item) => item && item.id === msgId
|
(item) => item && item.id === msgId,
|
||||||
);
|
);
|
||||||
if (exists) return;
|
if (exists) return;
|
||||||
|
|
||||||
const userId =
|
const userId =
|
||||||
(state.vuex_user && (state.vuex_user.Id || state.vuex_user.id)) || "";
|
(state.vuex_user && (state.vuex_user.Id || state.vuex_user.id)) || "";
|
||||||
const senderId = msg.senderId || msg.SenderId || "";
|
const senderId = msg.senderId || msg.SenderId || "";
|
||||||
const isSelf =
|
const isSelf = userId && senderId && String(userId) === String(senderId);
|
||||||
userId && senderId && String(userId) === String(senderId);
|
|
||||||
|
|
||||||
state.vuex_aiMessageGroups.push({
|
state.vuex_aiMessageGroups.push({
|
||||||
id: msgId,
|
id: msgId,
|
||||||
|
|
@ -243,7 +250,7 @@ const store = new Vuex.Store({
|
||||||
push_AiMsg(state, msg) {
|
push_AiMsg(state, msg) {
|
||||||
if (!msg || !msg.id) return;
|
if (!msg || !msg.id) return;
|
||||||
const exists = (state.vuex_aiMessageGroups || []).some(
|
const exists = (state.vuex_aiMessageGroups || []).some(
|
||||||
(item) => item && item.id === msg.id
|
(item) => item && item.id === msg.id,
|
||||||
);
|
);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
state.vuex_aiMessageGroups.push(msg);
|
state.vuex_aiMessageGroups.push(msg);
|
||||||
|
|
@ -266,7 +273,7 @@ const store = new Vuex.Store({
|
||||||
// WebSocket 实时消息:更新会话列表的未读数、文案与时间
|
// WebSocket 实时消息:更新会话列表的未读数、文案与时间
|
||||||
apply_RealtimeMessageToList(
|
apply_RealtimeMessageToList(
|
||||||
state,
|
state,
|
||||||
{ dialogueId, message, sendDate, senderId, receiverId }
|
{ dialogueId, message, sendDate, senderId, receiverId },
|
||||||
) {
|
) {
|
||||||
if (!dialogueId) return;
|
if (!dialogueId) return;
|
||||||
const activeId =
|
const activeId =
|
||||||
|
|
@ -286,10 +293,7 @@ const store = new Vuex.Store({
|
||||||
found = true;
|
found = true;
|
||||||
const currentUnread =
|
const currentUnread =
|
||||||
Number(
|
Number(
|
||||||
item?.unReadCount ??
|
item?.unReadCount ?? item?.unreadCount ?? item?.unread ?? 0,
|
||||||
item?.unreadCount ??
|
|
||||||
item?.unread ??
|
|
||||||
0
|
|
||||||
) || 0;
|
) || 0;
|
||||||
const nextUnread = isActive ? 0 : currentUnread + 1;
|
const nextUnread = isActive ? 0 : currentUnread + 1;
|
||||||
return {
|
return {
|
||||||
|
|
@ -386,7 +390,7 @@ const store = new Vuex.Store({
|
||||||
// avatar: item.avatar,
|
// avatar: item.avatar,
|
||||||
unReadCount,
|
unReadCount,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
commit("set_UserMsgList", list);
|
commit("set_UserMsgList", list);
|
||||||
return list;
|
return list;
|
||||||
|
|
@ -403,7 +407,7 @@ const store = new Vuex.Store({
|
||||||
// 获取聊天记录(私聊)——仅在进入聊天页时加载一次
|
// 获取聊天记录(私聊)——仅在进入聊天页时加载一次
|
||||||
async fetchChatRecord(
|
async fetchChatRecord(
|
||||||
{ commit },
|
{ commit },
|
||||||
{ dialogueManagementId, PageIndex = 1, PageSize = 20 }
|
{ dialogueManagementId, PageIndex = 1, PageSize = 20 },
|
||||||
) {
|
) {
|
||||||
return Vue.prototype.$u.api
|
return Vue.prototype.$u.api
|
||||||
.GetChatHistoryDataApi({
|
.GetChatHistoryDataApi({
|
||||||
|
|
@ -412,11 +416,13 @@ const store = new Vuex.Store({
|
||||||
PageSize,
|
PageSize,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const list =
|
const list = (
|
||||||
(res && res.data && Array.isArray(res.data.item1)
|
res && res.data && Array.isArray(res.data.item1)
|
||||||
? res.data.item1
|
? res.data.item1
|
||||||
: []
|
: []
|
||||||
).slice().reverse();
|
)
|
||||||
|
.slice()
|
||||||
|
.reverse();
|
||||||
commit("push_MsgList", list);
|
commit("push_MsgList", list);
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
|
|
@ -425,7 +431,7 @@ const store = new Vuex.Store({
|
||||||
// 获取下一页历史消息(滚动到顶部触发)
|
// 获取下一页历史消息(滚动到顶部触发)
|
||||||
async fetchChatRecordNextPage(
|
async fetchChatRecordNextPage(
|
||||||
{ commit },
|
{ commit },
|
||||||
{ dialogueManagementId, PageIndex = 1, PageSize = 20 }
|
{ dialogueManagementId, PageIndex = 1, PageSize = 20 },
|
||||||
) {
|
) {
|
||||||
return Vue.prototype.$u.api
|
return Vue.prototype.$u.api
|
||||||
.GetChatHistoryDataApi({
|
.GetChatHistoryDataApi({
|
||||||
|
|
@ -434,11 +440,13 @@ const store = new Vuex.Store({
|
||||||
PageSize,
|
PageSize,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const list =
|
const list = (
|
||||||
(res && res.data && Array.isArray(res.data.item1)
|
res && res.data && Array.isArray(res.data.item1)
|
||||||
? res.data.item1
|
? res.data.item1
|
||||||
: []
|
: []
|
||||||
).slice().reverse();
|
)
|
||||||
|
.slice()
|
||||||
|
.reverse();
|
||||||
if (!list.length) return [];
|
if (!list.length) return [];
|
||||||
commit("prepend_MsgList", list);
|
commit("prepend_MsgList", list);
|
||||||
return list;
|
return list;
|
||||||
|
|
@ -462,7 +470,7 @@ const store = new Vuex.Store({
|
||||||
// 点击聊天记录,切换到该会话
|
// 点击聊天记录,切换到该会话
|
||||||
selectTeacherChatItem(
|
selectTeacherChatItem(
|
||||||
{ commit, dispatch },
|
{ commit, dispatch },
|
||||||
{ id, receiverId, navigate = true } = {}
|
{ id, receiverId, navigate = true } = {},
|
||||||
) {
|
) {
|
||||||
if (!id || !receiverId) return;
|
if (!id || !receiverId) return;
|
||||||
// 清空消息列表,避免旧消息干扰
|
// 清空消息列表,避免旧消息干扰
|
||||||
|
|
@ -472,7 +480,7 @@ const store = new Vuex.Store({
|
||||||
.GetReceiverUserInfoApi({ Id: receiverId })
|
.GetReceiverUserInfoApi({ Id: receiverId })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.succeed && res.data) {
|
if (res.succeed && res.data) {
|
||||||
commit("set_MsgUser", { ...res.data, dialogueManagementId: id });
|
commit("update_MsgUser", { ...res.data, dialogueManagementId: id });
|
||||||
if (navigate) {
|
if (navigate) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/chat/index`,
|
url: `/pages/chat/index`,
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ export function processChatMessageContent(message) {
|
||||||
export function sortChatMessages(list = []) {
|
export function sortChatMessages(list = []) {
|
||||||
const processedList = (list || []).map((item) => ({
|
const processedList = (list || []).map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
|
rawMessage: item && item.message,
|
||||||
message: processChatMessageContent(item && item.message),
|
message: processChatMessageContent(item && item.message),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue