Skip to content

Commit 3e8cfe9

Browse files
authored
Message center (#297)
* feat(*): 消息中心组件基本完成 * feat(*): 完善消息中心组件
1 parent 96262b2 commit 3e8cfe9

File tree

9 files changed

+440
-148
lines changed

9 files changed

+440
-148
lines changed

.env.development

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
ENV = 'development'
22

3-
VUE_APP_BASE_URL = 'http://localhost:5000/'
3+
VUE_APP_BASE_URL = 'http://api.s.colorful3.com'

src/components/layout/NavBar.vue

+61-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
<breadcrumb />
55
<!-- 暂时放这里 -->
66
<div class="right-info">
7-
<notify v-permission="'消息推送'" v-show="false" />
7+
<lin-notify
8+
@readMessages="readMessages"
9+
:messages="messages"
10+
@readAll="readAll"
11+
@viewAll="viewAll"
12+
class="lin-notify"
13+
:value="value"
14+
:hidden="hidden"
15+
>
16+
</lin-notify>
817
<clear-tab></clear-tab>
918
<screenfull /> <user></user>
1019
</div>
@@ -13,26 +22,74 @@
1322
</template>
1423

1524
<script>
16-
import Notify from '@/components/notify/notify'
1725
import Breadcrumb from './Breadcrumb'
1826
import Screenfull from './Screenfull'
1927
import User from './User'
2028
import ClearTab from './ClearTab'
29+
import { getToken } from '@/lin/utils/token'
30+
import store from '@/store'
2131
2232
export default {
2333
name: 'NavBar',
24-
created() {},
34+
created() {
35+
this.$connect(this.path, { format: 'json' })
36+
this.$options.sockets.onmessage = data => {
37+
console.log(JSON.parse(data.data))
38+
this.messages.push(JSON.parse(data.data))
39+
}
40+
this.$options.sockets.onerror = err => {
41+
console.log(err)
42+
this.$message.error('token已过期,请重新登录')
43+
store.dispatch('loginOut')
44+
const { origin } = window.location
45+
window.location.href = origin
46+
}
47+
},
48+
watch: {
49+
messages() {
50+
// eslint-disable-next-line
51+
this.value = this.messages.filter(msg => {
52+
return msg.is_read === false
53+
}).length
54+
if (this.value === 0) {
55+
this.hidden = true
56+
} else {
57+
this.hidden = false
58+
}
59+
},
60+
},
61+
data() {
62+
return {
63+
value: 0,
64+
hidden: true,
65+
messages: [],
66+
path: `//api.s.colorful3.com/ws/message?token=${getToken('access_token').split(' ')[1]}`,
67+
}
68+
},
69+
methods: {
70+
readAll() {
71+
console.log('点击了readAll')
72+
},
73+
viewAll() {
74+
console.log('点击了viewAll')
75+
},
76+
readMessages(msg, index) {
77+
this.messages[index].is_read = true
78+
},
79+
},
2580
components: {
2681
Breadcrumb,
2782
User,
28-
Notify,
2983
Screenfull,
3084
ClearTab,
3185
},
3286
}
3387
</script>
3488

3589
<style lang="scss" scoped>
90+
.lin-notify {
91+
margin-right: 20px;
92+
}
3693
.app-nav-bar {
3794
width: 100%;
3895
height: $navbar-height;

src/components/layout/User.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ export default {
348348
done()
349349
},
350350
outLogin() {
351-
this.loginOut()
352351
window.location.reload(true)
352+
this.loginOut()
353353
},
354354
submitForm(formName) {
355355
if (this.form.old_password === '' && this.form.new_password === '' && this.form.confirm_password === '') {

src/components/notify/Emitter.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* eslint-disable*/
2+
class Emitter {
3+
constructor() {
4+
this.listeners = new Map()
5+
}
6+
7+
addListener(label, callback, vm) {
8+
if (typeof callback === 'function') {
9+
this.listeners.has(label) || this.listeners.set(label, [])
10+
this.listeners.get(label).push({ callback, vm })
11+
return true
12+
}
13+
return false
14+
}
15+
16+
removeListener(label, callback, vm) {
17+
const listeners = this.listeners.get(label)
18+
let index
19+
20+
if (listeners && listeners.length) {
21+
index = listeners.reduce((i, listener, index) => {
22+
if (typeof listener.callback === 'function' && listener.callback === callback && listener.vm === vm) {
23+
i = index
24+
}
25+
return i
26+
}, -1)
27+
28+
if (index > -1) {
29+
listeners.splice(index, 1)
30+
this.listeners.set(label, listeners)
31+
return true
32+
}
33+
}
34+
return false
35+
}
36+
37+
emit(label, ...args) {
38+
const listeners = this.listeners.get(label)
39+
40+
if (listeners && listeners.length) {
41+
listeners.forEach(listener => {
42+
listener.callback.call(listener.vm, ...args)
43+
})
44+
return true
45+
}
46+
return false
47+
}
48+
}
49+
50+
export default new Emitter()

src/components/notify/Observer.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* eslint-disable*/
2+
import Emitter from './Emitter'
3+
4+
export default class {
5+
constructor(connectionUrl, opts = {}) {
6+
this.format = opts.format && opts.format.toLowerCase()
7+
8+
if (connectionUrl.startsWith('//')) {
9+
const scheme = window.location.protocol === 'https:' ? 'wss' : 'ws'
10+
connectionUrl = `${scheme}:${connectionUrl}`
11+
}
12+
13+
this.connectionUrl = connectionUrl
14+
this.opts = opts
15+
16+
this.reconnection = this.opts.reconnection || false
17+
this.reconnectionAttempts = this.opts.reconnectionAttempts || Infinity
18+
this.reconnectionDelay = this.opts.reconnectionDelay || 1000
19+
this.reconnectTimeoutId = 0
20+
this.reconnectionCount = 0
21+
22+
this.passToStoreHandler = this.opts.passToStoreHandler || false
23+
24+
this.connect(connectionUrl, opts)
25+
26+
if (opts.store) {
27+
this.store = opts.store
28+
}
29+
if (opts.mutations) {
30+
this.mutations = opts.mutations
31+
}
32+
this.onEvent()
33+
}
34+
35+
connect(connectionUrl, opts = {}) {
36+
const protocol = opts.protocol || ''
37+
this.WebSocket =
38+
opts.WebSocket || (protocol === '' ? new WebSocket(connectionUrl) : new WebSocket(connectionUrl, protocol))
39+
if (this.format === 'json') {
40+
if (!('sendObj' in this.WebSocket)) {
41+
this.WebSocket.sendObj = obj => this.WebSocket.send(JSON.stringify(obj))
42+
}
43+
}
44+
45+
return this.WebSocket
46+
}
47+
48+
reconnect() {
49+
if (this.reconnectionCount <= this.reconnectionAttempts) {
50+
this.reconnectionCount++
51+
clearTimeout(this.reconnectTimeoutId)
52+
53+
this.reconnectTimeoutId = setTimeout(() => {
54+
if (this.store) {
55+
this.passToStore('SOCKET_RECONNECT', this.reconnectionCount)
56+
}
57+
58+
this.connect(this.connectionUrl, this.opts)
59+
this.onEvent()
60+
}, this.reconnectionDelay)
61+
} else if (this.store) {
62+
this.passToStore('SOCKET_RECONNECT_ERROR', true)
63+
}
64+
}
65+
66+
onEvent() {
67+
;['onmessage', 'onclose', 'onerror', 'onopen'].forEach(eventType => {
68+
this.WebSocket[eventType] = event => {
69+
Emitter.emit(eventType, event)
70+
71+
if (this.store) {
72+
this.passToStore(`SOCKET_${eventType}`, event)
73+
}
74+
75+
if (this.reconnection && eventType === 'onopen') {
76+
this.opts.$setInstance(event.currentTarget)
77+
this.reconnectionCount = 0
78+
}
79+
80+
if (this.reconnection && eventType === 'onclose') {
81+
this.reconnect()
82+
}
83+
}
84+
})
85+
}
86+
87+
passToStore(eventName, event) {
88+
if (this.passToStoreHandler) {
89+
this.passToStoreHandler(eventName, event, this.defaultPassToStore.bind(this))
90+
} else {
91+
this.defaultPassToStore(eventName, event)
92+
}
93+
}
94+
95+
defaultPassToStore(eventName, event) {
96+
if (!eventName.startsWith('SOCKET_')) {
97+
return
98+
}
99+
let method = 'commit'
100+
let target = eventName.toUpperCase()
101+
let msg = event
102+
if (this.format === 'json' && event.data) {
103+
msg = JSON.parse(event.data)
104+
if (msg.mutation) {
105+
target = [msg.namespace || '', msg.mutation].filter(e => !!e).join('/')
106+
} else if (msg.action) {
107+
method = 'dispatch'
108+
target = [msg.namespace || '', msg.action].filter(e => !!e).join('/')
109+
}
110+
}
111+
if (this.mutations) {
112+
target = this.mutations[target] || target
113+
}
114+
this.store[method](target, msg)
115+
}
116+
}

src/components/notify/index.js

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* eslint-disable*/
2+
3+
/* Author: https://github.com/nathantsoi/vue-native-websocket */
4+
import Notify from './notify.vue'
5+
import Observer from './Observer'
6+
import Emitter from './Emitter'
7+
8+
export default {
9+
install(Vue, connection, opts = {}) {
10+
if (typeof connection === 'object') {
11+
opts = connection
12+
connection = ''
13+
}
14+
let observer = null
15+
16+
opts.$setInstance = wsInstance => {
17+
Vue.prototype.$socket = wsInstance
18+
}
19+
Vue.prototype.$connect = (connectionUrl = connection, connectionOpts = opts) => {
20+
connectionOpts.$setInstance = opts.$setInstance
21+
observer = new Observer(connectionUrl, connectionOpts)
22+
Vue.prototype.$socket = observer.WebSocket
23+
}
24+
25+
Vue.prototype.$disconnect = () => {
26+
if (observer && observer.reconnection) {
27+
observer.reconnection = false
28+
}
29+
if (Vue.prototype.$socket) {
30+
Vue.prototype.$socket.close()
31+
delete Vue.prototype.$socket
32+
}
33+
}
34+
const hasProxy = typeof Proxy !== 'undefined' && typeof Proxy === 'function' && /native code/.test(Proxy.toString())
35+
Vue.component('LinNotify', Notify)
36+
Vue.mixin({
37+
created() {
38+
const vm = this
39+
const { sockets } = this.$options
40+
41+
if (hasProxy) {
42+
this.$options.sockets = new Proxy(
43+
{},
44+
{
45+
set(target, key, value) {
46+
Emitter.addListener(key, value, vm)
47+
target[key] = value
48+
return true
49+
},
50+
deleteProperty(target, key) {
51+
Emitter.removeListener(key, vm.$options.sockets[key], vm)
52+
delete target.key
53+
return true
54+
},
55+
},
56+
)
57+
if (sockets) {
58+
Object.keys(sockets).forEach(key => {
59+
this.$options.sockets[key] = sockets[key]
60+
})
61+
}
62+
} else {
63+
Object.seal(this.$options.sockets)
64+
65+
// if !hasProxy need addListener
66+
if (sockets) {
67+
Object.keys(sockets).forEach(key => {
68+
Emitter.addListener(key, sockets[key], vm)
69+
})
70+
}
71+
}
72+
},
73+
beforeDestroy() {
74+
if (hasProxy) {
75+
const { sockets } = this.$options
76+
77+
if (sockets) {
78+
Object.keys(sockets).forEach(key => {
79+
delete this.$options.sockets[key]
80+
})
81+
}
82+
}
83+
},
84+
})
85+
},
86+
}

0 commit comments

Comments
 (0)