mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
fix user
This commit is contained in:
Vendored
+1
-1
@@ -122,8 +122,8 @@ export interface NavLink extends NavLinkProps, Partial<AclProperties> {
|
|||||||
|
|
||||||
export interface NavMenu extends NavLink {
|
export interface NavMenu extends NavLink {
|
||||||
header: string
|
header: string
|
||||||
admin: boolean
|
|
||||||
description?: string
|
description?: string
|
||||||
|
permission?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 👉 Vertical nav group
|
// 👉 Vertical nav group
|
||||||
|
|||||||
+59
-9
@@ -18,18 +18,65 @@ const appMode = computed(() => {
|
|||||||
return localStorage.getItem('MP_APPMODE') != '0' && display.mdAndDown.value
|
return localStorage.getItem('MP_APPMODE') != '0' && display.mdAndDown.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从Vuex Store中获取superuser信息
|
// 是否超级用户
|
||||||
const superUser = store.state.auth.superUser
|
let superUser = store.state.auth.superUser
|
||||||
|
|
||||||
|
// 用户权限
|
||||||
|
const permissions = store.state.auth.permissions
|
||||||
|
|
||||||
|
// 检查是否有权限
|
||||||
|
function hasPermission(permission: string | null = null) {
|
||||||
|
if (!permission) return true
|
||||||
|
// permission是一个以.分隔的字符串,例如:'user.create'
|
||||||
|
const permissionList = permission.split('.')
|
||||||
|
let permissions_obj = permissions
|
||||||
|
for (const element of permissionList) {
|
||||||
|
if (!permissions_obj[element]) {
|
||||||
|
return false
|
||||||
|
} else if (typeof permissions_obj[element] === 'object') {
|
||||||
|
permissions_obj = permissions_obj[element]
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始菜单项
|
||||||
|
const startMenus = ref<NavMenu[]>([])
|
||||||
|
|
||||||
|
// 发现菜单项
|
||||||
|
const discoveryMenus = ref<NavMenu[]>([])
|
||||||
|
|
||||||
|
// 订阅菜单项
|
||||||
|
const subscribeMenus = ref<NavMenu[]>([])
|
||||||
|
|
||||||
|
// 整理菜单项
|
||||||
|
const organizeMenus = ref<NavMenu[]>([])
|
||||||
|
|
||||||
|
// 系统菜单项
|
||||||
|
const systemMenus = ref<NavMenu[]>([])
|
||||||
|
|
||||||
// 根据分类获取菜单列表
|
// 根据分类获取菜单列表
|
||||||
const getMenuList = (header: string) => {
|
const getMenuList = (header: string) => {
|
||||||
return SystemNavMenus.filter((item: NavMenu) => item.header === header && (!item.admin || superUser))
|
return SystemNavMenus.filter(
|
||||||
|
(item: NavMenu) => item.header === header && (superUser || hasPermission(item.permission)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
function goBack() {
|
function goBack() {
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取菜单列表
|
||||||
|
startMenus.value = getMenuList('开始')
|
||||||
|
discoveryMenus.value = getMenuList('发现')
|
||||||
|
subscribeMenus.value = getMenuList('订阅')
|
||||||
|
organizeMenus.value = getMenuList('整理')
|
||||||
|
systemMenus.value = getMenuList('系统')
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -61,36 +108,39 @@ function goBack() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #vertical-nav-content>
|
<template #vertical-nav-content>
|
||||||
<VerticalNavLink v-for="item in getMenuList('开始')" :item="item" />
|
<VerticalNavLink v-for="item in startMenus" :item="item" />
|
||||||
<!-- 👉 发现 -->
|
<!-- 👉 发现 -->
|
||||||
<VerticalNavSectionTitle
|
<VerticalNavSectionTitle
|
||||||
|
v-if="discoveryMenus.length > 0"
|
||||||
:item="{
|
:item="{
|
||||||
heading: '发现',
|
heading: '发现',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink v-for="item in getMenuList('发现')" :item="item" />
|
<VerticalNavLink v-for="item in discoveryMenus" :item="item" />
|
||||||
<!-- 👉 订阅 -->
|
<!-- 👉 订阅 -->
|
||||||
<VerticalNavSectionTitle
|
<VerticalNavSectionTitle
|
||||||
|
v-if="subscribeMenus.length > 0"
|
||||||
:item="{
|
:item="{
|
||||||
heading: '订阅',
|
heading: '订阅',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink v-for="item in getMenuList('订阅')" :item="item" />
|
<VerticalNavLink v-for="item in subscribeMenus" :item="item" />
|
||||||
<!-- 👉 整理 -->
|
<!-- 👉 整理 -->
|
||||||
<VerticalNavSectionTitle
|
<VerticalNavSectionTitle
|
||||||
|
v-if="organizeMenus.length > 0"
|
||||||
:item="{
|
:item="{
|
||||||
heading: '整理',
|
heading: '整理',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink v-for="item in getMenuList('整理')" :item="item" />
|
<VerticalNavLink v-for="item in organizeMenus" :item="item" />
|
||||||
<!-- 👉 系统 -->
|
<!-- 👉 系统 -->
|
||||||
<VerticalNavSectionTitle
|
<VerticalNavSectionTitle
|
||||||
v-if="superUser"
|
v-if="systemMenus.length > 0"
|
||||||
:item="{
|
:item="{
|
||||||
heading: '系统',
|
heading: '系统',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink v-for="item in getMenuList('系统')" :item="item" />
|
<VerticalNavLink v-for="item in systemMenus" :item="item" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #after-vertical-nav-items />
|
<template #after-vertical-nav-items />
|
||||||
@@ -111,11 +111,11 @@ watch(isCompactMode, value => {
|
|||||||
<VDivider class="my-2" />
|
<VDivider class="my-2" />
|
||||||
|
|
||||||
<!-- 👉 Profile -->
|
<!-- 👉 Profile -->
|
||||||
<VListItem v-if="superUser" link @click="router.push('/setting?tab=account')">
|
<VListItem link @click="router.push('/profile')">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon class="me-2" icon="mdi-account-outline" size="22" />
|
<VIcon class="me-2" icon="mdi-account-outline" size="22" />
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>设定</VListItemTitle>
|
<VListItemTitle>个人信息</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<!-- 👉 FAQ -->
|
<!-- 👉 FAQ -->
|
||||||
@@ -123,7 +123,7 @@ watch(isCompactMode, value => {
|
|||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon class="me-2" icon="mdi-help-circle-outline" size="22" />
|
<VIcon class="me-2" icon="mdi-help-circle-outline" size="22" />
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle>帮助</VListItemTitle>
|
<VListItemTitle>帮助文档</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
|
|
||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import DefaultLayoutWithVerticalNav from './components/DefaultLayoutWithVerticalNav.vue'
|
import DefaultLayout from './components/DefaultLayout.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DefaultLayoutWithVerticalNav>
|
<DefaultLayout>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="Component" v-if="route.meta.keepAlive" :key="route.fullPath" />
|
<component :is="Component" v-if="route.meta.keepAlive" :key="route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<component :is="Component" v-if="!route.meta.keepAlive" :key="route.fullPath" />
|
<component :is="Component" v-if="!route.meta.keepAlive" :key="route.fullPath" />
|
||||||
</router-view>
|
</router-view>
|
||||||
</DefaultLayoutWithVerticalNav>
|
</DefaultLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
+2
-1
@@ -163,9 +163,10 @@ function login() {
|
|||||||
const avatar = response.avatar
|
const avatar = response.avatar
|
||||||
const level = response.level
|
const level = response.level
|
||||||
const remember = form.value.remember
|
const remember = form.value.remember
|
||||||
|
const permissions = response.permissions
|
||||||
|
|
||||||
// 更新token和remember状态到Vuex Store
|
// 更新token和remember状态到Vuex Store
|
||||||
store.dispatch('auth/login', { token, remember, superUser, userName, avatar, level })
|
store.dispatch('auth/login', { token, remember, superUser, userName, avatar, level, permissions })
|
||||||
|
|
||||||
// 登录后处理
|
// 登录后处理
|
||||||
afterLogin(superUser)
|
afterLogin(superUser)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import UserProfileView from '@/views/user/UserProfileView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<UserProfileView />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import AccountSettingAccount from '@/views/setting/AccountSettingAccount.vue'
|
|
||||||
import AccountSettingNotification from '@/views/setting/AccountSettingNotification.vue'
|
import AccountSettingNotification from '@/views/setting/AccountSettingNotification.vue'
|
||||||
import AccountSettingSite from '@/views/setting/AccountSettingSite.vue'
|
import AccountSettingSite from '@/views/setting/AccountSettingSite.vue'
|
||||||
import AccountSettingWords from '@/views/setting/AccountSettingWords.vue'
|
import AccountSettingWords from '@/views/setting/AccountSettingWords.vue'
|
||||||
@@ -38,13 +37,6 @@ function jumpTab(tab: string) {
|
|||||||
</VTabs>
|
</VTabs>
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||||
<!-- 用户 -->
|
|
||||||
<VWindowItem value="account">
|
|
||||||
<transition name="fade-slide" appear>
|
|
||||||
<AccountSettingAccount />
|
|
||||||
</transition>
|
|
||||||
</VWindowItem>
|
|
||||||
|
|
||||||
<!-- 连接 -->
|
<!-- 连接 -->
|
||||||
<VWindowItem value="system">
|
<VWindowItem value="system">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import UserListView from '@/views/user/UserListView.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<UserListView />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -90,6 +90,22 @@ const router = createRouter({
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
component: () => import('../pages/user.vue'),
|
||||||
|
meta: {
|
||||||
|
keepAlive: true,
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
component: () => import('../pages/profile.vue'),
|
||||||
|
meta: {
|
||||||
|
keepAlive: true,
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/plugins',
|
path: '/plugins',
|
||||||
component: () => import('../pages/plugin.vue'),
|
component: () => import('../pages/plugin.vue'),
|
||||||
|
|||||||
+19
-18
@@ -5,21 +5,21 @@ export const SystemNavMenus = [
|
|||||||
icon: 'mdi-home-outline',
|
icon: 'mdi-home-outline',
|
||||||
to: '/dashboard',
|
to: '/dashboard',
|
||||||
header: '开始',
|
header: '开始',
|
||||||
admin: false,
|
permission: 'dashboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '推荐',
|
title: '推荐',
|
||||||
icon: 'mdi-star-outline',
|
icon: 'mdi-star-outline',
|
||||||
to: '/ranking',
|
to: '/ranking',
|
||||||
header: '发现',
|
header: '发现',
|
||||||
admin: false,
|
permission: 'ranking',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '资源搜索',
|
title: '资源搜索',
|
||||||
icon: 'mdi-magnify',
|
icon: 'mdi-magnify',
|
||||||
to: '/resource',
|
to: '/resource',
|
||||||
header: '发现',
|
header: '发现',
|
||||||
admin: false,
|
permission: 'resource.search',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电影',
|
title: '电影',
|
||||||
@@ -27,7 +27,7 @@ export const SystemNavMenus = [
|
|||||||
icon: 'mdi-movie-open-outline',
|
icon: 'mdi-movie-open-outline',
|
||||||
to: '/subscribe/movie',
|
to: '/subscribe/movie',
|
||||||
header: '订阅',
|
header: '订阅',
|
||||||
admin: false,
|
permission: 'subscribe.movie',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '电视剧',
|
title: '电视剧',
|
||||||
@@ -35,7 +35,7 @@ export const SystemNavMenus = [
|
|||||||
icon: 'mdi-television',
|
icon: 'mdi-television',
|
||||||
to: '/subscribe/tv',
|
to: '/subscribe/tv',
|
||||||
header: '订阅',
|
header: '订阅',
|
||||||
admin: false,
|
permission: 'subscribe.tv',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '日历',
|
title: '日历',
|
||||||
@@ -43,49 +43,56 @@ export const SystemNavMenus = [
|
|||||||
icon: 'mdi-calendar',
|
icon: 'mdi-calendar',
|
||||||
to: '/calendar',
|
to: '/calendar',
|
||||||
header: '订阅',
|
header: '订阅',
|
||||||
admin: false,
|
permission: 'subscribe.calendar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '正在下载',
|
title: '正在下载',
|
||||||
icon: 'mdi-download-outline',
|
icon: 'mdi-download-outline',
|
||||||
to: '/downloading',
|
to: '/downloading',
|
||||||
header: '整理',
|
header: '整理',
|
||||||
admin: false,
|
permission: 'downloading.view',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '历史记录',
|
title: '历史记录',
|
||||||
icon: 'mdi-history',
|
icon: 'mdi-history',
|
||||||
to: '/history',
|
to: '/history',
|
||||||
header: '整理',
|
header: '整理',
|
||||||
admin: true,
|
permission: 'admin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '文件管理',
|
title: '文件管理',
|
||||||
icon: 'mdi-folder-multiple-outline',
|
icon: 'mdi-folder-multiple-outline',
|
||||||
to: '/filemanager',
|
to: '/filemanager',
|
||||||
header: '整理',
|
header: '整理',
|
||||||
admin: true,
|
permission: 'admin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '插件',
|
title: '插件',
|
||||||
icon: 'mdi-apps',
|
icon: 'mdi-apps',
|
||||||
to: '/plugins',
|
to: '/plugins',
|
||||||
header: '系统',
|
header: '系统',
|
||||||
admin: true,
|
permission: 'admin',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '站点管理',
|
title: '站点管理',
|
||||||
icon: 'mdi-web',
|
icon: 'mdi-web',
|
||||||
to: '/site',
|
to: '/site',
|
||||||
header: '系统',
|
header: '系统',
|
||||||
admin: true,
|
permission: 'admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户管理',
|
||||||
|
icon: 'mdi-account-group',
|
||||||
|
to: '/user',
|
||||||
|
header: '系统',
|
||||||
|
permission: 'usermanage',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '设定',
|
title: '设定',
|
||||||
icon: 'mdi-cog',
|
icon: 'mdi-cog',
|
||||||
to: '/setting',
|
to: '/setting',
|
||||||
header: '系统',
|
header: '系统',
|
||||||
admin: true,
|
permission: 'admin',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -120,12 +127,6 @@ export const UserfulMenus = [
|
|||||||
|
|
||||||
// 设定标签页
|
// 设定标签页
|
||||||
export const SettingTabs = [
|
export const SettingTabs = [
|
||||||
{
|
|
||||||
title: '用户',
|
|
||||||
icon: 'mdi-account',
|
|
||||||
tab: 'account',
|
|
||||||
description: '个人信息、用户管理、修改密码、双重认证',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '连接',
|
title: '连接',
|
||||||
icon: 'mdi-server-network',
|
icon: 'mdi-server-network',
|
||||||
|
|||||||
+38
-9
@@ -9,6 +9,7 @@ interface AuthState {
|
|||||||
avatar: string
|
avatar: string
|
||||||
originalPath: string | null
|
originalPath: string | null
|
||||||
level: number
|
level: number
|
||||||
|
permissions: { [key: string]: any }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义根状态类型
|
// 定义根状态类型
|
||||||
@@ -16,17 +17,40 @@ interface RootState {
|
|||||||
auth: AuthState
|
auth: AuthState
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出模块
|
// 用户信息模块
|
||||||
const authModule: Module<AuthState, RootState> = {
|
const authModule: Module<AuthState, RootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
token: null,
|
token: null, // 用户令牌
|
||||||
remember: false,
|
remember: false, // 记住我
|
||||||
superUser: false,
|
superUser: false, // 超级管理员
|
||||||
userName: '',
|
userName: '', // 用户名
|
||||||
avatar: '',
|
avatar: '', // 头像
|
||||||
originalPath: null,
|
originalPath: null, // 原始路径
|
||||||
level: 1,
|
level: 1, // 用户认证等级 1-未认证 2-已认证
|
||||||
|
permissions: {
|
||||||
|
admin: false, // 管理员
|
||||||
|
usermanage: false, // 用户管理
|
||||||
|
dashboard: true, //仪表板
|
||||||
|
ranking: true, // 推荐榜单
|
||||||
|
resource: {
|
||||||
|
search: false, // 搜索站点资源
|
||||||
|
download: false, // 下载站点资源
|
||||||
|
},
|
||||||
|
subscribe: {
|
||||||
|
movie: true, // 查看电影订阅
|
||||||
|
tv: true, // 电视剧订阅
|
||||||
|
request: true, // 提交订阅请求
|
||||||
|
autopass: false, // 订阅请求自动批准
|
||||||
|
approve: false, // 审批订阅请求
|
||||||
|
calendar: true, // 查看订阅日历
|
||||||
|
manage: false, // 管理所有订阅
|
||||||
|
},
|
||||||
|
downloading: {
|
||||||
|
view: true, // 查看正在下载任务
|
||||||
|
manager: false, // 管理正在下载任务
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setToken(state, token: string) {
|
setToken(state, token: string) {
|
||||||
@@ -53,15 +77,19 @@ const authModule: Module<AuthState, RootState> = {
|
|||||||
setLevel(state, level: number) {
|
setLevel(state, level: number) {
|
||||||
state.level = level
|
state.level = level
|
||||||
},
|
},
|
||||||
|
setPermissions(state, permissions: object) {
|
||||||
|
state.permissions = permissions
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
login({ commit }, { token, remember, superUser, userName, avatar, level }) {
|
login({ commit }, { token, remember, superUser, userName, avatar, level, permissions }) {
|
||||||
commit('setToken', token)
|
commit('setToken', token)
|
||||||
commit('setRemember', remember)
|
commit('setRemember', remember)
|
||||||
commit('setSuperUser', superUser)
|
commit('setSuperUser', superUser)
|
||||||
commit('setUserName', userName)
|
commit('setUserName', userName)
|
||||||
commit('setAvatar', avatar)
|
commit('setAvatar', avatar)
|
||||||
commit('setLevel', level)
|
commit('setLevel', level)
|
||||||
|
commit('setPermissions', permissions)
|
||||||
},
|
},
|
||||||
logout({ commit }) {
|
logout({ commit }) {
|
||||||
commit('clearToken')
|
commit('clearToken')
|
||||||
@@ -76,6 +104,7 @@ const authModule: Module<AuthState, RootState> = {
|
|||||||
getAvatar: state => state.avatar,
|
getAvatar: state => state.avatar,
|
||||||
getOriginalPath: state => state.originalPath,
|
getOriginalPath: state => state.originalPath,
|
||||||
getLevel: state => state.level,
|
getLevel: state => state.level,
|
||||||
|
getPermissions: state => state.permissions,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useToast } from 'vue-toast-notification'
|
||||||
|
import { VForm } from 'vuetify/lib/components/index.mjs'
|
||||||
|
import { requiredValidator } from '@/@validators'
|
||||||
|
import api from '@/api'
|
||||||
|
import type { User } from '@/api/types'
|
||||||
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
|
|
||||||
|
// 提示框
|
||||||
|
const $toast = useToast()
|
||||||
|
|
||||||
|
// 新增用户窗口
|
||||||
|
const addUserDialog = ref(false)
|
||||||
|
|
||||||
|
// 新增用户表单
|
||||||
|
const userForm = reactive({
|
||||||
|
name: '',
|
||||||
|
password: '',
|
||||||
|
email: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前用户信息
|
||||||
|
const accountInfo = ref<User>({
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
password: '',
|
||||||
|
email: '',
|
||||||
|
is_active: false,
|
||||||
|
is_superuser: false,
|
||||||
|
avatar: '',
|
||||||
|
is_otp: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 所有用户信息
|
||||||
|
const allUsers = ref<User[]>([])
|
||||||
|
|
||||||
|
// 调用API,加载当前用户数据
|
||||||
|
async function loadAccountInfo() {
|
||||||
|
try {
|
||||||
|
const user: User = await api.get('user/current')
|
||||||
|
console.log(user)
|
||||||
|
accountInfo.value = user
|
||||||
|
if (!accountInfo.value.avatar) accountInfo.value.avatar = avatar1
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API,查询所有用户
|
||||||
|
async function loadAllUsers() {
|
||||||
|
try {
|
||||||
|
const result: User[] = await api.get('/user/')
|
||||||
|
|
||||||
|
allUsers.value = result
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
async function deleteUser(user: User) {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.delete(`user/${user.name}`)
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success('用户删除成功!')
|
||||||
|
loadAllUsers()
|
||||||
|
} else {
|
||||||
|
$toast.error(`用户删除失败:${result.message}!`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冻结用户
|
||||||
|
async function deactivateUser(user: User) {
|
||||||
|
try {
|
||||||
|
user.is_active = !user.is_active
|
||||||
|
|
||||||
|
const result: { [key: string]: any } = await api.put('user/', user)
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success('用户冻结成功!')
|
||||||
|
loadAllUsers()
|
||||||
|
} else {
|
||||||
|
$toast.error(`用户冻结失败:${result.message}!`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增用户
|
||||||
|
async function addUser() {
|
||||||
|
if (!userForm.name || !userForm.password || !userForm.email) {
|
||||||
|
$toast.error('请填写完整信息!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.post('user/', userForm)
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success('用户新增成功!')
|
||||||
|
loadAllUsers()
|
||||||
|
addUserDialog.value = false
|
||||||
|
} else {
|
||||||
|
$toast.error(`用户新增失败:${result.message}!`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载当前用户数据
|
||||||
|
onMounted(() => {
|
||||||
|
loadAccountInfo()
|
||||||
|
loadAllUsers()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<VRow>
|
||||||
|
<VCol v-if="accountInfo.is_superuser" cols="12">
|
||||||
|
<!-- 👉 Accounts -->
|
||||||
|
<VCard title="所有用户">
|
||||||
|
<template #append>
|
||||||
|
<IconBtn @click.stop="addUserDialog = true">
|
||||||
|
<VIcon icon="mdi-plus" />
|
||||||
|
</IconBtn>
|
||||||
|
</template>
|
||||||
|
<VTable class="text-no-wrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">用户名</th>
|
||||||
|
<th scope="col">邮箱</th>
|
||||||
|
<th scope="col">状态</th>
|
||||||
|
<th scope="col">管理员</th>
|
||||||
|
<th scope="col" class="w-5" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="user in allUsers" :key="user.name">
|
||||||
|
<td>
|
||||||
|
{{ user.name }}
|
||||||
|
</td>
|
||||||
|
<td>{{ user.email }}</td>
|
||||||
|
<td>
|
||||||
|
<VChip v-if="user.is_active" color="success" text-color="white"> 激活 </VChip>
|
||||||
|
<VChip v-else color="error" text-color="white"> 冻结 </VChip>
|
||||||
|
</td>
|
||||||
|
<td>{{ user.is_superuser ? '是' : '否' }}</td>
|
||||||
|
<td>
|
||||||
|
<IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name">
|
||||||
|
<VIcon icon="mdi-dots-vertical" />
|
||||||
|
<VMenu activator="parent" close-on-content-click>
|
||||||
|
<VList>
|
||||||
|
<VListItem variant="plain" @click="deactivateUser(user)">
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-lock" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>
|
||||||
|
{{ user.is_active ? '冻结' : '解冻' }}
|
||||||
|
</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem variant="plain" base-color="error" @click="deleteUser(user)">
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-delete" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>删除</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</VTable>
|
||||||
|
</VCard>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
<!-- =弹窗 -->
|
||||||
|
<VDialog v-model="addUserDialog" max-width="50rem" persistent z-index="1010">
|
||||||
|
<!-- Dialog Content -->
|
||||||
|
<VCard title="新增用户">
|
||||||
|
<VCardText>
|
||||||
|
<VForm @submit.prevent="() => {}">
|
||||||
|
<VRow>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.name" label="用户名" :rules="[requiredValidator]" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="userForm.password"
|
||||||
|
label="密码"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
|
:type="isPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.email" :rules="[requiredValidator]" label="邮箱" />
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VForm>
|
||||||
|
</VCardText>
|
||||||
|
<VCardActions>
|
||||||
|
<VBtn @click="addUserDialog = false"> 取消 </VBtn>
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn @click="addUser"> 确定 </VBtn>
|
||||||
|
</VCardActions>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import QrcodeVue from 'qrcode.vue'
|
import QrcodeVue from 'qrcode.vue'
|
||||||
import { VForm } from 'vuetify/lib/components/index.mjs'
|
import { VForm } from 'vuetify/lib/components/index.mjs'
|
||||||
import { requiredValidator } from '@/@validators'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { User } from '@/api/types'
|
import type { User } from '@/api/types'
|
||||||
import avatar1 from '@images/avatars/avatar-1.png'
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
@@ -13,7 +12,6 @@ const display = useDisplay()
|
|||||||
|
|
||||||
const isNewPasswordVisible = ref(false)
|
const isNewPasswordVisible = ref(false)
|
||||||
const isConfirmPasswordVisible = ref(false)
|
const isConfirmPasswordVisible = ref(false)
|
||||||
const isPasswordVisible = ref(false)
|
|
||||||
const newPassword = ref('')
|
const newPassword = ref('')
|
||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
|
|
||||||
@@ -22,9 +20,6 @@ const $toast = useToast()
|
|||||||
|
|
||||||
const refInputEl = ref<HTMLElement>()
|
const refInputEl = ref<HTMLElement>()
|
||||||
|
|
||||||
// 新增用户窗口
|
|
||||||
const addUserDialog = ref(false)
|
|
||||||
|
|
||||||
// 开启双重验证窗口
|
// 开启双重验证窗口
|
||||||
const otpDialog = ref(false)
|
const otpDialog = ref(false)
|
||||||
|
|
||||||
@@ -37,13 +32,6 @@ const secret = ref('')
|
|||||||
// 确认双重验证密码
|
// 确认双重验证密码
|
||||||
const otpPassword = ref('')
|
const otpPassword = ref('')
|
||||||
|
|
||||||
// 新增用户表单
|
|
||||||
const userForm = reactive({
|
|
||||||
name: '',
|
|
||||||
password: '',
|
|
||||||
email: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 当前用户信息
|
// 当前用户信息
|
||||||
const accountInfo = ref<User>({
|
const accountInfo = ref<User>({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -125,58 +113,6 @@ async function loadAllUsers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除用户
|
|
||||||
async function deleteUser(user: User) {
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.delete(`user/${user.name}`)
|
|
||||||
if (result.success) {
|
|
||||||
$toast.success('用户删除成功!')
|
|
||||||
loadAllUsers()
|
|
||||||
} else {
|
|
||||||
$toast.error(`用户删除失败:${result.message}!`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 冻结用户
|
|
||||||
async function deactivateUser(user: User) {
|
|
||||||
try {
|
|
||||||
user.is_active = !user.is_active
|
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.put('user/', user)
|
|
||||||
if (result.success) {
|
|
||||||
$toast.success('用户冻结成功!')
|
|
||||||
loadAllUsers()
|
|
||||||
} else {
|
|
||||||
$toast.error(`用户冻结失败:${result.message}!`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增用户
|
|
||||||
async function addUser() {
|
|
||||||
if (!userForm.name || !userForm.password || !userForm.email) {
|
|
||||||
$toast.error('请填写完整信息!')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.post('user/', userForm)
|
|
||||||
if (result.success) {
|
|
||||||
$toast.success('用户新增成功!')
|
|
||||||
loadAllUsers()
|
|
||||||
addUserDialog.value = false
|
|
||||||
} else {
|
|
||||||
$toast.error(`用户新增失败:${result.message}!`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为当前用户获取Otp Uri
|
// 为当前用户获取Otp Uri
|
||||||
async function getOtpUri() {
|
async function getOtpUri() {
|
||||||
try {
|
try {
|
||||||
@@ -335,98 +271,7 @@ onMounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|
||||||
<VCol v-if="accountInfo.is_superuser" cols="12">
|
|
||||||
<!-- 👉 Accounts -->
|
|
||||||
<VCard title="所有用户">
|
|
||||||
<template #append>
|
|
||||||
<IconBtn @click.stop="addUserDialog = true">
|
|
||||||
<VIcon icon="mdi-plus" />
|
|
||||||
</IconBtn>
|
|
||||||
</template>
|
|
||||||
<VTable class="text-no-wrap">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">用户名</th>
|
|
||||||
<th scope="col">邮箱</th>
|
|
||||||
<th scope="col">状态</th>
|
|
||||||
<th scope="col">管理员</th>
|
|
||||||
<th scope="col" class="w-5" />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="user in allUsers" :key="user.name">
|
|
||||||
<td>
|
|
||||||
{{ user.name }}
|
|
||||||
</td>
|
|
||||||
<td>{{ user.email }}</td>
|
|
||||||
<td>
|
|
||||||
<VChip v-if="user.is_active" color="success" text-color="white"> 激活 </VChip>
|
|
||||||
<VChip v-else color="error" text-color="white"> 冻结 </VChip>
|
|
||||||
</td>
|
|
||||||
<td>{{ user.is_superuser ? '是' : '否' }}</td>
|
|
||||||
<td>
|
|
||||||
<IconBtn v-show="accountInfo.is_superuser && accountInfo.name !== user.name">
|
|
||||||
<VIcon icon="mdi-dots-vertical" />
|
|
||||||
<VMenu activator="parent" close-on-content-click>
|
|
||||||
<VList>
|
|
||||||
<VListItem variant="plain" @click="deactivateUser(user)">
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-lock" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>
|
|
||||||
{{ user.is_active ? '冻结' : '解冻' }}
|
|
||||||
</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
<VListItem variant="plain" base-color="error" @click="deleteUser(user)">
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-delete" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>删除</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
</VMenu>
|
|
||||||
</IconBtn>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</VTable>
|
|
||||||
</VCard>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
</VRow>
|
||||||
<!-- =弹窗 -->
|
|
||||||
<VDialog v-model="addUserDialog" max-width="50rem" persistent z-index="1010">
|
|
||||||
<!-- Dialog Content -->
|
|
||||||
<VCard title="新增用户">
|
|
||||||
<VCardText>
|
|
||||||
<VForm @submit.prevent="() => {}">
|
|
||||||
<VRow>
|
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VTextField v-model="userForm.name" label="用户名" :rules="[requiredValidator]" />
|
|
||||||
</VCol>
|
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VTextField
|
|
||||||
v-model="userForm.password"
|
|
||||||
label="密码"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
:type="isPasswordVisible ? 'text' : 'password'"
|
|
||||||
:append-inner-icon="isPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
||||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol cols="12" md="6">
|
|
||||||
<VTextField v-model="userForm.email" :rules="[requiredValidator]" label="邮箱" />
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
</VForm>
|
|
||||||
</VCardText>
|
|
||||||
<VCardActions>
|
|
||||||
<VBtn @click="addUserDialog = false"> 取消 </VBtn>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn @click="addUser"> 确定 </VBtn>
|
|
||||||
</VCardActions>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
|
|
||||||
<!-- 双重验证弹窗 -->
|
<!-- 双重验证弹窗 -->
|
||||||
<VDialog v-model="otpDialog" max-width="45rem" persistent z-index="1010">
|
<VDialog v-model="otpDialog" max-width="45rem" persistent z-index="1010">
|
||||||
Reference in New Issue
Block a user