Compare commits

..

No commits in common. "main" and "main-1.0终版(可以新建对话)" have entirely different histories.

21 changed files with 215 additions and 1143 deletions

View File

@ -109,7 +109,7 @@ export default {
window.location.protocol.indexOf("https") === 0 ? "wss" : "ws";
const Id =
(this.vuex_user && (this.vuex_user.id || this.vuex_user.Id)) || "";
return `${protocol}://120.55.234.65:8073/api/Dialogue/HandleConnection?Id=${Id}`; // &equipmentType=0
return `${protocol}://120.55.234.65:8082/api/Dialogue/HandleConnection?Id=${Id}`; // &equipmentType=0
},
// WebSocket
initWebSocket() {

View File

@ -208,12 +208,6 @@ const install = (Vue, vm) => {
// 验证码登录-教师
let TeacherLoginByCode = (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);
/** 用户-个人中心 */
// 获取用户信息
@ -257,9 +251,6 @@ const install = (Vue, vm) => {
// 转人工服务
let TransferToALiveAgentApi = (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 = {
@ -318,8 +309,6 @@ const install = (Vue, vm) => {
GetTeacherVerifyCode,
TeacherLogin,
TeacherLoginByCode,
RequestForgotPasswordSMSCode,
ForgotPasswordChangePassword,
GetUserApi,
UpdateUserApi,
GetTeacherListApi,
@ -333,7 +322,6 @@ const install = (Vue, vm) => {
ReadMessageApi,
DeleteDialogueApi,
TransferToALiveAgentApi,
EndLiveAgentApi,
};
};

View File

@ -4,8 +4,8 @@ const install = (Vue, vm) => {
Vue.prototype.$u.http.setConfig({
// baseUrl: 'https://xy.apps.service.zheke.com',
// imgUrl: 'https://xy.apps.service.zheke.com/',
baseUrl: "http://120.55.234.65:8073",
imgUrl: "http://120.55.234.65:8073/",
baseUrl: "http://120.55.234.65:8082",
imgUrl: "http://120.55.234.65:8082/",
// imgUrl:'http://115.238.47.235:8987/',
// baseUrl: 'http://115.238.47.235:8993',
// 如果将此值设置为true拦截回调中将会返回服务端返回的所有数据response而不是response.data

View File

@ -10,31 +10,11 @@
>
<view class="phone-popup">
<view class="phone-title">招生电话</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-content">0790-6764666/6765666</view>
<view class="phone-button">
<view class="cancel-btn" @click="closePopup">取消</view>
<u-button class="cancel-button" type="default" @click="closePopup"
>取消</u-button
>
</view>
</view>
</u-popup>
@ -68,11 +48,6 @@ export default {
},
},
methods: {
makeCall(phoneNumber) {
uni.makePhoneCall({
phoneNumber,
});
},
closePopup() {
this.showPopup = false;
this.$emit("update:show", false);
@ -91,6 +66,7 @@ export default {
background-size: 630rpx 100rpx;
background-position: -20rpx 0;
.phone-title {
text-align: center;
font-family: DouyinSans;
@ -100,72 +76,19 @@ export default {
margin-bottom: 40rpx;
}
.phone-card-list {
padding: 0 48rpx;
margin-bottom: 60rpx;
.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;
letter-spacing: 2rpx;
margin-bottom: 10rpx;
}
.phone-desc {
font-size: 24rpx;
color: #999999;
}
}
}
.phone-content {
margin-bottom: 120rpx;
text-align: center;
font-family: PingFang SC;
font-size: 32rpx;
color: #333333;
}
.phone-button {
padding: 0 48rpx;
padding: 0 40rpx;
.cancel-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
background: #ffffff;
border: 1px solid #e5e5e5;
.cancel-button {
border-radius: 16rpx;
font-size: 32rpx;
color: #333333;
}
}
}

View File

@ -4,15 +4,15 @@
*/
export const TAB_BAR_CONFIG = [
// {
// text: "在线咨询",
// icon: "/static/tabbar/tabbar-icon1.png",
// activeIcon: "/static/tabbar/tabbar-icon1-active.png",
// pagePath: "/pages/consultation/index",
// // 可选配置
// badge: "", // 角标文字
// dot: false, // 是否显示小红点
// },
{
text: "在线咨询",
icon: "/static/tabbar/tabbar-icon1.png",
activeIcon: "/static/tabbar/tabbar-icon1-active.png",
pagePath: "/pages/consultation/index",
// 可选配置
badge: "", // 角标文字
dot: false, // 是否显示小红点
},
{
text: "人工转接",
icon: "/static/tabbar/tabbar-icon4.png",

View File

@ -9,10 +9,10 @@ const getWebSocketUrl = () => {
// #ifdef H5
// H5 开发环境
if (process.env.NODE_ENV === 'development') {
return 'ws://localhost:8073/ws/chat'
return 'ws://localhost:8082/ws/chat'
}
// H5 生产环境
return 'wss://120.55.234.65:8073/ws/chat'
return 'wss://120.55.234.65:8082/ws/chat'
// #endif
}

View File

@ -57,13 +57,6 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/my/change-password",
"style": {
"navigationBarTitleText": "修改密码",
"navigationStyle": "custom"
}
},
{
"path": "pages/home/admissions/index",
"style": {
@ -80,14 +73,14 @@
"navigationStyle": "custom"
}
},
// {
// "path": "pages/consultation/index",
// "style": {
// "navigationBarTitleText": "在线咨询",
// "enablePullDownRefresh": false,
// "navigationStyle": "custom"
// }
// },
{
"path": "pages/consultation/index",
"style": {
"navigationBarTitleText": "在线咨询",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/transfer/index",
"style": {
@ -141,13 +134,12 @@
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
// 线便
// {
// "pagePath": "pages/consultation/index",
// "iconPath": "static/tabbar/icon_home.png",
// "selectedIconPath": "static/tabbar/icon_home_active.png",
// "text": "在线咨询"
// },
{
"pagePath": "pages/consultation/index",
"iconPath": "static/tabbar/icon_home.png",
"selectedIconPath": "static/tabbar/icon_home_active.png",
"text": "在线咨询"
},
{
"pagePath": "pages/transfer/index",
"iconPath": "static/tabbar/icon_message.png",

View File

@ -2,26 +2,9 @@
<view class="home-container">
<header-bar
title="源小新"
:showLeftIcon="false"
leftIcon="list"
@leftClick="handleLeftClick"
>
<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>
<!-- 这是新版 -->
></header-bar>
<!-- 首页 -->
<view class="main-content" v-if="!isChat">
@ -76,16 +59,10 @@
:style="{
background: item.background,
}"
@click="handleFeatureClick(item)"
>
<image :src="item.icon" class="feature-icon"></image>
<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>
<text class="feature-text">{{ item.title }}</text>
</view>
</view>
</view>
@ -248,69 +225,25 @@
"
:id="'msg-' + message.id"
>
<view class="message-content-container-AI">
<image
class="ai-avatar"
src="/static/common/images/avatar_ai.png"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
'message-content-structured':
getStructuredMessageBlocks(message).length,
}"
>
<!-- 加载动画 -->
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></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" />
<image
class="ai-avatar"
src="/static/common/images/avatar_ai.png"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
}"
>
<!-- 加载动画 -->
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
<!-- 正常消息内容 -->
<markdown-viewer v-else :content="message.message" />
</view>
<!-- 回答反馈点赞/点踩 -->
@ -354,68 +287,23 @@
v-else-if="message.interactMode === 8"
:id="'msg-' + message.id"
>
<view class="message-content-container-Teacher">
<image
class="ai-avatar"
:src="receiverHeadSculptureUrl"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
'message-content-structured':
getStructuredMessageBlocks(message).length,
}"
>
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></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" />
<image
class="ai-avatar"
:src="receiverHeadSculptureUrl"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
}"
>
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
<markdown-viewer v-else :content="message.message" />
</view>
</view>
</block>
@ -428,7 +316,7 @@
<view class="floating-tabs">
<view
class="tab-item"
v-for="(tab, index) in displayFloatingTabs"
v-for="(tab, index) in floatingTabs"
:key="index"
@click="handleFeatureClick({ title: tab.title, path: tab.path })"
>
@ -534,17 +422,16 @@ export default {
"我什么时候能推知道自己是否被录取?",
],
features: [
// {
// title: "线",
// icon: "/static/common/images/icon_admissions.png",
// path: "/pages/home/admissions/index",
// background: "linear-gradient(0deg, #F4FBFE 0%, #F4FBFE 100%)",
// },
{
title: "在线咨询",
icon: "/static/common/images/icon_admissions.png",
path: "/pages/home/admissions/index",
background: "linear-gradient(0deg, #F4FBFE 0%, #F4FBFE 100%)",
},
{
title: "电话咨询",
tip: "欢迎致电本校招生咨询热线",
icon: "/static/common/images/icon-phone1.png",
background: "#fff",
icon: "/static/common/images/icon_phone.png",
background: "linear-gradient(0deg, #F4FBF9 0%, #F4FBF9 100%)",
},
],
floatingTabs: [
@ -552,11 +439,11 @@ export default {
title: "首页",
icon: "/static/common/images/icon_home.png",
},
// {
// title: "线",
// icon: "/static/common/images/icon_admissions2.png",
// path: "/pages/home/admissions/index",
// },
{
title: "招生在线",
icon: "/static/common/images/icon_admissions2.png",
path: "/pages/home/admissions/index",
},
{
title: "转人工",
icon: "/static/common/images/icon_conversation.png",
@ -629,10 +516,6 @@ export default {
},
displayFeatures() {
const list = Array.isArray(this.features) ? this.features : [];
return list;
},
displayFloatingTabs() {
const list = Array.isArray(this.floatingTabs) ? this.floatingTabs : [];
return list.map((item) => {
if (item && item.title === "转人工") {
return {
@ -693,18 +576,6 @@ export default {
this.handlePopupShow();
},
//
handleSettingClick() {
uni.navigateTo({
url: "/pages/home/userSetting/index",
});
},
//
// handleRecordClick() {
// console.log("");
// },
//
resetChatState({
conversationId = "",
@ -787,46 +658,38 @@ export default {
extraOffset: 200,
});
},
//
handleFeatureClick(item) {
const actions = {
首页: () => this.resetChatState({ isChat: false }),
转人工: () => this.handleTransferEntryClick(),
结束会话: () => this.handleEndConversation(),
电话咨询: () => {
this.advicePhoneShow = true;
},
};
if (item.title === "首页") {
this.resetChatState({ isChat: false });
return;
}
if (item.title === "转人工") {
this.handleTransferEntryClick();
return;
}
if (item.title === "结束会话") {
this.$store.commit("set_IsTransferChat", false);
return;
}
const action = actions[item.title];
if (action) {
action();
if (item.title === "电话咨询") {
this.advicePhoneShow = true;
return;
} else if (item.path) {
uni.navigateTo({ url: item.path });
uni.navigateTo({
url: item.path,
});
return;
} else {
this.$refs.uToast.show({ title: "暂未开放", type: "warning" });
this.$refs.uToast.show({
title: "暂未开放",
type: "warning",
});
return;
}
},
//
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) {
return {
id: Math.random().toString(36).substring(2, 15),
@ -841,7 +704,6 @@ export default {
};
},
//
createTransferCardMessage() {
return {
id: "transfer_" + Math.random().toString(36).substring(2, 15),
@ -860,7 +722,6 @@ export default {
};
},
//
handleTransferEntryClick() {
if (!this.isChat) {
this.resetChatState();
@ -946,14 +807,22 @@ export default {
return;
}
// dialogueManagementId
const dialogueManagementId =
(res && res.data && res.data.dialogueManagementId) || "";
// receiverId 线
const receiverId = (res && res.data && res.data.receiverId) || "";
console.log(dialogueManagementId, "dialogueManagementId");
console.log(receiverId, "receiverId");
this.currentDMid ||
(res &&
res.data &&
(res.data.dialogueManagementId ||
res.data.DialogueManagementId)) ||
"";
const receiverId =
(res &&
res.data &&
(res.data.receiverId ||
res.data.ReceiverId ||
res.data.liveAgentId ||
res.data.agentId ||
res.data.userId)) ||
"";
if (!this.currentDMid && dialogueManagementId) {
this.currentDMid = dialogueManagementId;
@ -966,17 +835,12 @@ export default {
if (dialogueManagementId && receiverId) {
this.$store.commit("set_IsTransferChat", true);
this.$store.commit("update_MsgUser", {
dialogueManagementId,
receiverId,
});
this.$store.dispatch("selectTeacherChatItem", {
id: dialogueManagementId,
receiverId,
navigate: false,
});
}
console.log(this.vuex_msgUser, "vuex_msgUser");
})
.catch(() => {
updateTransferCard({
@ -1101,11 +965,10 @@ export default {
},
//
async handleQAClick(item) {
handleQAClick(item) {
//
if (!this.isChat) {
// this.resetChatState();
await this.handleStartChat(); // ,
this.resetChatState();
}
const sendMessage = item.content;
@ -1172,20 +1035,12 @@ export default {
(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
const aiMessage = {
id:
this.currentDMid || Math.random().toString(36).substring(2, 15),
message: message,
data.conversationId ||
Math.random().toString(36).substring(2, 15),
message: data.detailedExplanation,
sendDate: "",
isSend: true,
isRead: false,
@ -1253,107 +1108,6 @@ export default {
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
processMessageContent(message) {
return processChatMessageContent(message);
@ -1366,11 +1120,11 @@ export default {
//
handleGetConversationDetail() {
// if (!this.currentDMid) return;
if (!this.currentDMid) return;
return this.$u.api
this.$u.api
.GetConversationDetail({
// "Item1.Id": "08de2e31-4cdf-4b0c-8b83-d3185f604a5a",
"Item1.Id": this.currentDMid,
PageIndex: this.pageQuery.PageIndex,
PageSize: this.pageQuery.PageSize,
})
@ -1391,14 +1145,14 @@ export default {
// >1退
refreshPageWithFallback() {
// if (!this.currentDMid) return;
if (!this.currentDMid) return;
const currentIndex = this.pageQuery.PageIndex || 1;
const pageSize = this.pageQuery.PageSize;
return this.$u.api
.GetConversationDetail({
// "Item1.Id": this.currentDMid,
"Item1.Id": this.currentDMid,
PageIndex: currentIndex,
PageSize: pageSize,
})
@ -1408,7 +1162,7 @@ export default {
const prevIndex = currentIndex - 1;
return this.$u.api
.GetConversationDetail({
// "Item1.Id": this.currentDMid,
"Item1.Id": this.currentDMid,
PageIndex: prevIndex,
PageSize: pageSize,
})
@ -1439,9 +1193,6 @@ export default {
//
handleStartChat() {
this.resetChatState();
//
return this.handleGetConversationDetail();
},
//
@ -1468,7 +1219,7 @@ export default {
this.pageQuery.PageIndex++;
this.$u.api
.GetConversationDetail({
// "Item1.Id": this.currentDMid,
"Item1.Id": this.currentDMid,
PageIndex: this.pageQuery.PageIndex,
PageSize: this.pageQuery.PageSize,
})
@ -1501,14 +1252,7 @@ export default {
// /&退
handleFeedback(message, isHelp) {
this.$u.api.ModifyStatus({ id: message.id, isHelp }).then((res) => {
if (!res.succeed) {
uni.showToast({
title: res.error || "操作失败",
icon: "none",
});
return;
}
if (!res.succeed) return;
this.$u.toast("操作成功");
// 退
this.refreshPageWithFallback();
@ -1582,11 +1326,6 @@ export default {
/* Header样式移至HeaderBar组件 */
.header-icon {
width: 40rpx;
height: 40rpx;
}
.main-content {
padding: 30rpx;
padding-top: 60rpx;
@ -1725,59 +1464,29 @@ export default {
border-radius: 16rpx;
padding: 30rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-top: 32rpx;
gap: 30rpx;
.feature-item {
height: 140rpx;
height: 150rpx;
border-radius: 16rpx;
display: flex;
justify-content: flex-start;
align-items: center;
// padding-left: 30rpx;
gap: 30rpx;
padding-left: 30rpx;
gap: 20rpx;
flex: 1;
.feature-icon {
width: 80rpx;
height: 80rpx;
margin-top: 16rpx;
}
.feature-text-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 12rpx;
flex: 1;
.feature-title {
font-size: 28rpx;
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;
}
.feature-text {
font-size: 26rpx;
color: #333333;
}
}
}
@ -1895,14 +1604,8 @@ export default {
}
.message-left {
flex-direction: column;
.message-content-container-AI,
.message-content-container-Teacher {
display: flex;
justify-content: flex-start;
flex-wrap: wrap; /* 允许反馈区换行到消息下方 */
}
justify-content: flex-start;
flex-wrap: wrap; /* 允许反馈区换行到消息下方 */
.ai-avatar {
width: 64rpx;
@ -1921,12 +1624,6 @@ export default {
font-size: 28rpx;
line-height: 1.5;
&.message-content-structured {
background-color: transparent;
padding: 0;
border-radius: 0;
}
/* 加载动画样式 */
.loading-dots {
display: flex;
@ -1999,88 +1696,15 @@ export default {
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 {
max-width: 70%;
width: 70%;
}
// }
/* 回答反馈容器,跟随左侧消息,右下角对齐 */
.feedback-container {
max-width: 70%;
width: 70%;
margin-left: 80rpx; /* 头像宽度64rpx + 间距16rpx */
display: flex;
justify-content: flex-end;

View File

@ -1,5 +1,4 @@
<template>
<!-- 留言板页面 此页面已废弃 -->
<view class="message-board-page">
<header-bar title="留言板" @leftClick="handleLeftClick"></header-bar>
<view class="custom-tabs-box">

View File

@ -464,7 +464,7 @@ export default {
//
const redirect = () => {
const url = this.isTeacher
? "/pages/transfer/index"
? "/pages/consultation/index"
: "/pages/home/index/index";
uni.reLaunch({
url: url,
@ -821,3 +821,4 @@ export default {
}
</style>

View File

@ -1,372 +0,0 @@
<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>

View File

@ -123,8 +123,9 @@ export default {
url: '/pages/my/personalInfo'
});
} else if (route === 'change-password') {
uni.navigateTo({
url: '/pages/my/change-password'
uni.showToast({
title: '功能开发中',
icon: 'none'
});
} else if (route === 'logout-records') {
uni.showModal({

View File

@ -10,18 +10,7 @@
<view class="content-wrapper">
<view class="form-container">
<view class="form-card calendar-card">
<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="card-title">{{ monthTitle }}</view>
<view class="week-row">
<text
v-for="(label, index) in weekLabels"
@ -93,9 +82,6 @@ export default {
monthTitle() {
return `${this.currentYear}${String(this.currentMonth).padStart(2, "0")}`;
},
monthValue() {
return `${this.currentYear}-${String(this.currentMonth).padStart(2, "0")}`;
},
selectedDateTitle() {
const dayText = String(this.selectedDay).padStart(2, "0");
return `${this.currentYear}${String(this.currentMonth).padStart(2, "0")}${dayText}`;
@ -111,21 +97,16 @@ export default {
},
},
methods: {
fetchSchedule(
year = this.currentYear,
month = this.currentMonth,
day = this.selectedDay
) {
fetchSchedule() {
const userInfo = this.vuex_user || {};
const teacherId = userInfo.id || userInfo.Id;
if (!teacherId) {
this.$u.toast("缺少教师ID");
return;
}
const scheduleDate = new Date(year, month - 1, day, 12, 0, 0);
this.$u.api
.getShiftSchedulingData({
SchedulingTime: scheduleDate.toISOString(),
SchedulingTime: new Date().toISOString(),
TeacherManagementId: teacherId,
})
.then((res) => {
@ -196,40 +177,6 @@ export default {
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>
@ -266,33 +213,11 @@ export default {
margin-bottom: 20rpx;
}
.calendar-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #333333;
}
.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;
margin-bottom: 20rpx;
}
.week-row {

View File

@ -250,7 +250,7 @@ export default {
//
testLocalServer() {
this.wsUrl = 'ws://localhost:8073/ws/chat?token=test-token'
this.wsUrl = 'ws://localhost:8082/ws/chat?token=test-token'
this.messageToSend = JSON.stringify({
type: 'message',
fromUserId: 'test_user',

View File

@ -231,7 +231,7 @@ export default {
this.isLoading = true;
try {
const res = await this.$u.api.GetDialogueListApi({
"Item1.OnlineConsultationType": 1,
"Item1.OnlineConsultationType": 2,
});
const list = (res && res.data && res.data.item1) || [];
this.chatList = this.normalizeDialogueList(list);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -149,14 +149,6 @@ 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
push_MsgList(state, list) {
state.vuex_msgList = list || [];
@ -181,7 +173,7 @@ const store = new Vuex.Store({
// 注现在的消息没有id无法去重暂时用push_MsgList
if (!msg || !msg.id) return;
const exists = (state.vuex_msgList || []).some(
(item) => item && item.id === msg.id,
(item) => item && item.id === msg.id
);
if (!exists) {
state.vuex_msgList.push(msg);
@ -206,14 +198,15 @@ const store = new Vuex.Store({
if (!msgId) return;
const exists = (state.vuex_aiMessageGroups || []).some(
(item) => item && item.id === msgId,
(item) => item && item.id === msgId
);
if (exists) return;
const userId =
(state.vuex_user && (state.vuex_user.Id || state.vuex_user.id)) || "";
const senderId = msg.senderId || msg.SenderId || "";
const isSelf = userId && senderId && String(userId) === String(senderId);
const isSelf =
userId && senderId && String(userId) === String(senderId);
state.vuex_aiMessageGroups.push({
id: msgId,
@ -250,7 +243,7 @@ const store = new Vuex.Store({
push_AiMsg(state, msg) {
if (!msg || !msg.id) return;
const exists = (state.vuex_aiMessageGroups || []).some(
(item) => item && item.id === msg.id,
(item) => item && item.id === msg.id
);
if (!exists) {
state.vuex_aiMessageGroups.push(msg);
@ -273,7 +266,7 @@ const store = new Vuex.Store({
// WebSocket 实时消息:更新会话列表的未读数、文案与时间
apply_RealtimeMessageToList(
state,
{ dialogueId, message, sendDate, senderId, receiverId },
{ dialogueId, message, sendDate, senderId, receiverId }
) {
if (!dialogueId) return;
const activeId =
@ -293,7 +286,10 @@ const store = new Vuex.Store({
found = true;
const currentUnread =
Number(
item?.unReadCount ?? item?.unreadCount ?? item?.unread ?? 0,
item?.unReadCount ??
item?.unreadCount ??
item?.unread ??
0
) || 0;
const nextUnread = isActive ? 0 : currentUnread + 1;
return {
@ -390,7 +386,7 @@ const store = new Vuex.Store({
// avatar: item.avatar,
unReadCount,
};
},
}
);
commit("set_UserMsgList", list);
return list;
@ -407,7 +403,7 @@ const store = new Vuex.Store({
// 获取聊天记录(私聊)——仅在进入聊天页时加载一次
async fetchChatRecord(
{ commit },
{ dialogueManagementId, PageIndex = 1, PageSize = 20 },
{ dialogueManagementId, PageIndex = 1, PageSize = 20 }
) {
return Vue.prototype.$u.api
.GetChatHistoryDataApi({
@ -416,13 +412,11 @@ const store = new Vuex.Store({
PageSize,
})
.then((res) => {
const list = (
res && res.data && Array.isArray(res.data.item1)
const list =
(res && res.data && Array.isArray(res.data.item1)
? res.data.item1
: []
)
.slice()
.reverse();
).slice().reverse();
commit("push_MsgList", list);
return list;
});
@ -431,7 +425,7 @@ const store = new Vuex.Store({
// 获取下一页历史消息(滚动到顶部触发)
async fetchChatRecordNextPage(
{ commit },
{ dialogueManagementId, PageIndex = 1, PageSize = 20 },
{ dialogueManagementId, PageIndex = 1, PageSize = 20 }
) {
return Vue.prototype.$u.api
.GetChatHistoryDataApi({
@ -440,13 +434,11 @@ const store = new Vuex.Store({
PageSize,
})
.then((res) => {
const list = (
res && res.data && Array.isArray(res.data.item1)
const list =
(res && res.data && Array.isArray(res.data.item1)
? res.data.item1
: []
)
.slice()
.reverse();
).slice().reverse();
if (!list.length) return [];
commit("prepend_MsgList", list);
return list;
@ -470,7 +462,7 @@ const store = new Vuex.Store({
// 点击聊天记录,切换到该会话
selectTeacherChatItem(
{ commit, dispatch },
{ id, receiverId, navigate = true } = {},
{ id, receiverId, navigate = true } = {}
) {
if (!id || !receiverId) return;
// 清空消息列表,避免旧消息干扰
@ -480,7 +472,7 @@ const store = new Vuex.Store({
.GetReceiverUserInfoApi({ Id: receiverId })
.then((res) => {
if (res.succeed && res.data) {
commit("update_MsgUser", { ...res.data, dialogueManagementId: id });
commit("set_MsgUser", { ...res.data, dialogueManagementId: id });
if (navigate) {
uni.navigateTo({
url: `/pages/chat/index`,

View File

@ -164,7 +164,6 @@ export function processChatMessageContent(message) {
export function sortChatMessages(list = []) {
const processedList = (list || []).map((item) => ({
...item,
rawMessage: item && item.message,
message: processChatMessageContent(item && item.message),
}));