Compare commits

..

No commits in common. "main" and "main-ai对话" have entirely different histories.

367 changed files with 13970 additions and 3410 deletions

View File

@ -34,8 +34,6 @@ export default {
}
},
});
//
return
if (!that.vuex_token) {
const type = that.vuex_userType || 0; // 0: 1:

View File

@ -48,12 +48,11 @@ const install = (Vue, vm) => {
Vue.prototype.$u.http.interceptor.response = (res) => {
// uni.hideLoading();
// 先注释
// 检查是否为401未授权错误
// if (res.statusCode === 401) {
// handleAuthError(vm);
// return false;
// }
if (res.statusCode === 401) {
handleAuthError(vm);
return false;
}
return res.data;

117
components/NoData copy.vue Normal file
View File

@ -0,0 +1,117 @@
<template>
<view class="empty-container">
<image :src="getImage" class="empty-image" mode="widthFix"></image>
<text class="empty-text">{{ text }}</text>
</view>
</template>
<script>
//
import noDataDefault from "@/static/common/img/noData/no-data.png";
//
import noDataSearch from "@/static/common/img/noData/no-search.png";
//
import noDataMessage from "@/static/common/img/noData/no-message.png";
//
import errorNet from "@/static/common/img/noData/error-net.png";
//
import errorData from "@/static/common/img/noData/error-data.png";
//
import maintenance from "@/static/common/img/noData/maintenance.png";
//
import noAttachmentResume from "@/static/common/img/noData/no-attachment-resume.png";
//
import noCollection from "@/static/common/img/noData/no-collection.png";
//
import noComments from "@/static/common/img/noData/no-comments.png";
//
import noContent from "@/static/common/img/noData/no-content.png";
//
import noFile from "@/static/common/img/noData/no-file.png";
//
import noFriends from "@/static/common/img/noData/no-friends.png";
//
import noInternet from "@/static/common/img/noData/no-internet.png";
//
import noPermissions from "@/static/common/img/noData/no-permissions.png";
//
import noPicture from "@/static/common/img/noData/no-picture.png";
export default {
name: "NoData",
props: {
//
type: {
type: String,
default: "default",
},
// ""
text: {
type: String,
default: "暂无数据",
},
},
computed: {
getImage() {
const imageMap = {
default: noDataDefault,
search: noDataSearch,
message: noDataMessage,
errorNet: errorNet,
errorData: errorData,
maintenance: maintenance,
resume: noAttachmentResume,
collection: noCollection,
comments: noComments,
content: noContent,
file: noFile,
friends: noFriends,
internet: noInternet,
permissions: noPermissions,
picture: noPicture,
};
return imageMap[this.type] || imageMap.default;
},
},
data: {
/*
<no-data type="default" text="暂无数据" />
<no-data type="errorNet" text="网络错误" />
<no-data type="search" text="暂无搜索内容" />
<no-data type="errorData" text="数据加载失败" />
<no-data type="message" text="暂无消息" />
<no-data type="maintenance" text="系统维护中" />
<no-data type="resume" text="暂无简历附件" />
<no-data type="collection" text="暂无收藏" />
<no-data type="comments" text="暂无评论" />
<no-data type="content" text="暂无内容" />
<no-data type="file" text="暂无文件" />
<no-data type="friends" text="暂无好友" />
<no-data type="internet" text="无网络连接" />
<no-data type="permissions" text="暂无权限" />
<no-data type="picture" text="暂无图片" /> */
},
};
</script>
<style scoped>
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx;
text-align: center;
}
.empty-image {
width: 400rpx;
height: 400rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 32rpx;
color: #000;
}
</style>

View File

@ -1,167 +0,0 @@
<template>
<u-tabbar
v-if="show"
:value="currentIndex"
:show="true"
:bg-color="style.backgroundColor"
:border-top="style.borderTop"
:fixed="true"
:height="style.height"
:list="formattedTabList"
@change="handleTabChange"
>
</u-tabbar>
</template>
<script>
import tabbarConfig from "@/config/tabbar.config.js";
export default {
name: "TabBar",
props: {
//
currentPath: {
type: String,
default: "",
},
// TabBar
show: {
type: Boolean,
default: true,
},
},
data() {
return {
currentIndex: 0,
tabList: tabbarConfig.tabs,
style: tabbarConfig.style,
};
},
computed: {
// uview tabbar
formattedTabList() {
return this.tabList.map((item) => ({
text: item.text,
iconPath: item.icon,
selectedIconPath: item.activeIcon,
pagePath: item.pagePath,
count: item.badge || 0,
isDot: item.dot || false,
}));
},
},
watch: {
// currentPath
currentPath: {
handler(newPath) {
if (newPath) {
this.updateActiveTab(newPath);
}
},
immediate: true,
},
},
mounted() {
this.initActiveTab();
},
methods: {
/**
* 初始化激活的 tab
*/
initActiveTab() {
const path = this.currentPath || this.getCurrentPagePath();
this.updateActiveTab(path);
},
/**
* 更新激活的 tab
*/
updateActiveTab(path) {
const index = this.tabList.findIndex((item) =>
this.isPathMatch(path, item.pagePath)
);
if (index !== -1) {
this.currentIndex = index;
}
},
/**
* 路径匹配判断
*/
isPathMatch(currentPath, targetPath) {
const normalize = (path) => path.replace(/^\//, "");
return normalize(currentPath) === normalize(targetPath);
},
/**
* 获取当前页面路径
*/
getCurrentPagePath() {
const pages = getCurrentPages();
console.log('pages',pages);
if (pages.length > 0) {
return "/" + pages[pages.length - 1].route;
}
return "";
},
/**
* 处理 tab 切换
*/
handleTabChange(index) {
if (this.currentIndex === index) {
this.$emit("reclick", this.tabList[index].pagePath, index);
return;
}
const targetTab = this.tabList[index];
if (!targetTab) return;
this.currentIndex = index;
this.$emit("change", targetTab.pagePath, index);
//
this.navigateToTab(targetTab.pagePath);
},
/**
* 跳转到 tab 页面
*/
navigateToTab(url) {
uni.switchTab({
url,
fail: () => {
//
uni.reLaunch({ url });
},
});
},
/**
* 设置角标
* @param {number} index - tab 索引
* @param {string|number} badge - 角标内容
*/
setBadge(index, badge) {
if (this.tabList[index]) {
this.$set(this.tabList[index], "badge", badge);
}
},
/**
* 显示/隐藏小红点
* @param {number} index - tab 索引
* @param {boolean} show - 是否显示
*/
setDot(index, show) {
if (this.tabList[index]) {
this.$set(this.tabList[index], "dot", show);
}
},
},
};
</script>
<style scoped lang="scss"></style>

View File

@ -1,47 +0,0 @@
/**
* TabBar 配置文件
* 集中管理底部导航栏配置
*/
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-icon4.png",
activeIcon: "/static/tabbar/tabbar-icon4-active.png",
pagePath: "/pages/transfer/index",
badge: "",
dot: false,
},
{
text: "我的",
icon: "/static/tabbar/tabbar-icon3.png",
activeIcon: "/static/tabbar/tabbar-icon3-active.png",
pagePath: "/pages/my/index",
badge: "",
dot: false,
},
];
// TabBar 样式配置
export const TAB_BAR_STYLE = {
color: "#999999", // 未激活颜色
selectedColor: "#4a6cf7", // 激活颜色
backgroundColor: "#ffffff", // 背景色
borderTop: false, // 是否显示顶部边框
height: 148, // 高度rpx
};
export default {
tabs: TAB_BAR_CONFIG,
style: TAB_BAR_STYLE,
};

View File

@ -1,100 +0,0 @@
/**
* WebSocket 配置文件
*
* 集中管理 WebSocket 相关配置
*/
// 根据环境选择 WebSocket 地址
const getWebSocketUrl = () => {
// #ifdef H5
// H5 开发环境
if (process.env.NODE_ENV === 'development') {
return 'ws://localhost:8082/ws/chat'
}
// H5 生产环境
return 'wss://120.55.234.65:8082/ws/chat'
// #endif
// #ifdef MP-WEIXIN
// 小程序必须使用 wss加密连接
return 'wss://120.55.234.65:8082/ws/chat'
// #endif
// #ifdef APP-PLUS
// App 可以使用 ws 或 wss
return 'wss://120.55.234.65:8082/ws/chat'
// #endif
// 默认地址
return 'ws://120.55.234.65:8082/ws/chat'
}
export default {
// WebSocket 服务器地址(动态获取)
url: getWebSocketUrl(),
// 重连配置
reconnect: {
enabled: true, // 是否启用自动重连
maxAttempts: 10, // 最大重连次数
interval: 3000, // 重连间隔(毫秒)
incrementInterval: true, // 是否递增重连间隔
maxInterval: 30000 // 最大重连间隔
},
// 心跳配置
heartbeat: {
enabled: true, // 是否启用心跳
interval: 30000, // 心跳间隔(毫秒)
timeout: 10000, // 心跳超时时间
pingMessage: { // 心跳消息格式
type: 'ping',
timestamp: () => Date.now()
}
},
// 消息队列配置
messageQueue: {
enabled: true, // 是否启用消息队列
maxSize: 100, // 队列最大长度
clearOnConnect: false // 连接成功后是否清空队列
},
// 日志配置
log: {
enabled: true, // 是否启用日志
level: 'info' // 日志级别debug, info, warn, error
},
// 消息类型定义
messageTypes: {
// 文字消息
TEXT: 'text',
// 图片消息
IMAGE: 'image',
// 语音消息
VOICE: 'voice',
// 系统消息
SYSTEM: 'system',
// 心跳
PING: 'ping',
PONG: 'pong',
// 已读回执
READ: 'read',
// 输入中
TYPING: 'typing',
// 人工转接
TRANSFER: 'transfer'
},
// 事件名称定义
events: {
OPEN: 'open',
CLOSE: 'close',
ERROR: 'error',
MESSAGE: 'message',
RECONNECT: 'reconnect',
RECONNECT_FAILED: 'reconnect_failed'
}
}

View File

@ -1,607 +0,0 @@
# WebSocket 使用示例
## 📦 已创建的文件
`/utils/websocket-manager.js` - WebSocket 管理器(已完成)
---
## 🚀 快速开始
### 1. 在 App.vue 中初始化连接
```vue
<script>
import wsManager from '@/utils/websocket-manager.js'
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['vuex_token', 'vuex_user'])
},
onLaunch() {
console.log('App Launch')
// 如果已登录,连接 WebSocket
if (this.vuex_token) {
this.connectWebSocket()
}
},
methods: {
connectWebSocket() {
// 替换为你的 WebSocket 服务器地址
const wsUrl = `wss://your-server.com/chat?token=${this.vuex_token}`
wsManager.connect(wsUrl, {
reconnectInterval: 3000, // 重连间隔 3 秒
maxReconnectAttempts: 10, // 最多重连 10 次
heartbeatInterval: 30000 // 心跳间隔 30 秒
})
// 监听连接成功
wsManager.on('open', () => {
console.log('[App] WebSocket 连接成功')
uni.showToast({
title: '已连接',
icon: 'success'
})
})
// 监听连接关闭
wsManager.on('close', (res) => {
console.log('[App] WebSocket 已关闭', res)
})
// 监听连接错误
wsManager.on('error', (err) => {
console.error('[App] WebSocket 错误', err)
uni.showToast({
title: '连接失败',
icon: 'none'
})
})
// 监听重连
wsManager.on('reconnect', (attempts) => {
console.log(`[App] 正在重连 (${attempts})`)
})
// 监听消息(全局消息处理)
wsManager.on('message', (data) => {
console.log('[App] 收到消息:', data)
// 根据消息类型处理
switch (data.type) {
case 'message':
// 新消息
this.handleNewMessage(data)
break
case 'notification':
// 系统通知
this.handleNotification(data)
break
default:
console.log('[App] 未知消息类型:', data.type)
}
})
},
handleNewMessage(data) {
// 更新 Vuex 中的消息列表
this.$store.commit('addMessage', data)
// 显示新消息提示
uni.showTabBarRedDot({
index: 1 // 消息页的 tabBar 索引
})
},
handleNotification(data) {
uni.showToast({
title: data.content,
icon: 'none'
})
}
},
onHide() {
// App 进入后台,保持连接
console.log('App Hide')
},
onShow() {
// App 从后台回到前台
console.log('App Show')
// 如果连接已断开,重新连接
const state = wsManager.getState()
if (!state.isConnected && !state.isConnecting) {
this.connectWebSocket()
}
}
}
</script>
```
---
### 2. 在聊天页面中使用
#### 方式 A修改现有的 `dialogBox.vue`
```vue
<template>
<view>
<!-- 消息列表 -->
<scroll-view
:scroll-top="scrollTop"
:scroll-with-animation="true"
class="scroll"
scroll-y="true"
>
<view v-for="(msg, index) in messageList" :key="index" class="message-item">
<!-- 我方消息 -->
<view v-if="msg.fromUserId == vuex_user.id" class="my-message">
<text>{{ msg.content }}</text>
</view>
<!-- 对方消息 -->
<view v-else class="other-message">
<text>{{ msg.content }}</text>
</view>
</view>
</scroll-view>
<!-- 输入框 -->
<view class="input-box">
<input
v-model="inputValue"
placeholder="请输入消息"
@confirm="sendMessage"
/>
<button @click="sendMessage">发送</button>
</view>
</view>
</template>
<script>
import wsManager from '@/utils/websocket-manager.js'
import { mapState } from 'vuex'
export default {
data() {
return {
messageList: [],
inputValue: '',
scrollTop: 0,
targetUserId: '', // 对方用户 ID
messageHandler: null
}
},
computed: {
...mapState(['vuex_user'])
},
onLoad(options) {
// 获取对方用户 ID
this.targetUserId = options.userId || ''
// 加载历史消息
this.loadHistoryMessages()
// 监听新消息
this.messageHandler = (data) => {
// 只处理与当前聊天对象相关的消息
if (data.fromUserId === this.targetUserId || data.toUserId === this.targetUserId) {
this.messageList.push({
fromUserId: data.fromUserId,
toUserId: data.toUserId,
content: data.content,
timestamp: data.timestamp
})
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom()
})
// 发送已读回执
this.sendReadReceipt(data.messageId)
}
}
wsManager.on('message', this.messageHandler)
},
onUnload() {
// 移除消息监听
if (this.messageHandler) {
wsManager.off('message', this.messageHandler)
}
},
methods: {
// 加载历史消息
async loadHistoryMessages() {
try {
const res = await this.$u.api.getMessages({
userId: this.targetUserId,
page: 1,
pageSize: 50
})
this.messageList = res.data || []
this.$nextTick(() => {
this.scrollToBottom()
})
} catch (e) {
console.error('加载历史消息失败:', e)
}
},
// 发送消息
sendMessage() {
if (!this.inputValue.trim()) {
uni.showToast({
title: '请输入消息',
icon: 'none'
})
return
}
// 通过 WebSocket 发送
wsManager.send({
type: 'message',
fromUserId: this.vuex_user.id,
toUserId: this.targetUserId,
content: this.inputValue,
timestamp: Date.now()
})
// 立即显示到界面(乐观更新)
this.messageList.push({
fromUserId: this.vuex_user.id,
toUserId: this.targetUserId,
content: this.inputValue,
timestamp: Date.now(),
sending: true // 标记为发送中
})
// 清空输入框
this.inputValue = ''
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom()
})
},
// 发送已读回执
sendReadReceipt(messageId) {
wsManager.send({
type: 'read',
messageId: messageId,
userId: this.vuex_user.id
})
},
// 滚动到底部
scrollToBottom() {
this.scrollTop = 99999
}
}
}
</script>
```
---
### 3. 在 Vuex 中管理 WebSocket 状态(可选)
```javascript
// store/index.js
export default new Vuex.Store({
state: {
// ... 其他状态
wsConnected: false,
unreadCount: 0,
messages: []
},
mutations: {
// WebSocket 连接状态
SET_WS_CONNECTED(state, connected) {
state.wsConnected = connected
},
// 新消息
ADD_MESSAGE(state, message) {
state.messages.push(message)
state.unreadCount++
},
// 清空未读
CLEAR_UNREAD(state) {
state.unreadCount = 0
}
},
actions: {
// 在 App.vue 中调用
wsConnected({ commit }) {
commit('SET_WS_CONNECTED', true)
},
wsDisconnected({ commit }) {
commit('SET_WS_CONNECTED', false)
}
}
})
```
---
## 📋 消息协议设计
### 客户端 → 服务器
#### 1. 发送文字消息
```json
{
"type": "message",
"fromUserId": "user123",
"toUserId": "user456",
"content": "你好",
"timestamp": 1635678901234
}
```
#### 2. 发送心跳
```json
{
"type": "ping",
"timestamp": 1635678901234
}
```
#### 3. 发送已读回执
```json
{
"type": "read",
"messageId": "msg123",
"userId": "user123"
}
```
---
### 服务器 → 客户端
#### 1. 推送消息
```json
{
"type": "message",
"messageId": "msg123",
"fromUserId": "user456",
"toUserId": "user123",
"content": "你好",
"timestamp": 1635678901234
}
```
#### 2. 心跳响应
```json
{
"type": "pong",
"timestamp": 1635678901234
}
```
#### 3. 系统通知
```json
{
"type": "notification",
"content": "系统维护通知",
"timestamp": 1635678901234
}
```
#### 4. 错误消息
```json
{
"type": "error",
"code": 401,
"message": "未授权",
"timestamp": 1635678901234
}
```
---
## 🔧 后端实现参考
### Node.js + ws
```javascript
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
// 存储所有连接(用户 ID -> WebSocket
const clients = new Map()
wss.on('connection', (ws, req) => {
// 从 URL 获取 token
const token = new URL(req.url, 'http://localhost').searchParams.get('token')
// 验证 token获取用户 ID
const userId = verifyToken(token)
if (!userId) {
ws.close(4001, '未授权')
return
}
// 保存连接
clients.set(userId, ws)
console.log(`用户 ${userId} 已连接`)
// 处理消息
ws.on('message', (message) => {
const data = JSON.parse(message)
switch (data.type) {
case 'message':
// 转发消息
handleMessage(data)
break
case 'ping':
// 心跳响应
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }))
break
case 'read':
// 已读回执
handleReadReceipt(data)
break
}
})
// 处理断开
ws.on('close', () => {
clients.delete(userId)
console.log(`用户 ${userId} 已断开`)
})
})
// 转发消息
function handleMessage(data) {
const { toUserId, fromUserId, content } = data
// 保存到数据库
saveMessage({ fromUserId, toUserId, content })
// 推送给接收者
const targetWs = clients.get(toUserId)
if (targetWs && targetWs.readyState === WebSocket.OPEN) {
targetWs.send(JSON.stringify({
type: 'message',
messageId: generateId(),
fromUserId,
toUserId,
content,
timestamp: Date.now()
}))
}
}
```
---
## 📊 完整流程图
```
┌─────────────┐
│ App 启动 │
└──────┬──────┘
┌─────────────┐
│ 检查登录状态 │
└──────┬──────┘
▼ (已登录)
┌─────────────┐
│ 连接 WS │ ← wsManager.connect()
└──────┬──────┘
┌─────────────┐
│ 监听消息 │ ← wsManager.on('message')
└──────┬──────┘
├─→ (收到消息) → 更新 UI
└─→ (发送消息) → wsManager.send()
┌─────────────┐
│ 服务器处理 │
└──────┬──────┘
┌─────────────┐
│ 推送给对方 │
└─────────────┘
```
---
## ✅ 测试清单
### 功能测试
- [ ] 连接成功
- [ ] 发送消息
- [ ] 接收消息
- [ ] 心跳正常
- [ ] 手动断开
- [ ] 自动重连
- [ ] 消息队列
### 场景测试
- [ ] App 切换到后台
- [ ] App 从后台恢复
- [ ] 网络断开
- [ ] 网络恢复
- [ ] 服务器重启
- [ ] 并发多人聊天
### 性能测试
- [ ] 消息延迟 < 200ms
- [ ] 重连时间 < 5s
- [ ] 内存占用正常
- [ ] CPU 占用正常
---
## 🎉 总结
现在你已经有:
1. ✅ **WebSocket 管理器** (`utils/websocket-manager.js`)
- 自动重连
- 心跳检测
- 消息队列
- 事件监听
2. ✅ **使用示例**
- App.vue 初始化
- 聊天页面使用
- Vuex 状态管理
3. ✅ **消息协议**
- 客户端 → 服务器
- 服务器 → 客户端
4. ✅ **后端参考**
- Node.js + ws 实现
---
## 🚀 下一步
1. **确认后端技术栈**
- 告诉我你用什么后端,我帮你写完整的服务端代码
2. **集成到现有聊天页面**
- 我帮你修改 `dialogBox.vue`
3. **添加更多功能**
- 图片消息
- 语音消息
- 消息已读
- 输入中状态
随时告诉我需要什么!🎯

View File

@ -1,616 +0,0 @@
# WebSocket 实施计划
## 🎯 项目信息
- **项目名称**英星AI - 浙江科技大学招生咨询系统
- **技术栈**uni-app + Vue 2
- **后端**ASP.NET Core推测
- **方案选择**WebSocket 原生方案 ✅
---
## 📋 实施步骤
### 阶段一:前端准备(已完成 ✅)
#### 1. WebSocket 管理器
- ✅ 文件:`utils/websocket-manager.js`
- ✅ 功能:自动重连、心跳检测、消息队列、事件监听
#### 2. WebSocket 配置
- ✅ 文件:`config/websocket.config.js`
- ✅ 功能:集中管理 WebSocket 配置
#### 3. 使用文档
- ✅ 文件:`docs/WebSocket使用示例.md`
- ✅ 文件:`docs/实时通讯方案对比.md`
- ✅ 文件:`docs/实时通讯快速选型.md`
---
### 阶段二:后端开发(待实施 ⏳)
#### 选项 AASP.NET Core WebSocket 服务(推荐)
**时间**3-5 天
**工作量:**
1. 创建 WebSocket 中间件1 天)
2. 实现消息路由1 天)
3. 集成用户认证1 天)
4. 消息持久化1 天)
5. 测试调试1 天)
**文件清单:**
- `WebSocketMiddleware.cs` - WebSocket 中间件
- `WebSocketConnectionManager.cs` - 连接管理器
- `WebSocketMessageHandler.cs` - 消息处理器
- `ChatController.cs` - HTTP API获取历史消息
- `MessageRepository.cs` - 消息存储
**后端代码**:见下文 `.NET Core WebSocket 服务端代码`
#### 选项 B使用现有 SignalR备选
你的项目已经有 SignalR
```javascript
// main.js
var connection = new HubConnectionBuilder()
.withUrl("http://sl.vrgon.com:8003/ChatHub")
.build();
```
**建议:**
- 如果 SignalR 服务正常,可以继续使用
- 如果需要切换到原生 WebSocket按选项 A 实施
---
### 阶段三前端集成2-3 天)
#### 1. 在 App.vue 初始化 WebSocket
**文件**`App.vue`
```javascript
import wsManager from '@/utils/websocket-manager.js'
import wsConfig from '@/config/websocket.config.js'
export default {
onLaunch() {
// 登录后连接 WebSocket
if (this.vuex_token) {
this.connectWebSocket()
}
},
methods: {
connectWebSocket() {
// 构建 WebSocket URL带 token
const wsUrl = `${wsConfig.url}?token=${this.vuex_token}`
// 连接
wsManager.connect(wsUrl, {
reconnectInterval: wsConfig.reconnect.interval,
maxReconnectAttempts: wsConfig.reconnect.maxAttempts,
heartbeatInterval: wsConfig.heartbeat.interval
})
// 监听事件
this.setupWebSocketListeners()
},
setupWebSocketListeners() {
// 连接成功
wsManager.on('open', () => {
console.log('[App] WebSocket 已连接')
})
// 收到消息
wsManager.on('message', (data) => {
this.handleWebSocketMessage(data)
})
// 连接关闭
wsManager.on('close', () => {
console.log('[App] WebSocket 已断开')
})
},
handleWebSocketMessage(data) {
// 根据消息类型处理
switch (data.type) {
case 'message':
// 新消息提醒
this.$store.commit('addMessage', data)
break
case 'system':
// 系统通知
uni.showToast({ title: data.content, icon: 'none' })
break
}
}
}
}
```
#### 2. 修改聊天页面
**文件**`pages/message/dialogBox/dialogBox.vue`
需要:
1. 导入 WebSocket 管理器
2. 监听实时消息
3. 发送消息通过 WebSocket
4. 加载历史消息通过 HTTP API
**代码示例**:见 `docs/WebSocket使用示例.md`
#### 3. 添加聊天相关 API
**文件**`common/http.api.js`
```javascript
// 获取聊天历史记录
let getChatHistory = (params = {}) => vm.$u.get('api/Chat/GetHistory', params);
// 发送离线消息WebSocket 断开时使用)
let sendOfflineMessage = (params = {}) => vm.$u.post('api/Chat/SendOffline', params);
// 标记消息已读
let markMessageRead = (params = {}) => vm.$u.post('api/Chat/MarkRead', params);
```
---
### 阶段四测试验证2-3 天)
#### 1. 功能测试
- [ ] 连接建立
- [ ] 发送文字消息
- [ ] 接收文字消息
- [ ] 心跳保持
- [ ] 自动重连
- [ ] 离线消息推送
- [ ] 消息已读回执
#### 2. 场景测试
- [ ] 单人聊天
- [ ] 多人并发
- [ ] 网络断开
- [ ] 网络恢复
- [ ] App 切换后台
- [ ] App 恢复前台
- [ ] 登录/登出
#### 3. 性能测试
- [ ] 消息延迟 < 200ms
- [ ] 重连时间 < 5s
- [ ] 内存占用正常
- [ ] CPU 占用正常
- [ ] 100+ 并发用户
#### 4. 兼容性测试
- [ ] H5Chrome、Safari
- [ ] 微信小程序
- [ ] Android App
- [ ] iOS App
---
## 🔧 后端实现
### .NET Core WebSocket 服务端代码
#### 1. WebSocket 中间件
**文件**`WebSocketMiddleware.cs`
```csharp
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
public class WebSocketMiddleware
{
private readonly RequestDelegate _next;
private readonly WebSocketConnectionManager _connectionManager;
public WebSocketMiddleware(RequestDelegate next, WebSocketConnectionManager connectionManager)
{
_next = next;
_connectionManager = connectionManager;
}
public async Task InvokeAsync(HttpContext context)
{
// 检查是否是 WebSocket 请求
if (!context.WebSockets.IsWebSocketRequest)
{
await _next(context);
return;
}
// 验证 token
var token = context.Request.Query["token"].ToString();
var userId = ValidateToken(token);
if (string.IsNullOrEmpty(userId))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized");
return;
}
// 接受 WebSocket 连接
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
// 保存连接
_connectionManager.AddConnection(userId, webSocket);
Console.WriteLine($"[WebSocket] 用户 {userId} 已连接");
try
{
await HandleWebSocketAsync(userId, webSocket);
}
finally
{
// 移除连接
_connectionManager.RemoveConnection(userId);
Console.WriteLine($"[WebSocket] 用户 {userId} 已断开");
}
}
private async Task HandleWebSocketAsync(string userId, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer),
CancellationToken.None
);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"关闭连接",
CancellationToken.None
);
break;
}
// 解析消息
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
await HandleMessageAsync(userId, message);
}
}
private async Task HandleMessageAsync(string userId, string message)
{
try
{
var data = JsonConvert.DeserializeObject<WebSocketMessage>(message);
switch (data.Type)
{
case "message":
// 处理聊天消息
await HandleChatMessageAsync(userId, data);
break;
case "ping":
// 处理心跳
await SendToUserAsync(userId, new
{
type = "pong",
timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
});
break;
case "read":
// 处理已读回执
await HandleReadReceiptAsync(userId, data);
break;
default:
Console.WriteLine($"未知消息类型: {data.Type}");
break;
}
}
catch (Exception ex)
{
Console.WriteLine($"消息处理失败: {ex.Message}");
}
}
private async Task HandleChatMessageAsync(string fromUserId, WebSocketMessage data)
{
var toUserId = data.ToUserId;
var content = data.Content;
// 保存消息到数据库
var messageId = await SaveMessageAsync(fromUserId, toUserId, content);
// 构建消息对象
var messageObj = new
{
type = "message",
messageId = messageId,
fromUserId = fromUserId,
toUserId = toUserId,
content = content,
timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
// 发送给接收者
await SendToUserAsync(toUserId, messageObj);
// 发送给发送者(确认消息已发送)
await SendToUserAsync(fromUserId, messageObj);
}
private async Task<string> SaveMessageAsync(string fromUserId, string toUserId, string content)
{
// TODO: 保存消息到数据库
// 返回消息 ID
return Guid.NewGuid().ToString();
}
private async Task HandleReadReceiptAsync(string userId, WebSocketMessage data)
{
// TODO: 更新消息已读状态
Console.WriteLine($"用户 {userId} 已读消息 {data.MessageId}");
}
private async Task SendToUserAsync(string userId, object message)
{
var webSocket = _connectionManager.GetConnection(userId);
if (webSocket != null && webSocket.State == WebSocketState.Open)
{
var json = JsonConvert.SerializeObject(message);
var bytes = Encoding.UTF8.GetBytes(json);
await webSocket.SendAsync(
new ArraySegment<byte>(bytes),
WebSocketMessageType.Text,
true,
CancellationToken.None
);
}
}
private string ValidateToken(string token)
{
// TODO: 验证 JWT token返回用户 ID
// 这里简化处理,直接返回一个用户 ID
return "user123";
}
}
// 消息实体
public class WebSocketMessage
{
public string Type { get; set; }
public string FromUserId { get; set; }
public string ToUserId { get; set; }
public string Content { get; set; }
public string MessageId { get; set; }
public long Timestamp { get; set; }
}
```
#### 2. 连接管理器
**文件**`WebSocketConnectionManager.cs`
```csharp
using System.Collections.Concurrent;
using System.Net.WebSockets;
public class WebSocketConnectionManager
{
// 存储所有连接(用户 ID -> WebSocket
private readonly ConcurrentDictionary<string, WebSocket> _connections =
new ConcurrentDictionary<string, WebSocket>();
// 添加连接
public void AddConnection(string userId, WebSocket webSocket)
{
_connections.TryAdd(userId, webSocket);
}
// 移除连接
public void RemoveConnection(string userId)
{
_connections.TryRemove(userId, out _);
}
// 获取连接
public WebSocket GetConnection(string userId)
{
_connections.TryGetValue(userId, out var webSocket);
return webSocket;
}
// 获取所有在线用户
public IEnumerable<string> GetOnlineUsers()
{
return _connections.Keys;
}
// 获取在线用户数
public int GetOnlineCount()
{
return _connections.Count;
}
}
```
#### 3. 注册中间件
**文件**`Startup.cs`
```csharp
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 注册连接管理器为单例
services.AddSingleton<WebSocketConnectionManager>();
// 其他服务...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 启用 WebSocket
app.UseWebSockets(new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(30)
});
// 注册 WebSocket 中间件
app.Map("/ws/chat", builder =>
{
builder.UseMiddleware<WebSocketMiddleware>();
});
// 其他中间件...
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
```
#### 4. HTTP API获取历史消息
**文件**`ChatController.cs`
```csharp
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
// 获取聊天历史
[HttpGet("GetHistory")]
public async Task<IActionResult> GetHistory(string userId, int page = 1, int pageSize = 50)
{
// TODO: 从数据库查询历史消息
var messages = new List<object>
{
new
{
messageId = "msg001",
fromUserId = "user123",
toUserId = userId,
content = "你好",
timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
}
};
return Ok(new
{
success = true,
data = messages,
total = messages.Count
});
}
// 发送离线消息
[HttpPost("SendOffline")]
public async Task<IActionResult> SendOffline([FromBody] SendMessageRequest request)
{
// TODO: 保存离线消息到数据库
return Ok(new { success = true });
}
// 标记已读
[HttpPost("MarkRead")]
public async Task<IActionResult> MarkRead([FromBody] MarkReadRequest request)
{
// TODO: 更新消息已读状态
return Ok(new { success = true });
}
}
public class SendMessageRequest
{
public string ToUserId { get; set; }
public string Content { get; set; }
}
public class MarkReadRequest
{
public string MessageId { get; set; }
}
```
---
## 📊 时间安排
| 阶段 | 工作内容 | 预计时间 | 负责人 |
|------|----------|----------|--------|
| **阶段一** | 前端准备 | ✅ 已完成 | 前端 |
| **阶段二** | 后端开发 | 3-5 天 | 后端 |
| **阶段三** | 前端集成 | 2-3 天 | 前端 |
| **阶段四** | 测试验证 | 2-3 天 | 全员 |
| **总计** | | **7-11 天** | |
---
## 🎯 下一步行动
### 立即可做(前端)
1. **在 App.vue 中初始化 WebSocket**
- 导入 `websocket-manager.js`
- 在 `onLaunch` 中调用 `wsManager.connect()`
- 设置事件监听
2. **测试 WebSocket 管理器**
- 使用在线 WebSocket 测试服务(如 `wss://echo.websocket.org`
- 验证连接、发送、接收功能
### 需要后端配合
1. **确认后端技术栈**
- 是否是 ASP.NET Core
- 后端开发人员是谁?
2. **部署 WebSocket 服务**
- 使用上面提供的 .NET Core 代码
- 或者告诉我你的后端技术,我提供对应代码
3. **配置服务器**
- 确保服务器支持 WebSocket
- 配置 Nginx 反向代理(如果需要)
---
## 📞 联系与支持
如果你需要:
1. ✅ 其他后端语言的 WebSocket 服务端代码Node.js/Java/Go/Python
2. ✅ 帮助修改 `dialogBox.vue` 集成 WebSocket
3. ✅ Nginx 配置 WebSocket 反向代理
4. ✅ 调试和测试支持
随时告诉我!🚀

View File

@ -27,7 +27,20 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/notes/index",
"style": {
"navigationBarTitleText": "留言板",
"navigationStyle": "custom"
}
},
{
"path": "pages/my/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/my/personalInfo",
"style": {
@ -38,7 +51,7 @@
{
"path": "pages/home/admissions/index",
"style": {
"navigationBarTitleText": "在线咨询",
"navigationBarTitleText": "招办在线",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
@ -52,28 +65,13 @@
}
},
{
"path": "pages/consultation/index",
"path": "pages/home/conversations/index",
"style": {
"navigationBarTitleText": "在线咨询",
"navigationBarTitleText": "会话列表",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/transfer/index",
"style": {
"navigationBarTitleText": "人工转接",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/my/index",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/login/login/index",
"style": {
@ -91,18 +89,138 @@
}
},
{
"path": "pages/home/history/history",
"path": "pages/login/login/login",
"style": {
"navigationBarTitleText": "登录",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/register/register",
"style": {
"navigationBarTitleText": "注册",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/perfect/perfect",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/graduateCertification/graduateCertification",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/msgList/msgList",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/dialogBox/dialogBox",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/interactionList/interactionList",
"style": {
"navigationBarTitleText": "互动消息",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/sysList/sysList",
"style": {
"navigationBarTitleText": "系统消息",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/attentionList/attentionList",
"style": {
"navigationBarTitleText": "新增关注",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/message/adminList/adminList",
"style": {
"navigationBarTitleText": "管理列表",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/ForgetPassword/ForgetPassword",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/confirmPwd/confirmPwd",
"style": {
"navigationBarTitleText": "确认密码",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/roleSelection",
"style": {
"navigationBarTitleText": "角色选择",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/teacherCertification",
"style": {
"navigationBarTitleText": "教职工认证",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/recognitionResult/recognitionResult",
"style": {
"navigationBarTitleText": "提示",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/recognitionResult/recognitionFailed",
"style": {
"navigationBarTitleText": "认证失败",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}
],
"subPackages": [],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "源小新",
"navigationBarTitleText": "英星AI",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
@ -113,16 +231,16 @@
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/consultation/index",
"pagePath": "pages/home/conversations/index",
"iconPath": "static/tabbar/icon_home.png",
"selectedIconPath": "static/tabbar/icon_home_active.png",
"text": "在线咨询"
"text": "会话列表"
},
{
"pagePath": "pages/transfer/index",
"pagePath": "pages/notes/index",
"iconPath": "static/tabbar/icon_message.png",
"selectedIconPath": "static/tabbar/icon_message_active.png",
"text": "人工转接"
"text": "留言板"
},
{
"pagePath": "pages/my/index",

View File

@ -1,224 +0,0 @@
<template>
<view class="page-container">
<view class="page-header">
<PageHeader title="在线咨询" :is-back="false" :border-bottom="false" />
</view>
<scroll-view
class="page-main"
scroll-y
enable-back-to-top
>
<!-- 内容内层 - 添加内边距和安全区域 -->
<view class="main-content">
<!-- 咨询入口列表 -->
<view
class="consultation-item"
v-for="(item, index) in consultationList"
:key="index"
@click="handleConsultation(item)"
>
<view class="item-left">
<image class="item-icon" :src="item.icon"></image>
<view class="item-info">
<text class="item-title">{{ item.title }}</text>
<text class="item-desc">{{ item.desc }}</text>
</view>
</view>
<u-icon name="arrow-right" color="#999" size="20"></u-icon>
</view>
<!-- 无数据提示 -->
<view class="empty-tip" v-if="consultationList.length === 0">
暂无咨询服务
</view>
</view>
</scroll-view>
<view class="page-tabbar">
<TabBar :currentPath="'/pages/consultation/index'" @change="handleTabChange" />
</view>
<u-modal
v-model="showModal"
:show-cancel-button="false"
title="提示"
:content="modalContent"
@confirm="showModal = false"
></u-modal>
</view>
</template>
<script>
import TabBar from "@/components/TabBar-optimized.vue";
import PageHeader from "@/components/PageHeader.vue";
export default {
name: "ConsultationPage",
components: {
TabBar,
PageHeader,
},
data() {
return {
showModal: false,
modalContent: '',
consultationList: [
{
id: 1,
title: "智能问答",
desc: "AI智能机器人为您解答",
icon: "/static/common/icon/robot.png",
type: "ai"
},
{
id: 2,
title: "招生咨询",
desc: "招生相关问题咨询",
icon: "/static/common/icon/admissions.png",
type: "admissions"
},
{
id: 3,
title: "教务咨询",
desc: "教务相关问题咨询",
icon: "/static/common/icon/academic.png",
type: "academic"
},
],
};
},
methods: {
handleTabChange(path, index) {
console.log("切换到标签页:", path, index);
},
handleConsultation(item) {
//
this.modalContent = `即将进入${item.title}`;
this.showModal = true;
},
},
};
</script>
<style scoped>
/* ===== 页面容器 - 主流三段式布局 ===== */
.page-container {
/* 固定定位,占满整个视口 */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* Flex 布局 */
display: flex;
flex-direction: column;
/* 背景色 */
background-color: #f5f6fa;
/* 防止溢出 */
overflow: hidden;
}
/* ===== 头部导航 ===== */
.page-header {
/* 不收缩,固定高度 */
flex-shrink: 0;
/* 层级 */
z-index: 100;
}
/* ===== 内容区域 ===== */
.page-main {
/* 占据剩余空间 */
flex: 1;
/* 重要:防止 flex 子元素溢出 */
height: 0;
/* 允许滚动 */
overflow-y: auto;
/* iOS 滚动优化 */
-webkit-overflow-scrolling: touch;
}
/* ===== 内容内层 ===== */
.main-content {
/* 内边距 */
padding: 10px;
/* 底部留出 TabBar 空间 + 安全区域 */
padding-bottom: calc(50px + env(safe-area-inset-bottom) + 10px);
/* 最小高度(确保可以滚动) */
min-height: 100%;
}
/* ===== 底部导航 ===== */
.page-tabbar {
/* 不收缩,固定高度 */
flex-shrink: 0;
/* 层级 */
z-index: 100;
}
.consultation-item {
background-color: #fff;
padding: 20px;
margin-bottom: 10px;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.3s;
}
.consultation-item:active {
background-color: #f8f8f8;
}
.item-left {
display: flex;
align-items: center;
flex: 1;
}
.item-icon {
width: 48px;
height: 48px;
margin-right: 15px;
border-radius: 8px;
}
.item-info {
display: flex;
flex-direction: column;
}
.item-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 5px;
}
.item-desc {
font-size: 13px;
color: #999;
}
.empty-tip {
text-align: center;
padding: 50px 20px;
color: #999;
font-size: 14px;
}
</style>

View File

@ -1,26 +1,23 @@
<template>
<view class="admissions-container">
<header-bar title="在线咨询" @leftClick="handleLeftClick"></header-bar>
<view class="header">
<div class="header-left">
<u-icon
class="header-left-icon"
name="arrow-left"
@click="handleLeftClick"
></u-icon>
</div>
<text class="header-title">招办在线</text>
<div></div>
</view>
<view class="admissions-content">
<!-- 自定义tab -->
<view class="custom-tab">
<view
v-for="tab in tabList"
:key="tab.id"
:class="['tab-item', { active: activeTab === tab.id }]"
@click="switchTab(tab.id)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="tab-underline" v-if="activeTab === tab.id"></view>
</view>
</view>
<view class="teacher-list">
<!-- 教师列表 -->
<view
class="teacher-item"
v-for="(teacher, index) in currentList"
v-for="(teacher, index) in teacherList"
:key="index"
>
<image class="teacher-avatar" :src="teacher.avatar"></image>
@ -35,9 +32,9 @@
</view>
<view class="teacher-department">{{ teacher.department }}</view>
</view>
<view class="ask-button" @click="handleAskQuestion(teacher)">
立即提问
</view>
<view class="ask-button" @click="handleAskQuestion(teacher)">{{
teacher.online ? "立即提问" : "留言"
}}</view>
</view>
</view>
</view>
@ -53,21 +50,14 @@
</template>
<script>
import HeaderBar from "@/components/HeaderBar.vue"; //
import LeaveMessage from "@/components/LeaveMessage.vue";
export default {
components: {
HeaderBar, //
LeaveMessage,
},
data() {
return {
activeTab: "1", // tab
tabList: [
{ id: "1", name: "招生在线", key: "admissions" },
{ id: "2", name: "迎新在线", key: "welcome" },
],
showLeaveMessage: false,
teacherList: [
{
@ -99,41 +89,10 @@ export default {
online: false,
},
],
welcomeList: [
{
id: 5,
name: "李老师",
department: "学生处",
avatar: "/static/common/images/avatar.png",
online: true,
},
{
id: 6,
name: "张老师",
department: "宿管中心",
avatar: "/static/common/images/student.png",
online: true,
},
{
id: 7,
name: "迎新办陈老师",
department: "后勤服务中心",
avatar: "/static/common/images/student.png",
online: false,
},
],
currentTeacher: null,
};
},
computed: {
currentList() {
return this.activeTab === "1" ? this.teacherList : this.welcomeList;
},
},
methods: {
switchTab(tab) {
this.activeTab = tab;
},
handleLeftClick() {
uni.navigateBack();
},
@ -200,44 +159,6 @@ export default {
}
.admissions-content {
.custom-tab {
padding: 0 30rpx;
margin: 24rpx 0;
display: flex;
align-items: center;
.tab-item {
position: relative;
padding: 12rpx 0;
margin-right: 60rpx;
cursor: pointer;
.tab-text {
font-size: 32rpx;
font-weight: 500;
color: #505866;
letter-spacing: 0.04rpx;
}
&.active .tab-text {
color: #4f6aff;
font-weight: 600;
}
.tab-underline {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
right: 0;
width: 80rpx;
height: 4rpx;
background-color: #4f6aff;
border-radius: 2rpx;
}
}
}
.teacher-list {
.teacher-item {
display: flex;

View File

@ -1,655 +0,0 @@
<template>
<view class="history-page">
<header-bar title="历史记录" @leftClick="handleLeftClick"></header-bar>
<view class="tab-container">
<view class="custom-tab">
<view
v-for="tab in tabList"
:key="tab.id"
:class="['tab-item', { active: activeTab === tab.id }]"
@click="switchTab(tab.id)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="tab-underline" v-if="activeTab === tab.id"></view>
</view>
</view>
<view class="more-icon-container" @click="toggleMoreMenu">
<image
class="more-icon"
src="/static/common/images/icon-more.png"
mode="scaleToFill"
></image>
</view>
<!-- 右上角弹出菜单批量删除 -->
<view v-if="isMoreMenuVisible" class="more-menu" @click.stop>
<view class="menu-card" @click="handleBatchDeleteClick">
<!-- <image
class="menu-icon"
src="/static/common/images/icon_delete.png"
mode="widthFix"
/> -->
<u-icon name="trash" color="#FF647D" size="28"></u-icon>
<text class="menu-text">批量删除</text>
</view>
</view>
</view>
<!-- 点击其他区域关闭弹层 -->
<view v-if="isMoreMenuVisible" class="overlay" @click="hideMoreMenu"></view>
<!-- 内容区域包装器 -->
<view class="content-wrapper">
<!-- 历史记录列表按日期分组日期固定在上方 -->
<view class="history-list">
<view
class="date-group"
v-for="group in groupedHistoryList"
:key="group.header"
>
<view class="date-header">{{ group.header }}</view>
<view class="history-item" v-for="item in group.items" :key="item.id">
<!-- 批量删除模式下的选择框 -->
<view
class="item-checkbox"
v-if="isBatchDeleteMode"
@click="toggleItemSelection(item.id)"
>
<view
class="checkbox"
:class="{ checked: selectedItems.includes(item.id) }"
>
<text class="checkmark" v-if="selectedItems.includes(item.id)"
></text
>
</view>
</view>
<!-- 历史记录内容 -->
<view class="item-content">
<view class="item-header">
<view class="item-icon">
<text class="icon-text">B</text>
</view>
<view class="item-title">{{ item.title }}</view>
</view>
<view class="item-description">{{ item.content }}</view>
</view>
</view>
</view>
</view>
</view>
<!-- 批量删除操作栏 -->
<view class="batch-actions" v-if="isBatchDeleteMode">
<view class="select-all-container" @click="toggleSelectAll">
<view class="checkbox" :class="{ checked: selectAll }">
<text class="checkmark" v-if="selectAll"></text>
</view>
<text class="select-all-text">全选</text>
</view>
<view class="function-btn">
<view class="cancel-btn" @click="cancelBatchDelete">取消</view>
<view class="delete-btn" @click="batchDelete">删除</view>
</view>
</view>
<!-- 提示 -->
<u-toast ref="uToast" />
</view>
</template>
<script>
import HeaderBar from "@/components/HeaderBar.vue";
export default {
components: {
HeaderBar,
},
data() {
return {
activeTab: "1", // tab
tabList: [
{ id: "1", name: "AI咨询" },
{ id: "2", name: "人工咨询" },
],
isBatchDeleteMode: false, //
isMoreMenuVisible: false, //
selectedItems: [], //
selectAll: false, //
historyList: [
{
id: 1,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime(), //
type: "ai",
},
{
id: 2,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime(), //
type: "ai",
},
{
id: 3,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime(), //
type: "ai",
},
{
id: 4,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime(), //
type: "ai",
},
{
id: 5,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime() - 24 * 60 * 60 * 1000 * 2, // 2
type: "ai",
},
{
id: 6,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime() - 365 * 24 * 60 * 60 * 1000, //
type: "human",
},
{
id: 7,
title: "学校哪些专业比较好?",
content:
"学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?学校哪些专业比较好?",
time: new Date().getTime() - 24 * 60 * 60 * 1000 * 5, // 5
type: "human",
},
],
};
},
computed: {
// tab
filteredHistoryList() {
return this.historyList.filter((item) => {
if (this.activeTab === "1") {
return item.type === "ai";
} else {
return item.type === "human";
}
});
},
//
groupedHistoryList() {
//
const list = [...this.filteredHistoryList].sort(
(a, b) => b.time - a.time
);
const groups = [];
let currentHeader = null;
let currentItems = [];
list.forEach((item) => {
const header = this.formatTime(item.time);
if (header !== currentHeader) {
if (currentItems.length) {
groups.push({ header: currentHeader, items: currentItems });
}
currentHeader = header;
currentItems = [item];
} else {
currentItems.push(item);
}
});
if (currentItems.length) {
groups.push({ header: currentHeader, items: currentItems });
}
return groups;
},
},
methods: {
//
formatTime(timestamp) {
const now = new Date();
const date = new Date(timestamp);
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const itemDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate()
);
//
const diffTime = today.getTime() - itemDate.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
//
if (diffDays === 0) {
return `${String(date.getMonth() + 1).padStart(2, "0")}/${String(
date.getDate()
).padStart(2, "0")}`;
}
// 1-6
if (diffDays > 0 && diffDays <= 6) {
const weekdays = [
"周日",
"周一",
"周二",
"周三",
"周四",
"周五",
"周六",
];
return weekdays[date.getDay()];
}
//
if (date.getFullYear() === now.getFullYear()) {
return `${String(date.getMonth() + 1).padStart(2, "0")}/${String(
date.getDate()
).padStart(2, "0")}`;
}
//
return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(
2,
"0"
)}/${String(date.getDate()).padStart(2, "0")}`;
},
handleLeftClick() {
uni.navigateBack();
},
switchTab(tab) {
this.activeTab = tab;
},
// /
toggleMoreMenu() {
this.isMoreMenuVisible = !this.isMoreMenuVisible;
},
//
hideMoreMenu() {
this.isMoreMenuVisible = false;
},
//
cancelBatchDelete() {
this.isBatchDeleteMode = false;
},
//
handleBatchDeleteClick() {
this.isMoreMenuVisible = false;
if (!this.isBatchDeleteMode) {
this.toggleBatchDeleteMode();
}
},
//
toggleBatchDeleteMode() {
this.isBatchDeleteMode = !this.isBatchDeleteMode;
if (!this.isBatchDeleteMode) {
// 退
this.selectedItems = [];
this.selectAll = false;
}
},
//
toggleItemSelection(itemId) {
const index = this.selectedItems.indexOf(itemId);
if (index > -1) {
this.selectedItems.splice(index, 1);
} else {
this.selectedItems.push(itemId);
}
//
this.selectAll =
this.selectedItems.length === this.filteredHistoryList.length;
},
//
toggleSelectAll() {
this.selectAll = !this.selectAll;
if (this.selectAll) {
this.selectedItems = this.filteredHistoryList.map((item) => item.id);
} else {
this.selectedItems = [];
}
},
//
batchDelete() {
if (this.selectedItems.length === 0) {
this.$refs.uToast.show({
title: "请选择要删除的项目",
type: "warning",
});
return;
}
//
this.historyList = this.historyList.filter(
(item) => !this.selectedItems.includes(item.id)
);
//
this.selectedItems = [];
this.selectAll = false;
this.isBatchDeleteMode = false;
this.$refs.uToast.show({
title: "删除成功",
type: "success",
});
},
handleDelete(item, callback) {
console.log("handleDelete", item);
setTimeout(() => {
this.$refs.uToast.show({
title: "撤回成功",
type: "success",
});
callback(true);
}, 1500);
},
},
};
</script>
<style lang="scss" scoped>
.history-page {
height: 100vh;
background-color: #ffffff;
display: flex;
flex-direction: column;
position: relative;
padding-top: 88rpx;
.tab-container {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.custom-tab {
padding: 0 30rpx;
margin: 24rpx 0;
display: flex;
justify-content: space-between;
align-items: center;
.tab-item {
position: relative;
padding: 12rpx 0;
margin-right: 80rpx;
cursor: pointer;
.tab-text {
font-size: 32rpx;
font-weight: 500;
color: #505866;
letter-spacing: 0.04rpx;
}
&.active .tab-text {
color: #4f6aff;
font-weight: 600;
}
.tab-underline {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
right: 0;
width: 80rpx;
height: 4rpx;
background-color: #4f6aff;
border-radius: 2rpx;
}
}
}
.more-icon-container {
width: 50rpx;
height: 50rpx;
margin-right: 30rpx;
.more-icon {
width: 50rpx;
height: 10rpx;
margin-right: 30rpx;
}
}
/* 右上角更多菜单弹层 */
.more-menu {
position: absolute;
top: 80rpx;
right: 30rpx;
z-index: 100;
background-color: #ffffff;
border: 1rpx solid rgba(79, 106, 255, 0.12);
border-radius: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
padding: 24rpx 28rpx;
}
.menu-card {
display: flex;
align-items: center;
gap: 6rpx;
// .menu-icon {
// color: #FF647D;
// width: 32rpx;
// height: 32rpx;
// margin-right: 12rpx;
// }
.menu-text {
font-size: 28rpx;
color: #ff647d;
letter-spacing: 2rpx;
}
}
/* 点击其它区域关闭的遮罩(透明) */
.overlay {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 99;
background: rgba(0, 0, 0, 0);
}
.content-wrapper {
padding: 30rpx;
flex: 1;
overflow-y: auto;
}
.history-list {
.date-group {
margin-bottom: 32rpx;
}
.date-header {
font-size: 26rpx;
color: #9aa0a8;
margin-bottom: 16rpx;
line-height: 1;
}
.history-item {
display: flex;
align-items: flex-start;
padding: 24rpx 0;
.item-checkbox {
margin-right: 24rpx;
padding-top: 8rpx;
cursor: pointer;
width: 48rpx;
flex: 0 0 48rpx; /* 固定宽度,避免内容因显隐而抖动 */
.checkbox {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #d9d9d9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
&.checked {
background-color: #ff4757;
border-color: #ff4757;
.checkmark {
color: #ffffff;
font-size: 22rpx;
font-weight: bold;
}
}
}
}
.item-content {
flex: 1;
.item-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.item-icon {
width: 48rpx;
height: 48rpx;
background-color: #4f6aff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
.icon-text {
color: #ffffff;
font-size: 24rpx;
font-weight: bold;
}
}
.item-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
flex: 1;
}
}
.item-description {
letter-spacing: 0.04rpx;
font-size: 28rpx;
color: #666666;
line-height: 1.5;
margin-bottom: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
}
}
}
.batch-actions {
height: 130rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
border-top: 1rpx solid #eeeeee;
.select-all-container {
display: flex;
align-items: center;
cursor: pointer;
.checkbox {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #d9d9d9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
&.checked {
background-color: #ff4757;
border-color: #ff4757;
.checkmark {
color: #ffffff;
font-size: 22rpx;
font-weight: bold;
}
}
}
.select-all-text {
margin-left: 16rpx;
font-size: 28rpx;
color: #333333;
}
}
.function-btn {
display: flex;
align-items: center;
gap: 20rpx;
.cancel-btn {
background-color: #e5e3e3e4;
color: #333333;
padding: 16rpx 32rpx;
border-radius: 16rpx;
font-size: 28rpx;
cursor: pointer;
}
.delete-btn {
background-color: #ff4757;
color: #ffffff;
padding: 16rpx 32rpx;
border-radius: 16rpx;
font-size: 28rpx;
cursor: pointer;
}
}
}
}
</style>

View File

@ -19,7 +19,7 @@
>
</view>
<view class="qa-section">
<!-- <view class="qa-section">
<view class="qa-header">
<text class="qa-title">大家都在问</text>
<view class="more-link">
@ -37,6 +37,16 @@
</view>
</view>
<button class="chat-button" @click="isChat = true">
<image
class="chat-icon"
src="/static/common/images/icon_chat.png"
></image>
<text class="chat-text">开启对话</text>
</button>
</view> -->
<view class="start-chat">
<button class="chat-button" @click="handleStartChat">
<image
class="chat-icon"
@ -51,9 +61,6 @@
class="feature-item"
v-for="(item, index) in features"
:key="index"
:style="{
background: item.background,
}"
@click="handleFeatureClick(item)"
>
<image :src="item.icon" class="feature-icon"></image>
@ -104,7 +111,7 @@
<view class="loading-more" v-if="isLoading">
<u-loading mode="circle" color="#4370fe"></u-loading>
</view>
<!-- 到顶部提示 -->
<view class="no-more-data" v-if="noMoreData">
<text>已经到顶了</text>
@ -272,7 +279,7 @@ export default {
isLoadingMore: false, //
noMoreData: false, //
isSwitchingConversation: false, //
pageQuery: {
PageIndex: 1,
PageSize: 20,
@ -287,20 +294,18 @@ export default {
],
features: [
{
title: "在线咨询",
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_messageBoard.png",
// path: "/pages/home/messageBoard/index",
// },
{
title: "留言板",
icon: "/static/common/images/icon_messageBoard.png",
path: "/pages/home/messageBoard/index",
},
{
title: "电话咨询",
icon: "/static/common/images/icon_phone.png",
background: "linear-gradient(0deg, #F4FBF9 0%, #F4FBF9 100%)",
},
],
popupShow: false,
@ -500,7 +505,7 @@ export default {
//
this.messageGroups.push(userMessage);
this.messageValue = "";
// AI
const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15),
@ -514,7 +519,7 @@ export default {
displayTime: "",
isLoading: true, //
};
//
this.messageGroups.push(loadingMessage);
@ -554,12 +559,12 @@ export default {
})
.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),
@ -572,7 +577,7 @@ export default {
timeLabel: 0,
displayTime: "",
};
this.messageGroups.push(errorMessage);
});
},
@ -640,18 +645,18 @@ export default {
//
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;
@ -661,18 +666,18 @@ export default {
//
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;
@ -687,7 +692,7 @@ export default {
//
onScrollToUpper() {
console.log("触发上拉刷新");
//
if (this.noMoreData || this.isSwitchingConversation) {
return;
@ -766,7 +771,7 @@ export default {
.main-content {
padding: 30rpx;
padding-top: 60rpx;
padding-top: 100rpx;
.welcome-message {
// display: inline-block;
@ -779,8 +784,8 @@ export default {
.avatar {
display: inline-block;
width: 50rpx;
height: 44rpx;
width: 42rpx;
height: 34rpx;
margin-right: 12rpx;
}
@ -889,19 +894,20 @@ export default {
gap: 30rpx;
.feature-item {
height: 150rpx;
height: 210rpx;
border-radius: 16rpx;
background-color: #fafafc;
display: flex;
justify-content: flex-start;
flex-direction: column;
align-items: center;
padding-left: 30rpx;
gap: 20rpx;
justify-content: center;
// gap: 20rpx;
flex: 1;
.feature-icon {
width: 80rpx;
height: 80rpx;
margin-top: 16rpx;
margin-bottom: 12rpx;
}
.feature-text {
@ -976,7 +982,7 @@ export default {
text-align: center;
margin-bottom: 32rpx;
}
.no-more-data {
text-align: center;
font-size: 24rpx;
@ -1024,7 +1030,7 @@ export default {
display: flex;
align-items: center;
justify-content: flex-start;
.dot {
display: inline-block;
width: 12rpx;
@ -1034,20 +1040,20 @@ export default {
margin: 0 6rpx;
opacity: 0.6;
animation: dot-flashing 1.5s infinite linear alternate;
&:nth-child(1) {
animation-delay: 0s;
}
&:nth-child(2) {
animation-delay: 0.5s;
}
&:nth-child(3) {
animation-delay: 1s;
}
}
@keyframes dot-flashing {
0% {
opacity: 0.6;

View File

@ -0,0 +1,425 @@
<template>
<view class="flex-col page">
<!-- <image src="/static/common/img/16530269846087107196.png" class="image"/> -->
<u-navbar title="重置密码"></u-navbar>
<view class="flex-col group" v-if="state === 1">
<text class="text">重置密码</text>
<view class="flex-col group_7">
<view class="flex-col">
<!-- <text class="text_1">手机号</text> -->
<view class="text-wrapper flex-row view">
<u-input
v-model="phone"
:disabled="disabled"
placeholder="请输入手机号"
type="text"
/>
</view>
</view>
<view class="flex-col group_6">
<!-- <text class="text_1">短信验证码</text> -->
<view class="text-wrapper flex-row view" style="padding: 0">
<u-input
v-model="code"
placeholder="请输入短信验证码"
type="text"
/>
<u-button
:disabled="!(send == '发送验证码')"
class="custom-style"
style="background: transparent"
@click="sendCode"
>{{ send }}
</u-button>
</view>
</view>
</view>
<!-- <navigator class="text_6" url="/pages/login/login/login">登录</navigator> -->
<u-button
class="flex-col items-center text-wrapper_1"
shape="circle"
type="primary"
@click="login"
>
<text class="text_7">重置密码</text>
</u-button>
</view>
<view class="flex-col group" v-if="state === 2">
<text class="text">遇见身边的校友</text>
<view class="flex-col group_7">
<view class="flex-col">
<u-input
v-model="passWord"
class="view text-wrapper"
placeholder="请输入新密码"
type="password"
/>
</view>
<view class="flex-col group_6">
<u-input
v-model="passWordTwo"
class="view text-wrapper"
placeholder="请确认新密码"
type="password"
/>
</view>
</view>
<view class="tips">
<image src="/static/common/img/infoCircle.png"></image>
提示密码长度应该大于5必须包含字母数字特殊字符且不能包含中文!
</view>
<u-button
class="flex-col items-center text-wrapper_1"
shape="circle"
throttle-time="1000"
type="primary"
@click="login"
>
<text class="text_7">立即重置</text>
</u-button>
<u-button
class="flex-col items-center text-wrapper_1"
shape="circle"
style=" margin-top: 10px"
type="primary"
@click="state = 1"
>
<text class="text_7">返回</text>
</u-button>
</view>
<u-top-tips ref="uTips" :navbar-height="0"></u-top-tips>
<u-toast ref="uToast"/>
</view>
</template>
<script>
import md5 from "js-md5";
import {aesEncrypt} from "@/utils/encrypt.js";
export default {
data() {
return {
show: false,
phone: "",
passWord: "",
code: "",
passWordTwo: "",
agreement: false,
disabled: false,
send: "发送验证码",
state: 1,
};
},
onLoad(e) {
},
methods: {
//
tips(title, type, time) {
this.$refs.uToast.show({
title: title ? title : "",
type: type ? type : "success",
duration: time ? time + "" : "1500",
});
},
//
sendCode() {
var reg = new RegExp("^[1][3,4,5,6,7,8,9][0-9]{9}$", "g"); //
if (this.phone === "" || !reg.test(this.phone)) {
// this.$tips("", "error");
this.$refs.uToast.show({
title: '请输入正确的手机号码',
type: 'error',
})
return;
}
const timestamp = Date.now();
const base64Key = btoa('xiaoyout!#@12345');
const signStr = this.phone + timestamp + base64Key;
const sign = md5(signStr);
this.$u.apiList
.GetAppValidateCode({
phone: this.phone,
t: timestamp.toString(),
sign: sign,
type: 2,
})
.then((res) => {
//
// let result =this.userName.replace(this.userName.substring(3,7),"****")
// this.userName = result
this.disabled = true;
// this.$tips("");
this.$refs.uToast.show({
title: '发送成功',
type: 'success',
})
var second = 60;
this.send = second + "秒后重试";
var time = setInterval(() => {
this.send = second + "秒后重试";
second--;
if (second <= 0) {
this.disabled = false;
this.send = "发送验证码";
clearInterval(time);
}
}, 1000);
})
.catch((err) => {
// this.$tips(err.error, "error");
this.$refs.uToast.show({
title: err.error,
type: 'error',
})
});
},
login() {
if (this.state == 1) {
if (this.phone === "") {
return this.tips("请输入手机号", "error");
}
if (this.code === "") {
return this.tips("请输入验证码", "error");
}
this.$u.apiList.ForgotPasswordValidate({
phone: this.phone,
code: this.code,
}).then((res) => {
console.info("🚀 ~ file:ForgetPassword method: line:189 -----", res)
this.state = 2;
}).catch((err) => {
this.tips(err.error, "error");
});
return
}
if (this.state == 2) {
if (this.passWord === "") {
return this.tips("请输入新密码", "error");
}
let reg = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[\W_]).{5,}$/;
if (!reg.test(this.passWord)) {
/* return this.$tips(
"密码必须包含字母、数字、特殊字符,且不能包含中文!",
"error"
); */
return this.$refs.uToast.show({
title: "密码必须包含字母、数字、特殊字符,且不能包含中文!",
type: 'error',
})
}
if (this.passWordTwo === "") {
return this.tips("请确认新密码", "error");
}
if (this.passWord != this.passWordTwo) {
return this.tips("请检查两次密码是否一致", "error");
}
}
const req = {
phone: this.phone,
code: this.code,
newPassWord: aesEncrypt(this.passWord),
};
console.log(req, "res-");
// return
this.$u.apiList
.ForgotPassword(req)
.then((res) => {
console.log(res, "res---");
uni.showToast({
title: "重置成功",
duration: 2000,
});
setTimeout(() => {
uni.navigateTo({
url: "/",
});
}, 2000);
})
.catch((err) => {
console.log(err, "err---");
// this.$tips(err.error, "error");
this.$refs.uToast.show({
title: err.error,
type: 'error',
})
});
},
},
};
</script>
<style scoped lang="scss">
.custom-style {
color: #3cb5fb !important;
padding: 0 !important;
height: 70rpx !important;
line-height: 60rpx !important;
}
.custom-style::after {
border: none;
}
.page {
padding-bottom: 0.015rem;
background-color: rgb(255, 255, 255);
overflow-y: auto;
width: 100%;
overflow-x: hidden;
height: 100%;
.image {
flex-shrink: 0;
align-self: flex-start;
width: 102%;
}
.group {
padding: 1.0rem 0.29rem;
.text {
align-self: left;
// color: rgb(46, 155, 255);
color: #000;
font-size: 0.22rem;
font-family: PingFang;
line-height: 0.17rem;
font-weight: 600;
}
.group_7 {
margin-top: 0.29rem;
.text-wrapper {
border: solid 2rpx #dcdfe6;
border-radius: 100rpx;
background: #f6f8fa;
}
.group_6 {
margin-top: 0.2rem;
}
.text_1 {
margin-left: 0.01rem;
align-self: flex-start;
color: rgb(51, 51, 51);
font-size: 0.15rem;
font-family: PingFang;
line-height: 0.14rem;
}
.view {
display: flex;
align-items: center;
margin-top: 20rpx;
padding: 20rpx 50rpx !important;
}
}
.text_6 {
width: 100%;
text-align: right;
margin-left: 0.01rem;
margin-top: 0.2rem;
align-self: flex-start;
color: #1F2232;
font-size: 0.13rem;
font-family: PingFang;
line-height: 0.13rem;
}
.text-wrapper_1 {
margin: 50rpx 0rpx 40rpx;
padding: 50rpx 0 50rpx;
// background-image: linear-gradient(90deg, rgb(135, 230, 254) 0%, rgb(91, 192, 254) 52%, rgb(46, 155, 255) 100%);
background: #3cb4fb;
box-shadow: 0px 6rpx 9rpx rgba(38, 122, 199, 0.34);
border-radius: 100rpx;
.text_7 {
color: rgb(255, 255, 255);
font-size: 32rpx;
font-family: PingFang;
line-height: 32rpx;
}
}
.group_1 {
margin-top: 0.12rem;
.text_8 {
color: rgb(102, 102, 102);
font-size: 24rpx;
font-family: PingFang;
line-height: 0.13rem;
}
.text_9 {
margin-left: 0.035rem;
color: rgb(25, 140, 255);
font-size: 0.14rem;
font-family: PingFang;
line-height: 0.13rem;
}
}
.group_2 {
margin-top: 0.36rem;
justify-content: center;
.section_1 {
flex-shrink: 0;
width: 0.17rem;
}
.group_3 {
margin-left: 0.11rem;
height: 0.18rem;
line-height: 0.18rem;
font-size: 0;
.text_10 {
color: rgb(153, 153, 153);
font-size: 0.11rem;
font-family: PingFang;
}
.text_11 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
.text_12 {
color: rgb(102, 102, 102);
font-size: 0.11rem;
font-family: PingFang;
}
.text_13 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
}
}
}
}
.tips {
font-size: 24rpx;
color: #EF3920;
display: flex;
margin-top: 20rpx;
image {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
margin-top: 5rpx;
}
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<view class="authentication-page">
<u-navbar title="身份认证"> </u-navbar>
<view class="title">请填写您的真实信息</view>
<view class="form-container">
<view class="input-item">
<text>姓名</text>
<input
v-model="name"
maxlength="20"
type="text"
placeholder="请输入"
placeholder-class="placeholder"
/>
</view>
<view class="input-item">
<text>身份证号</text>
<input
v-model="idCard"
maxlength="18"
type="text"
placeholder="请输入"
placeholder-class="placeholder"
/>
</view>
</view>
<view class="btn-container">
<button class="next-btn" @click="handleNext">下一步</button>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import test from "@/uview-ui/libs/function/test.js";
export default {
data() {
return {
name: "",
idCard: "",
};
},
methods: {
handleNext() {
// APItoken
this.getTokenFromBackend();
if (!this.name) {
return this.$refs.uToast.show({
title: "请输入姓名",
type: "warning",
});
}
if (!this.idCard) {
return this.$refs.uToast.show({
title: "请输入身份证号",
type: "warning",
});
}
//
if (!test.idCard(this.idCard)) {
return this.$refs.uToast.show({
title: "请输入正确的身份证号码",
type: "warning",
});
}
//
// console.log("", this.name, this.idCard);
},
// token
getTokenFromBackend() {
this.$u.api.getAPIToken().then((res) => {
const token = res.verifyToken
//
this.redirectToBaiduVerification(token);
});
},
redirectToBaiduVerification(token) {
//
const currentDomain = window.location.origin;
// URL
// const callbackUrl = encodeURIComponent(`${currentDomain}/success.html`);
const callbackUrl = "/pages/login/recognitionResult/recognitionResult";
// URL
const verifyUrl = `https://brain.baidu.com/face/print/verify/verify?token=${token}&successUrl=${callbackUrl}&failedUrl=${callbackUrl}`;
const verifyObj = {
url: verifyUrl,
name:'身份认证'
};
// uni-app
this.$u.route({
url: `/pages/webview/index?data=${encodeURIComponent(
JSON.stringify(verifyObj)
)}`,
});
},
},
};
</script>
<style lang="scss">
.authentication-page {
padding: 0 30rpx;
.title {
font-family: PingFang SC, PingFang SC;
font-weight: bold;
font-size: 32rpx;
color: rgba(0, 0, 0, 0.9);
line-height: 52rpx;
text-align: left;
margin-top: 30rpx;
margin-bottom: 40rpx;
}
.form-container {
background-color: #ffffff;
border-radius: 16rpx;
padding: 0 30rpx;
margin-bottom: 60rpx;
.input-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:last-child {
border-bottom: none;
}
text {
font-size: 28rpx;
color: rgba(0, 0, 0, 0.9);
}
input {
flex: 1;
text-align: right;
font-size: 28rpx;
}
.placeholder {
color: rgba(0, 0, 0, 0.3);
}
}
}
.btn-container {
.next-btn {
background-color: #45b5ff;
color: #ffffff;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
}
}
}
</style>

View File

@ -0,0 +1,338 @@
<template>
<view class="flex-col page">
<!-- <image src="/static/common/img/16530269846087107196.png" class="image" /> -->
<u-navbar title="确认密码"></u-navbar>
<view class="flex-col group">
<text class="text">确认密码后登录</text>
<view class="flex-col group_7">
<view class="flex-col">
<view class="flex-row" style="padding: 0">
<u-input type="password" v-model="user.passWord" placeholder="请输入密码" class="view text-wrapper"/>
</view>
</view>
<view class="flex-col group_6">
<u-input type="password" v-model="isPassWord" placeholder="请确认密码" class="view text-wrapper" />
</view>
<view class="tips">
<image src="/static/common/img/infoCircle.png"></image> 提示密码长度应该大于5必须包含字母数字特殊字符且不能包含中文!
</view>
</view>
<u-button class="flex-col items-center text-wrapper_1" throttle-time="1000" type="primary" shape="circle"
@click="login"><text class="text_7">确定</text></u-button>
<!-- 此次接入人脸验证 登录 ==> 确定 -->
</view>
<u-toast ref="uToast" />
<u-top-tips ref="uTips" :navbar-height="0"></u-top-tips>
</view>
</template>
<script>
import {aesEncrypt} from '@/utils/encrypt.js'
import md5 from "js-md5";
export default {
data() {
return {
show: false,
userName: "",
isPassWord: "",
agreement: false,
user: {
phone: '',
passWord: '',
code:''
}
};
},
onLoad(e) {
if (e.phone) {
this.user.phone = e.phone
this.user.code = e.code
} else {
uni.navigateTo({
url: "../register/register",
});
}
},
methods: {
login() {
if (this.user.passWord === "") {
return this.$refs.uToast.show({
title: '请输入密码',
type: 'error',
})
}
if (this.user.passWord.length < 5) {
// return this.$tips("5", "error");
return this.$refs.uToast.show({
title: '您的密码长度应该大于5',
type: 'error',
})
}
if (this.isPassWord === "") {
return this.$refs.uToast.show({
title: '请确认密码',
type: 'error',
})
// return this.$tips("", "error");
}
if (this.isPassWord !== this.user.passWord) {
return this.$refs.uToast.show({
title: '请确认两次密码是否一致',
type: 'error',
})
// return this.$tips("", "error");
}
uni.showLoading({
title: "注册中",
});
const req = {...this.user}
req.passWord = aesEncrypt(this.user.passWord)
// 13
const timestamp = Date.now();
//
const base64Key = btoa('xiaoyout!#@12345');
const signStr = this.user.phone + timestamp + base64Key;
const sign = md5(signStr);
req.timeStamp = timestamp.toString();
req.sign = sign;
console.log(req,'req')
// return
this.$u.api.RegisterUser(req).then((res) => {
const newReq = {...this.user}
newReq.passWord = aesEncrypt(this.user.passWord)
this.$u.api.LoginApp(newReq).then((ress) => {
uni.hideLoading();
//
this.$u.vuex('vuex_user', ress.user)
this.$u.vuex('vuex_token', ress.token)
this.$refs.uToast.show({
title: '注册成功',
type: 'success',
})
// uni.navigateTo({
// url: "../perfect/perfect",
// });
setTimeout(()=>{
//
this.toBaiduApi();
return
uni.navigateTo({
url: "/pages/login/roleSelection",
});
},300)
}).catch((e) => {
uni.hideLoading();
});
}).catch((e) => {
uni.hideLoading();
// this.$tips( e.error, 'error')
this.$refs.uToast.show({
title: e.error,
type: 'error',
})
});
},
//
toBaiduApi(){
this.$u.api.getAPIToken().then((res) => {
const token = res.result.verify_token;
//
this.redirectToBaiduVerification(token);
});
},
redirectToBaiduVerification(token) {
//
const currentDomain = window.location.origin;
// URL - 使uni-app
// URLverify_resultverify_info
const successUrl = encodeURIComponent(
`${currentDomain}/#/pages/login/recognitionResult/recognitionResult?token=${token}`
);
const failedUrl = encodeURIComponent(
`${currentDomain}/#/pages/login/recognitionResult/recognitionFailed?token=${token}`
);
// URL
const verifyUrl = `https://brain.baidu.com/face/print/verify/verify?token=${token}&successUrl=${successUrl}&failedUrl=${failedUrl}`;
console.log("跳转到百度人脸核验页面:", verifyUrl);
//
window.location.href = verifyUrl;
return;
},
},
};
</script>
<style scoped lang="scss">
.custom-style {
color: #606266;
}
.custom-style::after {
border: none;
}
.page {
padding-bottom: 0.015rem;
background-color: rgb(255, 255, 255);
overflow-y: auto;
width: 100%;
overflow-x: hidden;
height: 100%;
.image {
flex-shrink: 0;
align-self: flex-start;
width: 102%;
}
.group {
padding: 0.15rem 0.29rem;
.text {
font-family: PingFang;
font-weight: bold;
font-size: 48rpx;
color: #000000;
margin-top: 200rpx;
}
.group_7 {
margin-top: 0.29rem;
.text-wrapper {
border: solid 2rpx #dcdfe6;
border-radius: 100rpx;
background: #f6f8fa;
}
.group_6 {
margin-top: 0.2rem;
}
.text_1 {
margin-left: 0.01rem;
align-self: flex-start;
color: rgb(51, 51, 51);
font-size: 0.15rem;
font-family: PingFang;
line-height: 0.14rem;
}
.view {
margin-top: 20rpx;
padding: 20rpx 50rpx !important;
}
}
.text_6 {
margin-left: 0.01rem;
margin-top: 0.13rem;
align-self: flex-start;
color: rgb(25, 140, 255);
font-size: 0.13rem;
font-family: PingFang;
line-height: 0.13rem;
}
.text-wrapper_1 {
margin: 0.35rem 0rem 0;
padding: 20rpx 50rpx;
height: 108rpx;
/* background-image: linear-gradient(90deg,
rgb(135, 230, 254) 0%,
rgb(91, 192, 254) 52%,
rgb(46, 155, 255) 100%);
box-shadow: 0px 0.03rem 0.09rem rgba(38, 122, 199, 0.34); */
background: #3cb5fb;
border-radius: 0.23rem;
.text_7 {
color: rgb(255, 255, 255);
font-size: 0.16rem;
font-family: PingFang;
line-height: 0.15rem;
}
}
.group_1 {
margin-top: 0.12rem;
.text_8 {
color: rgb(102, 102, 102);
font-size: 0.14rem;
font-family: PingFang;
line-height: 0.13rem;
}
.text_9 {
margin-left: 0.035rem;
color: rgb(25, 140, 255);
font-size: 0.14rem;
font-family: PingFang;
line-height: 0.13rem;
}
}
.group_2 {
margin-top: 0.36rem;
justify-content: center;
.section_1 {
flex-shrink: 0;
width: 0.17rem;
}
.group_3 {
margin-left: 0.11rem;
height: 0.18rem;
line-height: 0.18rem;
font-size: 0;
.text_10 {
color: rgb(153, 153, 153);
font-size: 0.11rem;
font-family: PingFang;
}
.text_11 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
.text_12 {
color: rgb(102, 102, 102);
font-size: 0.11rem;
font-family: PingFang;
}
.text_13 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
}
}
}
.tips{
font-size: 24rpx;
color: #EF3920;
display: flex;
margin-top: 20rpx;
image{
width: 32rpx;
height: 32rpx;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -357,9 +357,6 @@ export default {
//
login() {
console.log("登录...");
return;
// const res = {
// data: {
// // token:
@ -369,18 +366,18 @@ export default {
// },
// };
const token = res.data.token;
//
const userInfo = getUserInfoFromJWT(token);
//
this.$u.vuex("vuex_token", token);
this.$u.vuex("vuex_user", userInfo);
// const token = res.data.token;
// //
// const userInfo = getUserInfoFromJWT(token);
// //
// this.$u.vuex("vuex_token", token);
// this.$u.vuex("vuex_user", userInfo);
//
uni.reLaunch({
url: "/pages/home/index/index",
});
return;
// //
// uni.reLaunch({
// url: "/pages/home/index/index",
// });
// return;
//
if (!this.validatePhone()) {

532
pages/login/login/login.vue Normal file
View File

@ -0,0 +1,532 @@
<template>
<view class="flex-col page">
<!-- <image src="/static/common/img/loginBg.png" class="image" /> -->
<view class="img-box">
</view>
<view class="login-box">
<view class="tab-list">
<view v-for="tab in tabList" :key="tab.id" class="tab-item"
:class="activeTab === tab.id ? 'tab-selected' : 'not-selected'" @click="onTab(tab.id)">
<text class="tab-text">{{tab.label}}</text>
</view>
</view>
<view class="flex-col group">
<text class="text"></text>
<view class="flex-col group_7">
<view class="flex-col">
<!-- <text class="text_1">手机号</text> -->
<!-- @input="changeInput($event)" -->
<!-- <u-input type="number" v-model="userName" placeholder="请输入手机号" class="view text-wrapper" /> -->
<u-input type="number" v-model="userName" placeholder="请输入手机号" class="view text-wrapper" maxlength="11"/>
</view>
<view class="error-tips" v-if="errorPhone"><image class="error-img" src="/static/common/img/infoCircle.png"></image> 请输入正确手机号</view>
<view style="height: 0;width: 0;border: 0;padding: 0;margin: 0;overflow: hidden;">
<u-input placeholder='' />
</view>
<view class="flex-col group_6">
<!-- <text class="text_1">密码</text> -->
<u-input type="password" v-model="passWord" placeholder="请输入密码" class="view text-wrapper" />
</view>
<!-- 密码必须包含字母数字特殊字符且不能包含中文 -->
<view class="error-tips" v-if="errorPwd"><image class="error-img" src="/static/common/img/infoCircle.png"></image> {{errormsg}}</view>
</view>
<navigator class="text_6" style="width: 100%; text-align: right;" url="/pages/login/ForgetPassword/ForgetPassword">忘记密码</navigator>
<u-button class="flex-col items-center text-wrapper_1" throttle-time='1000' type="primary" shape="circle"
@click="login"><text class="text_7">登录</text></u-button>
<view class="justify-center group_1">
<text class="text_8">还没有账号</text>
<navigator class="text_9" url="/pages/login/register/register">立即注册</navigator>
</view>
<!-- <view class="flex-row group_2">
<u-checkbox-group class="section_1" :size='34'>
<u-checkbox v-model="agreement"> </u-checkbox>
</u-checkbox-group>
<view class="group_3">
<text class="text_10">已阅读并同意</text>
<text class="text_11">用户服务协议</text>
<text class="text_12"></text>
<text class="text_13">用户隐私保护政策</text>
</view>
</view> -->
</view>
</view>
<u-top-tips ref="uTips" :navbar-height='0'></u-top-tips>
<u-toast ref="uToast" />
</view>
</template>
<script>
import {
toBaiduApi
} from '@/utils/faceVerify.js'
import {
aesEncrypt
} from '@/utils/encrypt.js'
export default {
data() {
return {
show: false,
userName: "",
passWord: "",
agreement: true,
tabList: [{
id: 1,
label: '密码登录'
},
{
id: 2,
label: '验证码登录'
}
],
activeTab: 1,
errorPhone: false,
errorPwd: false,
errormsg: "",
};
},
onLoad(e) {},
onHide(){
this.userName= ""
this.passWord= ""
},
onShow() {
this.userName= ""
this.passWord= ""
setTimeout(() => {
const lifeData = uni.getStorageSync('lifeData');
if (lifeData.vuex_user && lifeData.vuex_token) {
this.$u.api.getUser().then(res => {
// if(!this.vuex_user.isCard){
// uni.navigateTo({
// url: '/pages/Face/index/index'
// });
// return
// }
uni.switchTab({
url: '../../main/index/index'
});
})
}
}, 200)
},
methods: {
changeInput(e){
setTimeout(() => {
this.userName = e.target.value
}, 0);
},
onTab(id) {
if(id == 1){
this.activeTab = id
}else{
/* uni.showToast({
title: '暂未开放',
icon: 'none'
}) */
this.$refs.uToast.show({
title: '暂未开放',
type: "warning",
});
}
},
login() {
// this.$u.vuex("vuex_msgList", '');
// this.$u.vuex('vuex_user', '')
// this.$u.vuex('vuex_token', '')
// uni.clearStorage();
if (!/^1[3-9]\d{9}$/.test(this.userName)) {
//
this.errorPhone = true;
// return this.$tips("", "error");
return;
} else {
this.errorPhone = false;
}
// 1q2w3e
// if (!/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*?&.])[A-Za-z\d@$!%*?&.]{5,}$/.test(this.passWord)) {
/* if (this.passWord.length < 6) {
// 6
this.errorPwd = true;
// return this.$tips("", "error");
return;
} else {
this.errorPwd = false;
} */
/* if (!this.agreement) {
return this.$tips("请同意用户服务协议及用户隐私保护政策", "error");
} */
if (this.passWord.length===0) {
this.errorPwd = true;
this.errormsg = "请输入密码";
console.log("请输入密码");
return
}else if (this.passWord.length>0&&this.passWord.length < 6) {
this.errorPwd = true;
this.errormsg = "密码长度不能少于6位";
console.log("密码长度不能少于6位");
return
}else{
this.errorPwd = false;
this.errormsg = "";
}
uni.showLoading({
title: "登录中",
});
//
let data = {
phone: this.userName,
password: aesEncrypt(this.passWord),
}
this.$u.api.LoginApp(data).then((res) => {
uni.hideLoading();
if (!res.succeed && !res.token) {
/* uni.showToast({
title: res.error,
duration: 2000,
icon: 'none'
}); */
// this.$tips(res.error, "error");
this.$refs.uToast.show({
title: err.error,
type: "error",
});
return
}
// this.$tips("", "success")
this.$refs.uToast.show({
title: "登录成功",
type: "success",
});
/* uni.showToast({
title: "登录成功",
duration: 2000,
}); */
//
this.$u.vuex('vuex_user', res.user)
this.$u.vuex('vuex_token', res.token)
this.$u.vuex('vuex_glyType', res.glyType)
// if(!res.user.isCard){
// uni.navigateTo({
// url: '/pages/Face/index/index'
// });
// return
// }
if (res.user.isFill) {
uni.switchTab({
url: '/pages/home/home/home'
});
} else {
// uni.navigateTo({
// // url: "../perfect/perfect",
// url: "../roleSelection",
// });
//
toBaiduApi(this);
return
//
uni.navigateTo({
url: "/pages/login/roleSelection?source=login",
});
}
}).catch(err => {
// this.$tips(err.error, "error");
this.$refs.uToast.show({
title: err.error,
type: "error",
});
/* uni.showToast({
title: err.error,
icon: 'none'
}) */
})
},
},
};
</script>
<style scoped lang="scss">
.page {
padding-bottom: 30rpx;
background-color: rgb(255, 255, 255);
overflow-y: auto;
width: 100%;
overflow-x: hidden;
height: 100%;
background: url(/static/common/img/loginBg.png);
background-size: contain;
background-repeat: no-repeat;
background-position: top;
.img-box {
height: 420rpx;
}
.image {
flex-shrink: 0;
align-self: flex-start;
width: 102%;
}
.group {
background: #fff;
padding: 20rpx 25rpx;
width: calc(100% - 50rpx);
margin: 0 auto;
border-radius: 30rpx;
.text {
align-self: center;
color: rgb(46, 155, 255);
font-size: 32rpx;
font-family: PingFang;
line-height: 32rpx;
}
.group_7 {
margin-top: 40rpx;
.text-wrapper {
border: solid 2rpx #dcdfe6;
border-radius: 100rpx;
background: #f6f8fa;
}
.group_6 {
margin-top: 30rpx;
}
.text_1 {
margin-left: 10rpx;
align-self: flex-start;
color: rgb(51, 51, 51);
font-size: 32rpx;
font-family: PingFang;
line-height: 30rpx;
}
.view {
margin-top: 20rpx;
padding: 20rpx 50rpx !important;
}
}
.text_6 {
margin-left: 5rpx;
margin-top: 50rpx;
align-self: flex-start;
// color: rgb(25, 140, 255);
color:#1F2232;
font-size: 28rpx;
font-family: PingFang;
line-height: 28rpx;
}
.text-wrapper_1 {
margin: 50rpx 0rpx 40rpx;
padding: 50rpx 0 50rpx;
// background-image: linear-gradient(90deg, rgb(135, 230, 254) 0%, rgb(91, 192, 254) 52%, rgb(46, 155, 255) 100%);
background: #3cb4fb;
box-shadow: 0px 6rpx 9rpx rgba(38, 122, 199, 0.34);
border-radius: 100rpx;
.text_7 {
color: rgb(255, 255, 255);
font-size: 32rpx;
font-family: PingFang;
line-height: 30rpx;
}
}
.group_1 {
margin-top: 24rpx;
.text_8 {
color: #8697AC;
font-size: 30rpx;
font-family: PingFang;
line-height: 26rpx;
}
.text_9 {
margin-left: 0.035rem;
color: #2E9CFE;
font-size: 30rpx;
font-family: PingFang;
line-height: 26rpx;
}
}
.group_2 {
margin-top: 40rpx;
justify-content: center;
.section_1 {
flex-shrink: 0;
width: 34rpx;
}
.group_3 {
margin-left: 24rpx;
height: 24rpx;
font-size: 0;
.text_10 {
color: rgb(153, 153, 153);
font-size: 24rpx;
font-family: PingFang;
}
.text_11 {
color: rgb(25, 140, 255);
font-size: 24rpx;
font-family: PingFang;
}
.text_12 {
color: rgb(102, 102, 102);
font-size: 24rpx;
font-family: PingFang;
}
.text_13 {
color: rgb(25, 140, 255);
font-size: 24rpx;
font-family: PingFang;
}
}
}
}
}
.login-box{
background: #fff;
width: calc(100% - 50rpx);
margin: 0 auto;
border-radius: 30rpx;
// height: calc(100% - 350rpx);
overflow: hidden;
padding-bottom:100rpx;
}
$tab-height: 52px;
$active-color: #fff;
$default-color: #90ecf1;
$primary-color: #666;
.tab-list {
display: flex;
position: relative;
z-index: 2;
border-radius: 30rpx 30rpx 0 0;
background-color: $default-color;
overflow: hidden; //
.tab-item {
flex: 1;
height: $tab-height;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
// opacity: 0.65; //
font-weight: 600;
position: relative;
}
.tab-selected {
color:#3cb5fb;
opacity: 1;
background: #ffffff;
border-radius: 24rpx 24rpx 0 0;
box-shadow: 48rpx 80rpx 0 $active-color, -34rpx 90rpx 0 0 $active-color; //
.tab-text::after{
content: '';
position: absolute;
left: 160rpx;
bottom: 10rpx;
width: 32rpx;
height: 8rpx;
background: #3CB5FB;
border-radius: 20rpx;
}
}
.tab-selected::before {
content: '';
position: absolute;
left: -12rpx;
bottom: 0;
width: 24rpx;
height: $tab-height;
border-top-left-radius: 48rpx;
background-color: $active-color;
transform: skewX(-15deg); //
}
.tab-selected::after {
content: '';
position: absolute;
right: -12rpx;
bottom: 0;
width: 24rpx;
height: $tab-height;
border-top-right-radius: 24rpx;
background-color: $active-color;
transform: skewX(15deg); //
}
.not-selected {
color: $primary-color;
}
.not-selected::before {
content: '';
position: absolute;
left: 12rpx;
bottom: 0;
width: 24rpx;
height: $tab-height;
background: $default-color;
border-bottom-left-radius: 24rpx;
transform: skewX(15deg); //
}
.not-selected::after {
content: '';
position: absolute;
right: 12rpx;
bottom: 0;
width: 24rpx;
height: $tab-height;
background: $default-color;
border-bottom-right-radius: 24rpx;
transform: skewX(-15deg); //
z-index: -1;
}
}
.error-tips{
font-size: 24rpx;
color: #EF3920;
display: flex;
text-align: center;
margin-top: 10rpx;
// padding-left: 30rpx;
.error-img{
width: 32rpx;
height: 32rpx;
margin-right: 5rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
<template>
<view class="flex-col page">
<u-navbar :border-bottom="false" :background="background"> </u-navbar>
<view class="flex-col group_3">
<h3 class="text_3">填写学校信息</h3>
<view class="flex-col group_4">
<view class="justify-between list-item group_5">
<text class="text_8">学历</text>
<u-input v-model="form.educational" placeholder="请选择学历" type="select" @click="show = true" />
<u-action-sheet :list="educationalList" v-model="show" @click="educationalSelect()" ></u-action-sheet>
</view>
<view class="justify-between list-item">
<text class="text_8">学校</text>
<u-input v-model="form.shool" placeholder="请选择学校" type="select" @click="show = true" />
</view>
<view class="justify-between list-item">
<text class="text_8">学院</text>
<u-input v-model="form.college" placeholder="请选择学院" type="select" @click="show = true" />
</view>
<view class="justify-between list-item">
<text class="text_8">专业</text>
<u-input v-model="form.major" placeholder="请选择专业" type="select" @click="show = true" />
<u-action-sheet :list="actionSheetList" v-model="show" @click="actionSheetCallback()" ></u-action-sheet>
</view>
<view class="justify-between list-item">
<text class="text_8">时间</text>
<u-input v-model="form.time" placeholder="请选择时间" type="select" @click="show = true" />
</view>
</view>
<u-button shape="circle" class="button text-wrapper_1" type="primary"
>完成</u-button
>
</view>
</view>
</template>
<script>
import uButton from "../../../uview-ui/components/u-button/u-button.vue";
export default {
components: { uButton },
data() {
return {
form:{
educational:'',//
shool:'',//
college:'',//
major:'',//
time:'',//
},
background: {
backgroundColor: "#fff",
},
value: "",
show: false,
educationalList:[//
{
text:'专科',
},
{
text:'本科',
},
{
text:'专科',
}
],
actionSheetList: [
{
text: "男",
},
{
text: "女",
},
{
text: "保密",
},
],
};
},
onLoad(e) {
;
},
methods: {
educationalSelect(index){
this.form.educational = this.actionSheetList[index].text;
},
actionSheetCallback(index) {
this.form.major = this.actionSheetList[index].text;
},
save() {
uni.switchTab({
url: "/pages/main/index/index",
});
},
skip() {
uni.switchTab({
url: "/pages/main/index/index",
});
},
},
};
</script>
<style scoped lang="scss">
.page {
background: #fff;
height: 100%;
.text-wrapper_1 {
padding: 0.15rem 0 0.14rem;
background-image: linear-gradient(
90deg,
#87e6fe 0%,
#5bc0fe 52%,
#2e9bff 100%
);
box-shadow: 0px 0.03rem 0.09rem rgba(38, 122, 199, 0.34);
border-radius: 0.23rem;
}
.button {
margin: 0.6rem 0.15rem 0;
}
}
.group_3 {
padding-left: 0.16rem;
padding-top: 0.3rem;
.text_3 {
align-self: center;
color: rgb(51, 51, 51);
font-size: 0.18rem;
font-family: PingFang;
line-height: 0.17rem;
}
.group_4 {
margin-top: 0.44rem;
.list-item {
padding: 0.1rem 0.2rem 0.09rem 0;
border-bottom: solid 0.005rem rgb(199, 199, 204);
.text_8 {
color: #000;
font-size: 0.18rem;
font-family: PingFang;
line-height: 0.38rem;
margin-right: 0.1rem;
}
.text_26 {
color: #000;
font-size: 0.17rem;
font-family: PingFang;
line-height: 0.16rem;
}
}
.group_5 {
margin-top: 0;
}
}
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<view class="recognition-result-page">
<u-navbar title="认证结果" :is-back="false"></u-navbar>
<view class="result-container">
<image
:src="isSuccess ? resultSuccess : resultError"
class="result-image"
mode="widthFix"
></image>
<text class="result-text">{{
isSuccess ? "认证通过" : "认证未通过"
}}</text>
</view>
<view class="btn-container">
<button class="next-btn" @click="handleNext">
{{ isSuccess ? "进入系统" : "重新识别" }}
</button>
</view>
</view>
</template>
<script>
import resultSuccess from "@/static/common/img/result-success.png";
import resultError from "@/static/common/img/result-error.png";
export default {
data() {
return {
resultSuccess,
resultError,
isSuccess: false,
verifyResult: null,
verifyInfo: null,
token: null,
};
},
onLoad(options) {
// token
if (options && options.token) {
this.token = options.token;
console.log("获取到token:", this.token);
// 使token
this.queryVerificationResult();
}
},
methods: {
//
queryVerificationResult() {
if (!this.token) {
console.error("token为空无法查询认证结果");
return;
}
// API
this.$u.api
.getVerifyInfo(this.token)
.then((res) => {})
.catch((err) => {
console.log("API调用过程中出错:", err);
});
},
handleNext() {
if (this.isSuccess) {
uni.switchTab({
url: "/pages/main/index/index",
});
} else {
this.toBaiduApi();
}
},
toBaiduApi() {
this.$u.api.getAPIToken().then((res) => {
const token = res.result.verify_token;
//
this.redirectToBaiduVerification(token);
});
},
redirectToBaiduVerification(token) {
//
const currentDomain = window.location.origin;
// URL - 使uni-app
// URLverify_resultverify_info
const successUrl = encodeURIComponent(
`${currentDomain}/#/pages/login/recognitionResult/recognitionResult?token=${token}`
);
const failedUrl = encodeURIComponent(
`${currentDomain}/#/pages/login/recognitionResult/recognitionFailed?token=${token}`
);
// URL
const verifyUrl = `https://brain.baidu.com/face/print/verify/verify?token=${token}&successUrl=${successUrl}&failedUrl=${failedUrl}`;
console.log("跳转到百度人脸核验页面:", verifyUrl);
//
window.location.href = verifyUrl;
return;
},
},
};
</script>
<style lang="scss">
.recognition-result-page {
padding: 0 24rpx;
.result-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 130rpx 0 36rpx;
text-align: center;
.result-image {
width: 160rpx;
height: 160rpx;
margin-bottom: 36rpx;
}
.result-text {
font-family: PingFang SC, PingFang SC;
font-size: 32rpx;
color: #000;
}
}
.btn-container {
.next-btn {
background-color: #45b5ff;
color: #ffffff;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
}
}
}
</style>

View File

@ -0,0 +1,155 @@
<template>
<view class="recognition-result-page">
<u-navbar title="认证结果" :is-back="false"></u-navbar>
<view class="result-container">
<image
:src="isSuccess ? resultSuccess : resultError"
class="result-image"
mode="widthFix"
></image>
<text class="result-text">{{
isSuccess ? "认证通过" : "认证未通过"
}}</text>
</view>
<view class="btn-container">
<button class="next-btn" @click="handleNext">
{{ isSuccess ? "进入系统" : "重新识别" }}
</button>
</view>
</view>
</template>
<script>
import resultSuccess from "@/static/common/img/result-success.png";
import resultError from "@/static/common/img/result-error.png";
export default {
data() {
return {
resultSuccess,
resultError,
isSuccess: true,
token: null,
userInfo: {
realName: "", //
// idCard: "", //
},
};
},
onLoad(options) {
// token
if (options && options.token) {
this.token = options.token;
console.log("获取到token:", this.token);
// 使token
this.queryVerificationResult();
}
},
methods: {
//
queryVerificationResult() {
if (!this.token) {
console.log("token为空无法查询认证结果");
return;
}
// API
this.$u.api.getVerifyInfo(this.token).then((res) => {
const result = res.result.idcard_confirm;
this.userInfo.realName = result.name;
// this.userInfo.idCard = result.idcard_number;
// console.log("this.userInfo", this.userInfo);
if (result.idcard_number) {
this.getSchoolInfo(result.idcard_number);
this.updateCardFn(result.idcard_number);
} else {
this.$u.vuex("vuex_userInfo", this.userInfo);
}
});
// .catch((err) => {
// console.log("API:", err);
// });
},
//
getSchoolInfo(card) {
this.$u.api
.getInfoByCard({
card,
// card: "362429199209242513",
})
.then((res) => {
// console.log(":", res);
if (res.length > 0) {
this.userInfo = {
...this.userInfo,
...res[0],
};
}
this.$u.vuex("vuex_userInfo", this.userInfo);
});
},
// idCard
updateCardFn(card) {
this.$u.api
.updateCard({
userId: this.vuex_user.id,
idCode: card,
})
.then((res) => {
console.log("返回结果:", res);
});
},
handleNext() {
uni.navigateTo({
url: "/pages/login/roleSelection",
});
},
},
};
</script>
<style lang="scss">
.recognition-result-page {
padding: 0 24rpx;
.result-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 130rpx 0 36rpx;
text-align: center;
.result-image {
width: 160rpx;
height: 160rpx;
margin-bottom: 36rpx;
}
.result-text {
font-family: PingFang SC, PingFang SC;
font-size: 32rpx;
color: #000;
}
}
.btn-container {
.next-btn {
background-color: #45b5ff;
color: #ffffff;
height: 90rpx;
line-height: 90rpx;
border-radius: 45rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
}
}
}
</style>

View File

@ -0,0 +1,633 @@
<template>
<view class="flex-col page">
<!-- <image src="/static/common/img/16530269846087107196.png" class="image" /> -->
<u-navbar title="注册"></u-navbar>
<view class="flex-col group">
<text class="text">手机号注册</text>
<view class="flex-col group_7">
<view class="flex-col">
<!-- <text class="text_1">手机号</text> -->
<view class="text-wrapper flex-row view" style="padding: 0">
<u-input type="number" v-model="userName" placeholder="请输入手机号" maxlength="11" :disabled="disabled" />
</view>
</view>
<view class="flex-col group_6" v-show="showCap">
<view class="text-wrapper flex-row view" style="padding: 0">
<u-input type="text" v-model="captchaCode" placeholder="请输入图形验证码" />
<image :src="captchaImg" class="captcha-img" @click="refreshCaptcha" />
</view>
</view>
<view class="flex-col group_6">
<!-- <text class="text_1">短信验证码</text> -->
<view class="text-wrapper flex-row view" style="padding: 0">
<u-input type="text" v-model="passWord" placeholder="请输入短信验证码" />
<u-button style='background:transparent;' class="custom-style" :disabled="!(send == '获取验证码')" @click="sendCode">{{ send }}
</u-button>
</view>
</view>
</view>
<u-button class="flex-col items-center text-wrapper_1" throttle-time="1000" type="primary" shape="circle"
@click="login"><text class="text_7">注册</text></u-button>
<view class="justify-center group_1">
<text class="text_8">已有账号</text>
<navigator class="text_9" url="/pages/login/login/login">立即登录</navigator>
</view>
<!-- <view class="flex-row group_2">
<u-checkbox-group class="section_1" :size="34">
<u-checkbox v-model="agreement"> </u-checkbox>
</u-checkbox-group>
<view class="group_3">
<text class="text_10">已阅读并同意</text>
<text class="text_11">用户服务协议</text>
<text class="text_12"></text>
<text class="text_13">用户隐私保护政策</text>
</view>
</view> -->
</view>
<u-top-tips ref="uTips" :navbar-height="0"></u-top-tips>
<u-toast ref="uToast" />
</view>
</template>
<script>
import md5 from "js-md5";
export default {
data() {
return {
show: false,
userName: "",
passWord: "",
agreement: false,
send: "获取验证码",
disabled: false,
captchaCode: "", //
captchaImg: "", //
captchaId: "", // ID
showCap: false,
};
},
onLoad(e) {
// this.refreshCaptcha();
},
methods: {
//
refreshCaptcha() {
const timestamp = Date.now();
this.captchaId = timestamp;
uni.request({
url: 'http://sl.vrgon.com:8003/api/Token/Captcha?id=' + timestamp,
method: 'GET',
responseType: 'arraybuffer',
headers: {
'Accept': '*/*',
},
success: (res) => {
if (res.statusCode === 200 && res.data) {
// res.data ArrayBuffer Blob
const blob = new Blob([res.data], { type: 'image/gif' }); //
const reader = new FileReader();
reader.onload = () => {
// Blob Data URL (Base64 )
this.captchaImg = reader.result;
};
reader.onerror = (e) => {
console.error("FileReader error:", e);
this.$refs.uToast.show({
title: '图片转换失败',
type: "error",
});
};
reader.readAsDataURL(blob);
} else {
console.error('获取验证码失败,状态码或数据异常:', res);
this.$refs.uToast.show({
title: '获取验证码失败',
type: "error",
});
}
},
fail: (err) => {
console.error('验证码请求失败:', err);
this.$refs.uToast.show({
title: '获取验证码失败请检查网络或CORS设置',
type: "error",
});
}
});
},
//
sendCode() {
var reg = new RegExp("^[1][3,4,5,6,7,8,9][0-9]{9}$", "g"); //
if (this.userName === "" || !reg.test(this.userName)) {
this.$refs.uToast.show({
title: "请输入正确的手机号码",
type: "error",
});
return;
}
// if (!this.captchaCode) {
// this.$refs.uToast.show({
// title: "",
// type: "error",
// });
// return;
// }
// 13
const timestamp = Date.now();
//
const base64Key = btoa('xiaoyout!#@12345');
const signStr = this.userName + timestamp + base64Key;
const sign = md5(signStr);
this.$u.apiList.GetPhoneValidateCode({
phone: this.userName,
t: timestamp.toString(),
sign: sign,
captchaCode: this.showCap ? this.captchaCode : '' ,
captchaId: this.showCap ? this.captchaId.toString() : ''
}).then((res)=>{
//
// let result =this.userName.replace(this.userName.substring(3,7),"****")
// this.userName = result
this.disabled = true
// this.$tips("");
this.$refs.uToast.show({
title: "发送成功",
type: "success",
});
var second = 60;
this.send = second + "秒后重试";
var time = setInterval(() => {
this.send = second + "秒后重试";
second--;
if (second <= 0) {
this.disabled = false
this.send = "获取验证码";
clearInterval(time);
}
}, 1000);
})
.catch((err)=>{
console.log('err-----',err)
if(err?.data?.needcap && !this.showCap) {
this.showCap = true
this.$refs.uToast.show({
title: '请输入图形验证码',
type: "error",
});
this.refreshCaptcha()
return
}
this.$refs.uToast.show({
title: err.error,
type: "error",
});
this.refreshCaptcha()
})
},
login() {
// uni.navigateTo({
// url: "/pages/login/confirmPwd/confirmPwd?phone="+this.userName,
// });
// return
var reg = new RegExp("^[1][3,4,5,7,8,9][0-9]{9}$", "g"); //
if (this.userName === "" || !reg.test(this.userName)) {
// this.$tips("", "error");
this.$refs.uToast.show({
title: "请输入正确的手机号码",
type: "error",
});
return;
}
if (this.passWord === "") {
// return this.$tips("", "error");
return this.$refs.uToast.show({
title: "请输入验证码",
type: "error",
});
}
// if (!this.agreement) {
// return this.$tips("", "error");
// }
const data = {
phone:this.userName,
code : this.passWord
}
this.$u.apiList.IsPhoneCode(data).then((res)=>{
setTimeout(() => {
uni.navigateTo({
url: "/pages/login/confirmPwd/confirmPwd?phone="+this.userName+"&code="+this.passWord,
});
}, 1000);
})
.catch((err)=>{
// this.$tips(err.error, "error");
this.$refs.uToast.show({
title: err.error,
type: "error",
});
})
},
},
};
</script>
<style scoped lang="scss">
.custom-style {
color: #3CB5FB !important;
padding: 0 !important;
height: 70rpx !important;
line-height: 60rpx !important;
}
.custom-style::after {
border: none;
}
.page {
padding-bottom: 0.015rem;
background-color: rgb(255, 255, 255);
overflow-y: auto;
width: 100%;
overflow-x: hidden;
height: 100%;
.image {
flex-shrink: 0;
align-self: flex-start;
width: 102%;
}
.group {
padding: 0.7rem 0.29rem;
.text {
align-self: left;
// color: rgb(46, 155, 255);
color: #000;
font-size: 0.22rem;
font-family: PingFang;
line-height: 0.17rem;
font-weight: 600;
}
.group_7 {
margin-top: 0.29rem;
.text-wrapper {
border: solid 2rpx #dcdfe6;
border-radius: 100rpx;
background: #f6f8fa;
}
.group_6 {
margin-top: 0.2rem;
}
.text_1 {
margin-left: 0.01rem;
align-self: flex-start;
color: rgb(51, 51, 51);
font-size: 0.15rem;
font-family: PingFang;
line-height: 0.14rem;
}
.view {
display: flex;
align-items: center;
margin-top: 20rpx;
padding: 20rpx 50rpx !important;
}
}
.text_6 {
margin-left: 0.01rem;
margin-top: 0.13rem;
align-self: flex-start;
color: rgb(25, 140, 255);
font-size: 0.13rem;
font-family: PingFang;
line-height: 0.13rem;
}
.text-wrapper_1 {
margin: 50rpx 0rpx 40rpx;
padding: 50rpx 0 50rpx;
// background-image: linear-gradient(90deg, rgb(135, 230, 254) 0%, rgb(91, 192, 254) 52%, rgb(46, 155, 255) 100%);
background: #3cb4fb;
box-shadow: 0px 6rpx 9rpx rgba(38, 122, 199, 0.34);
border-radius: 100rpx;
.text_7 {
color: rgb(255, 255, 255);
font-size: 32rpx;
font-family: PingFang;
line-height: 32rpx;
}
}
.group_1 {
margin-top: 0.12rem;
.text_8 {
color: rgb(102, 102, 102);
font-size: 0.14rem;
font-family: PingFang;
line-height: 0.13rem;
}
.text_9 {
margin-left: 0.035rem;
color: rgb(25, 140, 255);
font-size: 0.14rem;
font-family: PingFang;
line-height: 0.13rem;
}
}
.group_2 {
margin-top: 0.36rem;
justify-content: center;
.section_1 {
flex-shrink: 0;
width: 0.17rem;
}
.group_3 {
margin-left: 0.11rem;
height: 0.18rem;
line-height: 0.18rem;
font-size: 0;
.text_10 {
color: rgb(153, 153, 153);
font-size: 0.11rem;
font-family: PingFang;
}
.text_11 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
.text_12 {
color: rgb(102, 102, 102);
font-size: 0.11rem;
font-family: PingFang;
}
.text_13 {
color: rgb(25, 140, 255);
font-size: 0.11rem;
font-family: PingFang;
}
}
}
}
.captcha-img {
width: 200rpx;
height: 70rpx;
margin-right: 20rpx;
}
}
</style>
<!-- <template>
<view class="flex-col page">
<view class="flex-col group">
<text class="text">注册账号</text>
<view class="flex-col group_1">
<view class="flex-col group_2">
<u-select
@click="show = true"
:mode="'single-column'"
v-model="show"
:list="shoolList"
@confirm="confirm"
@cancel="cancel"
></u-select>
<view class="justify-between section_1" @click="show = true">
<text
:style="{ color: this.shoolname !== '' ? '#000' : '#c1c4cc' }"
>{{ this.shoolname != "" ? this.shoolname : "请选择学校" }}</text
>
<image
src="/static/common/img/select.png"
class="image_1"
/>
</view>
<view class="text-wrapper flex-col">
<u-input
v-model="userName"
type="text"
placeholder="请输入用户名"
/>
</view>
<view class="text-wrapper flex-col view_1">
<u-input
v-model="passWord"
type="password"
placeholder="请输入密码"
/>
</view>
<view class="text-wrapper flex-col">
<u-input
v-model="twopassWord"
type="password"
placeholder="请确认密码"
/>
</view>
</view>
<view class="flex-row group_3">
<view class="flex-col text-wrapper_1">
<u-input
v-model="Code"
type="text"
placeholder="请输入验证码"
/>
<text class="text_6"></text>
</view>
<view class="flex-col items-end text-wrapper_2">
<text>6789</text>
</view>
</view>
</view>
<view>
<u-button class="flex-col items-center text-wrapper_3" @click="register" :throttle-time='1000'>注册</u-button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
show: false,
userName: "",
passWord: "",
twopassWord: "",
Code: "",
shoolname: "",
shoolList: [
{
value: "1",
label: "学校一",
},
{
value: "2",
label: "学校二",
},
{
value: "3",
label: "学校三",
},
{
value: "4",
label: "学校四",
}
],
};
},
onLoad(e) {
;
},
methods: {
confirm(e) {
this.shoolname = "";
e.map((val, index) => {
this.shoolname += this.shoolname == "" ? val.label : "-" + val.label;
});
},
cancel(e) {
;
},
register(){
uni.showLoading({
title: '注册中'
});
setTimeout(()=>{
uni.hideLoading();
uni.showToast({
title: '注册成功',
duration: 2000
});
},1000)
}
},
};
</script>
<style scoped lang="css">
.text-wrapper {
margin-top: 0.095rem;
padding: 0.03rem 0.17rem;
background-color: rgb(246, 247, 250);
border-radius: 0.1rem;
}
.text_2 {
margin-left: 0.17rem;
}
.page {
padding: 0.045rem 0 1.5rem;
background-color: rgb(255, 255, 255);
width: 100%;
height: 100%;
overflow-y: auto;
}
.image {
margin-left: 0.26rem;
margin-right: 0.23rem;
width: 3.27rem;
height: 0.09rem;
}
.group {
margin-top: 0.72rem;
padding-left: 0.34rem;
padding-right: 0.31rem;
}
.text {
margin-left: 0.045rem;
color: rgb(0, 0, 0);
font-size: 0.24rem;
line-height: 0.23rem;
letter-spacing: 0.024rem;
white-space: nowrap;
}
.group_1 {
margin-top: 0.67rem;
}
.text-wrapper_3 {
margin-top: 0.54rem;
padding: 0.13rem 0;
color: rgb(255, 255, 255);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.03rem;
white-space: nowrap;
background-color: rgba(46, 155, 255, 0.5);
border-radius: 0.1rem;
}
.group_2 {
color: rgb(193, 196, 204);
font-size: 0.15rem;
line-height: 0.14rem;
white-space: nowrap;
}
.group_3 {
margin-top: 0.08rem;
}
.section_1 {
padding: 0.13rem 0.17rem;
background-color: rgb(246, 247, 250);
border-radius: 0.1rem;
}
.view_1 {
margin-top: 0.09rem;
}
.text-wrapper_1 {
padding: 0 0.17rem;
flex: 1 1 auto;
color: rgb(193, 196, 204);
font-size: 0.15rem;
line-height: 0.14rem;
white-space: nowrap;
background-color: rgb(246, 247, 250);
border-radius: 0.1rem;
height: 0.4rem;
}
.text-wrapper_2 {
margin-left: 0.08rem;
padding: 0.14rem 0.1rem ;
color: rgb(106, 134, 241);
font-size: 0.18rem;
line-height: 0.14rem;
letter-spacing: 0.036rem;
white-space: nowrap;
background-color: rgba(211, 220, 255, 0.5);
border-radius: 0.1rem;
height: 0.4rem;
}
.image_1 {
margin-right: 0.035rem;
margin-top: 0.05rem;
width: 0.14rem;
height: 0.09rem;
}
.text_6 {
margin-left: 0.17rem;
}
</style> -->

View File

@ -0,0 +1,242 @@
<template>
<view class="flex-col page">
<u-navbar
:custom-back="backFn"
:background="background"
:border-bottom="false"
title=""
></u-navbar>
<view>
<view class="title">
<h3>请选择登录角色</h3>
<text class="tips"
>温馨提示因为系统中存在多个角色请选择角色后再登录</text
>
</view>
<view class="selectList">
<view
class="item flex-row"
:class="{ 'item-selected': [0,2].includes(vuex_user.userType) }"
@click="goStudent"
>
<view class="name">
<text class="text">我是学生</text>
<text class="text text-en">Student</text>
</view>
<view class="student"></view>
</view>
<view
class="item flex-row"
:class="{ 'item-selected': vuex_user.userType == '1' }"
@click="goTeacher"
>
<view class="name">
<text class="text">我是教师</text>
<text class="text text-en">Teacher</text>
</view>
<view class="teacher"></view>
</view>
<!-- <view
class="item flex-row"
:class="{ 'item-selected': vuex_user.userType == '2' }"
@click="goGraduate"
>
<view class="name">
<text class="text">我是毕业生</text>
<text class="text text-en">Graduate</text>
</view>
<view class="graduate"></view>
</view> -->
</view>
</view>
</view>
</template>
<script>
import uButton from "../../uview-ui/components/u-button/u-button.vue";
export default {
components: { uButton },
data() {
return {
background: {
backgroundColor: "transparent",
},
source: "",
userRole: uni.getStorageSync("role"),
};
},
onLoad(e) {
console.log(e, "e");
this.source = e.source;
},
methods: {
backFn() {
console.log(this.source, "source---");
if (this.source === "my") {
uni.switchTab({
url: "/pages/my/my/my",
});
return;
}
if (this.source === "login") {
this.$u.vuex("vuex_msgList", "");
this.$u.vuex("vuex_user", "");
this.$u.vuex("vuex_token", "");
this.$u.vuex("vuex_userInfo", "");
uni.clearStorage();
this.$u.route({
url: "/pages/login/login/login",
});
return;
}
uni.navigateBack();
},
async goTeacher() {
const res = await this.$u.apiList.SelectUserTypeApi();
console.log(res, "res---");
const jzgFlag = res.jzgFlag;
// const studentFlag = res.studentFlag
if (jzgFlag === false) {
uni.setStorageSync("role", "teacher");
uni.navigateTo({
url: "/pages/login/teacherCertification",
});
return;
}
const req = {
userType: 1,
};
const result = await this.$u.apiList.UpdateUserTypeApi(req);
console.log(result, "result---");
this.vuex_user.userType = 1;
uni.switchTab({
url: "/pages/my/my/my",
});
},
async goStudent() {
const res = await this.$u.apiList.SelectUserTypeApi();
console.log(res, "res---");
// const jzgFlag = res.jzgFlag;
const studentFlag = res.studentFlag;
if (studentFlag === false) {
uni.setStorageSync("role", "student");
uni.navigateTo({
url: "/pages/login/perfect/perfect",
});
return;
}
const req = {
userType: 0,
};
const result = await this.$u.apiList.UpdateUserTypeApi(req);
console.log(result, "result---");
this.vuex_user.userType = 0;
uni.switchTab({
url: "/pages/my/my/my",
});
},
async goGraduate() {
uni.navigateTo({
url: "/pages/login/graduateCertification/graduateCertification",
});
// uni.$u.toast("");
},
},
};
</script>
<style lang="scss" scoped>
.page {
background: linear-gradient(
0deg,
rgba(80, 123, 244, 0) 80%,
rgba(95, 147, 238, 0.3) 100%
);
height: 100%;
.button {
margin: 0.6rem 0.15rem 0;
}
}
.selectList {
width: calc(100% - 30rpx);
margin: 0 auto;
.item {
height: 278rpx;
padding: 15rpx;
margin-bottom: 15rpx;
box-shadow: 0rpx 20rpx 40rpx 0rpx rgba(0, 0, 0, 0.05);
border-radius: 32rpx;
border: 4rpx solid #e7ecf0;
.name {
width: 60%;
display: flex;
justify-content: center;
// align-items: center;
flex-direction: column;
padding-top: 20rpx;
padding-left: 100rpx;
.text {
margin-top: 10rpx;
font-size: 48rpx;
font-weight: bold;
color: #333;
// margin-right: 200rpx;
letter-spacing: 2rpx;
}
.text-en {
font-size: 40rpx;
color: #666;
letter-spacing: 2rpx;
font-weight: normal;
}
}
}
.item-selected {
border: 4rpx solid #8ad3fd;
}
}
.title {
width: calc(100% - 60rpx);
margin: 70rpx auto 100rpx;
h3 {
font-weight: 800;
font-size: 48rpx;
color: #000;
line-height: 60rpx;
}
.tips {
font-weight: 500;
font-size: 24rpx;
color: #666666;
}
}
.teacher {
width: 256rpx;
height: 256rpx;
-moz-background-image: url("/static/common/img/teacher.png");
-webkit-background-image: url("/static/common/img/teacher.png");
background-image: url("/static/common/img/teacher.png");
background-repeat: no-repeat;
background-size: cover;
}
.student {
width: 256rpx;
height: 256rpx;
-moz-background-image: url("/static/common/img/student.png");
-webkit-background-image: url("/static/common/img/student.png");
background-image: url("/static/common/img/student.png");
background-repeat: no-repeat;
background-size: cover;
}
.graduate {
width: 256rpx;
height: 256rpx;
-moz-background-image: url("/static/common/img/graduate.png");
-webkit-background-image: url("/static/common/img/graduate.png");
background-image: url("/static/common/img/graduate.png");
background-repeat: no-repeat;
background-size: cover;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
<template>
<view>
<u-navbar title="管理列表" :border-bottom="false"></u-navbar>
<scroll-view scroll-y="true" class="scroll">
<view class="flex-col list" v-if='list.length'>
<view class="flex-row list_item" v-for="(item, i) in list" @click="GoChat(item)" :key="i">
<u-avatar class="image" :show-sex="vuex_msgList.indexOf(item.fromUserId+'admin,')>=0" sex-icon=""
sex-bg-color="red" :src="$u.http.config.imgUrl+item.fromUserHead"></u-avatar>
<view class="right-section flex-col">
<view class="top-group justify-between view_1">
<view class="flex-row">
<text class="text_1">{{item.fromUserName}}</text>
<view class="right-text-wrapper flex-col items-end">
<text class="text_3">{{item.schoolName}}</text>
</view>
</view>
<text
v-if='new Date(item.sendDate) - 0 + (3600000*24)> new Date()&&new Date(item.sendDate).getDate()==new Date().getDate()'
class="text_5">{{ item.sendDate.slice(10,16)}}</text>
<text v-else class="text_5">{{ item.sendDate.slice(0,10)}}</text>
</view>
<view class="bottom-group justify-between">
<text class="text_7">{{item.message}}</text>
<text class="text_9" v-if="item.messageState == 0">未读</text>
<text class="text_9" v-if="item.messageState == 1">已读</text>
<text class="text_10" v-if="item.messageState == 2">待回复</text>
<text class="text_10" v-if="item.messageState == 3">回复中...</text>
<text class="text_10" v-if="item.messageState == 4">已回复</text>
</view>
</view>
</view>
</view>
<view v-else style='padding-top:30vh'>
<!-- <u-empty text="暂无消息列表" mode="message"></u-empty> -->
<no-data text="暂无消息列表"></no-data>
</view>
</scroll-view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import NoData from 'components/NoData.vue'
export default {
components: {
NoData
},
data() {
return {
list: [],
};
},
onLoad() {},
onShow() {
this.getList()
},
methods: {
//
GoChat(data) {
//
this.$u.api.toBind({
manageUser: this.vuex_user.id,
lockUser: data.fromUserId
}).then(res => {
uni.navigateTo({
url: '../dialogBox/dialogBox?id=admin' + '&shoolid=' + data.schoolId + '&fromid=' + data
.fromUserId
});
}).catch(err=>{
/* uni.showToast({
title: '已有老师回复',
duration: 2000,
icon:'none'
}); */
this.$refs.uToast.show({
title: '已有老师回复',
type: 'warning',
});
})
},
getList() {
this.$u.api.getAdminList().then(res => {
this.list = res
})
},
//
router() {
uni.switchTab({
url: '../../message/msgList/msgList'
})
},
}
};
</script>
<style lang="scss" scoped>
::v-deep .u-avatar__sex {
width: 0.1rem !important;
height: 0.1rem !important;
border: none;
}
.scroll {
background-color: rgb(246, 247, 250);
width: 100%;
height: calc(100vh - 0.44rem);
}
.list_item {
padding-left: 0.2rem;
background: #fff;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
border-bottom: 1px solid rgb(234, 234, 234);
}
.image {
align-self: center;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
}
.right-section {
margin-left: 0.14rem;
padding: 0.13rem 0.015rem 0.15rem;
flex: 1 1 auto;
height: 0.66rem;
}
.list_item:last-child .right-section {
border: none;
}
.top-group {
margin-top: 0.09rem;
width: 0.4rem;
height: 0.4rem;
}
.bottom-group {
margin-top: 0.05rem;
}
.text_5 {
text-align: right;
margin-right: 0.1rem;
margin-top: 0.02rem;
color: rgb(193, 196, 204);
font-size: 0.12rem;
line-height: 0.095rem;
letter-spacing: 0.012rem;
white-space: nowrap;
min-width: 0.5rem;
}
.text_7 {
margin-top: 0.035rem;
color: rgb(177, 179, 182);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
text-align: right;
}
.text_9 {
margin-right: 0.085rem;
margin-bottom: 0.035rem;
color: rgb(46, 155, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
min-width: 0.5rem;
text-align: right;
}
.text_10 {
color: rgb(177, 179, 182);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
min-width: 0.5rem;
text-align: right;
margin-right: 0.1rem;
margin-top: 0.035rem;
}
.text_1 {
margin: 0.02rem 0;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
max-width: 0.65rem;
overflow: hidden;
text-overflow: ellipsis;
}
.right-text-wrapper {
margin-left: 0.045rem;
padding: 0.035rem 0.08rem;
color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
height: 0.18rem;
}
.text_3 {
margin-right: 0.03rem;
}
.list {
padding-top: 0.01rem;
}
.view_1 {
margin-top: initial;
width: initial;
height: initial;
}
</style>

View File

@ -0,0 +1,673 @@
<template>
<view>
<u-navbar back-text="" title="新增关注"></u-navbar>
<scroll-view scroll-y="true" class="scroll">
<view class="guanzhu">
<view class="flex-col renzheng" v-if="!vuex_user.isAttestationXY&& false">
<view class="flex-col section_4">
<view class="flex-row section_5">
<text class="text_3">待认证请先完成认证~</text>
<text class="text_4" @click="onAuto">去认证 >></text>
</view>
</view>
<view class="flex-col group_3">
<view class="flex-col">
<image src="/static/common/img/16535374500948048054.png" class="image_1" />
</view>
<text class="text_5">暂无数据请尽快认证补充资料~</text>
</view>
</view>
<view class="flex-col group_2" v-else>
<view class="flex-col list">
<view class="list-item flex-row group_3" v-for="(item, index) in UserFollowList" :key='index'
@click='toDetail(item.carewUserId, item.userRole, item.carewUserHead)'>
<u-avatar :src="$u.http.config.imgUrl + item.carewUserHead" class="text_3 image_1"></u-avatar>
<view class="bottom-text-wrapper justify-between view_1">
<view class="">
<text class="text_5 text_7"><text>{{ item.carewUserName }}</text><text>{{
"(" + item.carewName +
")" }}</text></text>
<view class="center-text-wrapper flex-col items-end">
<text class="text_8">{{ item.school }}</text>
</view>
</view>
<view v-if='item.carewType == 2 && item.carewName != "管理员"'
class="right-text-wrapper flex-col items-center">
<text class="text_10">互相关注</text>
</view>
<view v-if='item.carewType == 1 && item.carewName != "管理员"'
class="right-text-wrapper flex-col items-center view_21">
<text class="text_10">已关注</text>
</view>
</view>
</view>
<view v-if="!UserFollowList.length"
style="margin-top:20vh;display: flex;justify-content: center;align-items: center;flex-direction: column;">
<image src="/static/common/img/empty.png" class="image" />
<view style="color:grey;margin-top: -5vh;">暂无关注</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import noData from '@/components/NoData.vue';
export default {
components: {
noData
},
data() {
return {
list: [],
UserFollowList: [],
};
},
onLoad() {
// this.getList()
//
this.$connection.on("SystemMessage", (title, content, time) => {
if (this.route.indexOf('sysList') >= 0) {
this.list.unshift({
messageDate: time,
title: title,
message: content
})
}
});
},
onShow() {
this.FollowList()
if (this.vuex_msgList) {
var msgList = this.vuex_msgList;
msgList = msgList.replace('SystemMessage,', '')
this.$u.vuex('vuex_msgList', msgList)
if (!msgList) {
var tab = this.vuex_tabbar;
tab[1].isDot = false;
this.$u.vuex('vuex_tabbar', tab);
}
}
},
methods: {
FollowList() {
this.$u.api.getFollowList().then(res => {
this.UserFollowList = res
})
},
toDetail(id, role, head) {
if (role == 0) {
this.$u.route({
url: '/pages/AlumniCircle/userDetail/userDetail?id=' + id + '&type=0'
})
} else {
uni.navigateTo({
url: "../dialogBox/dialogBox?id=" + id + '&chatType=1&type=0&head=' + head,
});
}
},
getList() {
this.$u.api.getSysList().then(res => {
this.list = res
})
},
//
router() {
uni.switchTab({
url: '../../message/msgList/msgList'
})
},
}
};
</script>
<style lang="scss" scoped>
.scroll {
// background-color: rgb(246, 247, 250);
background-color: #fff;
width: 100%;
height: calc(100vh - 0.44rem);
}
.list-item {
margin-top: 0.1rem;
padding: 20rpx;
background-color: rgb(255, 255, 255);
border-radius: 20rpx;
}
.items-title {
display: flex;
padding-bottom: 20rpx;
margin-bottom: 20rpx;
border-bottom: 2rpx solid #F6F8F9;
image {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.title-time {
margin-left: 0.1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5rpx 0;
// align-items: flex-start;
}
}
.title-text {
font-weight: 600;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.9);
}
.text_1 {
// align-self: center;
color: rgb(193, 196, 204);
font-size: 0.12rem;
line-height: 0.095rem;
// letter-spacing: 0.012rem;
white-space: nowrap;
}
.bottom-section {
padding-left: 10rpx;
}
.text_3 {
color: rgb(2, 2, 2);
font-size: 28rpx;
line-height: 0.16rem;
// letter-spacing: 0.018rem;
white-space: nowrap;
}
.text_5 {
margin-left: 0.015rem;
margin-top: 0.1rem;
color: #908a8a;
font-size: 28rpx;
line-height: 1.5;
// letter-spacing: 0.01rem;
// text-indent: 0.3rem;
}
.text_6 {
margin-top: 0.1rem;
font-size: 0.16rem;
line-height: 0.16rem;
letter-spacing: 0.01rem;
text {
margin-left: 0.015rem;
text-indent: 0.3rem;
display: block;
color: #908a8a;
font-size: 0.14rem;
margin-top: 0.1rem;
}
}
.list {
padding: 0rem 0.15rem;
flex: 1 1 auto;
overflow-y: auto;
}
.guanzhu {
.list-item {
padding: 0.14rem 0 0.1rem;
align-self: flex-end;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
width: 3.02rem;
position: relative;
}
.text_3 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.bottom-text-wrapper {
margin-top: 0.1rem;
padding: 0.035rem 0;
// color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
// background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
color: #3CB5FB;
background-color: rgba(60,181,251,0.1);
}
.text_5 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.center-text-wrapper {
padding: 0.035rem 0.08rem;
color: #3CB5FB;
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(60,181,251,0.1);
border-radius: 0.09rem;
display: inline-block;
margin-top: 16rpx;
}
.right-text-wrapper {
// margin-right: 0.06rem;
// margin-top: 0.03rem;
// margin-left: 0.06rem;
// padding: 0.085rem 0.08rem;
color: #3CB5FB;
font-size: 24rpx;
line-height: 58rpx;
white-space: nowrap;
// background-color: rgb(46, 155, 255);
border-radius: 0.15rem;
border: 2rpx #3CB5FB solid;
width: 120rpx;
height: 64rpx;
position: relative;
}
.text_8 {
margin-right: 0.03rem;
}
.text_10 {
text-transform: uppercase;
}
.text_12 {
margin-left: 0.05rem;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_14 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.group_2 {
// padding: 0.11rem 0 4.94rem;
// flex: 1 1 auto;
// overflow-y: auto;
position: relative;
}
.text-wrapper {
// margin-left: 0.17rem;
padding: 0.11rem 0 0.12rem;
color: rgb(255, 255, 255);
font-size: 0.18rem;
line-height: 0.17rem;
letter-spacing: 0.018rem;
white-space: nowrap;
background-size: 100% 100%;
background-repeat: no-repeat;
width: 0.4rem;
height: 0.4rem;
margin-top: 0.08rem;
background: #6574fc;
border-radius: 50%;
text {
display: inline-block;
width: 100%;
text-align: center;
}
}
.list {}
.text_2 {
color: #fff;
}
.group_3 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
// box-shadow: 0px 0.005rem #eaeaea;
// padding-left: 0.2rem;
}
.group_4 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
margin-top: 0.015rem;
}
.group_5 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_6 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_7 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_8 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_9 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.image_1 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
overflow: hidden;
// margin-top: 0.12rem;
margin-top: 26rpx;
}
.view_1 {
margin-top: initial;
// padding: 0.1rem 0;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.08rem;
margin-right: 0.04rem;
flex: 1 1 auto;
position: relative;
align-items: center;
}
.image_2 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_4 {
margin-top: initial;
padding: 0.14rem 0 0.1rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_3 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_8 {
margin-top: initial;
padding: 0.15rem 0 0.14rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.13rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_4 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_12 {
margin-top: initial;
padding: 0.17rem 0 0.08rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_5 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_16 {
margin-top: initial;
padding: 0.15rem 0 0.11rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
}
.image_6 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_19 {
margin-top: initial;
padding: 0.18rem 0 0.18rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
position: relative;
}
.text_7 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 28rpx;
line-height: 0.14rem;
// letter-spacing: 0.015rem;
white-space: nowrap;
display: block;
font-weight: bold;
text:nth-child(1) {
display: inline-block;
white-space: nowrap;
max-width: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
}
.text_26 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.view_20 {
left: -0.03rem;
bottom: 0.075rem;
}
.view_21 {
// margin-right: 0.06rem;
// margin-top: initial;
// padding: 0.09rem 0 0.06rem;
color: rgb(73, 76, 87);
background-color: initial;
border: solid 0.01rem rgb(185, 185, 185);
}
}
.renzheng {
padding-bottom: 0rem;
.section_4 {
padding: 0.33rem 0 0.34rem;
background-color: rgb(255, 255, 255);
.section_5 {
margin-left: 0.18rem;
margin-right: 0.24rem;
padding: 0.2rem 0.18rem 0.18rem 0.24rem;
background-color: rgb(246, 247, 250);
justify-between: space-between;
.text_3 {
color: rgb(160, 162, 172);
font-size: 0.15rem;
line-height: 0.15rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-left: 0.05rem;
color: rgb(46, 155, 255);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
text-transform: uppercase;
}
}
}
.group_3 {
margin-top: 0.76rem;
padding-left: 0.8rem;
padding-right: 0.69rem;
color: rgb(163, 182, 202);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
.text_5 {
margin-left: 0.02rem;
margin-top: 0.13rem;
}
.image_1 {
margin-right: 0.14rem;
width: 2.12rem;
height: 1.89rem;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,438 @@
<template>
<view>
<u-navbar title="互动消息" :border-bottom="false"></u-navbar>
<u-tabs-swiper ref="uTabs" :list="tabList" :current="current" :is-scroll="false"
:active-item-style="{ color: '#3CB5FB', fontSize: '0.15rem' }" :bar-style="{ background: '#3CB5FB' }"
@change="tabsChange"></u-tabs-swiper>
<scroll-view scroll-y="true" class="scroll">
<view class="flex-col list" v-if='list.length'>
<template v-for="(item, i) in list">
<view v-if='item.interType != 3' class="list-item flex-col section_4" :key="i">
<view class="image_2 flex-row view_14">
<u-avatar :src="$u.http.config.imgUrl + item.userHead"
@click='toDetil(item.interUserId)' class="image_2"></u-avatar>
<view class="flex-col group_2">
<view class="flex-row group_3">
<text class="text_16">{{ item.interUserName }}</text>
<text class="text_18">{{ item.interDate }}</text>
</view>
<view class="right-text-wrapper">
<text v-if="item.interType == 0">点赞了你的作品</text>
<text v-if="item.interType == 1">收藏了你的作品</text>
<text v-if="item.interType == 2">转发了你的作品</text>
</view>
</view>
</view>
<view class="bottom-section flex-row">
<image v-if='item.dynamicHead' :src="$u.http.config.imgUrl + item.dynamicHead.split(',')[0]"
class="image_4" />
<text v-if="item.interType == 0" class="text_6">{{ item.commentContent }}</text>
<text v-if="item.interType == 1" class="text_6">{{ item.dynamicTitle }}</text>
<text v-if="item.interType == 2" class="text_6">{{ item.dynamicTitle }}</text>
</view>
<view class="flex-row group_4">
</view>
</view>
<view v-else class="list-item flex-col section_4">
<view class="top-group flex-row view_13">
<view class="image_2 flex-row view_14">
<u-avatar :src="$u.http.config.imgUrl + item.userHead"
@click='toDetil(item.interUserId)' class="image_2"></u-avatar>
<view class="flex-col group_2">
<view class="flex-row group_3">
<text class="text_16">{{ item.interUserName }}</text>
<!-- <view class="flex-col items-center text-wrapper_1">
<text>{{ item.commentContent == '【我的评论】' ? '评论' : '回复' }}</text>
</view> -->
<text class="text_18">{{ item.interDate }}</text>
</view>
<text class="text_20">{{ item.commentContent == '【我的评论】' ? '评论' :
'回复' }}了你:{{ item.content }}</text>
</view>
</view>
<!-- <view class="right-group flex-col items-center view_15">
<text class="top-group_2">回复</text>
</view> -->
</view>
<view class="bottom-section flex-row">
<text v-if="item.commentContent == '【我的评论】'" class="text_6">{{ item.dynamicTitle }}</text>
<text v-else class="text_6">{{ item.commentContent }}</text>
</view>
<view class="flex-row group_4">
<!-- <image
src="https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/6216dee45a7e3f031061d0f1/621c59e162a7d90011002985/16460348611151830106.png"
class="image_11" />
<text class="text_22">还有10条评论 ></text> -->
</view>
</view>
</template>
</view>
<view v-else style='padding-top:30vh'>
<!-- <u-empty text="暂无消息列表" mode="message"></u-empty> -->
<no-data text="暂无消息列表"></no-data>
</view>
</scroll-view>
</view>
</template>
<script>
import NoData from 'components/NoData.vue'
export default {
components: {
NoData
},
data() {
return {
list: [],
tabList: [
{
name: "全部",
},
{
name: "评论",
},
{
name: "点赞",
},
{
name: "收藏",
},
{
name: "转发",
},
],
current:0,
};
},
onLoad() {
this.getList(-1)
//
this.$connection.on("InteractMessage", (data, type) => {
if (this.route.indexOf('interactionList') >= 0) {
this.list.unshift(data.data)
}
});
},
onShow() {
if (this.vuex_msgList) {
var msgList = this.vuex_msgList;
msgList = msgList.replace('InteractMessage,', '')
this.$u.vuex('vuex_msgList', msgList)
if (!msgList) {
var tab = this.vuex_tabbar;
tab[1].isDot = false;
this.$u.vuex('vuex_tabbar', tab);
}
}
},
methods: {
// tabschange
tabsChange(i) {
// this.current 0= 1= 2= 3= 4=
if (this.current !== i) {
this.current = i;
if (this.current == 0) {
this.getList(-1)
} else if (this.current == 1) {
this.getList(3)
} else {
this.getList(this.current - 2);
}
}
},
//type 0= 1= 2= 3=
getList(type) {
this.$u.api.getinteractionList(type).then(res => {
this.list = res
})
},
//
router() {
uni.switchTab({
url: '../../message/msgList/msgList'
})
},
toDetil(id) {
this.$u.route({
url: '/pages/AlumniCircle/userDetail/userDetail?id=' + id
})
},
}
};
</script>
<style lang="scss" scoped>
.scroll {
background-color: rgb(246, 247, 250);
width: 100%;
height: calc(100vh - 0.44rem);
}
.list-item {
padding: 0 0.14rem 0.17rem;
background-color: rgb(255, 255, 255);
border-radius: 0.1rem;
margin-top: 0.1rem;
}
.list-item:first-child {
margin-top: 0;
}
.top-group {
padding: 0.12rem 0 0.085rem;
}
.bottom-section {
margin-top: 10rpx;
margin-right: 0.055rem;
padding: 0.07rem 0.025rem 0.08rem 0.14rem;
color: rgb(54, 54, 54);
font-size: 0.12rem;
// letter-spacing: 0.012rem;
white-space: nowrap;
background-color:#F6F8F9;
border-radius: 24rpx;
// margin-left: 0.5rem;
width: 100%;
}
.image_2 {
margin-bottom: 0.025rem;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
}
.right-group {
// margin-left: 0.12rem;
// margin-top: 0.05rem;
}
.image_4 {
width: 0.35rem;
height: 0.35rem;
}
.text_6 {
margin-left: 0.025rem;
align-self: center;
overflow: hidden;
max-width: 84%;
text-overflow: ellipsis;
}
.top-group_1 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-left: 0.02rem;
margin-top: 0.085rem;
color: rgb(177, 179, 182);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.text_1 {
// margin: 0.02rem 0;
color: rgb(2, 2, 2);
font-size: 0.15rem;
// line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.right-text-wrapper {
// margin-left: 0.015rem;
padding: 0.03rem 0 0.04rem;
// color: rgb(46, 155, 255);
font-size: 0.12rem;
// line-height: 0.12rem;
// letter-spacing: 0.012rem;
white-space: nowrap;
// background-color: rgba(46, 155, 255, 0.1);
border-radius: 0.09rem;
// width: 0.65rem;
height: 0.18rem;
color: #000;
font-size: 24rpx;
}
.list {
padding: 0.14rem 0.14rem 0.14rem 0.15rem;
}
.view_2 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
}
.top-group {
padding: 0.12rem 0 0.085rem;
}
.image_2 {
margin-bottom: 0.025rem;
width: 0.4rem;
height: 0.4rem;
}
.right-group {
// margin-left: 0.12rem;
// margin-top: 0.05rem;
}
.section_4 {
padding: 0.15rem 0.16rem 0;
margin-top: 0.1rem;
}
.view_13 {
padding: initial;
}
.text_20 {
margin-right: initial;
padding: initial;
align-self: initial;
background-color: initial;
// margin-left: 0.53rem;
overflow: hidden;
text-overflow: ellipsis;
width: 80%;
font-size: 24rpx;
color: rgba(0, 0, 0, 0.9);
}
.text-wrapper_2 {
margin-right: 0.05rem;
margin-top: 0.1rem;
padding: 0.18rem 0 0.2rem;
color: rgb(54, 54, 54);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
background-color: rgb(246, 247, 250);
max-width: 84%;
min-width: 84%;
margin-left: 0.53rem;
}
.group_4 {
padding: 0.1rem 0.52rem 0.1rem;
color: rgb(177, 179, 182);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.view_14 {
margin-bottom: initial;
width: initial;
height: initial;
flex: 1 1 auto;
}
.view_15 {
margin-left: 0.2rem;
margin-top: initial;
margin-right: 0.07rem;
padding: 0.07rem 0;
color: rgb(255, 255, 255);
font-size: 0.12rem;
line-height: 0.12rem;
white-space: nowrap;
background-color: rgb(46, 155, 255);
border-radius: 0.13rem;
width: 0.56rem;
height: 0.25rem;
}
.view_15:active {
background: #fff;
color: rgb(46, 155, 255);
border: 1px solid rgb(46, 155, 255);
}
.text_21 {
margin-left: 0.14rem;
margin-right: 0.12rem;
max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
}
.image_11 {
width: 0.2rem;
height: 0.2rem;
}
.text_22 {
margin: 0.05rem 0 0.04rem 0.09rem;
}
.image_10 {
width: 0.4rem;
height: 0.4rem;
}
.group_2 {
margin-left: 0.11rem;
margin-bottom: 0.045rem;
flex: 1 1 auto;
}
.group_3 {
padding-left: 0.01rem;
display: flex;
align-items: center;
}
.text_18 {
// margin-top: 0.055rem;
color: rgb(177, 179, 182);
font-size: 0.12rem;
// line-height: 0.12rem;
// letter-spacing: 0.012rem;
white-space: nowrap;
margin-left: 10rpx;
}
.text_16 {
margin-bottom: 0.03rem;
color: rgb(2, 2, 2);
font-size:28rpx;
// line-height: 0.14rem;
// letter-spacing: 0.015rem;
white-space: nowrap;
}
.text-wrapper_1 {
margin-left: 0.04rem;
padding: 0.03rem 0 0.04rem;
color: rgb(46, 155, 255);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
background-color: rgba(46, 155, 255, 0.1);
border-radius: 0.09rem;
width: 0.65rem;
height: 0.18rem;
}
</style>

View File

@ -0,0 +1,938 @@
<template>
<view>
<u-navbar :is-back="false" title="">
<view class="slot-wrap">
<u-tabs-swiper ref="uTabs" :list="navList" :current="current" :is-scroll="false"
:active-item-style="{ color: '#3CB5FB', fontSize: '0.18rem' }"
:bar-style="{ background: '#3CB5FB' }" @change="tabsChange"></u-tabs-swiper>
</view>
</u-navbar>
<swiper :current="swiperCurrent" @transition="transition" @animationfinish="animationfinish"
style="height: calc(100vh - 0.95rem); width: 100%">
<swiper-item class="swiper-item" v-for="(item, index) in navList" :key="index"
style="height: calc(100vh - 0.95rem); width: 100%">
<scroll-view scroll-y="true" style="height: calc(100vh - 0.95rem); width: 100%"
@scrolltolower="onreachBottom">
<!-- 消息 -->
<view class="msg" v-if="index == 0">
<view class="flex-col renzheng" v-if="!vuex_user.isAttestationXY&&false">
<view class="flex-col section_4">
<view class="flex-row section_5">
<text class="text_3">待认证请先完成认证~</text>
<text class="text_4" @click="onAuto">去认证 >></text>
</view>
</view>
<view class="flex-col group_3">
<view class="flex-col">
<image src="/static/common/img/16535374500948048054.png" class="image_1" />
</view>
<text class="text_5">暂无数据请尽快认证补充资料~</text>
</view>
</view>
<view class="flex-col group_2">
<view class="flex-col list">
<navigator url="../adminList/adminList" class="list-item flex-row"
style='box-shadow: 0px 0.005rem #eaeaea;' v-if='vuex_user.isAttestationGLY'>
<u-avatar class="image_1" :show-sex="vuex_msgList.indexOf('admin')>=0" sex-icon=""
size="0.4rem" sex-bg-color="red" mode="circle"
src="/static/common/img/adminHeaderImg.png">
</u-avatar>
<view class="right-section flex-col">
<view class="top-group justify-between">
<text class="text_2">管理列表</text>
<!-- <text class="text_4">8:52</text> -->
</view>
<text class="text_6" v-if="vuex_msgList.indexOf('admin')>=0">你有新通知</text>
<text class="text_6" v-else>暂无新通知</text>
</view>
</navigator>
<navigator v-if='interInfo' url="../interactionList/interactionList"
class="list-item flex-row" style='box-shadow: 0px 0.005rem #eaeaea;'>
<u-avatar class="image_1" :show-sex="vuex_msgList.indexOf('InteractMessage')>=0"
sex-icon="" size="0.4rem" sex-bg-color="red" mode="circle"
src="/static/common/img/interactionhiderimg.png"></u-avatar>
<view class="right-section flex-col">
<view class="top-group justify-between">
<text class="text_2">互动消息</text>
<view style='display:none'>
{{interInfo.interDate?interInfo.interDate = interInfo.interDate.replace(/-/g, "/"):''}}
</view>
<text
v-if='new Date(interInfo.interDate) - 0 + (3600000*24)> new Date()&&new Date(interInfo.interDate).getDate()==new Date().getDate()'
class="text_4">{{interInfo.interDate.slice(10,16)}}</text>
<text v-else class="text_4">{{ interInfo.interDate.slice(5,10)}}</text>
<!-- <text class="text_4">{{interInfo.interDate}}</text> -->
</view>
<text v-if='interInfo.interType==0'
class="text_4 text_15">{{interInfo.interUserName+' 点赞了 '+interInfo.dynamicTitle}}</text>
<text v-if='interInfo.interType==1'
class="text_4 text_15">{{interInfo.interUserName+' 收藏了 '+interInfo.dynamicTitle}}</text>
<text v-if='interInfo.interType==2'
class="text_4 text_15">{{interInfo.interUserName+' 转发了 '+interInfo.dynamicTitle}}</text>
<text v-if='interInfo.interType==3'
class="text_4 text_15">{{interInfo.interUserName+' 评论了 '+interInfo.dynamicTitle}}</text>
</view>
</navigator>
<navigator v-if='sysInfo' url="../sysList/sysList" class="list-item flex-row group_4">
<u-avatar class="image_1" :show-sex="vuex_msgList.indexOf('SystemMessage')>=0"
sex-icon="" size="0.4rem" sex-bg-color="red" mode="circle"
src="/static/common/img/systemHeaderimg.png">
</u-avatar>
<view class="right-section flex-col view_2">
<view class="top-group justify-between view_3">
<text class="text_2 text_8">系统消息</text>
<view style='display:none'>
{{sysInfo.messageDate?sysInfo.messageDate = sysInfo.messageDate.replace(/-/g, "/"):''}}
</view>
<text
v-if='new Date(sysInfo.messageDate) - 0 + (3600000*24)> new Date()&&new Date(sysInfo.messageDate).getDate()==new Date().getDate()'
class="text_6 text_16">{{ sysInfo.messageDate.slice(10,16)}}</text>
<text v-else class="text_6 text_16">{{
sysInfo.messageDate.slice(5,10)}}</text>
</view>
<text class="text_4 text_15">{{sysInfo.title}} </text>
</view>
</navigator>
<view v-if="!sysInfo" style="margin-top:20vh;display: flex;justify-content: center;align-items: center;flex-direction: column;" >
<image src="/static/common/img/empty.png" class="image" />
<view style="color:grey;margin-top: -5vh;">暂无消息</view>
</view>
<template v-for="(v, i) in UserMsgList">
<view class="list-item flex-row group_5" :key="i"
@click="GoChat(v.userId,v.chatType,v.userHead)">
<u-avatar v-if='v.chatType==0' class="image_1"
:show-sex="vuex_msgList.indexOf(v.userId+',')>=0" sex-icon="" size="0.4rem"
sex-bg-color="red" mode="circle" :src="$u.http.config.imgUrl+v.userHead">
</u-avatar>
<u-avatar v-else class="image_1" :show-sex="vuex_msgList.indexOf('admin,')>=0"
sex-icon="" size="0.4rem" sex-bg-color="red" mode="circle"
:src="$u.http.config.imgUrl+v.userHead">
</u-avatar>
<view class="right-section justify-between view_4">
<view class="top-group flex-col view_5">
<view class="text_2 flex-row view_6">
<text class="text_11">{{v.userNetName}}</text>
<view class="right-text-wrapper flex-col items-end">
<text class="text_13">{{ v.userSchoolName }}</text>
</view>
</view>
<text class="text_4 text_15">{{v.message}}</text>
</view>
<view style='display:none'>
{{v.sendDate?v.sendDate = v.sendDate.replace(/-/g, "/"):''}}</view>
<text
v-if='new Date(v.sendDate) - 0 + (3600000*24)> new Date()&&new Date(v.sendDate).getDate()==new Date().getDate()'
class="text_6 text_16">{{ v.sendDate.slice(10,16)}}</text>
<text v-else class="text_6 text_16">{{ v.sendDate.slice(5,10)}}</text>
</view>
</view>
</template>
</view>
</view>
</view>
<!-- 关注 -->
<view class="guanzhu" v-else>
<view class="flex-col renzheng" v-if="!vuex_user.isAttestationXY&&false">
<view class="flex-col section_4">
<view class="flex-row section_5">
<text class="text_3">待认证请先完成认证~</text>
<text class="text_4" @click="onAuto">去认证 >></text>
</view>
</view>
<view class="flex-col group_3">
<view class="flex-col">
<image src="/static/common/img/16535374500948048054.png" class="image_1" />
</view>
<text class="text_5">暂无数据请尽快认证补充资料~</text>
</view>
</view>
<view class="flex-col group_2">
<view class="flex-col list">
<!-- <view class="list-item flex-row group_3">
<view class="flex-col items-end text-wrapper text_3">
<text class="text_2"></text>
</view>
<view class="bottom-text-wrapper justify-between view_1">
<text class="text_5 text_7">学校管理员</text>
<view class="center-text-wrapper flex-col items-end">
<text class="text_8">浙江大学</text>
</view>
</view>
</view> -->
<view class="list-item flex-row group_3" v-for="(item,index) in UserFollowList"
:key='index' @click='toDetail(item.carewUserId,item.userRole,item.carewUserHead)'>
<u-avatar :src="$u.http.config.imgUrl+item.carewUserHead" class="text_3 image_1"></u-avatar>
<view class="bottom-text-wrapper justify-between view_1">
<view class="">
<text
class="text_5 text_7"><text>{{item.carewUserName}}</text><text>{{"("+item.carewName+")"}}</text></text>
<view class="center-text-wrapper flex-col items-end">
<text class="text_8">{{item.school}}</text>
</view>
</view>
<view v-if='item.carewType==2&&item.carewName!="管理员"'
class="right-text-wrapper flex-col items-center">
<text class="text_10">互相关注</text>
</view>
<view v-if='item.carewType==1&&item.carewName!="管理员"'
class="right-text-wrapper flex-col items-center view_21">
<text class="text_10">已关注</text>
</view>
</view>
</view>
<view v-if="!UserFollowList.length" style="margin-top:20vh;display: flex;justify-content: center;align-items: center;flex-direction: column;" >
<image src="/static/common/img/empty.png" class="image" />
<view style="color:grey;margin-top: -5vh;">暂无关注</view>
</view>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
<u-tabbar :list="vuex_tabbar" :class="{phone:vuex_iPhone}"></u-tabbar>
</view>
</template>
<script>
export default {
data() {
return {
tabbar: "",
swiperCurrent: 0,
current: 0,
UserMsgList: [],
UserFollowList: [],
navList: [{
name: "消息",
},
{
name: "关注",
},
],
interInfo: '',
sysInfo: '',
};
},
watch: {
current() {
this.getList();
},
},
created() {
// onShow
// this.getList()
},
onLoad() {
//
this.$connection.on("ReceiveMessage", (user, message) => {
this.getList()
});
this.$connection.on("InteractMessage", (data, type) => {
this.getList()
});
this.$connection.on("SystemMessage", (title, content, time) => {
this.getList()
});
},
onShow() {
this.getList()
},
methods: {
toDetail(id, role, head) {
if (role == 0) {
this.$u.route({
url: '/pages/AlumniCircle/userDetail/userDetail?id=' + id + '&type=0'
})
} else {
uni.navigateTo({
url: "../dialogBox/dialogBox?id=" + id + '&chatType=1&type=0&head=' + head,
});
}
},
getList() {
if (this.current == 0) {
this.charList();
} else {
this.FollowList();
}
},
onAuto() {
this.$u.route({
url: "pages/my/ShoolList/ShoolList"
})
},
charList() {
const data = {
id: this.vuex_user.id,
userRole: this.vuex_user.isAttestationGLY ? 1 : 0
}
this.$u.api.getcharList(data).then(res => {
this.UserMsgList = res
})
this.$u.api.getinteractionList().then(res => {
this.interInfo = res[0]
})
this.$u.api.getSysList().then(res => {
this.sysInfo = res[0]
})
},
FollowList() {
this.$u.api.getFollowList().then(res => {
this.UserFollowList = res
})
},
//
GoChat(id, chatType, head) {
uni.navigateTo({
url: "../dialogBox/dialogBox?id=" + id + '&chatType=' + chatType + '&type=0&head=' + head,
});
},
// tabsswiper
tabsChange(index) {
this.swiperCurrent = index;
},
// swiper-itemtabs
transition(e) {
let dx = e.detail.dx;
this.$refs.uTabs.setDx(dx);
},
// swiperswiperdx
// swipertabsswiper
animationfinish(e) {
let current = e.detail.current;
this.$refs.uTabs.setFinishCurrent(current);
this.swiperCurrent = current;
this.current = current;
},
// scroll-view
onreachBottom() {},
},
};
</script>
<style lang="scss" scoped>
::v-deep .u-avatar__sex {
width: 0.1rem !important;
height: 0.1rem !important;
border: none;
}
.msg {
.list-item {
padding-left: 0.2rem;
}
.image_1 {
align-self: center;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
}
.right-section {
margin-left: 0.15rem;
padding: 0.14rem 0 0.18rem;
flex: 1 1 auto;
height: 0.66rem;
}
.top-group {
margin-right: 0.1rem;
}
.text_6 {
margin-top: 0.09rem;
color: rgb(180, 182, 189);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.text_2 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-bottom: 0.05rem;
color: rgb(193, 196, 204);
font-size: 0.12rem;
line-height: 0.095rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.text_11 {
margin-bottom: 0.03rem;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
max-width: 1rem;
overflow: hidden;
text-overflow: ellipsis;
}
.right-text-wrapper {
padding: 0.035rem 0.08rem;
color: rgb(115, 129, 255);
margin-left: 0.1rem;
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
height: 0.18rem;
}
.text_13 {
margin-right: 0.03rem;
}
// .group_2 {
// padding-bottom: 0.15rem;
// flex: 1 1 auto;
// overflow-y: auto;
// }
.group_4 {
padding: 0 0.01rem;
padding-left: 0.2rem;
box-shadow: 0px 0.005rem #eaeaea;
}
.group_5 {
padding-left: 0.2rem;
margin-top: 0.015rem;
box-shadow: 0px 0.005rem #eaeaea;
}
.group_4:active,
.group_5:active {
background: #efefef;
}
.group_8 {
padding-left: initial;
}
.view_2 {
margin-left: 0.15rem;
padding: 0.15rem 0 0.14rem;
height: 0.67rem;
}
.view_4 {
margin-left: 0.16rem;
padding: 0.15rem 0 0.14rem;
height: 0.67rem;
}
.view_3 {
margin-right: initial;
}
.view_5 {
margin-right: initial;
}
.text_16 {
margin-top: 0.0rem;
color: rgb(193, 196, 204);
line-height: 0.095rem;
margin-right: 0.095rem;
}
.text_28 {
margin-top: initial;
position: absolute;
left: 0;
bottom: 0.17rem;
}
.text_8 {
line-height: 0.14rem;
}
.text_9 {
margin-right: 0.025rem;
}
.view_6 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
}
.text_15 {
margin-bottom: initial;
color: rgb(180, 182, 189);
line-height: 1.2;
margin-top: 0.08rem;
width: 50%;
overflow: hidden;
display: block;
text-overflow: ellipsis;
white-space: nowrap;
}
.view_19 {
margin-left: 0.045rem;
}
}
.guanzhu {
.list-item {
padding: 0.14rem 0 0.1rem;
align-self: flex-end;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
width: 3.02rem;
position: relative;
}
.text_3 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.bottom-text-wrapper {
margin-top: 0.1rem;
padding: 0.035rem 0;
color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
}
.text_5 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.center-text-wrapper {
padding: 0.035rem 0.08rem;
color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
display: inline-block;
margin-top: 0.1rem;
}
.right-text-wrapper {
margin-right: 0.06rem;
margin-top: 0.03rem;
margin-left: 0.06rem;
padding: 0.085rem 0.08rem;
color: rgb(255, 255, 255);
font-size: 0.14rem;
line-height: 0.13rem;
white-space: nowrap;
background-color: rgb(46, 155, 255);
border-radius: 0.15rem;
width: 0.85rem;
height: 0.3rem;
position: relative;
}
.text_8 {
margin-right: 0.03rem;
}
.text_10 {
text-transform: uppercase;
}
.text_12 {
margin-left: 0.05rem;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_14 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.group_2 {
// padding: 0.11rem 0 4.94rem;
// flex: 1 1 auto;
// overflow-y: auto;
position: relative;
}
.text-wrapper {
// margin-left: 0.17rem;
padding: 0.11rem 0 0.12rem;
color: rgb(255, 255, 255);
font-size: 0.18rem;
line-height: 0.17rem;
letter-spacing: 0.018rem;
white-space: nowrap;
background-size: 100% 100%;
background-repeat: no-repeat;
width: 0.4rem;
height: 0.4rem;
margin-top: 0.08rem;
background: #6574fc;
border-radius: 50%;
text {
display: inline-block;
width: 100%;
text-align: center;
}
}
.list {}
.text_2 {
color: #fff;
}
.group_3 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
box-shadow: 0px 0.005rem #eaeaea;
padding-left: 0.2rem;
}
.group_4 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
margin-top: 0.015rem;
}
.group_5 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_6 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_7 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_8 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_9 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.image_1 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
overflow: hidden;
margin-top: 0.12rem;
}
.view_1 {
margin-top: initial;
padding: 0.1rem 0;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
margin-right: 0.04rem;
flex: 1 1 auto;
position: relative;
}
.image_2 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_4 {
margin-top: initial;
padding: 0.14rem 0 0.1rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_3 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_8 {
margin-top: initial;
padding: 0.15rem 0 0.14rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.13rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_4 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_12 {
margin-top: initial;
padding: 0.17rem 0 0.08rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_5 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_16 {
margin-top: initial;
padding: 0.15rem 0 0.11rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
}
.image_6 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_19 {
margin-top: initial;
padding: 0.18rem 0 0.18rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
position: relative;
}
.text_7 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
display: block;
text:nth-child(1) {
display: inline-block;
white-space: nowrap;
max-width: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
}
.text_26 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.view_20 {
left: -0.03rem;
bottom: 0.075rem;
}
.view_21 {
margin-right: 0.06rem;
margin-top: initial;
padding: 0.09rem 0 0.06rem;
color: rgb(73, 76, 87);
background-color: initial;
border: solid 0.01rem rgb(185, 185, 185);
}
}
.renzheng {
padding-bottom: 0rem;
.section_4 {
padding: 0.33rem 0 0.34rem;
background-color: rgb(255, 255, 255);
.section_5 {
margin-left: 0.18rem;
margin-right: 0.24rem;
padding: 0.2rem 0.18rem 0.18rem 0.24rem;
background-color: rgb(246, 247, 250);
justify-between: space-between;
.text_3 {
color: rgb(160, 162, 172);
font-size: 0.15rem;
line-height: 0.15rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-left: 0.05rem;
color: rgb(46, 155, 255);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
text-transform: uppercase;
}
}
}
.group_3 {
margin-top: 0.76rem;
padding-left: 0.8rem;
padding-right: 0.69rem;
color: rgb(163, 182, 202);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
.text_5 {
margin-left: 0.02rem;
margin-top: 0.13rem;
}
.image_1 {
margin-right: 0.14rem;
width: 2.12rem;
height: 1.89rem;
}
}
}
</style>

View File

@ -0,0 +1,908 @@
<template>
<view>
<view class="page-title">消息</view>
<view class="slot-wrap">
<!-- <u-tabs-swiper ref="uTabs" :list="navList" :current="current" :is-scroll="false"
:active-item-style="{ color: '#3CB5FB', fontSize: '0.18rem' }"
:bar-style="{ background: '#3CB5FB' }" @change="tabsChange"></u-tabs-swiper> -->
<view class="tabs-item" v-for="(item, index) in navList" :key="index" @click="tabsClick(item.type)">
<image :src="item.image"></image>
<text>{{ item.name }}</text>
</view>
</view>
<!-- <swiper :current="swiperCurrent" @transition="transition" @animationfinish="animationfinish"
style="height: calc(100vh - 2.1rem); width: 100%">
<swiper-item class="swiper-item" v-for="(item, index) in navList" :key="index"
style="height: calc(100vh - 2.1rem); width: 100%"> -->
<scroll-view scroll-y="true" style="height: calc(100vh - 2.5rem - 56px); width: 100%"
@scrolltolower="onreachBottom">
<!-- 消息 -->
<view class="msg" >
<view class="flex-col renzheng" v-if="!vuex_user.isAttestationXY && false">
<view class="flex-col section_4">
<view class="flex-row section_5">
<text class="text_3">待认证请先完成认证~</text>
<text class="text_4" @click="onAuto">去认证 >></text>
</view>
</view>
<view class="flex-col group_3">
<view class="flex-col">
<image src="/static/common/img/16535374500948048054.png" class="image_1" />
</view>
<text class="text_5">暂无数据请尽快认证补充资料~</text>
</view>
</view>
<view class="flex-col group_2">
<view class="flex-col list">
<navigator url="../adminList/adminList" class="list-item flex-row"
style='box-shadow: 0px 0.005rem #eaeaea;' v-if='vuex_user.isAttestationGLY'>
<u-avatar class="image_1" :show-sex="vuex_msgList.indexOf('admin') >= 0" sex-icon=""
size="0.4rem" sex-bg-color="red" mode="circle"
src="/static/common/img/adminHeaderImg.png">
</u-avatar>
<view class="right-section flex-col">
<view class="top-group justify-between">
<text class="text_2">管理列表</text>
<!-- <text class="text_4">8:52</text> -->
</view>
<text class="text_6" v-if="vuex_msgList.indexOf('admin') >= 0">你有新通知</text>
<text class="text_6" v-else>暂无管理消息</text>
</view>
</navigator>
<!-- <view v-if="!sysInfo"
style="margin-top:20vh;display: flex;justify-content: center;align-items: center;flex-direction: column;">
<image src="/static/common/img/empty.png" class="image" />
<view style="color:grey;margin-top: -5vh;">暂无消息</view>
</view> -->
<view v-if="UserMsgList.length">
<view v-for="(v, i) in UserMsgList" :key="i" class="list-item flex-row group_5"
@click="GoChat(v.userId, v.chatType, v.userHead)">
<u-avatar v-if='v.chatType == 0' class="image_1"
:show-sex="vuex_msgList.indexOf(v.userId + ',') >= 0" sex-icon=""
size="0.4rem" sex-bg-color="red" mode="circle"
:src="$u.http.config.imgUrl + v.userHead">
</u-avatar>
<u-avatar v-else class="image_1" :show-sex="vuex_msgList.indexOf('admin,') >= 0"
sex-icon="" size="0.4rem" sex-bg-color="red" mode="circle"
:src="$u.http.config.imgUrl + v.userHead">
</u-avatar>
<view class="right-section justify-between view_4">
<view class="top-group flex-col view_5">
<view class="text_2 flex-row view_6">
<text class="text_11">{{ v.userNetName }}</text>
<view class="right-text-wrapper flex-col items-end">
<text class="text_13">{{ v.userSchoolName }}</text>
</view>
</view>
<text class="text_4 text_15">{{ v.message }}</text>
</view>
<view style='display:none'>
{{ v.sendDate ? v.sendDate = v.sendDate.replace(/-/g, "/") : '' }}
</view>
<text
v-if='new Date(v.sendDate) - 0 + (3600000 * 24) > new Date() && new Date(v.sendDate).getDate() == new Date().getDate()'
class="text_6 text_16">{{ v.sendDate.slice(10, 16) }}</text>
<text v-else class="text_6 text_16">{{ v.sendDate.slice(5, 10) }}</text>
</view>
</view>
</view>
<no-data v-else type="message"></no-data>
</view>
</view>
</view>
</scroll-view>
<!-- </swiper-item>
</swiper> -->
<!-- 使用自定义TabBar -->
<custom-tab-bar></custom-tab-bar>
</view>
</template>
<script>
import noData from '@/components/NoData.vue'
import CustomTabBar from '@/components/custom-tab-bar/custom-tab-bar.vue';
export default {
components: {
noData,
CustomTabBar
},
data() {
return {
tabbar: "",
swiperCurrent: 0,
current: 0,
UserMsgList: [],
UserFollowList: [],
navList: [{
name: "互动消息",
image: "/static/common/img/message/interactive.png",
type: 0
},
{
name: "系统消息",
image: "/static/common/img/message/system.png",
type: 1
},
{
name: "新增关注",
image: "/static/common/img/message/attention.png",
type: 2
},
],
interInfo: '',
sysInfo: '',
};
},
watch: {
current() {
this.getList();
},
},
created() {
// onShow
// this.getList()
},
onLoad() {
//
this.$connection.on("ReceiveMessage", (user, message) => {
this.getList()
});
this.$connection.on("InteractMessage", (data, type) => {
this.getList()
});
this.$connection.on("SystemMessage", (title, content, time) => {
this.getList()
});
},
onShow() {
this.getList()
},
methods: {
tabsClick(type) {
switch (type) {
case 0:
this.$u.route({url:'/pages/message/interactionList/interactionList'})
break;
case 1:
this.$u.route({url:'/pages/message/sysList/sysList'})
break;
case 2:
this.$u.route({url:'/pages/message/attentionList/attentionList'})
break;
}
},
toDetail(id, role, head) {
if (role == 0) {
this.$u.route({
url: '/pages/AlumniCircle/userDetail/userDetail?id=' + id + '&type=0'
})
} else {
uni.navigateTo({
url: "../dialogBox/dialogBox?id=" + id + '&chatType=1&type=0&head=' + head,
});
}
},
getList() {
if (this.current == 0) {
this.charList();
} else {
this.FollowList();
}
},
onAuto() {
this.$u.route({
url: "pages/my/ShoolList/ShoolList"
})
},
charList() {
const data = {
id: this.vuex_user.id,
userRole: this.vuex_user.isAttestationGLY ? 1 : 0
}
this.$u.api.getcharList(data).then(res => {
this.UserMsgList = res
})
this.$u.api.getinteractionList(-1).then(res => {
this.interInfo = res[0]
})
this.$u.api.getSysList().then(res => {
this.sysInfo = res[0]
})
},
FollowList() {
this.$u.api.getFollowList().then(res => {
this.UserFollowList = res
})
},
//
GoChat(id, chatType, head) {
uni.navigateTo({
url: "../dialogBox/dialogBox?id=" + id + '&chatType=' + chatType + '&type=0&head=' + head,
});
},
// tabsswiper
tabsChange(index) {
this.swiperCurrent = index;
},
// swiper-itemtabs
transition(e) {
let dx = e.detail.dx;
this.$refs.uTabs.setDx(dx);
},
// swiperswiperdx
// swipertabsswiper
animationfinish(e) {
let current = e.detail.current;
this.$refs.uTabs.setFinishCurrent(current);
this.swiperCurrent = current;
this.current = current;
},
// scroll-view
onreachBottom() { },
},
};
</script>
<style lang="scss" scoped>
::v-deep .u-avatar__sex {
width: 0.1rem !important;
height: 0.1rem !important;
border: none;
}
.msg {
.list-item {
padding-left: 0.2rem;
}
.image_1 {
align-self: center;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
}
.right-section {
margin-left: 0.15rem;
padding: 0.14rem 0 0.18rem;
flex: 1 1 auto;
height: 0.66rem;
}
.top-group {
margin-right: 0.1rem;
}
.text_6 {
margin-top: 0.09rem;
color: rgb(180, 182, 189);
font-size: 0.12rem;
line-height: 0.12rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.text_2 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-bottom: 0.05rem;
color: rgb(193, 196, 204);
font-size: 0.12rem;
line-height: 0.095rem;
letter-spacing: 0.012rem;
white-space: nowrap;
}
.text_11 {
// margin-bottom: 0.03rem;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 36rpx;
// letter-spacing: 0.015rem;
white-space: nowrap;
max-width: 1rem;
overflow: hidden;
text-overflow: ellipsis;
}
.right-text-wrapper {
padding: 0.035rem 0.08rem;
color: #3CB5FB;
margin-left: 0.1rem;
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(60,181,251,0.1);
border-radius: 0.09rem;
height: 0.18rem;
}
.text_13 {
margin-right: 0.03rem;
}
// .group_2 {
// padding-bottom: 0.15rem;
// flex: 1 1 auto;
// overflow-y: auto;
// }
.group_4 {
padding: 0 0.01rem;
padding-left: 0.2rem;
box-shadow: 0px 0.005rem #eaeaea;
}
.group_5 {
padding-left: 0.2rem;
margin-top: 0.015rem;
box-shadow: 0px 0.005rem #eaeaea;
}
.group_4:active,
.group_5:active {
background: #efefef;
}
.group_8 {
padding-left: initial;
}
.view_2 {
margin-left: 0.15rem;
padding: 0.15rem 0 0.14rem;
height: 0.67rem;
}
.view_4 {
margin-left: 0.16rem;
padding: 0.15rem 0 0.14rem;
height: 0.67rem;
}
.view_3 {
margin-right: initial;
}
.view_5 {
margin-right: initial;
}
.text_16 {
margin-top: 0.0rem;
color: rgb(193, 196, 204);
line-height: 0.095rem;
margin-right: 0.095rem;
}
.text_28 {
margin-top: initial;
position: absolute;
left: 0;
bottom: 0.17rem;
}
.text_8 {
line-height: 0.14rem;
}
.text_9 {
margin-right: 0.025rem;
}
.view_6 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
}
.text_15 {
margin-bottom: initial;
color: rgb(180, 182, 189);
line-height: 1.2;
margin-top: 0.08rem;
width: 50%;
overflow: hidden;
display: block;
text-overflow: ellipsis;
white-space: nowrap;
}
.view_19 {
margin-left: 0.045rem;
}
}
.guanzhu {
.list-item {
padding: 0.14rem 0 0.1rem;
align-self: flex-end;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
width: 3.02rem;
position: relative;
}
.text_3 {
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.bottom-text-wrapper {
margin-top: 0.1rem;
padding: 0.035rem 0;
color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
}
.text_5 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.center-text-wrapper {
padding: 0.035rem 0.08rem;
color: rgb(115, 129, 255);
font-size: 0.11rem;
line-height: 0.11rem;
letter-spacing: 0.011rem;
white-space: nowrap;
background-color: rgba(115, 129, 255, 0.1);
border-radius: 0.09rem;
display: inline-block;
margin-top: 0.1rem;
}
.right-text-wrapper {
margin-right: 0.06rem;
margin-top: 0.03rem;
margin-left: 0.06rem;
padding: 0.085rem 0.08rem;
color: rgb(255, 255, 255);
font-size: 0.14rem;
line-height: 0.13rem;
white-space: nowrap;
background-color: rgb(46, 155, 255);
border-radius: 0.15rem;
width: 0.85rem;
height: 0.3rem;
position: relative;
}
.text_8 {
margin-right: 0.03rem;
}
.text_10 {
text-transform: uppercase;
}
.text_12 {
margin-left: 0.05rem;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_14 {
margin-left: 0.1rem;
margin-right: 0.03rem;
}
.group_2 {
// padding: 0.11rem 0 4.94rem;
// flex: 1 1 auto;
// overflow-y: auto;
position: relative;
}
.text-wrapper {
// margin-left: 0.17rem;
padding: 0.11rem 0 0.12rem;
color: rgb(255, 255, 255);
font-size: 0.18rem;
line-height: 0.17rem;
letter-spacing: 0.018rem;
white-space: nowrap;
background-size: 100% 100%;
background-repeat: no-repeat;
width: 0.4rem;
height: 0.4rem;
margin-top: 0.08rem;
background: #6574fc;
border-radius: 50%;
text {
display: inline-block;
width: 100%;
text-align: center;
}
}
.list {}
.text_2 {
color: #fff;
}
.group_3 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
box-shadow: 0px 0.005rem #eaeaea;
padding-left: 0.2rem;
}
.group_4 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
margin-top: 0.015rem;
}
.group_5 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_6 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_7 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_8 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.group_9 {
padding: initial;
align-self: initial;
box-shadow: initial;
width: initial;
position: initial;
}
.image_1 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
overflow: hidden;
margin-top: 0.12rem;
}
.view_1 {
margin-top: initial;
padding: 0.1rem 0;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
margin-right: 0.04rem;
flex: 1 1 auto;
position: relative;
}
.image_2 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_4 {
margin-top: initial;
padding: 0.14rem 0 0.1rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_3 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_8 {
margin-top: initial;
padding: 0.15rem 0 0.14rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.13rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_4 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_12 {
margin-top: initial;
padding: 0.17rem 0 0.08rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.12rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
}
.image_5 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_16 {
margin-top: initial;
padding: 0.15rem 0 0.11rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
}
.image_6 {
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
align-self: center;
width: 0.4rem;
height: 0.4rem;
}
.view_19 {
margin-top: initial;
padding: 0.18rem 0 0.18rem;
color: initial;
font-size: initial;
line-height: initial;
letter-spacing: initial;
white-space: initial;
background-color: initial;
border-radius: initial;
margin-left: 0.16rem;
flex: 1 1 auto;
box-shadow: 0px 0.005rem rgb(234, 234, 234);
height: 0.67rem;
position: relative;
}
.text_7 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
display: block;
text:nth-child(1) {
display: inline-block;
white-space: nowrap;
max-width: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
}
.text_26 {
margin-left: initial;
margin-right: initial;
color: rgb(2, 2, 2);
font-size: 0.15rem;
line-height: 0.14rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.view_20 {
left: -0.03rem;
bottom: 0.075rem;
}
.view_21 {
margin-right: 0.06rem;
margin-top: initial;
padding: 0.09rem 0 0.06rem;
color: rgb(73, 76, 87);
background-color: initial;
border: solid 0.01rem rgb(185, 185, 185);
}
}
.renzheng {
padding-bottom: 0rem;
.section_4 {
padding: 0.33rem 0 0.34rem;
background-color: rgb(255, 255, 255);
.section_5 {
margin-left: 0.18rem;
margin-right: 0.24rem;
padding: 0.2rem 0.18rem 0.18rem 0.24rem;
background-color: rgb(246, 247, 250);
justify-between: space-between;
.text_3 {
color: rgb(160, 162, 172);
font-size: 0.15rem;
line-height: 0.15rem;
letter-spacing: 0.015rem;
white-space: nowrap;
}
.text_4 {
margin-left: 0.05rem;
color: rgb(46, 155, 255);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
text-transform: uppercase;
}
}
}
.group_3 {
margin-top: 0.76rem;
padding-left: 0.8rem;
padding-right: 0.69rem;
color: rgb(163, 182, 202);
font-size: 0.15rem;
line-height: 0.15rem;
white-space: nowrap;
.text_5 {
margin-left: 0.02rem;
margin-top: 0.13rem;
}
.image_1 {
margin-right: 0.14rem;
width: 2.12rem;
height: 1.89rem;
}
}
}
.page-title{
padding: 50rpx ;
color: #000;
font-size: 36rpx;
font-weight: 800;
background: #ceedff; /* fallback for old browsers */
background: -webkit-linear-gradient(to top,#f6f7fa, #ceedff); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to top,#f6f7fa, #ceedff); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
.slot-wrap {
// height: 220rpx;
display: flex;
width: 100%;
justify-content: space-between;
padding: 0 60rpx;
margin-bottom: 40rpx;
}
.tabs-item {
width: 112rpx;
height: 168rpx;
// padding-top: 50rpx;
// margin-bottom: 50rpx;
display: flex;
flex-direction: column;
align-items: center;
image {
width: 104rpx;
height: 104rpx;
margin-bottom: 20rpx;
}
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<view>
<u-navbar back-text="" title="系统消息"></u-navbar>
<scroll-view scroll-y="true" class="scroll">
<view class="flex-col list" v-if='list.length'>
<view class="list-item flex-col group_1" :key="i" v-for="(item, i) in list">
<view class="items-title">
<image src="/static/common/img/message/system.png" mode=""></image>
<view class="title-time">
<text class="title-text">系统消息</text>
<text class="text_1">{{ item.messageDate }}</text>
</view>
</view>
<view class="bottom-section flex-col">
<text class="text_3">{{ item.title }}</text>
<text class="text_5">{{ item.message.split(',未通过原因:')[0] }}</text>
<text v-if="item.message.split(',未通过原因:').length >= 2" class="text_6">驳回理由<text>{{
item.message.split(',未通过原因:')[1] }}</text></text>
</view>
</view>
</view>
<view v-else style='padding-top:30vh'>
<no-data text="暂无系统消息"></no-data>
</view>
</scroll-view>
</view>
</template>
<script>
import noData from '@/components/NoData.vue';
export default {
components: {
noData
},
data() {
return {
list: []
};
},
onLoad() {
this.getList()
//
this.$connection.on("SystemMessage", (title, content, time) => {
if (this.route.indexOf('sysList') >= 0) {
this.list.unshift({
messageDate: time,
title: title,
message: content
})
}
});
},
onShow() {
if (this.vuex_msgList) {
var msgList = this.vuex_msgList;
msgList = msgList.replace('SystemMessage,', '')
this.$u.vuex('vuex_msgList', msgList)
if (!msgList) {
var tab = this.vuex_tabbar;
tab[1].isDot = false;
this.$u.vuex('vuex_tabbar', tab);
}
}
},
methods: {
getList() {
this.$u.api.getSysList().then(res => {
this.list = res
})
},
//
router() {
uni.switchTab({
url: '../../message/msgList/msgList'
})
},
}
};
</script>
<style lang="scss" scoped>
.scroll {
background-color: rgb(246, 247, 250);
width: 100%;
height: calc(100vh - 0.44rem);
}
.list-item {
margin-top: 0.1rem;
padding: 20rpx;
background-color: rgb(255, 255, 255);
border-radius: 20rpx;
}
.items-title {
display: flex;
padding-bottom: 20rpx;
margin-bottom: 20rpx;
border-bottom: 2rpx solid #F6F8F9;
image {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.title-time {
margin-left: 0.1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5rpx 0;
// align-items: flex-start;
}
}
.title-text {
font-weight: 600;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.9);
}
.text_1 {
// align-self: center;
color: rgb(193, 196, 204);
font-size: 0.12rem;
line-height: 0.095rem;
// letter-spacing: 0.012rem;
white-space: nowrap;
}
.bottom-section {
padding-left: 10rpx;
}
.text_3 {
color: rgb(2, 2, 2);
font-size: 28rpx;
line-height: 0.16rem;
// letter-spacing: 0.018rem;
white-space: nowrap;
}
.text_5 {
margin-left: 0.015rem;
margin-top: 0.1rem;
color: #908a8a;
font-size: 28rpx;
line-height: 1.5;
// letter-spacing: 0.01rem;
// text-indent: 0.3rem;
}
.text_6 {
margin-top: 0.1rem;
font-size: 0.16rem;
line-height: 0.16rem;
letter-spacing: 0.01rem;
text {
margin-left: 0.015rem;
text-indent: 0.3rem;
display: block;
color: #908a8a;
font-size: 0.14rem;
margin-top: 0.1rem;
}
}
.list {
padding: 0.18rem 0.15rem;
flex: 1 1 auto;
overflow-y: auto;
}
</style>

View File

@ -1,99 +1,89 @@
<template>
<view class="page-container">
<view class="page-header">
<PageHeader
title="我的"
:is-back="false"
:border-bottom="false"
:background="headerBackground"
/>
</view>
<div class="my-page">
<PageHeader
title="我的"
:is-back="false"
:border-bottom="false"
:background="headerBackground"
/>
<scroll-view
class="page-main"
scroll-y
enable-back-to-top
>
<view class="main-content">
<div class="user-info">
<div class="avatar">
<!-- <img src="" alt="用户头像" /> -->
<div class="content-wrapper">
<div class="user-info">
<div class="avatar">
<!-- <img src="" alt="用户头像" /> -->
</div>
<div class="info">
<div class="name">{{ teacherInfo.name }}</div>
<div class="tag">
<image
class="tag-icon"
src="@/static/notes/collage-icon.png"
></image>
{{ teacherInfo.collegeName }}
</div>
<div class="info">
<div class="name">{{ teacherInfo.name }}</div>
<div class="tag">
<image
class="tag-icon"
src="@/static/notes/collage-icon.png"
></image>
{{ teacherInfo.collegeName }}
</div>
<div class="tag">
<image class="tag-icon" src="@/static/notes/major-icon.png"></image>
{{ teacherInfo.professionalName }}
</div>
<div class="tag">
<image class="tag-icon" src="@/static/notes/major-icon.png"></image>
{{ teacherInfo.professionalName }}
</div>
</div>
</div>
<div class="statistics">
<div class="stat-item">
<div class="stat-num">36</div>
<div class="stat-label">总答题</div>
</div>
<div class="stat-item">
<div class="stat-num">10</div>
<div class="stat-label">已完成</div>
</div>
<div class="stat-item">
<div class="stat-num">26</div>
<div class="stat-label">未回复</div>
</div>
<div class="statistics">
<div class="stat-item">
<div class="stat-num">36</div>
<div class="stat-label">总答题</div>
</div>
<div class="banner">
<img src="@/static/notes/banner.png" alt="banner" />
<div class="stat-item">
<div class="stat-num">10</div>
<div class="stat-label">已完成</div>
</div>
<div class="menu-list">
<div class="menu-item" @click="navigateTo('personal-info')">
<div class="menu-icon">
<image src="@/static/notes/menu1.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">个人信息</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
<div class="menu-item" @click="navigateTo('change-password')">
<div class="menu-icon">
<image src="@/static/notes/menu2.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">修改密码</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
<div class="menu-item" @click="navigateTo('logout-records')">
<div class="menu-icon">
<image src="@/static/notes/menu3.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">退出登录</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
<div class="stat-item">
<div class="stat-num">26</div>
<div class="stat-label">未回复</div>
</div>
</view>
</scroll-view>
</div>
<view class="page-tabbar">
<TabBar :currentPath="'/pages/my/index'" @change="handleTabChange" />
</view>
</view>
<div class="banner">
<img src="@/static/notes/banner.png" alt="banner" />
</div>
<div class="menu-list">
<div class="menu-item" @click="navigateTo('personal-info')">
<div class="menu-icon">
<image src="@/static/notes/menu1.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">个人信息</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
<div class="menu-item" @click="navigateTo('change-password')">
<div class="menu-icon">
<image src="@/static/notes/menu2.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">修改密码</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
<div class="menu-item" @click="navigateTo('logout-records')">
<div class="menu-icon">
<image src="@/static/notes/menu3.png" class="menu-icon-img"></image>
</div>
<div class="menu-text">退出登录</div>
<view class="arrow-icon">
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</div>
</div>
</div>
<TabBar :currentPath="'/pages/my/index'" @change="handleTabChange" />
</div>
</template>
<script>
import TabBar from "@/components/TabBar-optimized.vue";
import TabBar from "@/components/TabBar.vue";
import PageHeader from "@/components/PageHeader.vue";
export default {
@ -159,69 +149,24 @@ export default {
</script>
<style scoped>
/* ===== 页面容器 - 主流三段式布局 ===== */
.page-container {
/* 固定定位,占满整个视口 */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* Flex 布局 */
display: flex;
flex-direction: column;
/* 背景图片(保留特色) */
.my-page {
height: 100vh;
/* background-color: #f5f6fa; */
background-image: url("@/static/notes/bg.png");
background-position: center top;
background-repeat: no-repeat;
background-size: 100% auto;
/* 防止溢出 */
overflow: hidden;
background-size: 100% auto; /* 以宽度为基准等比例缩放 */
display: flex;
flex-direction: column;
position: relative;
}
/* ===== 头部导航 ===== */
.page-header {
/* 不收缩,固定高度 */
flex-shrink: 0;
/* 层级 */
z-index: 100;
}
/* ===== 内容区域 ===== */
.page-main {
/* 占据剩余空间 */
.content-wrapper {
flex: 1;
/* 重要:防止 flex 子元素溢出 */
height: 0;
/* 允许滚动 */
overflow-y: auto;
/* iOS 滚动优化 */
-webkit-overflow-scrolling: touch;
}
/* ===== 内容内层 ===== */
.main-content {
/* 底部留出 TabBar 空间 + 安全区域 */
padding-bottom: calc(50px + env(safe-area-inset-bottom));
/* 最小高度(确保可以滚动) */
min-height: 100%;
}
/* ===== 底部导航 ===== */
.page-tabbar {
/* 不收缩,固定高度 */
flex-shrink: 0;
/* 层级 */
z-index: 100;
display: flex;
flex-direction: column;
padding-bottom: 60px; /* 为底部导航栏预留空间 */
overflow: hidden; /* 防止滚动条出现 */
}
.user-info {

466
pages/notes/index.vue Normal file
View File

@ -0,0 +1,466 @@
<template>
<view class="notes-page">
<PageHeader title="留言板" :is-back="false" :border-bottom="false" />
<view class="custom-tabs-box">
<u-tabs
:list="tabList"
:current="tabCurrent"
@change="onTabChange"
:is-scroll="false"
:height="80"
:font-size="28"
active-color="#4A6CF7"
inactive-color="#333333"
:bar-width="120"
:bar-height="4"
bg-color="#ffffff"
:item-width="200"
></u-tabs>
<view class="space"></view>
</view>
<!-- 内容区域包装器 -->
<view class="content-wrapper">
<!-- 留言列表 -->
<scroll-view class="message-list" scroll-y>
<view v-for="(item, index) in currentMessages" :key="index">
<u-swipe-action
:options="swipeOptions"
:show="item.show"
:index="index"
@click="handleSwipeClick"
@open="(e) => handleSwipeOpen(e, index)"
@close="(e) => handleSwipeClose(e, index)"
@content-click="() => closeOther(index)"
:btn-width="80"
:disabled="false"
>
<view class="message-item">
<view class="message-header">
<view class="user-info">
<image
class="avatar"
:src="item.avatar || defaultAvatar"
></image>
<text class="username">{{ item.username }}</text>
</view>
<view class="message-time">{{ item.time }}</view>
</view>
<view class="message-content">
<view class="question">
<view class="question-icon"></view>
<view class="question-text">{{ item.question }}</view>
</view>
<view
class="reply-button"
@click.stop="handleReply(item)"
v-if="!item.reply"
>
<u-icon name="chat" color="#4a6cf7" size="16"></u-icon>
<text>回复</text>
</view>
<view class="reply-content" v-if="item.reply">
<view class="reply-header">
<view class="reply-icon"></view>
<view class="reply-info">
<text>{{ item.replyUser }}</text>
</view>
</view>
<view
class="reply-text"
:class="{ expanded: item.expanded }"
>{{ item.reply }}</view
>
<view
class="expand-button"
v-if="item.reply.length > 100"
@click.stop="toggleExpand(item)"
>
{{ item.expanded ? "收起" : "展开" }}
<u-icon
:name="item.expanded ? 'arrow-up' : 'arrow-down'"
size="12"
></u-icon>
</view>
</view>
<view
class="reply-button"
@click.stop="handleReply(item)"
v-if="item.reply"
>
<u-icon name="chat" color="#4a6cf7" size="16"></u-icon>
<text>回复</text>
</view>
</view>
</view>
</u-swipe-action>
</view>
<!-- 无数据提示 -->
<view class="empty-tip" v-if="currentMessages.length === 0">
暂无留言数据
</view>
</scroll-view>
</view>
<!-- 使用TabBar组件 -->
<TabBar :currentPath="'/pages/notes/index'" @change="handleTabChange" />
<!-- 开发中提示弹窗 -->
<u-modal
v-model="showDevModal"
:show-cancel-button="false"
title="提示"
content="该功能正在开发中,敬请期待!"
@confirm="showDevModal = false"
></u-modal>
</view>
</template>
<script>
import TabBar from "@/components/TabBar.vue";
import PageHeader from "@/components/PageHeader.vue";
export default {
name: "NotesPage",
components: {
TabBar,
PageHeader,
},
data() {
return {
tabList: [{ name: "未回复" }, { name: "已回复" }],
tabCurrent: 0,
activeTab: "unread",
showDevModal: false,
defaultAvatar: "/static/avatar/default-avatar.png",
unreadMessages: [
{
id: 1,
avatar: "",
username: "浙江理工生13024",
time: "2023/6/26 15:45:12",
question: "学校在录取时有没有一些专业会有特殊要求?",
reply: "",
expanded: false,
show: false,
},
{
id: 2,
avatar: "",
username: "理工13024",
time: "2023/6/26 15:45:12",
question: "在录取时有没有一些专业会有特殊要求?",
reply: "",
expanded: false,
show: false,
},
],
repliedMessages: [
{
id: 3,
avatar: "",
username: "浙江理工生13024",
time: "2023/6/26 15:45:12",
question: "学校在录取时有没有一些专业会有特殊要求?",
reply:
"学生与体健康状况必须符合教育部、卫生部、中国残疾人联合会印发的《普通高等学校招生体检工作指导意见》和人力资源社会保障部、原卫生部、原教育部、原公安部、原国家人口计划生育委员会制定的有关规定。原卫生部、原教育部、原公安部、原国家人口计划生育委员会制定的有关规定。",
replyUser: "系统回复",
expanded: false,
show: false,
},
],
swipeOptions: [
{
text: "删除",
style: {
backgroundColor: "#fa3534",
color: "#ffffff",
fontSize: "15px",
},
},
],
};
},
computed: {
currentMessages() {
return this.activeTab === "unread"
? this.unreadMessages
: this.repliedMessages;
},
},
methods: {
onTabChange(index) {
this.tabCurrent = index;
// activeTab
this.activeTab = index === 0 ? "unread" : "replied";
//
this.closeAllSwipe();
},
switchTab(tab) {
this.activeTab = tab;
// tabCurrentactiveTab
this.tabCurrent = tab === "unread" ? 0 : 1;
//
this.closeAllSwipe();
},
handleTabChange(path, index) {
console.log("切换到标签页:", path, index);
},
handleReply(item) {
this.showDevModal = true;
},
toggleExpand(item) {
item.expanded = !item.expanded;
},
handleSwipeClick(e) {
const { index } = e; //
const btnIndex = e.index; //
if (btnIndex === 0) {
//
//
if (this.activeTab === "unread") {
this.unreadMessages.splice(index, 1);
} else {
this.repliedMessages.splice(index, 1);
}
}
},
handleSwipeOpen(e, index) {
//
this.closeOther(index);
},
handleSwipeClose(e, index) {
//
if (this.activeTab === "unread") {
this.unreadMessages[index].show = false;
} else {
this.repliedMessages[index].show = false;
}
},
closeOther(index) {
//
if (this.activeTab === "unread") {
this.unreadMessages.forEach((item, idx) => {
if (idx !== index) {
item.show = false;
}
});
} else {
this.repliedMessages.forEach((item, idx) => {
if (idx !== index) {
item.show = false;
}
});
}
},
closeAllSwipe() {
//
this.unreadMessages.forEach((item) => {
item.show = false;
});
this.repliedMessages.forEach((item) => {
item.show = false;
});
},
},
};
</script>
<style scoped>
.notes-page {
height: 100vh;
background-color: #f5f6fa;
display: flex;
flex-direction: column;
position: relative;
}
.custom-tabs-box {
display: flex;
align-items: center;
}
.space {
flex: 1;
background: #fff;
width: 100%;
height: 100%;
}
.custom-tabs-box >>> .u-tabs__wrapper__nav__item {
padding: 0 20px 0 0 !important; /* 调整标签项的padding */
}
.custom-tabs-box >>> .u-tabs__wrapper__nav__line {
bottom: 0 !important; /* 确保下划线位于底部 */
height: 4px !important; /* 设置下划线高度 */
border-radius: 2px !important; /* 圆角 */
}
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
padding-bottom: 60px; /* 为底部导航栏预留空间 */
overflow-y: auto; /* 允许内容滚动 */
}
/* 留言列表样式 */
.message-list {
flex: 1;
padding: 10px;
}
.message-item {
background-color: #fff;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.user-info {
display: flex;
align-items: center;
}
.avatar {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 8px;
background-color: #eee;
}
.username {
font-size: 12px;
color: #666;
}
.message-time {
font-size: 12px;
color: #999;
}
.message-content {
position: relative;
}
.question {
display: flex;
margin-bottom: 10px;
}
.question-icon {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #4a6cf7;
color: #fff;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 8px;
flex-shrink: 0;
}
.question-text {
font-size: 14px;
line-height: 1.5;
flex: 1;
}
.reply-button {
height: 32px;
background-color: #f0f2fd;
color: #4a6cf7;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
margin-top: 10px;
cursor: pointer;
}
.reply-button text {
margin-left: 5px;
}
.reply-content {
background-color: #f8f8f8;
border-radius: 4px;
padding: 10px;
margin-top: 10px;
}
.reply-header {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.reply-icon {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #ff9500;
color: #fff;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 8px;
}
.reply-info {
font-size: 12px;
color: #666;
}
.reply-text {
font-size: 14px;
line-height: 1.5;
color: #333;
margin-left: 28px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.reply-text.expanded {
-webkit-line-clamp: unset;
}
.expand-button {
text-align: right;
font-size: 12px;
color: #4a6cf7;
margin-top: 5px;
cursor: pointer;
}
.empty-tip {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
}
</style>

View File

@ -1,310 +0,0 @@
<template>
<view class="page-container">
<view class="page-header">
<PageHeader title="人工转接" :is-back="false" :border-bottom="false" />
</view>
<scroll-view
class="page-main"
scroll-y
enable-back-to-top
>
<view class="main-content">
<!-- 服务状态卡片 -->
<view class="status-card">
<view class="status-title">人工服务</view>
<view class="status-content">
<view class="status-badge" :class="isOnline ? 'online' : 'offline'">
{{ isOnline ? '在线' : '离线' }}
</view>
<view class="status-desc">
{{ isOnline ? '客服正在为您服务' : '当前暂无客服在线' }}
</view>
</view>
</view>
<!-- 人工客服列表 -->
<view class="section-title">在线客服</view>
<view class="agent-list">
<view class="agent-item" v-for="(agent, index) in agentList" :key="index" @click="handleTransfer(agent)">
<view class="agent-avatar-wrapper">
<image class="agent-avatar" :src="agent.avatar || defaultAvatar"></image>
<view class="online-dot" v-if="agent.online"></view>
</view>
<view class="agent-info">
<view class="agent-name">{{ agent.name }}</view>
<view class="agent-status">{{ agent.statusText }}</view>
</view>
<view class="agent-action">
<u-button
size="mini"
type="primary"
:disabled="!agent.online"
@click.stop="handleTransfer(agent)"
>
{{ agent.online ? '转接' : '离线' }}
</u-button>
</view>
</view>
</view>
<!-- 无数据提示 -->
<view class="empty-tip" v-if="agentList.length === 0">
暂无客服在线
</view>
</view>
</scroll-view>
<view class="page-tabbar">
<TabBar :currentPath="'/pages/manual-transfer/index'" @change="handleTabChange" />
</view>
<u-modal
v-model="showModal"
:show-cancel-button="true"
title="人工转接"
:content="modalContent"
@confirm="confirmTransfer"
@cancel="showModal = false"
></u-modal>
</view>
</template>
<script>
import TabBar from "@/components/TabBar-optimized.vue";
import PageHeader from "@/components/PageHeader.vue";
export default {
name: "TransferPage",
components: {
TabBar,
PageHeader,
},
data() {
return {
showModal: false,
modalContent: '',
selectedAgent: null,
defaultAvatar: "/static/avatar/default-avatar.png",
isOnline: true,
agentList: [
{
id: 1,
name: "客服小王",
avatar: "",
online: true,
statusText: "空闲中",
},
{
id: 2,
name: "客服小李",
avatar: "",
online: true,
statusText: "忙碌中",
},
{
id: 3,
name: "客服小张",
avatar: "",
online: false,
statusText: "离线",
},
],
};
},
methods: {
handleTabChange(path, index) {
console.log("切换到标签页:", path, index);
},
handleTransfer(agent) {
if (!agent.online) {
uni.showToast({
title: '该客服当前离线',
icon: 'none'
});
return;
}
this.selectedAgent = agent;
this.modalContent = `确认转接到${agent.name}吗?`;
this.showModal = true;
},
confirmTransfer() {
//
uni.showToast({
title: '正在为您转接...',
icon: 'none'
});
this.showModal = false;
//
setTimeout(() => {
// uni.navigateTo({
// url: '/pages/message/dialogBox/dialogBox?agentId=' + this.selectedAgent.id
// });
}, 1000);
},
},
};
</script>
<style scoped>
/* ===== 页面容器 - 主流三段式布局 ===== */
.page-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
background-color: #f5f6fa;
overflow: hidden;
}
/* ===== 头部导航 ===== */
.page-header {
flex-shrink: 0;
z-index: 100;
}
/* ===== 内容区域 ===== */
.page-main {
flex: 1;
height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* ===== 内容内层 ===== */
.main-content {
padding: 10px;
padding-bottom: calc(50px + env(safe-area-inset-bottom) + 10px);
min-height: 100%;
}
/* ===== 底部导航 ===== */
.page-tabbar {
flex-shrink: 0;
z-index: 100;
}
.status-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
margin-bottom: 15px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.status-title {
font-size: 16px;
font-weight: 500;
color: #fff;
margin-bottom: 10px;
}
.status-content {
display: flex;
align-items: center;
}
.status-badge {
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
margin-right: 10px;
}
.status-badge.online {
background-color: #52c41a;
color: #fff;
}
.status-badge.offline {
background-color: #999;
color: #fff;
}
.status-desc {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.section-title {
font-size: 15px;
font-weight: 500;
color: #333;
margin: 15px 0 10px 0;
}
.agent-list {
background-color: #fff;
border-radius: 12px;
overflow: hidden;
}
.agent-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
}
.agent-item:last-child {
border-bottom: none;
}
.agent-avatar-wrapper {
position: relative;
margin-right: 12px;
}
.agent-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #eee;
}
.online-dot {
position: absolute;
bottom: 2px;
right: 2px;
width: 10px;
height: 10px;
background-color: #52c41a;
border-radius: 50%;
border: 2px solid #fff;
}
.agent-info {
flex: 1;
display: flex;
flex-direction: column;
}
.agent-name {
font-size: 15px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.agent-status {
font-size: 13px;
color: #999;
}
.agent-action {
margin-left: 10px;
}
.empty-tip {
text-align: center;
padding: 50px 20px;
color: #999;
font-size: 14px;
}
</style>

View File

@ -0,0 +1 @@
<!-- 图标文件内容,这是二进制文件,实际使用时需要上传真实图片 -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
// 这是一个占位文件需要替换为实际的AI头像图标

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
// 这是一个占位文件,需要替换为实际的聊天图标

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

View File

@ -0,0 +1 @@

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More