1. add login action
2. add user info getter
3. add top menu ui
This commit is contained in:
Jay 2018-08-20 00:31:11 +08:00
parent b8a9e43033
commit e2de8f5d85
13 changed files with 261 additions and 14 deletions

9
jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"moduleResolution": "classic",
"baseUrl": ".",
"paths": {
"@": ["src/*"]
}
}
}

View File

@ -2,15 +2,18 @@
<div id="app">
<router-view/>
<Loading />
<Dialog />
</div>
</template>
<script>
import Loading from '@/components/Loading'
import Dialog from '@/components/Dialog'
export default {
name: 'root-view',
components: {
Loading
Loading,
Dialog
}
}
</script>

View File

@ -0,0 +1,14 @@
<template>
<sui-container>
</sui-container>
</template>
<script>
export default {
name: 'ChannelList',
data () {
return {}
}
}
</script>

View File

@ -0,0 +1,36 @@
<template>
<div class='cp-div'>
<TopMenu />
<router-view />
</div>
</template>
<style lang="less" scoped>
.cp-div {
padding-top: 60px;
}
</style>
<script>
import TopMenu from './topMenu'
import {mapActions} from 'vuex'
export default {
name: 'ControlPanel',
components: {
TopMenu
},
data () {
return {}
},
created () {
this.checkSession((flag) => {
if (flag === false) {
this.$router.replace('/')
}
})
},
methods: {
...mapActions(['checkSession'])
}
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<sui-menu fluid borderless fixed='top'>
<sui-container>
<sui-menu-menu position='right'>
<sui-menu-item>User: {{userInfo.name}}</sui-menu-item>
</sui-menu-menu>
</sui-container>
</sui-menu>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: 'TopMenu',
data () {
return {}
},
computed: {
...mapGetters(['userInfo'])
}
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<sui-modal v-model="openModal">
<sui-modal-content>
{{msg}}
</sui-modal-content>
<sui-modal-actions>
<sui-button @click.native="closeDialog">OK</sui-button>
</sui-modal-actions>
</sui-modal>
</template>
<script>
import {mapGetters, mapMutations} from 'vuex'
export default {
name: 'Dialog',
data () {
return {}
},
methods: {
...mapMutations(['removeDialog']),
closeDialog: function () {
if (typeof this.dialogMsg.act === 'function') this.dialogMsg.act()
this.removeDialog()
}
},
computed: {
...mapGetters(['dialogMsg']),
msg: function () {
if (this.dialogMsg === null) return ''
return this.dialogMsg.msg
},
openModal: function () {
if (this.dialogMsg === null) return false
return true
}
}
}
</script>

View File

@ -8,7 +8,7 @@
<input placeholder="Account" v-model="account">
</sui-form-field>
<sui-form-field>
<input placeholder="Password" v-model="password">
<input placeholder="Password" type="password" v-model="password">
</sui-form-field>
<sui-button type="submit" fluid color='teal'>Login</sui-button>
<sui-button type="button" @click="go_oauth" class="twitch-button" fluid color='violet'>Login with Twitch</sui-button>
@ -49,18 +49,22 @@ export default {
this.checkSession((flag) => {
// check login session
// true === isLogin
console.log(flag)
if (flag === true) {
this.$router.push('/cp')
}
})
},
methods: {
...mapMutations(['toggleLoading']),
...mapActions(['checkSession']),
...mapActions(['checkSession', 'sendLogin']),
login: function () {
console.log('login submit')
this.toggleLoading(1)
setTimeout(() => {
this.toggleLoading(0)
}, 1000)
this.sendLogin({
account: this.account,
password: this.password
}, () => {
this.$router.push('/cp')
})
},
go_oauth: function () {
window.location.href = apiUrl.replace(/\/$/, '') + '/twitch/login?tourl=' + encodeURIComponent(window.location.href)

View File

@ -2,6 +2,8 @@ import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
import ControlPanel from '@/components/ControlPanel'
import ChannelList from '@/components/ControlPanel/channelList'
Vue.use(Router)
@ -13,6 +15,23 @@ export default new Router({
path: '/',
name: 'Login',
component: Login
},
{
path: '/cp',
name: 'ControlPanel',
component: ControlPanel,
children: [
{
path: 'channels',
alias: '',
name: 'ChannelList',
component: ChannelList
},
{
path: '*',
redirect: 'channels'
}
]
}
]
})

View File

@ -1,5 +1,5 @@
import axios from 'axios'
import {apiUrl} from '@/tools'
import {apiUrl, chkObject} from '@/tools'
const client = axios.create({
baseURL: apiUrl,
@ -11,15 +11,52 @@ export default {
commit('toggleLoading', true)
let flag = false
try {
await client({
let result = await client({
method: 'get',
url: '/api/session'
})
if ('data' in result) {
commit('setUserInfo', {
name: result.data.user.name || '',
type: result.data.user.type || ''
})
}
flag = true
} catch (err) {
flag = false
}
if (typeof cb === 'function') cb(flag)
commit('toggleLoading', false)
},
async sendLogin ({commit}, {account, password}, cb = null) {
let chk = chkObject.bind({body: {account, password}})
if (!chk('account', 'string') || !chk('password', 'string')) {
commit('addDialog', {
msg: '帳號密碼不能留空'
})
return
}
let data = {
account,
password
}
commit('toggleLoading', true)
try {
await client({
method: 'post',
url: '/api/login',
data
})
if (typeof cb === 'function') cb()
} catch (err) {
let msg = ''
if ('response' in err && 'data' in err.response && 'message' in err.response.data) {
msg = err.response.data.message
} else {
msg = 'unknown error'
}
commit('addDialog', {msg})
}
commit('toggleLoading', false)
}
}

View File

@ -1 +1,3 @@
export const getLoading = state => !!state.loading
export const dialogMsg = state => state.dialog[0] || null
export const userInfo = state => state.user

View File

@ -6,10 +6,17 @@ import * as getters from './getters'
Vue.use(Vuex)
const state = {
loading: false,
dialog: [],
user: {
name: '',
type: ''
}
}
export default new Vuex.Store({
state: {
loading: false
},
state,
mutations,
getters,
actions

View File

@ -1,5 +1,23 @@
export default {
toggleLoading (state, loading = null) {
state.loading = !!loading
},
setUserInfo (state, {name, type}) {
if (typeof name !== 'string' || name.trim().length === 0) return
if (typeof type !== 'string' || type.trim().length === 0) return
name = name.trim()
type = type.trim()
state.user = {
...state.user,
name,
type
}
},
addDialog (state, {msg, act = null}) {
if (typeof msg !== 'string' || msg.trim().length === 0) return
state.dialog = [...state.dialog, {msg, act}]
},
removeDialog (state) {
state.dialog = state.dialog.slice(1)
}
}

View File

@ -1 +1,39 @@
export const apiUrl = 'https://bot.trj.tw'
export const apiUrl = 'https://bot.trj.tw'
export const chkObject = function (key = '', type = '', empty = false) {
const uuidChk = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
if (!(key in this.body)) return false
switch (type) {
case 'string':
if (typeof this.body[key] !== 'string' || (!empty && !this.body[key].trim())) return false
break
case 'number':
if (!isFinite(this.body[key])) return false
break
case 'boolean':
if (typeof this.body[key] !== 'boolean') return false
break
case 'array':
if (!Array.isArray(this.body[key]) || (!empty && this.body[key].length === 0)) return false
break
case 'uuid':
if (typeof this.body[key] !== 'string') return false
if (!empty && this.body[key] === '') return false
if (!empty && !uuidChk.test(this.body[key])) return false
break
case 'object':
if (typeof this.body[key] !== 'object') return false
try {
let str = JSON.stringify(this.body[key])
JSON.parse(str)
} catch (err) {
return false
}
break
default:
return false
}
return true
}