diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..60a614d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# 忽略 unpackage 文件夹
+/unpackage/
diff --git a/App.vue b/App.vue
index 0e94c79..ab2d988 100644
--- a/App.vue
+++ b/App.vue
@@ -1,31 +1,29 @@
@@ -305,7 +309,7 @@ uni-modal .uni-modal__btn:after {
uni-modal .uni-modal__btn_default {
background: #e6f6ff;
- color: #3CB5FB !important;
+ color: #3cb5fb !important;
margin-right: 20rpx;
}
@@ -322,7 +326,6 @@ uni-modal .uni-modal__btn_primary {
color: #ffffff !important;
}
-
uni-modal .uni-modal {
padding: 40rpx;
box-sizing: border-box;
@@ -357,8 +360,8 @@ uni-page-body {
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
- "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- "Microsoft Yahei", sans-serif;
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ "Microsoft Yahei", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
// max-width: 1536rpx;
diff --git a/common/http.api.js b/common/http.api.js
index 67f067c..9f58e68 100644
--- a/common/http.api.js
+++ b/common/http.api.js
@@ -100,7 +100,11 @@ const install = (Vue, vm) => {
/** 首页ai对话 */
// 发送消息
- let SendMessageApi = (params = {}) => vm.$u.post('api/ChatAI/CreateChat', params);
+ let SendMessageApi = (params = {}) => vm.$u.post('api/ChatAI/CreateChat', params, {showLoading: false});
+ // 获取历史对话列表
+ let GetConversationPage = (params = {}) => vm.$u.get('api/ChatAI/GetConversationPage', params);
+ // 获取对话详情
+ let GetConversationDetail = (params = {}) => vm.$u.get('api/ChatAI/GetHistoricalConversations', params);
/** 登录 */
// 获取图形验证码
@@ -157,7 +161,9 @@ const install = (Vue, vm) => {
SendMessageApi,
GetCaptcha,
GetStuVerifyCode,
- StuLogin
+ StuLogin,
+ GetConversationPage,
+ GetConversationDetail
};
}
diff --git a/common/http.interceptor.js b/common/http.interceptor.js
index 5122ba2..10c99f9 100644
--- a/common/http.interceptor.js
+++ b/common/http.interceptor.js
@@ -10,7 +10,7 @@ const install = (Vue, vm) => {
// baseUrl: 'http://115.238.47.235:8993',
// 如果将此值设置为true,拦截回调中将会返回服务端返回的所有数据response,而不是response.data
// 设置为true后,就需要在this.$u.http.interceptor.response进行多一次的判断,请打印查看具体值
- // originalData: true,
+ originalData: true,
// 设置自定义头部content-type
// header: {
// 'content-type': 'xxx'
@@ -31,9 +31,13 @@ const install = (Vue, vm) => {
// 方式四,如果token放在了Storage本地存储中,拦截是每次请求都执行的,所以哪怕您重新登录修改了Storage,下一次的请求将会是最新值
// const token = uni.getStorageSync('token');
// config.header.token = token;
- uni.showLoading({
- title: '加载中'
- });
+
+ // 注释 7/15
+ // uni.showLoading({
+ // title: '请求中...'
+ // });
+ // 注释
+
// setTimeout(function () {
// uni.hideLoading();
// }, 2000);
@@ -42,11 +46,20 @@ const install = (Vue, vm) => {
}
// 响应拦截,判断状态码是否通过
Vue.prototype.$u.http.interceptor.response = (res) => {
- uni.hideLoading();
+ // uni.hideLoading();
+
+ // 检查是否为401未授权错误
+ if (res.statusCode === 401) {
+ handleAuthError(vm);
+ return false;
+ }
+
+ return res.data;
+
if (res.succeed == true || res.success == true) {
// return res.data || res;
return res;
- } else {
+ } else {
// uni.showToast({
// title: res.error,
// duration: 2000,
@@ -55,13 +68,31 @@ const install = (Vue, vm) => {
// uni.navigateTo({
// url: "/pages/login/login/login",
// });
- return res;
- // return false;
-
+
+ return false;
}
}
}
+// 提取处理401错误的函数
+function handleAuthError(vm) {
+ vm.$u.vuex("vuex_user", "");
+ vm.$u.vuex("vuex_token", "");
+ uni.clearStorage();
+
+ // 显示提示
+ uni.showToast({
+ title: '登录已过期,请重新登录!',
+ icon: 'none',
+ duration: 1500
+ });
+
+ // 延迟跳转到登录页
+ uni.reLaunch({
+ url: '/pages/login/login/index'
+ });
+}
+
export default {
install
}
diff --git a/components/AdvicePhone.vue b/components/AdvicePhone.vue
index 812fa4c..bf6091b 100644
--- a/components/AdvicePhone.vue
+++ b/components/AdvicePhone.vue
@@ -60,8 +60,12 @@ export default {
.phone-popup {
width: 600rpx;
padding: 60rpx 0;
- background: #ffffff;
border-radius: 16rpx;
+ background-image: url("/static/common/images/images_bg_dialog.png");
+ background-repeat: no-repeat;
+ background-size: 630rpx 100rpx;
+ background-position: -20rpx 0;
+
.phone-title {
text-align: center;
diff --git a/components/ChatHistory.vue b/components/ChatHistory.vue
new file mode 100644
index 0000000..2934566
--- /dev/null
+++ b/components/ChatHistory.vue
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+ {{ group.id }}
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/PerfectInfo.vue b/components/PerfectInfo.vue
new file mode 100644
index 0000000..1d6afe3
--- /dev/null
+++ b/components/PerfectInfo.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/components/markdown-viewer/markdown-viewer.vue b/components/markdown-viewer/markdown-viewer.vue
index 33b011d..b7af3b1 100644
--- a/components/markdown-viewer/markdown-viewer.vue
+++ b/components/markdown-viewer/markdown-viewer.vue
@@ -9,9 +9,56 @@ import { marked } from 'marked';
import hljs from 'highlight.js';
import mpHtml from '@/node_modules/mp-html/dist/uni-app/components/mp-html/mp-html.vue';
+// 自定义渲染器,修改列表的渲染方式
+const renderer = new marked.Renderer();
+
+// 重写粗体渲染方法,确保粗体文本不会导致不当换行
+renderer.strong = function(text) {
+ // 确保文本周围没有不必要的空格
+ const trimmedText = text.trim();
+ return `${trimmedText}`;
+};
+
+// 重写列表项渲染方法 - 使用特殊的包装来确保内容在一行
+renderer.listitem = function(text) {
+ return `
${text}
`;
+};
+
+// 重写有序列表渲染方法
+renderer.list = function(body, ordered) {
+ const type = ordered ? 'ol' : 'ul';
+ const className = ordered ? 'custom-ordered-list' : 'custom-unordered-list';
+ return `<${type} class="${className}">${body}${type}>`;
+};
+
+// 重写段落渲染方法,确保换行正确
+renderer.paragraph = function(text) {
+ return `${text}
`;
+};
+
+// 重写链接渲染方法,确保链接文本正常显示
+renderer.link = function(href, title, text) {
+ return `${text}`;
+};
+
+// 重写表格渲染方法,添加表格容器使其可滚动
+renderer.table = function(header, body) {
+ return ``;
+};
+
+// 重写表格单元格渲染方法
+renderer.tablecell = function(content, flags) {
+ const type = flags.header ? 'th' : 'td';
+ const align = flags.align ? ` style="text-align:${flags.align}"` : '';
+ return `<${type}${align}>${content}${type}>`;
+};
+
// 配置marked
marked.use({
- renderer: new marked.Renderer(),
+ renderer: renderer,
highlight: function(code, lang) {
try {
return hljs.highlightAuto(code, [lang]).value;
@@ -54,15 +101,351 @@ export default {
renderedContent() {
if (!this.content) return '';
try {
- // 添加自定义样式
- const styleTag = this.customStyle ?
- `` : '';
+ // 添加自定义样式,确保内容自动换行和列表正确显示
+ const styleTag = ``;
- // 渲染Markdown为HTML
- const htmlContent = marked(this.content);
+ // 对Markdown内容进行预处理,修复可能导致问题的模式
+ let processedContent = this.content;
+
+ // 将Markdown内容分解为段落,分别处理每个段落
+ const paragraphs = processedContent.split('\n\n');
+ const processedParagraphs = paragraphs.map(paragraph => {
+ // 处理一个段落内的粗体文本
+ return paragraph.replace(/\*\*([^*]+?)\*\*/g, (match, content) => {
+ // 返回无缝拼接的HTML粗体标签
+ return `${content}`;
+ });
+ });
+
+ // 重新组合处理后的段落
+ processedContent = processedParagraphs.join('\n\n');
+
+ // 使用marked处理剩余的Markdown语法
+ let htmlContent = marked(processedContent);
+
+ // 后处理HTML,确保任何遗漏的粗体标记也能被处理
+ htmlContent = htmlContent
+ // 处理任何遗漏的**标记
+ .replace(/\*\*([^*]+?)\*\*/g, (match, content) => {
+ return `${content}`;
+ });
+
+ // 处理HTML,确保所有文本元素都有正确的显示属性
+ let processedHtml = htmlContent;
+
+ // 处理表格,确保表格正确显示
+ processedHtml = processedHtml.replace(/]*)>/g, (match, attrs) => {
+ return ``;
+ });
+
+ processedHtml = processedHtml.replace(/<\/table>/g, '
');
+
+ // 将所有段落包装在特殊div中,防止段落间的换行问题
+ processedHtml = processedHtml.replace(/]*)>(.*?)<\/p>/gs, (match, attrs, content) => {
+ // 确保段落内容连续显示,不被换行打断
+ return `
${content}
`;
+ });
+
+ // 确保强制使用行内样式
+ processedHtml = processedHtml.replace(
+ /');
+
+ // 添加强制粗体样式覆盖
+ processedHtml = `
+
+ ` + processedHtml;
// 返回带样式的HTML
- return styleTag + htmlContent;
+ return styleTag + processedHtml;
} catch (error) {
console.error('Markdown渲染错误:', error);
return `Markdown渲染错误: ${error.message}
`;
@@ -77,5 +460,306 @@ export default {
.markdown-container {
padding: 20rpx;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: normal;
+}
+
+/* 全局样式 */
+.markdown-container >>> * {
+ max-width: 100% !important;
+ white-space: normal !important;
+ word-break: normal !important;
+ overflow-wrap: break-word !important;
+ box-sizing: border-box !important;
+}
+
+/* 段落样式 */
+.markdown-container >>> .custom-paragraph,
+.markdown-container >>> p {
+ display: block !important;
+ /* margin: 10rpx 0 !important; */
+ width: 100% !important;
+ line-height: 1.6 !important;
+ text-align: left !important; /* 改为左对齐,不要两端对齐 */
+}
+
+/* 专门用于列表项内容包装的容器 */
+.markdown-container >>> .list-item-wrapper {
+ display: inline-block !important;
+ width: 100% !important;
+ white-space: normal !important;
+ /* 关键属性:防止内部元素分列 */
+ table-layout: fixed !important;
+ word-break: normal !important;
+ text-align: left !important;
+ text-justify: none !important;
+ letter-spacing: normal !important;
+ word-spacing: normal !important;
+ text-align-last: left !important;
+}
+
+/* 内联元素修复 */
+.markdown-container >>> span,
+.markdown-container >>> a,
+.markdown-container >>> em,
+.markdown-container >>> strong,
+.markdown-container >>> .custom-strong,
+.markdown-container >>> b {
+ display: inline !important;
+ white-space: normal !important;
+ vertical-align: baseline !important;
+ float: none !important;
+ position: static !important;
+ width: auto !important;
+ height: auto !important;
+ /* 确保内部不会有奇怪的换行 */
+ /* 下面不注释的话会有奇怪的换行 */
+ /* word-break: keep-all !important; */
+ line-height: inherit !important;
+}
+
+/* 特别针对粗体文本 */
+.markdown-container >>> .custom-strong,
+.markdown-container >>> strong,
+.markdown-container >>> b {
+ font-weight: 900 !important;
+ display: inline !important;
+ vertical-align: baseline !important;
+ position: static !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ float: none !important;
+ width: auto !important;
+ white-space: normal !important;
+ word-break: normal !important;
+ overflow-wrap: break-word !important;
+ word-wrap: normal !important;
+ line-height: inherit !important;
+ color: inherit !important; /* 确保颜色正确继承 */
+ font-size: inherit !important; /* 确保字体大小正确继承 */
+ font-family: inherit !important; /* 确保字体系列正确继承 */
+ text-decoration: none !important;
+}
+
+/* 专门修复mp-html组件生成的列表结构 */
+.markdown-container >>> div[data-v-7e5387f] {
+ display: inline !important;
+}
+
+/* 处理列表编号所在的容器 - 重要修复 */
+.markdown-container >>> div[data-v-7e5387f] {
+ display: inline !important;
+ white-space: nowrap !important; /* 防止编号和首个字符分开 */
+}
+
+/* 首行缩进优化(确保列表项不会被意外截断) */
+.markdown-container >>> div div {
+ display: inline !important;
+}
+
+/* 有序列表样式 */
+.markdown-container >>> .custom-ordered-list,
+.markdown-container >>> ol {
+ display: block !important;
+ counter-reset: item !important;
+ padding-left: 0 !important;
+ margin: 10rpx 0 !important;
+ width: 100% !important;
+ list-style-type: none !important;
+}
+
+/* 无序列表样式 */
+.markdown-container >>> .custom-unordered-list,
+.markdown-container >>> ul {
+ display: block !important;
+ padding-left: 0 !important;
+ margin: 10rpx 0 !important;
+ width: 100% !important;
+ list-style-type: none !important;
+}
+
+/* 针对mp-html生成的列表标记容器 */
+.markdown-container >>> div[style*="undefined"] {
+ display: inline !important;
+ white-space: nowrap !important;
+}
+
+/* 对列表项第一个元素特殊处理 */
+.markdown-container >>> h4 {
+ display: inline !important;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+/* 列表项共用样式 */
+.markdown-container >>> .custom-list-item,
+.markdown-container >>> ol li,
+.markdown-container >>> ul li {
+ display: flex !important;
+ padding-left: 0 !important;
+ margin-bottom: 8rpx !important;
+ width: 100% !important;
+ position: relative !important;
+ /* 确保内容在一行显示 */
+ flex-wrap: nowrap !important;
+ align-items: flex-start !important; /* 顶部对齐 */
+}
+
+/* 列表项内容样式 */
+.markdown-container >>> .custom-list-item > *:not(ol):not(ul),
+.markdown-container >>> ol li > *:not(ol):not(ul),
+.markdown-container >>> ul li > *:not(ol):not(ul) {
+ flex: 1 !important;
+ display: inline !important;
+ white-space: normal !important;
+ word-break: normal !important;
+ width: auto !important;
+ /* 确保文本不会被分列 */
+ column-count: 1 !important;
+ column-width: auto !important;
+ column-span: all !important;
+ text-align: left !important; /* 确保文本左对齐 */
+ justify-content: flex-start !important; /* 左对齐 */
+ text-justify: none !important; /* 禁用两端对齐 */
+ letter-spacing: normal !important; /* 正常字间距 */
+ word-spacing: normal !important; /* 正常词间距 */
+}
+
+/* 列表项内的p标签特殊处理 */
+.markdown-container >>> ol li p,
+.markdown-container >>> ul li p,
+.markdown-container >>> .custom-list-item p {
+ display: inline !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ width: auto !important;
+ white-space: normal !important;
+}
+
+/* 有序列表项编号 */
+.markdown-container >>> .custom-ordered-list > .custom-list-item::before,
+.markdown-container >>> ol > li::before {
+ counter-increment: item !important;
+ content: counter(item)"." !important;
+ min-width: 40rpx !important;
+ width: 40rpx !important;
+ font-weight: bold !important;
+ text-align: right !important;
+ margin-right: 8rpx !important;
+ padding-right: 0 !important;
+ flex-shrink: 0 !important;
+ align-self: flex-start !important; /* 顶部对齐 */
+ /* padding-top: 1rpx !important; 微调垂直位置 */
+}
+
+/* 无序列表项标记 */
+.markdown-container >>> .custom-unordered-list > .custom-list-item::before,
+.markdown-container >>> ul > li::before {
+ content: "-" !important;
+ min-width: 40rpx !important;
+ width: 40rpx !important;
+ font-weight: bold !important;
+ text-align: center !important;
+ margin-right: 8rpx !important;
+ padding-right: 0 !important;
+ flex-shrink: 0 !important;
+ align-self: flex-start !important; /* 顶部对齐 */
+ /* padding-top: 1rpx !important; 微调垂直位置 */
+}
+
+/* 禁用可能导致问题的CSS特性 */
+.markdown-container >>> * {
+ column-count: 1 !important;
+ column-gap: 0 !important;
+ column-rule: none !important;
+ column-span: all !important;
+ column-width: auto !important;
+ columns: auto !important;
+}
+
+/* 代码块样式 */
+.markdown-container >>> pre,
+.markdown-container >>> code {
+ white-space: pre-wrap !important;
+ word-break: break-all !important;
+ overflow-wrap: break-word !important;
+ max-width: 100% !important;
+}
+
+/* 表格样式 */
+.markdown-container >>> table {
+ border-collapse: collapse !important;
+ width: 100% !important;
+ margin: 0 !important;
+ table-layout: fixed !important; /* 使用固定表格布局 */
+ border: 1px solid #e0e0e0 !important;
+ font-size: 24rpx !important; /* 稍微减小字体大小,适应移动端 */
+ overflow-x: visible !important; /* 移除溢出隐藏 */
+}
+
+.markdown-container >>> table th,
+.markdown-container >>> table td {
+ border: 1px solid #e0e0e0 !important;
+ padding: 8rpx 10rpx !important; /* 减小内边距 */
+ text-align: left !important;
+ word-break: break-word !important; /* 确保长文本会自动换行 */
+ white-space: normal !important;
+ vertical-align: middle !important;
+ min-width: 80rpx !important; /* 设置最小宽度 */
+ max-width: none !important; /* 移除最大宽度限制 */
+ width: auto !important; /* 使列宽自动调整 */
+ overflow-wrap: break-word !important; /* 确保长单词会换行 */
+}
+
+.markdown-container >>> table th {
+ background-color: #f5f5f5 !important;
+ font-weight: bold !important;
+ color: #333333 !important;
+}
+
+.markdown-container >>> table tr:nth-child(even) {
+ background-color: #fafafa !important;
+}
+
+.markdown-container >>> table tr:hover {
+ background-color: #f0f0f0 !important;
+}
+
+/* 表格容器 - 移除水平滚动 */
+.markdown-container >>> .table-container {
+ width: 100% !important;
+ overflow-x: visible !important; /* 移除水平滚动 */
+ margin: 16rpx 0 30rpx !important; /* 增加底部边距,与下方内容分开 */
+ display: block !important;
+}
+
+/* 表格整体显示 */
+.markdown-container >>> .custom-table {
+ width: 100% !important;
+ table-layout: fixed !important; /* 固定布局 */
+ overflow: visible !important; /* 显示所有内容 */
+}
+
+/* 表格标题 */
+.markdown-container >>> table caption {
+ font-weight: bold !important;
+ padding: 10rpx !important;
+ caption-side: top !important;
+ text-align: center !important;
+ color: #333333 !important;
+}
+
+/* 分隔线样式优化 */
+.markdown-container >>> hr {
+ display: block !important;
+ height: 2rpx !important;
+ border: 0 !important;
+ border-top: 2rpx solid #e0e0e0 !important;
+ margin: 24rpx 0 !important; /* 增加上下边距 */
+ padding: 0 !important;
+ clear: both !important;
+ background-color: #e0e0e0 !important;
}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index e2fa85d..c26d043 100644
--- a/manifest.json
+++ b/manifest.json
@@ -129,7 +129,7 @@
},
"h5" : {
"devServer" : {
- "port" : 8080,
+ "port" : 8088,
"disableHostCheck" : true,
// "proxy" : {
// "/api" : {
diff --git a/pages/home/index/index.vue b/pages/home/index/index.vue
index 0e5c9d1..00ef9c8 100644
--- a/pages/home/index/index.vue
+++ b/pages/home/index/index.vue
@@ -13,7 +13,7 @@
-
+
Hi~
- 我是源小新,你们的AI校园助手,非常高兴认识您,我可以为你答疑解惑。Hi~ 我是源小新,你们的AI校园助手,非常高兴认识您,我可以为你答疑解惑。
@@ -54,7 +53,7 @@
-->
-
@@ -206,6 +221,7 @@
v-model="messageValue"
type="text"
class="chat-input"
+ :focus="true"
placeholder="请输入内容"
placeholder-style="color: #adadad;"
@confirm="sendMessageFn"
@@ -223,56 +239,16 @@
-
-
-
-
- 周三
-
-
- {{ item.content }}
-
-
- 6/27
-
-
- {{ item.content }}
-
-
+
-
-
-
+
+
@@ -286,17 +262,32 @@
import MarkdownViewer from "@/components/markdown-viewer/markdown-viewer";
import CustomTabBar from "@/components/custom-tab-bar/custom-tab-bar.vue";
import AdvicePhone from "@/components/AdvicePhone.vue";
+import PerfectInfo from "@/components/PerfectInfo.vue";
+import ChatHistory from "@/components/ChatHistory.vue"; // 导入新组件
export default {
components: {
CustomTabBar,
AdvicePhone,
MarkdownViewer,
+ PerfectInfo,
+ ChatHistory, // 注册新组件
},
data() {
return {
isChat: false,
+ currentConversationId: "",
advicePhoneShow: false,
+ perfectInfoShow: false,
+ isLoading: false, // loadingMore
+ isLoadingMore: false, // 是否正在加载更多的标志位
+ noMoreData: false, // 是否已加载全部历史消息
+ isSwitchingConversation: false, // 是否正在切换对话的标志位
+
+ pageQuery: {
+ PageIndex: 1,
+ PageSize: 20,
+ },
questionList: [
"学习哪些专业比较好?",
@@ -320,22 +311,37 @@ export default {
},
],
popupShow: false,
- chatHistoryList: [
- { content: "学校哪些专业比较好?" },
- { content: "如何报考学校综合合录取评澳市评招生?" },
- { content: "学校有新华专业?" },
- ],
- chatHistoryList2: [
- { content: "学校哪些专业比较好?" },
- { content: "如何报考学校综合合录取评澳市评招生?" },
- { content: "如何报考学校综合合录取评澳市评招生?" },
- { content: "如何报考学校综合合录取评澳市评招生?" },
- { content: "如何报考学校综合合录取评澳市评招生?" },
- { content: "学校有新华专业?" },
- { content: "学校有新华专业?" },
- { content: "学校有新华专业?" },
- { content: "学校有新华专业?" },
- { content: "学校有新华专业?" },
+ chatHistoryList3: [
+ // {
+ // "id": "今天",
+ // "conversation": [
+ // {
+ // "title": "你好",
+ // "id": "0d03f23c-9c5b-45ac-ae95-be51e9383a62",
+ // "startTime": "2025-07-10T15:28:02.832316"
+ // },
+ // {
+ // "title": "你知道今年的录取分数线吗",
+ // "id": "771ab434-348c-4a35-bd03-adebc21436d8",
+ // "startTime": "2025-07-10T15:11:32.885001"
+ // }
+ // ]
+ // },
+ // {
+ // "id": "周三",
+ // "conversation": [
+ // {
+ // "title": "今天是周三",
+ // "id": "0d03f23c-9c5b-45ac-ae95-be51e9383a62",
+ // "startTime": "2025-07-09T15:28:02.832316"
+ // },
+ // {
+ // "title": "你知道今年的录取分数线吗",
+ // "id": "771ab434-348c-4a35-bd03-adebc21436d8",
+ // "startTime": "2025-07-09T15:11:32.885001"
+ // }
+ // ]
+ // }
],
activeIndex: 0,
commonQuestions: [
@@ -346,57 +352,31 @@ export default {
"我什么时候能推知道自己是否被录取?",
],
scrollToView: "",
+ // 修改后的messageGroups数据结构
messageGroups: [
// {
- // time: "2025-07-01 10:00:00",
- // messages: [
- // { id: 1, content: "学校哪些专业比较好?", isUser: true },
- // {
- // id: 2,
- // content:
- // "我校有多个优势学科,包括计算机科学与技术、电子信息工程、机械工程、生物医学工程等。这些专业在国内都有较高的学术声誉和就业率。如果您对某个具体专业感兴趣,可以进一步咨询更多详情。",
- // isUser: false,
- // },
- // { id: 3, content: "计算机科学专业就业前景如何?", isUser: true },
- // {
- // id: 4,
- // content:
- // "计算机科学专业的就业前景非常好。根据最新数据,我校计算机专业毕业生就业率超过95%,平均起薪在全校名列前茅。主要就业方向包括互联网公司、IT企业、金融科技等领域,担任软件工程师、数据分析师、产品经理等职位。",
- // isUser: false,
- // },
- // ],
+ // id: "9cac8661-bf09-4b63-ab15-1a0a36b921e0",
+ // message: "你知道今年的录取分数线吗",
+ // sendDate: "2025-07-10T15:11:32.886075",
+ // isSend: true,
+ // isRead: false,
+ // interactMode: 0,
+ // messageType: 0,
+ // timeLabel: 1,
+ // displayTime: "15:11",
// },
// {
- // time: "2025-07-02 09:15:36",
- // messages: [
- // { id: 5, content: "新生报到流程", isUser: true },
- // {
- // id: 6,
- // content:
- // "新生报到流程:个人信息完善 --预报到 -- 照片信息采集 --缴费 --入校验审 -- 人脸识别 --打印报到单",
- // isUser: false,
- // },
- // { id: 7, content: "预报到需要准备哪些材料?", isUser: true },
- // {
- // id: 8,
- // content:
- // "预报到需要准备以下材料:\n1. 身份证原件及复印件\n2. 录取通知书原件\n3. 2寸蓝底证件照4张\n4. 学籍档案(密封)\n5. 党团关系材料(如有)\n\n建议提前准备齐全,以便快速完成报到流程。",
- // isUser: false,
- // },
- // ],
+ // id: "02306fc3-c821-4a23-ad66-0bd77d154105",
+ // message:
+ // "目前,2025年的录取分数线还没有正式公布,因为学校需要等招生工作结束后才会公布最终的录取分数线。不过,你可以参考2023-2024年的录取分数线作为大致的参考。\n\n如果你想知道具体专业的录取分数线,可以告诉我你所在省份和科类(历史类或物理类),我可以根据往年数据给你一些参考建议。如果需要更准确的信息,建议关注江西新能源科技职业学院的官网(www.jxnee.edu.cn)或者直接联系招生办(电话:0790-6764666/6765666)。",
+ // sendDate: "2025-07-10T15:11:32.88644",
+ // isSend: true,
+ // isRead: false,
+ // interactMode: 1,
+ // messageType: 0,
+ // timeLabel: 0,
+ // displayTime: "",
// },
- {
- time: "",
- messages: [
- // { id: 5, content: "新生报到流程", isUser: true },
- // {
- // id: 6,
- // content:
- // "新生报到流程:个人信息完善 --预报到 -- 照片信息采集 --缴费 --入校验审 -- 人脸识别 --打印报到单",
- // isUser: false,
- // },
- ],
- },
],
scrollTop: 0,
@@ -407,109 +387,83 @@ export default {
// 确保消息更新后自动滚动到底部
messageGroups: {
handler() {
- this.$nextTick(() => {
+ // 只有在不是加载更多的情况下才滚动到底部
+ if (!this.isLoadingMore) {
this.scrollToBottom();
- });
+ }
},
deep: true,
},
+
+ vuex_user: {
+ handler(newValue) {
+ // console.log('vuex_user',newValue);
+ if (newValue && newValue.Sex === 2) {
+ // this.perfectInfoShow = true;
+ }
+ },
+ immediate: true, // 这会使得组件创建时立即执行一次handler
+ deep: true,
+ },
},
onLoad() {},
- mounted() {
- this.$nextTick(() => {
- this.scrollToBottom();
- });
- },
- updated() {
- this.$nextTick(() => {
- this.scrollToBottom();
- });
- },
methods: {
- // 测试Markdown渲染效果(仅开发阶段使用)
- testMarkdownRender() {
- const markdownSample = `# 这是标题
-
- ## 这是二级标题
-
- 这是**加粗文本**和*斜体文本*
-
- > 这是引用文本
-
- - 列表项1
- - 列表项2
- - 列表项3
-
- \`\`\`javascript
- // 这是代码块
- function hello() {
- console.log('Hello World!');
- }
- \`\`\`
-
- [这是链接](https://www.example.com)
-
- | 表头1 | 表头2 |
- | ----- | ----- |
- | 内容1 | 内容2 |
- | 内容3 | 内容4 |
- `;
-
- this.messageGroups[0].messages.push({
- id: this.messageGroups[0].messages.length + 1,
- content: "测试Markdown渲染",
- isUser: true,
- });
-
- setTimeout(() => {
- this.messageGroups[0].messages.push({
- id: `test-${Date.now()}`,
- content: markdownSample,
- isUser: false,
- });
- }, 500);
- },
-
handleLeftClick() {
- this.$refs.uToast.show({
- title: "暂未开放",
- type: "warning",
- });
+ this.$u.api.GetConversationPage().then((res) => {
+ this.chatHistoryList3 = res.data;
+ if (this.chatHistoryList3.length > 0) {
+ this.chatHistoryList3 = res.data.map((group) => {
+ // 对每个组的conversation数组进行倒序排序
+ return {
+ ...group,
+ conversation: group.conversation.sort((a, b) => {
+ // 将日期字符串转换为时间戳并比较(倒序)
+ return (
+ new Date(b.startTime).getTime() -
+ new Date(a.startTime).getTime()
+ );
+ }),
+ };
+ });
+ }
+ console.log("this.chatHistoryList3", this.chatHistoryList3);
- return;
- this.popupShow = true;
- },
- selectChatItem(index) {
- this.activeIndex = index;
- },
- loadMoreMessages() {
- // 加载更多历史消息的实现
- console.log("加载更多历史消息");
- },
- onScroll(event) {
- // 滚动事件处理
+ this.popupShow = true;
+ });
},
+
scrollToBottom() {
+ // 如果正在加载更多,不执行滚动
+ if (this.isLoadingMore) return;
+
// 使用nextTick确保DOM已更新
this.$nextTick(() => {
- // 获取滚动区域的高度
+ // setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query
.select(".chat-content")
.boundingClientRect((data) => {
if (data) {
- // 计算滚动到底部的位置
- const scrollHeight = data.height;
- // 设置滚动位置,加100px确保滚动到底部
- this.scrollTop = scrollHeight + 100;
+ console.log("chat-content高度:", data.height);
+
+ // 同时设置scrollTop和scrollToView
+ this.scrollTop = data.height + 200;
+ // if (this.messageGroups.length > 0) {
+ // const lastMessage =
+ // this.messageGroups[this.messageGroups.length - 1];
+ // this.scrollToView = "msg-" + lastMessage.id;
+ // console.log("设置scrollToView:", this.scrollToView);
+ // }
}
})
.exec();
+ // }, 300);
});
},
handleFeatureClick(item) {
if (item.title === "电话咨询") {
this.advicePhoneShow = true;
+ return;
} else {
this.$refs.uToast.show({
title: "暂未开放",
@@ -520,42 +474,271 @@ export default {
}
},
+ // 修改发送消息的方法
sendMessageFn() {
if (!this.messageValue) {
return;
}
- const sendMessage = this.messageValue;
+ // 重置加载更多标志位
+ this.isLoadingMore = false;
+ const sendMessage = this.messageValue;
console.log("发送消息", sendMessage);
- this.messageGroups[0].messages.push({
- id: this.messageGroups[0].messages.length + 1,
- content: sendMessage,
- isUser: true,
- });
+
+ // 创建新的用户消息对象
+ const userMessage = {
+ id: Math.random().toString(36).substring(2, 15),
+ message: sendMessage,
+ sendDate: "",
+ isSend: true,
+ isRead: false,
+ interactMode: 0, // 用户消息
+ messageType: 0,
+ timeLabel: 0,
+ displayTime: "",
+ };
+
+ // 添加到消息列表
+ this.messageGroups.push(userMessage);
this.messageValue = "";
+
+ // 立即添加一个AI回复的加载状态消息
+ const loadingMessage = {
+ id: "loading_" + Math.random().toString(36).substring(2, 15),
+ message: "",
+ sendDate: "",
+ isSend: true,
+ isRead: false,
+ interactMode: 1, // AI消息
+ messageType: 0,
+ timeLabel: 0,
+ displayTime: "",
+ isLoading: true // 标记为加载状态
+ };
+
+ // 添加加载状态消息到列表
+ this.messageGroups.push(loadingMessage);
this.$u.api
.SendMessageApi({
query: sendMessage,
- conversationId: "",
+ conversationId: this.currentConversationId,
})
.then((res) => {
console.log("res.....", res);
const data = res.data;
- // 处理返回内容,确保Markdown格式正确
- let content = data.content;
+ this.currentConversationId = data.conversationId;
- // 如果返回的内容没有明确的Markdown格式,可以根据需要进行一些简单处理
- // 例如:将换行符转换为Markdown换行
- // content = content.replace(/\n/g, "\n\n");
+ // 从消息列表中移除加载状态消息
+ this.messageGroups = this.messageGroups.filter(msg => !msg.isLoading);
- this.messageGroups[0].messages.push({
- id: data.conversationId,
- content: content,
- isUser: false,
+ // 创建AI回复消息对象
+ const aiMessage = {
+ id:
+ data.conversationId ||
+ Math.random().toString(36).substring(2, 15),
+ message: data.content,
+ sendDate: "",
+ isSend: true,
+ isRead: false,
+ interactMode: 1, // AI消息
+ messageType: 0,
+ timeLabel: 0,
+ displayTime: "",
+ };
+
+ // 添加到消息列表
+ this.messageGroups.push(aiMessage);
+ })
+ .catch((error) => {
+ console.error("API请求失败:", error);
+
+ // 从消息列表中移除加载状态消息
+ this.messageGroups = this.messageGroups.filter(msg => !msg.isLoading);
+
+ // 添加错误消息
+ const errorMessage = {
+ id: "error_" + Math.random().toString(36).substring(2, 15),
+ message: "请求失败,请稍后重试",
+ sendDate: "",
+ isSend: true,
+ isRead: false,
+ interactMode: 1, // AI消息
+ messageType: 0,
+ timeLabel: 0,
+ displayTime: "",
+ };
+
+ this.messageGroups.push(errorMessage);
+ });
+ },
+
+ // 格式化时间的辅助方法
+ formatTime(date) {
+ const hours = date.getHours().toString().padStart(2, "0");
+ const minutes = date.getMinutes().toString().padStart(2, "0");
+ return `${hours}:${minutes}`;
+ },
+
+ // 处理选中的对话
+ handleSelectConversation(data) {
+ // 关闭弹窗
+ this.popupShow = false;
+
+ console.log("选中的对话:", data);
+ // 根据conversationId加载对应的对话内容
+ this.currentConversationId = data.conversationId;
+
+ this.isChat = true;
+
+ // 设置切换对话标志位,防止触发上拉刷新
+ this.isSwitchingConversation = true;
+
+ // 重置消息列表和分页相关状态
+ this.messageGroups = [];
+ this.pageQuery.PageIndex = 1;
+ this.isLoadingMore = false;
+ this.noMoreData = false;
+
+ this.$u.api
+ .GetConversationDetail({
+ "Item1.Id": this.currentConversationId,
+ PageIndex: this.pageQuery.PageIndex,
+ PageSize: this.pageQuery.PageSize,
+ })
+ .then((res) => {
+ console.log("GetConversationDetail.....", res.item2);
+
+ // 将消息按sendDate升序排列,时间相同时用户消息(interactMode=0)排在前面
+ this.messageGroups = res.item2.sort((a, b) => {
+ const timeA = new Date(a.sendDate).getTime();
+ const timeB = new Date(b.sendDate).getTime();
+
+ // 如果时间相同,按interactMode排序(0排在前,1排在后)
+ if (timeA === timeB) {
+ return a.interactMode - b.interactMode;
+ }
+
+ // 否则按时间升序排列
+ return timeA - timeB;
});
+ })
+ .finally(() => {
+ // 延迟重置切换对话标志位,确保滚动事件处理完成
+ setTimeout(() => {
+ this.isSwitchingConversation = false;
+ }, 500);
+ });
+ },
+
+ // 在开始新对话或选择对话时重置相关状态
+ handleCreateConversation() {
+ // 关闭弹窗
+ this.popupShow = false;
+ this.isChat = true;
+
+ // 设置切换对话标志位,防止触发上拉刷新
+ this.isSwitchingConversation = true;
+
+ this.currentConversationId = "";
+ this.messageGroups = [];
+
+ // 重置分页和加载状态
+ this.pageQuery.PageIndex = 1;
+ this.isLoadingMore = false;
+ this.noMoreData = false;
+
+ // 延迟重置切换对话标志位
+ setTimeout(() => {
+ this.isSwitchingConversation = false;
+ }, 500);
+ },
+
+ // 开始新对话
+ handleStartChat() {
+ this.isChat = true;
+
+ // 设置切换对话标志位,防止触发上拉刷新
+ this.isSwitchingConversation = true;
+
+ this.currentConversationId = "";
+ this.messageGroups = [];
+
+ // 重置分页和加载状态
+ this.pageQuery.PageIndex = 1;
+ this.isLoadingMore = false;
+ this.noMoreData = false;
+
+ // 延迟重置切换对话标志位
+ setTimeout(() => {
+ this.isSwitchingConversation = false;
+ }, 500);
+ },
+
+ // 滚动到底部事件处理
+ onScrollToLower() {
+ console.log("已滚动到底部");
+ },
+
+ // 滚动到顶部事件处理
+ onScrollToUpper() {
+ console.log("触发上拉刷新");
+
+ // 如果已经没有更多数据或正在切换对话,不再触发上拉刷新
+ if (this.noMoreData || this.isSwitchingConversation) {
+ return;
+ }
+
+ // 设置加载标志位为true,防止触发滚动到底部
+ this.isLoadingMore = true;
+ this.isLoading = true;
+
+ this.pageQuery.PageIndex++;
+ this.$u.api
+ .GetConversationDetail({
+ "Item1.Id": this.currentConversationId,
+ PageIndex: this.pageQuery.PageIndex,
+ PageSize: this.pageQuery.PageSize,
+ })
+ .then((res) => {
+ console.log("GetConversationDetail.....", res.item2);
+
+ // 如果返回的数据为空数组,说明没有更多历史消息了
+ if (!res.item2 || res.item2.length === 0) {
+ this.noMoreData = true; // 设置标记,不再触发上拉刷新
+ this.$refs.uToast.show({
+ title: "已经到顶了",
+ type: "warning",
+ duration: 1500
+ });
+ return;
+ }
+
+ // 将消息按sendDate升序排列,时间相同时用户消息(interactMode=0)排在前面
+ const nextPageMessageGroups = res.item2.sort((a, b) => {
+ const timeA = new Date(a.sendDate).getTime();
+ const timeB = new Date(b.sendDate).getTime();
+
+ // 如果时间相同,按interactMode排序(0排在前,1排在后)
+ if (timeA === timeB) {
+ return a.interactMode - b.interactMode;
+ }
+
+ // 否则按时间升序排列
+ return timeA - timeB;
+ });
+
+ this.messageGroups = [
+ ...nextPageMessageGroups,
+ ...this.messageGroups,
+ ];
+ })
+ .finally(() => {
+ setTimeout(() => {
+ this.isLoading = false;
+ }, 300);
});
},
},
@@ -563,15 +746,6 @@ export default {
diff --git a/pages/home/userInfo/index.vue b/pages/home/userInfo/index.vue
index 5e29740..2767430 100644
--- a/pages/home/userInfo/index.vue
+++ b/pages/home/userInfo/index.vue
@@ -21,7 +21,7 @@
姓名
- {{ userInfo.name }}
+
性别
- 请选择性别
- {{ userInfo.gender }}
+ {{ userInfo.genderText }}
>
@@ -106,9 +111,26 @@
+
+
+
+
+
+
diff --git a/pages/login/login/index.vue b/pages/login/login/index.vue
index 45d9594..83df457 100644
--- a/pages/login/login/index.vue
+++ b/pages/login/login/index.vue
@@ -37,14 +37,14 @@
-
- 手机号
+ 手机号
+
- 密码
+ />
+ 密码
+
-
+
- 图形验证码
+ 图形验证码
+
-
+
- 验证码
+ 验证码
+
{ //用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器
- uni.addInterceptor(item, {
- invoke(e) { // 调用前拦截
- //获取用户的token
- // console.log(e)
- const token = that.vuex_token,
- //获取要跳转的页面路径(url去掉"?"和"?"后的参数)
- url = e.url.split('?')[0];
- let notNeed = config.whiteList.includes(url)
- // 如果在whiteList里面就不需要登录
- // console.log(notNeed)
+import config from "./config.js";
+const initApp = function (vm) {
+ /**
+ * 页面跳转拦截器
+ */
+ let that = vm;
+ let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
+ list.forEach((item) => {
+ //用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器
+ uni.addInterceptor(item, {
+ invoke(e) {
+ // 调用前拦截
+ //获取用户的token
+ // console.log(e)
+ const token = that.vuex_token,
+ //获取要跳转的页面路径(url去掉"?"和"?"后的参数)
+ url = e.url.split("?")[0];
+ let notNeed = config.whiteList.includes(url);
+ // 如果在whiteList里面就不需要登录
+ // console.log(notNeed)
- if (notNeed) {
- return e
- } else {
- //需要登录
- if (token == '') {
- // uni.showToast({
- // title: '请先登录',
- // icon: 'none'
- // })
- // uni.navigateTo({
- // url: config.loginPage
- // })
- uni.navigateTo({
- url: '/'
- })
- return false
- } else {
- return e
- }
- }
-
- },
- fail(err) { // 失败回调拦截
- console.log(err)
- // if (Debug) {
- // uni.showModal({
- // content: JSON.stringify(err),
- // showCancel: false
- // });
- // }
- }
- })
- })
-}
+ if (notNeed) {
+ return e;
+ } else {
+ //需要登录
+ if (token == "") {
+ // uni.showToast({
+ // title: '请先登录',
+ // icon: 'none'
+ // })
+ // uni.navigateTo({
+ // url: config.loginPage
+ // })
+ uni.navigateTo({
+ url: "/pages/login/login/index",
+ });
+ return false;
+ } else {
+ return e;
+ }
+ }
+ },
+ fail(err) {
+ // 失败回调拦截
+ console.log(err);
+ // if (Debug) {
+ // uni.showModal({
+ // content: JSON.stringify(err),
+ // showCancel: false
+ // });
+ // }
+ },
+ });
+ });
+};
export default {
- initApp: initApp
-}
\ No newline at end of file
+ initApp: initApp,
+};
diff --git a/store/index.js b/store/index.js
index 31894b6..4570dda 100644
--- a/store/index.js
+++ b/store/index.js
@@ -13,7 +13,8 @@ try {
}
// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
-let saveStateKeys = ['vuex_user', 'vuex_token', 'vuex_msgList', 'vuex_glyType', 'vuex_userLocation','vuex_userInfo','vuex_user_hobby','vuex_teacherInfo'];
+// let saveStateKeys = ['vuex_user', 'vuex_token', 'vuex_msgList', 'vuex_glyType', 'vuex_userLocation','vuex_userInfo','vuex_user_hobby'];
+let saveStateKeys = ['vuex_user', 'vuex_token'];
// 保存变量到本地存储中
const saveLifeData = function (key, value) {
diff --git a/uview-ui/libs/request/index.js b/uview-ui/libs/request/index.js
index 1f5f471..cdd50f7 100644
--- a/uview-ui/libs/request/index.js
+++ b/uview-ui/libs/request/index.js
@@ -26,6 +26,9 @@ class Request {
options.header = Object.assign({}, this.config.header, options.header);
options.method = options.method || this.config.method;
+ // 从请求选项中获取showLoading参数,如果未定义,则使用全局配置
+ const showLoading = options.header.showLoading !== undefined ? options.header.showLoading : this.config.showLoading;
+
return new Promise((resolve, reject) => {
options.complete = (response) => {
// 请求返回后,隐藏loading(如果请求返回快的话,可能会没有loading)
@@ -81,7 +84,7 @@ class Request {
// 是否显示loading
// 加一个是否已有timer定时器的判断,否则有两个同时请求的时候,后者会清除前者的定时器id
// 而没有清除前者的定时器,导致前者超时,一直显示loading
- if(this.config.showLoading && !this.config.timer) {
+ if(showLoading && !this.config.timer) {
this.config.timer = setTimeout(() => {
uni.showLoading({
title: this.config.loadingText,
diff --git a/vue.config.js b/vue.config.js
index 9d4aa7b..d0f6b36 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -4,8 +4,8 @@ module.exports = {
devServer: {
// 调试时允许内网穿透,让外网的人访问到本地调试的H5页面
disableHostCheck: true,
- port: '8080',
+ port: '8088',
}
},
//productionSourceMap: false,
-}
+}