修改websocket
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
VUE_APP_NAME=超级车补管理后台
|
||||
VUE_APP_API_BASE_URL=https://hcb.liche.cn/pingan
|
||||
# webSocket配置
|
||||
VUE_APP_WS_URL=wss://api.ss.haodian.cn/wss
|
||||
VUE_APP_WS_PLATFORM=2
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
VUE_APP_API_BASE_URL=/pingan
|
||||
URL = http://agent.admin.haodian.cn
|
||||
PORT = 8200
|
||||
# webSocket配置
|
||||
VUE_APP_WS_URL=ws://api.ss.haodian.cn/wss
|
||||
VUE_APP_WS_PLATFORM=2
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 通知列表
|
||||
* @param params 查询条件
|
||||
*/
|
||||
export async function pageNotice(params) {
|
||||
const res = await request.get('/user/notice/page', {
|
||||
params
|
||||
});
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读消息数
|
||||
* @param params
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function unRead(params) {
|
||||
const res = await request.get('/user/notice/unRead', {
|
||||
params
|
||||
});
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function setRead(data) {
|
||||
const res = await request.post('/user/notice/read', data);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -63,3 +63,6 @@ export const MAP_KEY = '006d995d433058322319fa797f2876f5';
|
||||
// EleAdmin 授权码, 自带的只能用于演示, 正式项目请更换为自己的授权码
|
||||
export const LICENSE_CODE =
|
||||
'dk9mcwJyetRWQlxWRiojIqJWdzJCLi4Wa4QDN5ojI0NWZI1kI6ICZpJCLiwiIVNzVz4UW6Iibvl2cyVmdQfiETMuEjI0NW==';
|
||||
|
||||
// token 存储的名称
|
||||
export const WEB_SOCKET_TOKEN_STORE_NAME = 'websocket_access_token';
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<div class="ele-notice-group">
|
||||
<el-badge :value="unreadNum" :hidden="!unreadNum">
|
||||
<el-badge :value="noticeCount" :hidden="!noticeCount">
|
||||
<i class="el-icon-bell"></i>
|
||||
</el-badge>
|
||||
</div>
|
||||
</template>
|
||||
<el-tabs v-model="active">
|
||||
<el-tab-pane name="notice" :label="noticeTitle">
|
||||
<el-tab-pane
|
||||
name="notice"
|
||||
:label="'通知' + [noticeCount ? '(' + noticeCount + ')' : '']"
|
||||
>
|
||||
<div class="ele-notice-list ele-scrollbar-mini">
|
||||
<div
|
||||
v-for="(item, index) in notice"
|
||||
@@ -26,8 +29,8 @@
|
||||
<div class="ele-cell ele-notice-item-wrapper">
|
||||
<i :class="[item.icon, 'ele-notice-item-icon']"></i>
|
||||
<div class="ele-cell-content">
|
||||
<div class="ele-elip">{{ item.title }}</div>
|
||||
<div class="ele-text-secondary ele-elip">{{ item.time }}</div>
|
||||
<div class="ele-elip">{{ item.content }}</div>
|
||||
<div class="ele-text-secondary ele-elip">{{ item.c_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
@@ -36,12 +39,13 @@
|
||||
<div v-if="notice.length" class="ele-cell ele-notice-actions">
|
||||
<div class="ele-cell-content" @click="clearNotice">清空通知</div>
|
||||
<el-divider direction="vertical" class="line-color-light" />
|
||||
<router-link to="/user/message?type=notice" class="ele-cell-content">
|
||||
<router-link to="/user/notice?type=notice" class="ele-cell-content">
|
||||
查看更多
|
||||
</router-link>
|
||||
</div>
|
||||
<ele-empty v-if="!notice.length" text="已查看所有通知" />
|
||||
</el-tab-pane>
|
||||
<!--
|
||||
<el-tab-pane name="letter" :label="letterTitle">
|
||||
<div class="ele-notice-list ele-scrollbar-mini">
|
||||
<div
|
||||
@@ -101,12 +105,14 @@
|
||||
</div>
|
||||
<ele-empty v-if="!todo.length" text="已完成所有任务" />
|
||||
</el-tab-pane>
|
||||
-->
|
||||
</el-tabs>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUnreadNotice } from '@/api/layout';
|
||||
import { pageNotice, setRead } from '@/api/user/notice';
|
||||
// import websocket from '@/utils/websocket';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -117,58 +123,86 @@
|
||||
active: 'notice',
|
||||
// 通知数据
|
||||
notice: [],
|
||||
noticeCount: 0,
|
||||
// 私信数据
|
||||
letter: [],
|
||||
letterCount: 0,
|
||||
// 待办数据
|
||||
todo: []
|
||||
todo: [],
|
||||
todCount: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 通知标题
|
||||
noticeTitle() {
|
||||
return '通知' + (this.notice.length ? `(${this.notice.length})` : '');
|
||||
},
|
||||
// 私信标题
|
||||
letterTitle() {
|
||||
return '私信' + (this.letter.length ? `(${this.letter.length})` : '');
|
||||
},
|
||||
// 待办标题
|
||||
todoTitle() {
|
||||
return '待办' + (this.todo.length ? `(${this.todo.length})` : '');
|
||||
},
|
||||
// 未读数量
|
||||
unreadNum() {
|
||||
return this.notice.length + this.letter.length + this.todo.length;
|
||||
}
|
||||
mounted() {
|
||||
// 监听 WebSocket 连接状态
|
||||
window.EventBus.$on('ws:connected', (status) => {
|
||||
console.log('WebSocket 连接状态:', status);
|
||||
});
|
||||
|
||||
// 监听 WebSocket 消息
|
||||
window.EventBus.$on('ws:message', (message) => {
|
||||
console.log('收到 WebSocket 消息:', message);
|
||||
if (message.type === 'notice') {
|
||||
//弹窗通知有新消息
|
||||
this.$notify({
|
||||
title: '', // 消息标题
|
||||
message: '您收到一条新的系统通知,请及时查看', // 消息内容
|
||||
type: 'warning', // 类型:info/success/warning/error
|
||||
duration: 5000, // 5秒后自动关闭,0则不自动关闭
|
||||
position: 'top-left', // 弹窗位置,可选top-left/bottom-left/bottom-right
|
||||
showClose: true, // 显示关闭按钮
|
||||
onClick: () => {}
|
||||
});
|
||||
//怎么在最开头拼接
|
||||
this.notice = [message.data].concat(this.notice);
|
||||
this.noticeCount++;
|
||||
// this.notice = message.data;
|
||||
}
|
||||
// 处理具体的业务逻辑
|
||||
// this.handleWebSocketMessage(message);
|
||||
});
|
||||
|
||||
// 监听 WebSocket 错误
|
||||
window.EventBus.$on('ws:error', (error) => {
|
||||
console.error('WebSocket 错误:', error);
|
||||
});
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
this.query();
|
||||
},
|
||||
methods: {
|
||||
/* 查询数据 */
|
||||
query() {
|
||||
getUnreadNotice()
|
||||
.then((result) => {
|
||||
this.notice = result.notice;
|
||||
this.letter = result.letter;
|
||||
this.todo = result.todo;
|
||||
// getUnreadNotice()
|
||||
// .then((result) => {
|
||||
// this.notice = result.notice;
|
||||
// this.letter = result.letter;
|
||||
// this.todo = result.todo;
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// this.$message.error(e.message);
|
||||
// });
|
||||
pageNotice({ read: 0 }).then((result) => {
|
||||
this.notice = result.list;
|
||||
this.noticeCount = result.count;
|
||||
});
|
||||
},
|
||||
/* 清空通知 */
|
||||
clearNotice() {
|
||||
setRead({ type: 'all' })
|
||||
.then(() => {
|
||||
this.query();
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e.message);
|
||||
});
|
||||
},
|
||||
/* 清空通知 */
|
||||
clearNotice() {
|
||||
this.notice = [];
|
||||
},
|
||||
/* 清空通知 */
|
||||
clearLetter() {
|
||||
this.letter = [];
|
||||
},
|
||||
/* 清空通知 */
|
||||
clearTodo() {
|
||||
this.todo = [];
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前移除事件监听
|
||||
window.EventBus.$off('ws:connected');
|
||||
window.EventBus.$off('ws:message');
|
||||
window.EventBus.$off('ws:error');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
</div>
|
||||
-->
|
||||
<!-- 消息通知 -->
|
||||
<!--
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<header-notice />
|
||||
</div>
|
||||
-->
|
||||
<!-- 用户信息 -->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<el-dropdown @command="onUserDropClick">
|
||||
@@ -59,7 +57,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import HeaderNotice from './header-notice.vue';
|
||||
import HeaderNotice from './header-notice.vue';
|
||||
// import PasswordModal from './password-modal.vue';
|
||||
import SettingDrawer from './setting-drawer.vue';
|
||||
// import I18nIcon from './i18n-icon.vue';
|
||||
@@ -67,7 +65,7 @@
|
||||
|
||||
export default {
|
||||
// components: { HeaderNotice, PasswordModal, SettingDrawer, I18nIcon },
|
||||
components: { SettingDrawer },
|
||||
components: { SettingDrawer, HeaderNotice },
|
||||
props: {
|
||||
// 是否是全屏
|
||||
fullscreen: Boolean
|
||||
|
||||
+12
-1
@@ -8,6 +8,13 @@ import EleAdmin from 'ele-admin';
|
||||
import VueClipboard from 'vue-clipboard2';
|
||||
import i18n from './i18n';
|
||||
import './styles/index.scss';
|
||||
import websocket from '@/utils/websocket';
|
||||
|
||||
// 初始化事件总线(跨组件通信)
|
||||
Vue.prototype.$eventBus = new Vue();
|
||||
window.EventBus = new Vue();
|
||||
// 应用启动时自动检测 Token 并初始化 WebSocket
|
||||
websocket.init();
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
@@ -26,5 +33,9 @@ new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
render: (h) => h(App)
|
||||
render: (h) => h(App),
|
||||
beforeDestroy() {
|
||||
// 应用销毁时关闭连接
|
||||
this.$ws.close();
|
||||
}
|
||||
}).$mount('#app');
|
||||
|
||||
@@ -8,6 +8,7 @@ import { WHITE_LIST, REDIRECT_PATH, LAYOUT_PATH } from '@/config/setting';
|
||||
import store from '@/store';
|
||||
import { getToken } from '@/utils/token-util';
|
||||
import { routes, getMenuRoutes } from './routes';
|
||||
import websocket from '@/utils/websocket';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -44,6 +45,9 @@ router.beforeEach((to, from, next) => {
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
if (!websocket.isConnected) {
|
||||
websocket.init();
|
||||
}
|
||||
}
|
||||
} else if (WHITE_LIST.includes(to.path)) {
|
||||
next();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
/**
|
||||
* token 操作封装
|
||||
*/
|
||||
import { TOKEN_STORE_NAME } from '@/config/setting';
|
||||
import {
|
||||
TOKEN_STORE_NAME,
|
||||
WEB_SOCKET_TOKEN_STORE_NAME
|
||||
} from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 获取缓存的 token
|
||||
@@ -37,3 +40,14 @@ export function removeToken() {
|
||||
localStorage.removeItem(TOKEN_STORE_NAME);
|
||||
sessionStorage.removeItem(TOKEN_STORE_NAME);
|
||||
}
|
||||
|
||||
export function setWebSocketToken(token) {
|
||||
sessionStorage.setItem(WEB_SOCKET_TOKEN_STORE_NAME, token);
|
||||
}
|
||||
export function getWebSocketToken() {
|
||||
return sessionStorage.getItem(WEB_SOCKET_TOKEN_STORE_NAME);
|
||||
}
|
||||
|
||||
export function removeWebSocketToken() {
|
||||
sessionStorage.removeItem(WEB_SOCKET_TOKEN_STORE_NAME);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 判断字符串是否为有效的 JSON
|
||||
* @param {string} str - 需要判断的字符串
|
||||
* @returns {boolean} 是有效 JSON 则返回 true,否则返回 false
|
||||
*/
|
||||
export function isJson(str) {
|
||||
// 先排除非字符串的情况
|
||||
if (typeof str !== 'string') {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 尝试解析 JSON
|
||||
const obj = JSON.parse(str);
|
||||
// 解析成功后,需确认结果是对象或数组(避免纯字符串/数字被误判)
|
||||
return typeof obj === 'object' && obj !== null;
|
||||
} catch (e) {
|
||||
// 解析失败,不是有效 JSON
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { getToken } from '@/utils/token-util';
|
||||
|
||||
class WebSocketService {
|
||||
constructor() {
|
||||
this.ws = null; // 连接实例
|
||||
this.isConnected = false; // 连接状态
|
||||
this.isConnecting = false; // 添加此状态
|
||||
this.reconnectTimer = null; // 重连计时器
|
||||
this.heartbeatTimer = null; // 心跳计时器
|
||||
this.heartbeatInterval = 30000; // 心跳间隔(30秒)
|
||||
this.maxReconnectCount = 100; // 最大重连次数
|
||||
this.reconnectCount = 0; // 当前重连次数
|
||||
}
|
||||
|
||||
// 初始化连接(核心方法)
|
||||
init() {
|
||||
// 更严格的连接状态检查
|
||||
if (
|
||||
this.isConnecting ||
|
||||
(this.ws && this.ws.readyState === WebSocket.CONNECTING)
|
||||
) {
|
||||
console.log('连接已在进行中,跳过重复连接请求');
|
||||
return false;
|
||||
}
|
||||
// 检查是否已经建立连接
|
||||
if (this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
console.log('WebSocket 已连接,无需重复连接');
|
||||
return false;
|
||||
}
|
||||
// 1. 获取 Token 和用户 ID(优先 Vuex,其次 localStorage)
|
||||
let token = getToken();
|
||||
|
||||
// 2. 无 Token 则阻断连接,等待登录
|
||||
if (!token) {
|
||||
console.log('无有效 Token,等待登录后连接');
|
||||
return false;
|
||||
}
|
||||
this.isConnecting = true;
|
||||
// 3. 已存在连接则关闭旧连接
|
||||
if (this.ws) this.close();
|
||||
|
||||
// 4. 建立 WebSocket 连接(携带授权信息)
|
||||
const wsUrl = `${process.env.VUE_APP_WS_URL}?token=${token}&platform=${process.env.VUE_APP_WS_PLATFORM}`;
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
// 5. 连接成功回调
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket 连接成功');
|
||||
this.isConnected = true;
|
||||
this.reconnectCount = 0; // 重置重连次数
|
||||
this.startHeartbeat(); // 启动心跳
|
||||
window.EventBus.$emit('ws:connected', true); // 触发连接成功事件
|
||||
};
|
||||
|
||||
// 6. 接收消息回调
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
// console.log('WebSocket 收到消息:', message);
|
||||
// 处理 Token 失效(服务端约定 code: 401)
|
||||
if (message.code === 401) {
|
||||
this.handleTokenInvalid();
|
||||
return;
|
||||
}
|
||||
// 正常消息分发
|
||||
window.EventBus.$emit('ws:message', message);
|
||||
} catch (error) {
|
||||
console.error('消息解析失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 7. 连接关闭回调
|
||||
this.ws.onclose = (event) => {
|
||||
console.log(`WebSocket 关闭(code: ${event.code})`);
|
||||
// 确保重置连接状态
|
||||
this.isConnected = false;
|
||||
this.isConnecting = false; // 必须重置连接中状态
|
||||
this.stopHeartbeat();
|
||||
window.EventBus.$emit('ws:connected', false);
|
||||
|
||||
// 异常关闭则重连
|
||||
if (event.code !== 1000 && this.reconnectCount < this.maxReconnectCount) {
|
||||
this.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
// 8. 连接错误回调
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket 错误:', error);
|
||||
window.EventBus.$emit('ws:error', error);
|
||||
};
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
send(data) {
|
||||
if (this.isConnected && this.ws) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
} else {
|
||||
console.error('WebSocket 未连接,无法发送消息');
|
||||
}
|
||||
}
|
||||
|
||||
// 主动关闭连接
|
||||
close() {
|
||||
this.isConnecting = false; // 重置连接中状态
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, '手动关闭'); // 1000 表示正常关闭
|
||||
this.ws = null;
|
||||
this.isConnected = false;
|
||||
this.stopHeartbeat();
|
||||
this.clearReconnectTimer();
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳检测(维持连接)
|
||||
startHeartbeat() {
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.isConnected) {
|
||||
this.send({ type: 'heartbeat', content: 'ping' });
|
||||
}
|
||||
}, this.heartbeatInterval);
|
||||
}
|
||||
|
||||
// 停止心跳
|
||||
stopHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 重连机制
|
||||
reconnect() {
|
||||
this.clearReconnectTimer();
|
||||
this.isConnecting = false; // 重置连接状态
|
||||
|
||||
this.reconnectCount++;
|
||||
console.log(`第 ${this.reconnectCount} 次重连...`);
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
// 重连前确保连接已完全关闭
|
||||
if (this.ws) {
|
||||
this.ws.onopen = null;
|
||||
this.ws.onclose = null;
|
||||
this.ws.onerror = null;
|
||||
this.ws.onmessage = null;
|
||||
this.ws = null;
|
||||
}
|
||||
this.init();
|
||||
}, 3000 * this.reconnectCount); // 指数退避重连(3s, 6s, 9s...)
|
||||
}
|
||||
|
||||
// 清除重连计时器
|
||||
clearReconnectTimer() {
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
this.reconnectTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 Token 失效
|
||||
handleTokenInvalid() {
|
||||
this.close(); // 关闭连接
|
||||
// store.dispatch('user/logout'); // 清除 Token
|
||||
// router.push('/login?redirect=' + router.currentRoute.path); // 跳转登录页(带回调地址)
|
||||
// window.EventBus.$emit('ws:error', 'Token 已过期,请重新登录');
|
||||
}
|
||||
}
|
||||
|
||||
// 单例导出
|
||||
export default new WebSocketService();
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<ele-pro-table
|
||||
ref="table"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:selection.sync="selection"
|
||||
tool-class="ele-toolbar-actions"
|
||||
>
|
||||
<template v-slot:toolbar>
|
||||
<el-button size="small" type="primary" @click="read">
|
||||
标记已读
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="removeBatch">
|
||||
删除消息
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot:status="{ row }">
|
||||
<span :class="['ele-text-danger', 'ele-text-info'][row.status]">
|
||||
{{ ['未读', '已读'][row.status] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:action="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
icon="el-icon-chat-dot-square"
|
||||
@click="view(row)"
|
||||
>
|
||||
回复
|
||||
</el-link>
|
||||
<el-popconfirm
|
||||
class="ele-action"
|
||||
title="确定要删除此消息吗?"
|
||||
@confirm="remove(row)"
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<el-link type="danger" :underline="false" icon="el-icon-delete">
|
||||
删除
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { pageLetters } from '@/api/user/message';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 表格列配置
|
||||
columns: [
|
||||
{
|
||||
columnKey: 'selection',
|
||||
type: 'selection',
|
||||
width: 45,
|
||||
align: 'center',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
columnKey: 'index',
|
||||
type: 'index',
|
||||
width: 45,
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
prop: 'title',
|
||||
label: '私信内容',
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
prop: 'time',
|
||||
label: '发送时间',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 80,
|
||||
slot: 'status'
|
||||
},
|
||||
{
|
||||
columnKey: 'action',
|
||||
label: '操作',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 140,
|
||||
resizable: false,
|
||||
slot: 'action'
|
||||
}
|
||||
],
|
||||
// 列表选中数据
|
||||
selection: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 表格数据源 */
|
||||
datasource({ page, limit, where, order }) {
|
||||
return pageLetters({ ...where, ...order, page, limit });
|
||||
},
|
||||
/* 刷新表格 */
|
||||
reload(where) {
|
||||
this.$refs.table.reload({ page: 1, where: where });
|
||||
},
|
||||
/* 查看 */
|
||||
view(row) {
|
||||
this.$message.info(row.title);
|
||||
},
|
||||
/* 删除单个 */
|
||||
remove(row) {
|
||||
console.log(row);
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 批量删除 */
|
||||
removeBatch() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 标记已读 */
|
||||
read() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.selection.forEach((d) => {
|
||||
d.status = 1;
|
||||
});
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 触发更新未读数量事件 */
|
||||
updateUnReadNum() {
|
||||
this.$emit('update-data');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<ele-pro-table
|
||||
ref="table"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:selection.sync="selection"
|
||||
tool-class="ele-toolbar-actions"
|
||||
>
|
||||
<template v-slot:toolbar>
|
||||
<el-button size="small" type="primary" @click="read">
|
||||
批量已读
|
||||
</el-button>
|
||||
<!--
|
||||
<el-button size="small" type="danger" @click="removeBatch">
|
||||
删除通知
|
||||
</el-button>
|
||||
-->
|
||||
</template>
|
||||
<template v-slot:readCn="{ row }">
|
||||
<span :class="['ele-text-danger', 'ele-text-info'][row.read]">
|
||||
{{ row.readCn }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:action="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
icon="el-icon-view"
|
||||
@click="view(row)"
|
||||
>
|
||||
查看
|
||||
</el-link>
|
||||
<!--
|
||||
<el-popconfirm
|
||||
class="ele-action"
|
||||
title="确定要删除此通知吗?"
|
||||
@confirm="remove(row)"
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<el-link type="danger" :underline="false" icon="el-icon-delete">
|
||||
删除
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
-->
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { pageNotices } from '@/api/user/message';
|
||||
import { pageNotice, setRead } from '@/api/user/notice';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 表格列配置
|
||||
columns: [
|
||||
{
|
||||
columnKey: 'selection',
|
||||
type: 'selection',
|
||||
width: 45,
|
||||
align: 'center',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
prop: 'content',
|
||||
label: '通知内容',
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
prop: 'c_time',
|
||||
label: '通知时间',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
prop: 'readCn',
|
||||
label: '状态',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 80,
|
||||
slot: 'readCn'
|
||||
},
|
||||
{
|
||||
columnKey: 'action',
|
||||
label: '操作',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 140,
|
||||
resizable: false,
|
||||
slot: 'action'
|
||||
}
|
||||
],
|
||||
// 列表选中数据
|
||||
selection: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 表格数据源 */
|
||||
datasource({ page, limit, where, order }) {
|
||||
return pageNotice({ ...where, ...order, page, limit });
|
||||
},
|
||||
/* 刷新表格 */
|
||||
reload(where) {
|
||||
this.$refs.table.reload({ page: 1, where: where });
|
||||
},
|
||||
/* 查看 */
|
||||
view(row) {
|
||||
this.$message.info(row.content);
|
||||
},
|
||||
/* 删除单个 */
|
||||
remove(row) {
|
||||
console.log(row);
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 批量删除 */
|
||||
removeBatch() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 标记已读 */
|
||||
read() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
let ids = [];
|
||||
this.selection.forEach((d) => {
|
||||
ids.push(d.id);
|
||||
});
|
||||
setRead({ ids: ids })
|
||||
.then((result) => {
|
||||
console.log(result);
|
||||
this.selection.forEach((d) => {
|
||||
d.read = 1;
|
||||
d.readCn = '已读';
|
||||
});
|
||||
this.updateUnReadNum();
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e.message);
|
||||
});
|
||||
},
|
||||
/* 触发更新未读数量事件 */
|
||||
updateUnReadNum() {
|
||||
this.$emit('update-data');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<ele-pro-table
|
||||
ref="table"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:selection.sync="selection"
|
||||
tool-class="ele-toolbar-actions"
|
||||
>
|
||||
<template v-slot:toolbar>
|
||||
<el-button size="small" type="primary" @click="read">
|
||||
批量完成
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="removeBatch">
|
||||
删除待办
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot:status="{ row }">
|
||||
<span :class="['ele-text-danger', 'ele-text-info'][row.status]">
|
||||
{{ ['未完成', '已完成'][row.status] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot:action="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
icon="el-icon-finished"
|
||||
@click="view(row)"
|
||||
>
|
||||
完成
|
||||
</el-link>
|
||||
<el-popconfirm
|
||||
class="ele-action"
|
||||
title="确定要取消此待办吗?"
|
||||
@confirm="remove(row)"
|
||||
>
|
||||
<template v-slot:reference>
|
||||
<el-link type="danger" :underline="false" icon="el-icon-circle-close">
|
||||
取消
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { pageTodos } from '@/api/user/message';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 表格列配置
|
||||
columns: [
|
||||
{
|
||||
columnKey: 'selection',
|
||||
type: 'selection',
|
||||
width: 45,
|
||||
align: 'center',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
columnKey: 'index',
|
||||
type: 'index',
|
||||
width: 45,
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
prop: 'title',
|
||||
label: '待办内容',
|
||||
showOverflowTooltip: true,
|
||||
minWidth: 110
|
||||
},
|
||||
{
|
||||
prop: 'time',
|
||||
label: '结束时间',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 80,
|
||||
slot: 'status'
|
||||
},
|
||||
{
|
||||
columnKey: 'action',
|
||||
label: '操作',
|
||||
align: 'center',
|
||||
showOverflowTooltip: true,
|
||||
width: 140,
|
||||
resizable: false,
|
||||
slot: 'action'
|
||||
}
|
||||
],
|
||||
// 列表选中数据
|
||||
selection: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 表格数据源 */
|
||||
datasource({ page, limit, where, order }) {
|
||||
return pageTodos({ ...where, ...order, page, limit });
|
||||
},
|
||||
/* 刷新表格 */
|
||||
reload(where) {
|
||||
this.$refs.table.reload({ page: 1, where: where });
|
||||
},
|
||||
/* 查看 */
|
||||
view(row) {
|
||||
this.$message.info(row.title);
|
||||
},
|
||||
/* 删除单个 */
|
||||
remove(row) {
|
||||
console.log(row);
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 批量删除 */
|
||||
removeBatch() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.$message.info('点击了删除');
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 标记已读 */
|
||||
read() {
|
||||
if (!this.selection.length) {
|
||||
this.$message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
this.selection.forEach((d) => {
|
||||
d.status = 1;
|
||||
});
|
||||
this.updateUnReadNum();
|
||||
},
|
||||
/* 触发更新未读数量事件 */
|
||||
updateUnReadNum() {
|
||||
this.$emit('update-data');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div :class="['ele-body', { 'demo-message-responsive': styleResponsive }]">
|
||||
<el-card shadow="never" body-style="padding: 0;">
|
||||
<div class="ele-cell ele-cell-align-top ele-user-message">
|
||||
<el-menu
|
||||
:mode="mode"
|
||||
:default-active="active"
|
||||
class="ele-scrollbar-hide"
|
||||
>
|
||||
<el-menu-item index="notice">
|
||||
<router-link to="/user/notice?type=notice">
|
||||
<el-badge
|
||||
v-if="unReadNotice"
|
||||
:value="unReadNotice"
|
||||
class="ele-badge-static"
|
||||
/>
|
||||
<span>系统通知</span>
|
||||
</router-link>
|
||||
</el-menu-item>
|
||||
<!--
|
||||
<el-menu-item index="letter">
|
||||
<router-link to="/user/notice?type=letter">
|
||||
<el-badge
|
||||
v-if="unReadLetter"
|
||||
:value="unReadLetter"
|
||||
class="ele-badge-static"
|
||||
/>
|
||||
<span>用户私信</span>
|
||||
</router-link>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="todo">
|
||||
<router-link to="/user/notice?type=todo">
|
||||
<el-badge
|
||||
v-if="unReadTodo"
|
||||
:value="unReadTodo"
|
||||
class="ele-badge-static"
|
||||
/>
|
||||
<span>代办事项</span>
|
||||
</router-link>
|
||||
</el-menu-item>
|
||||
-->
|
||||
</el-menu>
|
||||
<div class="ele-cell-content" style="overflow-x: hidden">
|
||||
<transition name="slide-right" mode="out-in">
|
||||
<message-notice
|
||||
v-if="active === 'notice'"
|
||||
@update-data="queryUnReadNum"
|
||||
/>
|
||||
<message-letter
|
||||
v-else-if="active === 'letter'"
|
||||
@update-data="queryUnReadNum"
|
||||
/>
|
||||
<message-todo v-else @update-data="queryUnReadNum" />
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MessageNotice from './components/message-notice.vue';
|
||||
import MessageLetter from './components/message-letter.vue';
|
||||
import MessageTodo from './components/message-todo.vue';
|
||||
// import { getUnReadNum } from '@/api/user/message';
|
||||
import { unRead } from '@/api/user/notice';
|
||||
|
||||
export default {
|
||||
name: 'UserMessage',
|
||||
components: { MessageNotice, MessageLetter, MessageTodo },
|
||||
data() {
|
||||
return {
|
||||
// 导航选中
|
||||
active: null,
|
||||
// 通知未读数量
|
||||
unReadNotice: 0,
|
||||
// 私信未读数量
|
||||
unReadLetter: 0,
|
||||
// 代办未读数量
|
||||
unReadTodo: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 小屏幕水平导航
|
||||
mode() {
|
||||
return this.styleResponsive && this.$store.state.theme.screenWidth < 768
|
||||
? 'horizontal'
|
||||
: 'vertical';
|
||||
},
|
||||
// 是否开启响应式布局
|
||||
styleResponsive() {
|
||||
return this.$store.state.theme.styleResponsive;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler(route) {
|
||||
if (route.path === '/user/notice') {
|
||||
this.active = route?.query?.type || 'notice';
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.queryUnReadNum();
|
||||
},
|
||||
methods: {
|
||||
/* 查询未读数量 */
|
||||
queryUnReadNum() {
|
||||
unRead({})
|
||||
.then((result) => {
|
||||
this.unReadNotice = result.noticeCount;
|
||||
// this.unReadLetter = result.letter;
|
||||
// this.unReadTodo = result.todo;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.$message.error(e.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ele-user-message {
|
||||
:deep(.el-menu) {
|
||||
width: 151px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ele-badge-static {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.demo-message-responsive .ele-user-message :deep(.el-menu) {
|
||||
.el-menu-item {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.el-menu-item:first-child {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.demo-message-responsive .ele-user-message {
|
||||
display: block;
|
||||
|
||||
:deep(.el-menu) {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
|
||||
.el-menu-item {
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-badge-static {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user