Merge branch 'feature/update-login-process' into 'master'
Update login process Closes #11 See merge request pleroma/admin-fe!9
This commit is contained in:
commit
fea370c2a8
25 changed files with 469 additions and 288 deletions
|
@ -1,5 +1,4 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"dev"',
|
||||
BASE_API: '"http://localhost:4000"'
|
||||
ENV_CONFIG: '"dev"'
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
"eslint-loader": "2.0.0",
|
||||
"eslint-plugin-vue": "4.7.1",
|
||||
"file-loader": "1.1.11",
|
||||
"flush-promises": "^1.0.2",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"hash-sum": "1.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
|
|
59
src/api/__mocks__/login.js
Normal file
59
src/api/__mocks__/login.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const users = [
|
||||
{ username: 'bob', password: '123456' }
|
||||
]
|
||||
|
||||
export async function loginByUsername(username, password) {
|
||||
const user = users.find(user => user.username === username)
|
||||
const verifyPassword = user.password === password
|
||||
const data = {
|
||||
'token_type': 'Bearer',
|
||||
'scope': 'read write follow',
|
||||
'refresh_token': 'foo123',
|
||||
'me': 'bob',
|
||||
'expires_in': 600,
|
||||
'access_token': 'bar123'
|
||||
}
|
||||
|
||||
return verifyPassword
|
||||
? Promise.resolve({ data })
|
||||
: Promise.reject({ message: 'Invalid credentials' })
|
||||
}
|
||||
|
||||
export function getUserInfo(token, authHost) {
|
||||
const userInfo = {
|
||||
'name_html': 'bob',
|
||||
'background_image': null,
|
||||
'friends_count': 0,
|
||||
'description_html': '',
|
||||
'followers_count': 0,
|
||||
'locked': false,
|
||||
'follows_you': true,
|
||||
'statusnet_blocking': false,
|
||||
'statusnet_profile_url': '',
|
||||
'following': true,
|
||||
'id': '10',
|
||||
'is_local': true,
|
||||
'profile_image_url': '',
|
||||
'role': 'admin',
|
||||
'profile_image_url_profile_size': '',
|
||||
'rights': { 'admin': true, 'delete_others_notice': true },
|
||||
'token': 'foo123456',
|
||||
'no_rich_text': false,
|
||||
'statuses_count': 0,
|
||||
'cover_photo': '',
|
||||
'hide_follows': false,
|
||||
'pleroma': { 'confirmation_pending': false, 'deactivated': false, 'tags': ['force_nsfw'] },
|
||||
'profile_image_url_original': '',
|
||||
'created_at': 'Fri Mar 01 15:15:19 +0000 2019',
|
||||
'fields': [],
|
||||
'name': 'bob',
|
||||
'description': '',
|
||||
'favourites_count': 0,
|
||||
'default_scope': 'public',
|
||||
'profile_image_url_https': '',
|
||||
'hide_followers': false,
|
||||
'show_role': true,
|
||||
'screen_name': 'bob' }
|
||||
|
||||
return Promise.resolve({ data: userInfo })
|
||||
}
|
|
@ -4,7 +4,7 @@ const users = [
|
|||
{ deactivated: true, id: 'abc', nickname: 'john', local: true, roles: { admin: false, moderator: false }, tags: ['strip_media'] }
|
||||
]
|
||||
|
||||
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
|
||||
export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) {
|
||||
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
|
||||
return Promise.resolve({ data: {
|
||||
users: filteredUsers,
|
||||
|
@ -13,12 +13,12 @@ export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
|
|||
}})
|
||||
}
|
||||
|
||||
export async function toggleUserActivation(nickname, token) {
|
||||
export async function toggleUserActivation(nickname, authHost, token) {
|
||||
const response = users.find(user => user.nickname === nickname)
|
||||
return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }})
|
||||
}
|
||||
|
||||
export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
|
||||
export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) {
|
||||
const filteredUsers = showLocalUsersOnly ? users.filter(user => user.local) : users
|
||||
const response = filteredUsers.filter(user => user.nickname === query)
|
||||
return Promise.resolve({ data: {
|
||||
|
@ -28,28 +28,28 @@ export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
|
|||
}})
|
||||
}
|
||||
|
||||
export async function addRight(nickname, right, token) {
|
||||
export async function addRight(nickname, right, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
{ [`is_${right}`]: true }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteRight(nickname, right, token) {
|
||||
export async function deleteRight(nickname, right, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
{ [`is_${right}`]: false }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteUser(nickname, token) {
|
||||
export async function deleteUser(nickname, authHost, token) {
|
||||
return Promise.resolve({ data:
|
||||
nickname
|
||||
})
|
||||
}
|
||||
|
||||
export async function tagUser(nickname, tag, token) {
|
||||
export async function tagUser(nickname, tag, authHost, token) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
export async function untagUser(nickname, tag, token) {
|
||||
export async function untagUser(nickname, tag, authHost, token) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import request from '@/utils/request'
|
||||
import { baseName } from './utils'
|
||||
|
||||
export async function loginByUsername(username, password) {
|
||||
export async function loginByUsername(username, password, authHost) {
|
||||
const appsRequest = await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: '/api/v1/apps',
|
||||
method: 'post',
|
||||
data: {
|
||||
|
@ -14,6 +16,7 @@ export async function loginByUsername(username, password) {
|
|||
const app = appsRequest.data
|
||||
|
||||
return request({
|
||||
baseURL: baseName(authHost),
|
||||
url: '/oauth/token',
|
||||
method: 'post',
|
||||
data: {
|
||||
|
@ -26,8 +29,9 @@ export async function loginByUsername(username, password) {
|
|||
})
|
||||
}
|
||||
|
||||
export function getUserInfo(token) {
|
||||
export function getUserInfo(token, authHost) {
|
||||
return request({
|
||||
baseURL: baseName(authHost),
|
||||
url: '/api/account/verify_credentials',
|
||||
method: 'post',
|
||||
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
|
||||
|
|
|
@ -1,56 +1,64 @@
|
|||
import request from '@/utils/request'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { baseName } from './utils'
|
||||
|
||||
export async function fetchUsers(showLocalUsersOnly, token, page = 1) {
|
||||
export async function fetchUsers(showLocalUsersOnly, authHost, token, page = 1) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users?page=${page}&local_only=${showLocalUsersOnly}`,
|
||||
method: 'get',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function toggleUserActivation(nickname, token) {
|
||||
export async function toggleUserActivation(nickname, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
|
||||
method: 'patch',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function searchUsers(query, showLocalUsersOnly, token, page = 1) {
|
||||
export async function searchUsers(query, showLocalUsersOnly, authHost, token, page = 1) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/users?query=${query}&page=${page}&local_only=${showLocalUsersOnly}`,
|
||||
method: 'get',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function addRight(nickname, right, token) {
|
||||
export async function addRight(nickname, right, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/permission_group/${nickname}/${right}`,
|
||||
method: 'post',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteRight(nickname, right, token) {
|
||||
export async function deleteRight(nickname, right, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/permission_group/${nickname}/${right}`,
|
||||
method: 'delete',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteUser(nickname, token) {
|
||||
export async function deleteUser(nickname, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: `/api/pleroma/admin/user.json?nickname=${nickname}`,
|
||||
method: 'delete',
|
||||
headers: authHeaders(token)
|
||||
})
|
||||
}
|
||||
|
||||
export async function tagUser(nickname, tag, token) {
|
||||
export async function tagUser(nickname, tag, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: '/api/pleroma/admin/users/tag',
|
||||
method: 'put',
|
||||
headers: authHeaders(token),
|
||||
|
@ -58,8 +66,9 @@ export async function tagUser(nickname, tag, token) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function untagUser(nickname, tag, token) {
|
||||
export async function untagUser(nickname, tag, authHost, token) {
|
||||
return await request({
|
||||
baseURL: baseName(authHost),
|
||||
url: '/api/pleroma/admin/users/tag',
|
||||
method: 'delete',
|
||||
headers: authHeaders(token),
|
||||
|
|
5
src/api/utils.js
Normal file
5
src/api/utils.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const isLocalhost = (instanceName) =>
|
||||
instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:')
|
||||
|
||||
export const baseName = (instanceName) =>
|
||||
isLocalhost(instanceName) ? `http://${instanceName}` : `https://${instanceName}`
|
|
@ -76,8 +76,9 @@ export default {
|
|||
login: {
|
||||
title: 'Login Form',
|
||||
logIn: 'Log in',
|
||||
username: 'Username',
|
||||
username: 'Username@Host',
|
||||
password: 'Password',
|
||||
errorMessage: 'Username must contain username and host, e.g. john@pleroma.social',
|
||||
any: 'any',
|
||||
thirdparty: 'Or connect with',
|
||||
thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !'
|
||||
|
|
|
@ -16,7 +16,7 @@ function hasPermission(roles, permissionRoles) {
|
|||
|
||||
const whiteList = ['/login', '/auth-redirect']// no redirect whitelist
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
export const beforeEachRoute = (to, from, next) => {
|
||||
NProgress.start() // start progress bar
|
||||
if (getToken()) { // determine if there has token
|
||||
/* has token*/
|
||||
|
@ -24,12 +24,12 @@ router.beforeEach((to, from, next) => {
|
|||
next({ path: '/' })
|
||||
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
|
||||
} else {
|
||||
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
|
||||
store.dispatch('GetUserInfo').then(res => { // 拉取user_info
|
||||
if (store.getters.roles.length === 0) {
|
||||
store.dispatch('GetUserInfo').then(res => {
|
||||
const roles = res.data.rights.admin ? ['admin'] : []
|
||||
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
|
||||
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
|
||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
|
||||
store.dispatch('GenerateRoutes', { roles }).then(() => {
|
||||
router.addRoutes(store.getters.addRouters)
|
||||
next({ ...to, replace: true })
|
||||
})
|
||||
}).catch((err) => {
|
||||
store.dispatch('FedLogOut').then(() => {
|
||||
|
@ -38,25 +38,24 @@ router.beforeEach((to, from, next) => {
|
|||
})
|
||||
})
|
||||
} else {
|
||||
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
|
||||
if (hasPermission(store.getters.roles, to.meta.roles)) {
|
||||
next()
|
||||
} else {
|
||||
next({ path: '/401', replace: true, query: { noGoBack: true }})
|
||||
}
|
||||
// 可删 ↑
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* has no token*/
|
||||
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
next(`/login?redirect=${to.path}`)
|
||||
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
router.beforeEach(beforeEachRoute)
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done() // finish progress bar
|
||||
|
|
|
@ -15,6 +15,7 @@ const getters = {
|
|||
permission_routers: state => state.permission.routers,
|
||||
addRouters: state => state.permission.addRouters,
|
||||
errorLogs: state => state.errorLog.logs,
|
||||
users: state => state.users.fetchedUsers
|
||||
users: state => state.users.fetchedUsers,
|
||||
authHost: state => state.user.authHost
|
||||
}
|
||||
export default getters
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { loginByUsername, getUserInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import { getToken, setToken, removeToken, getAuthHost, setAuthHost, removeAuthHost } from '@/utils/auth'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
|
@ -8,6 +8,7 @@ const user = {
|
|||
status: '',
|
||||
code: '',
|
||||
token: getToken(),
|
||||
authHost: getAuthHost(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
|
@ -44,19 +45,24 @@ const user = {
|
|||
},
|
||||
SET_ID: (state, id) => {
|
||||
state.id = id
|
||||
},
|
||||
SET_AUTH_HOST: (state, authHost) => {
|
||||
state.authHost = authHost
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
LoginByUsername({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
LoginByUsername({ commit, dispatch }, { username, authHost, password }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
loginByUsername(username, userInfo.password).then(response => {
|
||||
loginByUsername(username, password, authHost).then(response => {
|
||||
const data = response.data
|
||||
commit('SET_TOKEN', data.access_token)
|
||||
commit('SET_AUTH_HOST', authHost)
|
||||
setToken(data.access_token)
|
||||
setAuthHost(authHost)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
dispatch('addErrorLog', { message: error.message })
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
@ -64,7 +70,7 @@ const user = {
|
|||
|
||||
GetUserInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo(state.token).then(response => {
|
||||
getUserInfo(state.token, state.authHost).then(response => {
|
||||
const data = response.data
|
||||
|
||||
if (!data) {
|
||||
|
@ -91,11 +97,13 @@ const user = {
|
|||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
removeToken()
|
||||
removeAuthHost()
|
||||
},
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
removeAuthHost()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -43,14 +43,14 @@ const users = {
|
|||
},
|
||||
actions: {
|
||||
async FetchUsers({ commit, state, getters }, { page }) {
|
||||
const response = await fetchUsers(state.showLocalUsersOnly, getters.token, page)
|
||||
const response = await fetchUsers(state.showLocalUsersOnly, getters.authHost, getters.token, page)
|
||||
|
||||
commit('SET_LOADING', true)
|
||||
|
||||
loadUsers(commit, page, response.data)
|
||||
},
|
||||
async ToggleUserActivation({ commit, getters }, nickname) {
|
||||
const response = await toggleUserActivation(nickname, getters.token)
|
||||
const response = await toggleUserActivation(nickname, getters.authHost, getters.token)
|
||||
|
||||
commit('SWAP_USER', response.data)
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ const users = {
|
|||
commit('SET_LOADING', true)
|
||||
commit('SET_SEARCH_QUERY', query)
|
||||
|
||||
const response = await searchUsers(query, state.showLocalUsersOnly, getters.token, page)
|
||||
const response = await searchUsers(query, state.showLocalUsersOnly, getters.authHost, getters.token, page)
|
||||
|
||||
loadUsers(commit, page, response.data)
|
||||
}
|
||||
|
@ -73,24 +73,24 @@ const users = {
|
|||
},
|
||||
async ToggleRight({ commit, getters }, { user, right }) {
|
||||
user.roles[right]
|
||||
? await deleteRight(user.nickname, right, getters.token)
|
||||
: await addRight(user.nickname, right, getters.token)
|
||||
? await deleteRight(user.nickname, right, getters.authHost, getters.token)
|
||||
: await addRight(user.nickname, right, getters.authHost, getters.token)
|
||||
|
||||
const updatedUser = { ...user, roles: { ...user.roles, [right]: !user.roles[right] }}
|
||||
commit('SWAP_USER', updatedUser)
|
||||
},
|
||||
async DeleteUser({ commit, getters }, user) {
|
||||
await deleteUser(user.nickname, getters.token)
|
||||
await deleteUser(user.nickname, getters.authHost, getters.token)
|
||||
const updatedUser = { ...user, deactivated: true }
|
||||
commit('SWAP_USER', updatedUser)
|
||||
},
|
||||
async ToggleTag({ commit, getters }, { user, tag }) {
|
||||
if (user.tags.includes(tag)) {
|
||||
await untagUser(user.nickname, tag, getters.token)
|
||||
await untagUser(user.nickname, tag, getters.authHost, getters.token)
|
||||
const updatedUser = { ...user, tags: user.tags.filter(userTag => userTag !== tag) }
|
||||
commit('SWAP_USER', updatedUser)
|
||||
} else {
|
||||
await tagUser(user.nickname, tag, getters.token)
|
||||
await tagUser(user.nickname, tag, getters.authHost, getters.token)
|
||||
const updatedUser = { ...user, tags: [...user.tags, tag] }
|
||||
commit('SWAP_USER', updatedUser)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
const AuthHostKey = 'Auth-Host'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
|
@ -13,3 +14,15 @@ export function setToken(token) {
|
|||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
|
||||
export function getAuthHost() {
|
||||
return Cookies.get(AuthHostKey)
|
||||
}
|
||||
|
||||
export function setAuthHost(token) {
|
||||
return Cookies.set(AuthHostKey, token)
|
||||
}
|
||||
|
||||
export function removeAuthHost() {
|
||||
return Cookies.remove(AuthHostKey)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Message } from 'element-ui'
|
|||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: process.env.BASE_API, // api 的 base_url
|
||||
timeout: 5000 // request timeout
|
||||
})
|
||||
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="box-card" style="margin-top:40px;">
|
||||
<div slot="header" class="clearfix">
|
||||
<svg-icon icon-class="international" />
|
||||
<span style="margin-left:10px;">{{ $t('i18nView.title') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-radio-group v-model="lang" size="small">
|
||||
<el-radio label="zh" border>简体中文</el-radio>
|
||||
<el-radio label="en" border>English</el-radio>
|
||||
<el-radio label="es" border>Español</el-radio>
|
||||
</el-radio-group>
|
||||
<el-tag style="margin-top:15px;display:block;" type="info">{{ $t('i18nView.note') }}</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20" style="margin:100px 15px 50px;">
|
||||
<el-col :span="12" :xs="24">
|
||||
<div class="block">
|
||||
<el-date-picker v-model="date" :placeholder="$t('i18nView.datePlaceholder')" type="date"/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-select v-model="value" :placeholder="$t('i18nView.selectPlaceholder')">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-button class="item-btn" size="small">{{ $t('i18nView.default') }}</el-button>
|
||||
<el-button class="item-btn" size="small" type="primary">{{ $t('i18nView.primary') }}</el-button>
|
||||
<el-button class="item-btn" size="small" type="success">{{ $t('i18nView.success') }}</el-button>
|
||||
<el-button class="item-btn" size="small" type="info">{{ $t('i18nView.info') }}</el-button>
|
||||
<el-button class="item-btn" size="small" type="warning">{{ $t('i18nView.warning') }}</el-button>
|
||||
<el-button class="item-btn" size="small" type="danger">{{ $t('i18nView.danger') }}</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12" :xs="24">
|
||||
<el-table :data="tableData" fit highlight-current-row border style="width: 100%">
|
||||
<el-table-column :label="$t('i18nView.tableName')" prop="name" width="100" align="center"/>
|
||||
<el-table-column :label="$t('i18nView.tableDate')" prop="date" width="120" align="center"/>
|
||||
<el-table-column :label="$t('i18nView.tableAddress')" prop="address"/>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import local from './local'
|
||||
const viewName = 'i18nView'
|
||||
|
||||
export default {
|
||||
name: 'I18n',
|
||||
data: function() {
|
||||
return {
|
||||
date: '',
|
||||
tableData: [{
|
||||
date: '2016-05-03',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-02',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-04',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-01',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
}],
|
||||
options: [],
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lang: {
|
||||
get() {
|
||||
return this.$store.state.app.language
|
||||
},
|
||||
set(lang) {
|
||||
this.$i18n.locale = lang
|
||||
this.$store.dispatch('setLanguage', lang)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lang() {
|
||||
this.setOptions()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.$i18n.getLocaleMessage('en')[viewName]) {
|
||||
this.$i18n.mergeLocaleMessage('en', local.en)
|
||||
this.$i18n.mergeLocaleMessage('zh', local.zh)
|
||||
this.$i18n.mergeLocaleMessage('es', local.es)
|
||||
}
|
||||
this.setOptions() // set default select options
|
||||
},
|
||||
methods: {
|
||||
setOptions() {
|
||||
this.options = [
|
||||
{
|
||||
value: '1',
|
||||
label: this.$t('i18nView.one')
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: this.$t('i18nView.two')
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: this.$t('i18nView.three')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box-card {
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.item-btn{
|
||||
margin-bottom: 15px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
.block {
|
||||
padding: 25px;
|
||||
}
|
||||
</style>
|
|
@ -1,63 +0,0 @@
|
|||
|
||||
export default {
|
||||
zh: {
|
||||
i18nView: {
|
||||
title: '切换语言',
|
||||
note: '本项目国际化基于 vue-i18n',
|
||||
datePlaceholder: '请选择日期',
|
||||
selectPlaceholder: '请选择',
|
||||
tableDate: '日期',
|
||||
tableName: '姓名',
|
||||
tableAddress: '地址',
|
||||
default: '默认按钮',
|
||||
primary: '主要按钮',
|
||||
success: '成功按钮',
|
||||
info: '信息按钮',
|
||||
warning: '警告按钮',
|
||||
danger: '危险按钮',
|
||||
one: '一',
|
||||
two: '二',
|
||||
three: '三'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
i18nView: {
|
||||
title: 'Switch Language',
|
||||
note: 'The internationalization of this project is based on vue-i18n',
|
||||
datePlaceholder: 'Pick a day',
|
||||
selectPlaceholder: 'Select',
|
||||
tableDate: 'tableDate',
|
||||
tableName: 'tableName',
|
||||
tableAddress: 'tableAddress',
|
||||
default: 'default:',
|
||||
primary: 'primary',
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warning: 'warning',
|
||||
danger: 'danger',
|
||||
one: 'One',
|
||||
two: 'Two',
|
||||
three: 'Three'
|
||||
}
|
||||
},
|
||||
es: {
|
||||
i18nView: {
|
||||
title: 'Switch Language',
|
||||
note: 'The internationalization of this project is based on vue-i18n',
|
||||
datePlaceholder: 'Pick a day',
|
||||
selectPlaceholder: 'Select',
|
||||
tableDate: 'tableDate',
|
||||
tableName: 'tableName',
|
||||
tableAddress: 'tableAddress',
|
||||
default: 'default:',
|
||||
primary: 'primary',
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warning: 'warning',
|
||||
danger: 'danger',
|
||||
one: 'One',
|
||||
two: 'Two',
|
||||
three: 'Three'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,8 +45,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { Message } from 'element-ui'
|
||||
import SvgIcon from '@/components/SvgIcon'
|
||||
import i18n from '@/lang'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: { 'svg-icon': SvgIcon },
|
||||
data: function() {
|
||||
return {
|
||||
loginForm: {
|
||||
|
@ -77,12 +82,35 @@ export default {
|
|||
},
|
||||
handleLogin() {
|
||||
this.loading = true
|
||||
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
|
||||
if (this.checkUsername()) {
|
||||
const loginData = this.getLoginData()
|
||||
this.$store.dispatch('LoginByUsername', loginData).then(() => {
|
||||
this.loading = false
|
||||
this.$router.push({ path: this.redirect || '/users/index' })
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
Message({
|
||||
message: i18n.t('login.errorMessage'),
|
||||
type: 'error',
|
||||
duration: 7000
|
||||
})
|
||||
this.$store.dispatch('addErrorLog', { message: i18n.t('login.errorMessage') })
|
||||
this.loading = false
|
||||
this.$router.push({ path: this.redirect || '/users/index' })
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
checkUsername() {
|
||||
return this.loginForm.username.includes('@')
|
||||
},
|
||||
getLoginData() {
|
||||
const [username, authHost] = this.loginForm.username.split('@')
|
||||
|
||||
return {
|
||||
username: username.trim(),
|
||||
authHost: authHost.trim(),
|
||||
password: this.loginForm.password
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
47
test/views/layout/index.test.js
Normal file
47
test/views/layout/index.test.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import Vuex from 'vuex'
|
||||
import VueRouter from 'vue-router'
|
||||
import { mount, createLocalVue, config } from '@vue/test-utils'
|
||||
import Element from 'element-ui'
|
||||
import Layout from '@/views/layout/Layout'
|
||||
import storeConfig from './store.conf'
|
||||
import routerConfig from './router.conf'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { beforeEachRoute } from '@/permission'
|
||||
|
||||
config.mocks["$t"] = () => {}
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(VueRouter)
|
||||
localVue.use(Element)
|
||||
|
||||
describe('Log out', () => {
|
||||
let store
|
||||
let router
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Vuex.Store(cloneDeep(storeConfig))
|
||||
router = new VueRouter(cloneDeep(routerConfig))
|
||||
router.beforeEach(beforeEachRoute)
|
||||
window.location.reload = jest.fn()
|
||||
})
|
||||
|
||||
it('logs out user', async (done) => {
|
||||
const wrapper = mount(Layout, {
|
||||
store,
|
||||
router,
|
||||
localVue
|
||||
})
|
||||
|
||||
const logoutButton = wrapper.find('span')
|
||||
expect(store.state.user.roles.length).toBe(1)
|
||||
expect(store.state.user.token).toBe('foo123')
|
||||
|
||||
logoutButton.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(store.state.user.roles.length).toBe(0)
|
||||
expect(store.state.user.token).toBe('')
|
||||
done()
|
||||
})
|
||||
})
|
40
test/views/layout/router.conf.js
Normal file
40
test/views/layout/router.conf.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import Layout from '@/views/layout/Layout'
|
||||
|
||||
export const constantRouterMap = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path*',
|
||||
component: () => import('@/views/redirect/index')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/index'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/auth-redirect',
|
||||
component: () => import('@/views/login/authredirect'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/errorPage/404'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/errorPage/401'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: '/users/index'
|
||||
}
|
||||
]
|
34
test/views/layout/store.conf.js
Normal file
34
test/views/layout/store.conf.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import app from '@/store/modules/app'
|
||||
import errorLog from '@/store/modules/errorLog'
|
||||
import permission from '@/store/modules/permission'
|
||||
import tagsView from '@/store/modules/tagsView'
|
||||
import user from '@/store/modules/user'
|
||||
import users from '@/store/modules/users'
|
||||
import getters from '@/store/getters'
|
||||
|
||||
export default {
|
||||
modules: {
|
||||
app,
|
||||
errorLog,
|
||||
permission,
|
||||
tagsView,
|
||||
user: {
|
||||
...user,
|
||||
state: {
|
||||
id:"10",
|
||||
status: '',
|
||||
code: '',
|
||||
token: "foo123",
|
||||
authHost:"apple",
|
||||
name:"bob",
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
roles: ['admin'],
|
||||
setting: {
|
||||
articlePlatform: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getters
|
||||
}
|
106
test/views/login/index.test.js
Normal file
106
test/views/login/index.test.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import Vuex from 'vuex'
|
||||
import VueRouter from 'vue-router'
|
||||
import { mount, createLocalVue, config } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
import Element from 'element-ui'
|
||||
import Login from '@/views/login/index'
|
||||
import storeConfig from './store.conf'
|
||||
import routerConfig from './router.conf'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { beforeEachRoute } from '@/permission'
|
||||
|
||||
config.mocks["$t"] = () => {}
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(VueRouter)
|
||||
localVue.use(Element)
|
||||
|
||||
jest.mock('@/api/login')
|
||||
|
||||
describe('Login', () => {
|
||||
let store
|
||||
let router
|
||||
|
||||
const usernameInput = 'input[name="username"]'
|
||||
const passwordInput = 'input[name="password"]'
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Vuex.Store(cloneDeep(storeConfig))
|
||||
router = new VueRouter(cloneDeep(routerConfig))
|
||||
router.beforeEach(beforeEachRoute)
|
||||
})
|
||||
|
||||
it('throws error if username does not contain authHost', () => {
|
||||
const wrapper = mount(Login, {
|
||||
store,
|
||||
router,
|
||||
localVue
|
||||
})
|
||||
|
||||
const errorLog = store.state.errorLog.logs
|
||||
expect(errorLog.length).toBe(0)
|
||||
const submitButton = wrapper.find('button')
|
||||
|
||||
wrapper.find(usernameInput).element.value = 'bob'
|
||||
wrapper.find(usernameInput).trigger('input')
|
||||
wrapper.find(passwordInput).element.value = '1234'
|
||||
wrapper.find(passwordInput).trigger('input')
|
||||
submitButton.trigger('click')
|
||||
|
||||
const updatedErrorLog = store.state.errorLog.logs
|
||||
expect(updatedErrorLog.length).toBe(1)
|
||||
expect(updatedErrorLog[0].message).toEqual(
|
||||
'Username must contain username and host, e.g. john@pleroma.social'
|
||||
)
|
||||
})
|
||||
|
||||
it('throws error if password is incorrect', async (done) => {
|
||||
const wrapper = mount(Login, {
|
||||
store,
|
||||
router,
|
||||
localVue
|
||||
})
|
||||
|
||||
const errorLog = store.state.errorLog.logs
|
||||
expect(errorLog.length).toBe(0)
|
||||
const submitButton = wrapper.find('button')
|
||||
|
||||
wrapper.find(usernameInput).element.value = 'bob@apple'
|
||||
wrapper.find(usernameInput).trigger('input')
|
||||
wrapper.find(passwordInput).element.value = '1234'
|
||||
wrapper.find(passwordInput).trigger('input')
|
||||
submitButton.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const updatedErrorLog = store.state.errorLog.logs
|
||||
expect(updatedErrorLog.length).toBe(1)
|
||||
expect(updatedErrorLog[0].message).toEqual(
|
||||
'Invalid credentials'
|
||||
)
|
||||
done()
|
||||
})
|
||||
|
||||
it('logs user in', async (done) => {
|
||||
const wrapper = mount(Login, {
|
||||
store,
|
||||
router,
|
||||
localVue
|
||||
})
|
||||
|
||||
const errorLog = store.state.errorLog.logs
|
||||
const submitButton = wrapper.find('button')
|
||||
expect(wrapper.vm.$route.path).toBe('/login')
|
||||
|
||||
wrapper.find(usernameInput).element.value = 'bob@apple'
|
||||
wrapper.find(usernameInput).trigger('input')
|
||||
wrapper.find(passwordInput).element.value = '123456'
|
||||
wrapper.find(passwordInput).trigger('input')
|
||||
submitButton.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(errorLog.length).toBe(0)
|
||||
expect(wrapper.vm.$route.path).toBe('/')
|
||||
done()
|
||||
})
|
||||
})
|
40
test/views/login/router.conf.js
Normal file
40
test/views/login/router.conf.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import Layout from '@/views/layout/Layout'
|
||||
|
||||
export const constantRouterMap = [
|
||||
{
|
||||
path: '/redirect',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path*',
|
||||
component: () => import('@/views/redirect/index')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/index'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/auth-redirect',
|
||||
component: () => import('@/views/login/authredirect'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/errorPage/404'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
component: () => import('@/views/errorPage/401'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
redirect: '/users/index'
|
||||
}
|
||||
]
|
16
test/views/login/store.conf.js
Normal file
16
test/views/login/store.conf.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import app from '@/store/modules/app'
|
||||
import errorLog from '@/store/modules/errorLog'
|
||||
import user from '@/store/modules/user'
|
||||
import users from '@/store/modules/users'
|
||||
import getters from '@/store/getters'
|
||||
|
||||
export default {
|
||||
modules: {
|
||||
app,
|
||||
errorLog,
|
||||
user,
|
||||
users
|
||||
},
|
||||
getters
|
||||
}
|
||||
|
|
@ -1,28 +1,13 @@
|
|||
import getters from '@/store/getters'
|
||||
import app from '@/store/modules/app'
|
||||
import user from '@/store/modules/user'
|
||||
import users from '@/store/modules/users'
|
||||
import getters from '@/store/getters'
|
||||
|
||||
export default {
|
||||
modules: {
|
||||
app,
|
||||
users,
|
||||
user: {
|
||||
state: {
|
||||
user: '',
|
||||
id: '1',
|
||||
status: '',
|
||||
code: '',
|
||||
token: 'MmwSkMgBW6lAkWCFspIjX9icmLfqSCohSi-GReAZrQw',
|
||||
name: 'john',
|
||||
avatar: '',
|
||||
introduction: '',
|
||||
roles: ["admin"],
|
||||
setting: {
|
||||
articlePlatform: []
|
||||
}
|
||||
}, ...user
|
||||
}
|
||||
user,
|
||||
users
|
||||
},
|
||||
getters
|
||||
}
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -4181,6 +4181,11 @@ flat-cache@^1.2.1:
|
|||
rimraf "~2.6.2"
|
||||
write "^0.2.1"
|
||||
|
||||
flush-promises@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced"
|
||||
integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==
|
||||
|
||||
flush-write-stream@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
||||
|
@ -10164,18 +10169,6 @@ vue-jest@4.0.0-beta.2:
|
|||
source-map "^0.5.6"
|
||||
ts-jest "^23.10.5"
|
||||
|
||||
vue-jest@4.0.0-beta.2:
|
||||
version "4.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.0-beta.2.tgz#f2120ea9d24224aad3a100c2010b0760d47ee6fe"
|
||||
integrity sha512-SywBIciuIfqsCb8Eb9UQ02s06+NV8Ry8KnbyhAfnvnyFFulIuh7ujtga9eJYq720nCS4Hz4TpVtS4pD1ZbUILQ==
|
||||
dependencies:
|
||||
"@babel/plugin-transform-modules-commonjs" "^7.2.0"
|
||||
"@vue/component-compiler-utils" "^2.4.0"
|
||||
chalk "^2.1.0"
|
||||
extract-from-css "^0.4.4"
|
||||
source-map "^0.5.6"
|
||||
ts-jest "^23.10.5"
|
||||
|
||||
vue-loader@15.3.0:
|
||||
version "15.3.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.3.0.tgz#b474d10a4e93d934a78c147fc3e314b370e9fc54"
|
||||
|
|
Loading…
Reference in a new issue