新增消息中心
This commit is contained in:
parent
e82724c16f
commit
a72be82a9b
|
|
@ -10,7 +10,8 @@ module.exports = {
|
|||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }],
|
||||
'no-undef': 'off'
|
||||
'no-undef': 'off',
|
||||
'camelcase': 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
|
|
|
|||
|
|
@ -28,3 +28,50 @@ export const logout = (token) => {
|
|||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export const getMessage = () => {
|
||||
return axios.request({
|
||||
url: 'message/init',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export const getContentByMsgId = msg_id => {
|
||||
return axios.request({
|
||||
url: 'message/content',
|
||||
method: 'get',
|
||||
params: {
|
||||
msg_id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const hasRead = msg_id => {
|
||||
return axios.request({
|
||||
url: 'message/has_read',
|
||||
method: 'post',
|
||||
data: {
|
||||
msg_id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const removeReaded = msg_id => {
|
||||
return axios.request({
|
||||
url: 'message/remove_readed',
|
||||
method: 'post',
|
||||
data: {
|
||||
msg_id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const restoreTrash = msg_id => {
|
||||
return axios.request({
|
||||
url: 'message/restore',
|
||||
method: 'post',
|
||||
data: {
|
||||
msg_id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,8 @@
|
|||
// height: 64px;
|
||||
vertical-align: middle;
|
||||
// line-height: 64px;
|
||||
.ivu-badge-dot{
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
<template>
|
||||
<div class="user-avator-dropdown">
|
||||
<Dropdown @on-click="handleClick">
|
||||
<Avatar :src="userAvator"/>
|
||||
<Badge :dot="!!messageUnreadCount">
|
||||
<Avatar :src="userAvator"/>
|
||||
</Badge>
|
||||
<Icon :size="18" type="md-arrow-dropdown"></Icon>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem name="message">
|
||||
消息中心<Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>
|
||||
</DropdownItem>
|
||||
<DropdownItem name="logout">退出登录</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
|
|
@ -19,20 +24,33 @@ export default {
|
|||
userAvator: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
messageUnreadCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'handleLogOut'
|
||||
]),
|
||||
logout () {
|
||||
this.handleLogOut().then(() => {
|
||||
this.$router.push({
|
||||
name: 'login'
|
||||
})
|
||||
})
|
||||
},
|
||||
message () {
|
||||
this.$router.push({
|
||||
name: 'message_page'
|
||||
})
|
||||
},
|
||||
handleClick (name) {
|
||||
switch (name) {
|
||||
case 'logout':
|
||||
this.handleLogOut().then(() => {
|
||||
this.$router.push({
|
||||
name: 'login'
|
||||
})
|
||||
})
|
||||
case 'logout': this.logout()
|
||||
break
|
||||
case 'message': this.message()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<Layout>
|
||||
<Header class="header-con">
|
||||
<header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
|
||||
<user :user-avator="userAvator"/>
|
||||
<user :message-unread-count="messageUnreadCount" :user-avator="userAvator"/>
|
||||
<language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/>
|
||||
<error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader" :has-read="hasReadErrorPage" :count="errorCount"></error-store>
|
||||
<fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
|
||||
|
|
@ -68,7 +68,8 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'errorCount'
|
||||
'errorCount',
|
||||
'messageUnreadCount'
|
||||
]),
|
||||
tagNavList () {
|
||||
return this.$store.state.app.tagNavList
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@ export default {
|
|||
error_logger_page: 'Error Logger',
|
||||
query: 'Query',
|
||||
params: 'Params',
|
||||
cropper_page: 'Cropper'
|
||||
cropper_page: 'Cropper',
|
||||
message_page: 'Message Center'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@ export default {
|
|||
error_logger_page: '错误日志',
|
||||
query: '带参路由',
|
||||
params: '动态路由',
|
||||
cropper_page: '图片裁剪'
|
||||
cropper_page: '图片裁剪',
|
||||
message_page: '消息中心'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,5 +35,6 @@ export default {
|
|||
error_logger_page: '錯誤日誌',
|
||||
query: '帶參路由',
|
||||
params: '動態路由',
|
||||
cropper_page: '圖片裁剪'
|
||||
cropper_page: '圖片裁剪',
|
||||
message_page: '消息中心'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import Mock from 'mockjs'
|
||||
import { login, logout, getUserInfo } from './login'
|
||||
import { getTableData, getDragList, uploadImage } from './data'
|
||||
import { getMessageInit, getContentByMsgId, hasRead, removeReaded, restoreTrash } from './user'
|
||||
|
||||
// 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果
|
||||
Mock.setup({
|
||||
timeout: 1000
|
||||
})
|
||||
|
||||
// 登录相关和获取用户信息
|
||||
Mock.mock(/\/login/, login)
|
||||
|
|
@ -10,5 +16,10 @@ Mock.mock(/\/get_table_data/, getTableData)
|
|||
Mock.mock(/\/get_drag_list/, getDragList)
|
||||
Mock.mock(/\/save_error_logger/, 'success')
|
||||
Mock.mock(/\/image\/upload/, uploadImage)
|
||||
Mock.mock(/\/message\/init/, getMessageInit)
|
||||
Mock.mock(/\/message\/content/, getContentByMsgId)
|
||||
Mock.mock(/\/message\/has_read/, hasRead)
|
||||
Mock.mock(/\/message\/remove_readed/, removeReaded)
|
||||
Mock.mock(/\/message\/restore/, restoreTrash)
|
||||
|
||||
export default Mock
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import Mock from 'mockjs'
|
||||
import { doCustomTimes } from '@/libs/util'
|
||||
const Random = Mock.Random
|
||||
|
||||
export const getMessageInit = () => {
|
||||
let unreadList = []
|
||||
doCustomTimes(3, () => {
|
||||
unreadList.push(Mock.mock({
|
||||
title: Random.cword(10, 15),
|
||||
create_time: '@date',
|
||||
msg_id: Random.increment(100)
|
||||
}))
|
||||
})
|
||||
let readedList = []
|
||||
doCustomTimes(4, () => {
|
||||
readedList.push(Mock.mock({
|
||||
title: Random.cword(10, 15),
|
||||
create_time: '@date',
|
||||
msg_id: Random.increment(100)
|
||||
}))
|
||||
})
|
||||
let trashList = []
|
||||
doCustomTimes(2, () => {
|
||||
trashList.push(Mock.mock({
|
||||
title: Random.cword(10, 15),
|
||||
create_time: '@date',
|
||||
msg_id: Random.increment(100)
|
||||
}))
|
||||
})
|
||||
return {
|
||||
unread: unreadList,
|
||||
readed: readedList,
|
||||
trash: trashList
|
||||
}
|
||||
}
|
||||
|
||||
export const getContentByMsgId = () => {
|
||||
return `<divcourier new',="" monospace;font-weight:="" normal;font-size:="" 12px;line-height:="" 18px;white-space:="" pre;"=""><div> <span style="font-size: medium;">这是消息内容,这个内容是使用<span style="color: rgb(255, 255, 255); background-color: rgb(28, 72, 127);">富文本编辑器</span>编辑的,所以你可以看到一些<span style="text-decoration-line: underline; font-style: italic; color: rgb(194, 79, 74);">格式</span></span></div><ol><li>你可以查看Mock返回的数据格式,和api请求的接口,来确定你的后端接口的开发</li><li>使用你的真实接口后,前端页面基本不需要修改即可满足基本需求</li><li>快来试试吧</li></ol><p>${Random.csentence(100, 200)}</p></divcourier>`
|
||||
}
|
||||
|
||||
export const hasRead = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const removeReaded = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const restoreTrash = () => {
|
||||
return true
|
||||
}
|
||||
|
|
@ -78,6 +78,26 @@ export default [
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/message',
|
||||
name: 'message',
|
||||
component: Main,
|
||||
meta: {
|
||||
hideInBread: true,
|
||||
hideInMenu: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'message_page',
|
||||
name: 'message_page',
|
||||
meta: {
|
||||
icon: 'md-notifications',
|
||||
title: '消息中心'
|
||||
},
|
||||
component: () => import('@/view/single-page/message/index.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
name: 'components',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
import { login, logout, getUserInfo } from '@/api/user'
|
||||
import {
|
||||
login,
|
||||
logout,
|
||||
getUserInfo,
|
||||
getMessage,
|
||||
getContentByMsgId,
|
||||
hasRead,
|
||||
removeReaded,
|
||||
restoreTrash
|
||||
} from '@/api/user'
|
||||
import { setToken, getToken } from '@/libs/util'
|
||||
|
||||
export default {
|
||||
|
|
@ -8,7 +17,11 @@ export default {
|
|||
avatorImgPath: '',
|
||||
token: getToken(),
|
||||
access: '',
|
||||
hasGetInfo: false
|
||||
hasGetInfo: false,
|
||||
messageUnreadList: [],
|
||||
messageReadedList: [],
|
||||
messageTrashList: [],
|
||||
messageContentStore: {}
|
||||
},
|
||||
mutations: {
|
||||
setAvator (state, avatorPath) {
|
||||
|
|
@ -29,8 +42,31 @@ export default {
|
|||
},
|
||||
setHasGetInfo (state, status) {
|
||||
state.hasGetInfo = status
|
||||
},
|
||||
setMessageUnreadList (state, list) {
|
||||
state.messageUnreadList = list
|
||||
},
|
||||
setMessageReadedList (state, list) {
|
||||
state.messageReadedList = list
|
||||
},
|
||||
setMessageTrashList (state, list) {
|
||||
state.messageTrashList = list
|
||||
},
|
||||
updateMessageContentStore (state, { msg_id, content }) {
|
||||
state.messageContentStore[msg_id] = content
|
||||
},
|
||||
moveMsg (state, { from, to, msg_id }) {
|
||||
const index = state[from].findIndex(_ => _.msg_id === msg_id)
|
||||
const msgItem = state[from].splice(index, 1)[0]
|
||||
msgItem.loading = false
|
||||
state[to].unshift(msgItem)
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
messageUnreadCount: state => state.messageUnreadList.length,
|
||||
messageReadedCount: state => state.messageReadedList.length,
|
||||
messageTrashCount: state => state.messageTrashList.length
|
||||
},
|
||||
actions: {
|
||||
// 登录
|
||||
handleLogin ({ commit }, {userName, password}) {
|
||||
|
|
@ -83,6 +119,86 @@ export default {
|
|||
reject(error)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取消息列表,其中包含未读、已读、回收站三个列表
|
||||
getMessageList ({ state, commit }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getMessage().then(res => {
|
||||
const { unread, readed, trash } = res.data
|
||||
commit('setMessageUnreadList', unread.sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
|
||||
commit('setMessageReadedList', readed.map(_ => {
|
||||
_.loading = false
|
||||
return _
|
||||
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
|
||||
commit('setMessageTrashList', trash.map(_ => {
|
||||
_.loading = false
|
||||
return _
|
||||
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 根据当前点击的消息的id获取内容
|
||||
getContentByMsgId ({ state, commit }, { msg_id }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let contentItem = state.messageContentStore[msg_id]
|
||||
if (contentItem) {
|
||||
resolve(contentItem)
|
||||
} else {
|
||||
getContentByMsgId(msg_id).then(res => {
|
||||
const content = res.data
|
||||
commit('updateMessageContentStore', { msg_id, content })
|
||||
resolve(content)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 把一个未读消息标记为已读
|
||||
hasRead ({ commit }, { msg_id }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
hasRead(msg_id).then(() => {
|
||||
commit('moveMsg', {
|
||||
from: 'messageUnreadList',
|
||||
to: 'messageReadedList',
|
||||
msg_id
|
||||
})
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 删除一个已读消息到回收站
|
||||
removeReaded ({ commit }, { msg_id }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
removeReaded(msg_id).then(() => {
|
||||
commit('moveMsg', {
|
||||
from: 'messageReadedList',
|
||||
to: 'messageTrashList',
|
||||
msg_id
|
||||
})
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 还原一个已删除消息到已读消息
|
||||
restoreTrash ({ commit }, { msg_id }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
restoreTrash(msg_id).then(() => {
|
||||
commit('moveMsg', {
|
||||
from: 'messageTrashList',
|
||||
to: 'messageReadedList',
|
||||
msg_id
|
||||
})
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<Card shadow>
|
||||
<div>
|
||||
<div class="message-page-con message-category-con">
|
||||
<Menu width="auto" active-name="unread" @on-select="handleSelect">
|
||||
<MenuItem name="unread">
|
||||
<span class="category-title">未读消息</span><Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>
|
||||
</MenuItem>
|
||||
<MenuItem name="readed">
|
||||
<span class="category-title">已读消息</span><Badge style="margin-left: 10px" class-name="gray-dadge" :count="messageReadedCount"></Badge>
|
||||
</MenuItem>
|
||||
<MenuItem name="trash">
|
||||
<span class="category-title">回收站</span><Badge style="margin-left: 10px" class-name="gray-dadge" :count="messageTrashCount"></Badge>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
<div class="message-page-con message-list-con">
|
||||
<Spin fix v-if="listLoading" size="large"></Spin>
|
||||
<Menu
|
||||
width="auto"
|
||||
active-name=""
|
||||
:class="titleClass"
|
||||
@on-select="handleView"
|
||||
>
|
||||
<MenuItem v-for="item in messageList" :name="item.msg_id" :key="`msg_${item.msg_id}`">
|
||||
<div>
|
||||
<p class="msg-title">{{ item.title }}</p>
|
||||
<Badge status="default" :text="item.create_time" />
|
||||
<Button
|
||||
style="float: right;margin-right: 20px;"
|
||||
:style="{ display: item.loading ? 'inline-block !important' : '' }"
|
||||
:loading="item.loading"
|
||||
size="small"
|
||||
:icon="currentMessageType === 'readed' ? 'md-trash' : 'md-redo'"
|
||||
:title="currentMessageType === 'readed' ? '删除' : '还原'"
|
||||
type="text"
|
||||
v-show="currentMessageType !== 'unread'"
|
||||
@click.native.stop="removeMsg(item)"></Button>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
<div class="message-page-con message-view-con">
|
||||
<Spin fix v-if="contentLoading" size="large"></Spin>
|
||||
<div class="message-view-header">
|
||||
<h2 class="message-view-title">{{ showingMsgItem.title }}</h2>
|
||||
<time class="message-view-time">{{ showingMsgItem.create_time }}</time>
|
||||
</div>
|
||||
<div v-html="messageContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
|
||||
const listDic = {
|
||||
unread: 'messageUnreadList',
|
||||
readed: 'messageReadedList',
|
||||
trash: 'messageTrashList'
|
||||
}
|
||||
export default {
|
||||
name: 'message_page',
|
||||
data () {
|
||||
return {
|
||||
listLoading: true,
|
||||
contentLoading: false,
|
||||
currentMessageType: 'unread',
|
||||
messageContent: '',
|
||||
showingMsgItem: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
messageUnreadList: state => state.user.messageUnreadList,
|
||||
messageReadedList: state => state.user.messageReadedList,
|
||||
messageTrashList: state => state.user.messageTrashList,
|
||||
messageList () {
|
||||
return this[listDic[this.currentMessageType]]
|
||||
},
|
||||
titleClass () {
|
||||
return {
|
||||
'not-unread-list': this.currentMessageType !== 'unread'
|
||||
}
|
||||
}
|
||||
}),
|
||||
...mapGetters([
|
||||
'messageUnreadCount',
|
||||
'messageReadedCount',
|
||||
'messageTrashCount'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
//
|
||||
]),
|
||||
...mapActions([
|
||||
'getContentByMsgId',
|
||||
'getMessageList',
|
||||
'hasRead',
|
||||
'removeReaded',
|
||||
'restoreTrash'
|
||||
]),
|
||||
stopLoading (name) {
|
||||
this[name] = false
|
||||
},
|
||||
handleSelect (name) {
|
||||
this.currentMessageType = name
|
||||
},
|
||||
handleView (msg_id) {
|
||||
this.contentLoading = true
|
||||
this.getContentByMsgId({ msg_id }).then(content => {
|
||||
this.messageContent = content
|
||||
const item = this.messageList.find(item => item.msg_id === msg_id)
|
||||
if (item) this.showingMsgItem = item
|
||||
if (this.currentMessageType === 'unread') this.hasRead({ msg_id })
|
||||
this.stopLoading('contentLoading')
|
||||
}).catch(() => {
|
||||
this.stopLoading('contentLoading')
|
||||
})
|
||||
},
|
||||
removeMsg (item) {
|
||||
item.loading = true
|
||||
const msg_id = item.msg_id
|
||||
if (this.currentMessageType === 'readed') this.removeReaded({ msg_id })
|
||||
else this.restoreTrash({ msg_id })
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.listLoading = true
|
||||
// 请求获取消息列表
|
||||
this.getMessageList().then(() => this.stopLoading('listLoading')).catch(() => this.stopLoading('listLoading'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.message-page{
|
||||
&-con{
|
||||
height: ~"calc(100vh - 176px)";
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
&.message-category-con{
|
||||
border-right: 1px solid #e6e6e6;
|
||||
width: 200px;
|
||||
}
|
||||
&.message-list-con{
|
||||
border-right: 1px solid #e6e6e6;
|
||||
width: 230px;
|
||||
}
|
||||
&.message-view-con{
|
||||
position: absolute;
|
||||
left: 446px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
overflow: auto;
|
||||
padding: 12px 20px 0;
|
||||
.message-view-header{
|
||||
margin-bottom: 20px;
|
||||
.message-view-title{
|
||||
display: inline-block;
|
||||
}
|
||||
.message-view-time{
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.category-title{
|
||||
display: inline-block;
|
||||
width: 65px;
|
||||
}
|
||||
.gray-dadge{
|
||||
background: gainsboro;
|
||||
}
|
||||
.not-unread-list{
|
||||
.msg-title{
|
||||
color: rgb(170, 169, 169);
|
||||
}
|
||||
.ivu-menu-item{
|
||||
.ivu-btn.ivu-btn-text.ivu-btn-small.ivu-btn-icon-only{
|
||||
display: none;
|
||||
}
|
||||
&:hover{
|
||||
.ivu-btn.ivu-btn-text.ivu-btn-small.ivu-btn-icon-only{
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue