mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-01 13:51:06 +08:00
fix user
This commit is contained in:
2
src/@layouts/types.d.ts
vendored
2
src/@layouts/types.d.ts
vendored
@@ -122,8 +122,8 @@ export interface NavLink extends NavLinkProps, Partial<AclProperties> {
|
||||
|
||||
export interface NavMenu extends NavLink {
|
||||
header: string
|
||||
admin: boolean
|
||||
description?: string
|
||||
permission?: string
|
||||
}
|
||||
|
||||
// 👉 Vertical nav group
|
||||
|
||||
@@ -18,18 +18,65 @@ const appMode = computed(() => {
|
||||
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) => {
|
||||
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() {
|
||||
history.back()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取菜单列表
|
||||
startMenus.value = getMenuList('开始')
|
||||
discoveryMenus.value = getMenuList('发现')
|
||||
subscribeMenus.value = getMenuList('订阅')
|
||||
organizeMenus.value = getMenuList('整理')
|
||||
systemMenus.value = getMenuList('系统')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,36 +108,39 @@ function goBack() {
|
||||
</template>
|
||||
|
||||
<template #vertical-nav-content>
|
||||
<VerticalNavLink v-for="item in getMenuList('开始')" :item="item" />
|
||||
<VerticalNavLink v-for="item in startMenus" :item="item" />
|
||||
<!-- 👉 发现 -->
|
||||
<VerticalNavSectionTitle
|
||||
v-if="discoveryMenus.length > 0"
|
||||
:item="{
|
||||
heading: '发现',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in getMenuList('发现')" :item="item" />
|
||||
<VerticalNavLink v-for="item in discoveryMenus" :item="item" />
|
||||
<!-- 👉 订阅 -->
|
||||
<VerticalNavSectionTitle
|
||||
v-if="subscribeMenus.length > 0"
|
||||
:item="{
|
||||
heading: '订阅',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in getMenuList('订阅')" :item="item" />
|
||||
<VerticalNavLink v-for="item in subscribeMenus" :item="item" />
|
||||
<!-- 👉 整理 -->
|
||||
<VerticalNavSectionTitle
|
||||
v-if="organizeMenus.length > 0"
|
||||
:item="{
|
||||
heading: '整理',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in getMenuList('整理')" :item="item" />
|
||||
<VerticalNavLink v-for="item in organizeMenus" :item="item" />
|
||||
<!-- 👉 系统 -->
|
||||
<VerticalNavSectionTitle
|
||||
v-if="superUser"
|
||||
v-if="systemMenus.length > 0"
|
||||
:item="{
|
||||
heading: '系统',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink v-for="item in getMenuList('系统')" :item="item" />
|
||||
<VerticalNavLink v-for="item in systemMenus" :item="item" />
|
||||
</template>
|
||||
|
||||
<template #after-vertical-nav-items />
|
||||
@@ -111,11 +111,11 @@ watch(isCompactMode, value => {
|
||||
<VDivider class="my-2" />
|
||||
|
||||
<!-- 👉 Profile -->
|
||||
<VListItem v-if="superUser" link @click="router.push('/setting?tab=account')">
|
||||
<VListItem link @click="router.push('/profile')">
|
||||
<template #prepend>
|
||||
<VIcon class="me-2" icon="mdi-account-outline" size="22" />
|
||||
</template>
|
||||
<VListItemTitle>设定</VListItemTitle>
|
||||
<VListItemTitle>个人信息</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- 👉 FAQ -->
|
||||
@@ -123,7 +123,7 @@ watch(isCompactMode, value => {
|
||||
<template #prepend>
|
||||
<VIcon class="me-2" icon="mdi-help-circle-outline" size="22" />
|
||||
</template>
|
||||
<VListItemTitle>帮助</VListItemTitle>
|
||||
<VListItemTitle>帮助文档</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<!-- Divider -->
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import DefaultLayoutWithVerticalNav from './components/DefaultLayoutWithVerticalNav.vue'
|
||||
import DefaultLayout from './components/DefaultLayout.vue'
|
||||
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultLayoutWithVerticalNav>
|
||||
<DefaultLayout>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component :is="Component" v-if="route.meta.keepAlive" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-if="!route.meta.keepAlive" :key="route.fullPath" />
|
||||
</router-view>
|
||||
</DefaultLayoutWithVerticalNav>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -163,9 +163,10 @@ function login() {
|
||||
const avatar = response.avatar
|
||||
const level = response.level
|
||||
const remember = form.value.remember
|
||||
const permissions = response.permissions
|
||||
|
||||
// 更新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)
|
||||
|
||||
9
src/pages/profile.vue
Normal file
9
src/pages/profile.vue
Normal file
@@ -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>
|
||||
import { useRoute } from 'vue-router'
|
||||
import router from '@/router'
|
||||
import AccountSettingAccount from '@/views/setting/AccountSettingAccount.vue'
|
||||
import AccountSettingNotification from '@/views/setting/AccountSettingNotification.vue'
|
||||
import AccountSettingSite from '@/views/setting/AccountSettingSite.vue'
|
||||
import AccountSettingWords from '@/views/setting/AccountSettingWords.vue'
|
||||
@@ -38,13 +37,6 @@ function jumpTab(tab: string) {
|
||||
</VTabs>
|
||||
|
||||
<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">
|
||||
<transition name="fade-slide" appear>
|
||||
|
||||
9
src/pages/user.vue
Normal file
9
src/pages/user.vue
Normal file
@@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
component: () => import('../pages/plugin.vue'),
|
||||
|
||||
@@ -5,21 +5,21 @@ export const SystemNavMenus = [
|
||||
icon: 'mdi-home-outline',
|
||||
to: '/dashboard',
|
||||
header: '开始',
|
||||
admin: false,
|
||||
permission: 'dashboard',
|
||||
},
|
||||
{
|
||||
title: '推荐',
|
||||
icon: 'mdi-star-outline',
|
||||
to: '/ranking',
|
||||
header: '发现',
|
||||
admin: false,
|
||||
permission: 'ranking',
|
||||
},
|
||||
{
|
||||
title: '资源搜索',
|
||||
icon: 'mdi-magnify',
|
||||
to: '/resource',
|
||||
header: '发现',
|
||||
admin: false,
|
||||
permission: 'resource.search',
|
||||
},
|
||||
{
|
||||
title: '电影',
|
||||
@@ -27,7 +27,7 @@ export const SystemNavMenus = [
|
||||
icon: 'mdi-movie-open-outline',
|
||||
to: '/subscribe/movie',
|
||||
header: '订阅',
|
||||
admin: false,
|
||||
permission: 'subscribe.movie',
|
||||
},
|
||||
{
|
||||
title: '电视剧',
|
||||
@@ -35,7 +35,7 @@ export const SystemNavMenus = [
|
||||
icon: 'mdi-television',
|
||||
to: '/subscribe/tv',
|
||||
header: '订阅',
|
||||
admin: false,
|
||||
permission: 'subscribe.tv',
|
||||
},
|
||||
{
|
||||
title: '日历',
|
||||
@@ -43,49 +43,56 @@ export const SystemNavMenus = [
|
||||
icon: 'mdi-calendar',
|
||||
to: '/calendar',
|
||||
header: '订阅',
|
||||
admin: false,
|
||||
permission: 'subscribe.calendar',
|
||||
},
|
||||
{
|
||||
title: '正在下载',
|
||||
icon: 'mdi-download-outline',
|
||||
to: '/downloading',
|
||||
header: '整理',
|
||||
admin: false,
|
||||
permission: 'downloading.view',
|
||||
},
|
||||
{
|
||||
title: '历史记录',
|
||||
icon: 'mdi-history',
|
||||
to: '/history',
|
||||
header: '整理',
|
||||
admin: true,
|
||||
permission: 'admin',
|
||||
},
|
||||
{
|
||||
title: '文件管理',
|
||||
icon: 'mdi-folder-multiple-outline',
|
||||
to: '/filemanager',
|
||||
header: '整理',
|
||||
admin: true,
|
||||
permission: 'admin',
|
||||
},
|
||||
{
|
||||
title: '插件',
|
||||
icon: 'mdi-apps',
|
||||
to: '/plugins',
|
||||
header: '系统',
|
||||
admin: true,
|
||||
permission: 'admin',
|
||||
},
|
||||
{
|
||||
title: '站点管理',
|
||||
icon: 'mdi-web',
|
||||
to: '/site',
|
||||
header: '系统',
|
||||
admin: true,
|
||||
permission: 'admin',
|
||||
},
|
||||
{
|
||||
title: '用户管理',
|
||||
icon: 'mdi-account-group',
|
||||
to: '/user',
|
||||
header: '系统',
|
||||
permission: 'usermanage',
|
||||
},
|
||||
{
|
||||
title: '设定',
|
||||
icon: 'mdi-cog',
|
||||
to: '/setting',
|
||||
header: '系统',
|
||||
admin: true,
|
||||
permission: 'admin',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -120,12 +127,6 @@ export const UserfulMenus = [
|
||||
|
||||
// 设定标签页
|
||||
export const SettingTabs = [
|
||||
{
|
||||
title: '用户',
|
||||
icon: 'mdi-account',
|
||||
tab: 'account',
|
||||
description: '个人信息、用户管理、修改密码、双重认证',
|
||||
},
|
||||
{
|
||||
title: '连接',
|
||||
icon: 'mdi-server-network',
|
||||
|
||||
@@ -9,6 +9,7 @@ interface AuthState {
|
||||
avatar: string
|
||||
originalPath: string | null
|
||||
level: number
|
||||
permissions: { [key: string]: any }
|
||||
}
|
||||
|
||||
// 定义根状态类型
|
||||
@@ -16,17 +17,40 @@ interface RootState {
|
||||
auth: AuthState
|
||||
}
|
||||
|
||||
// 导出模块
|
||||
// 用户信息模块
|
||||
const authModule: Module<AuthState, RootState> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
token: null,
|
||||
remember: false,
|
||||
superUser: false,
|
||||
userName: '',
|
||||
avatar: '',
|
||||
originalPath: null,
|
||||
level: 1,
|
||||
token: null, // 用户令牌
|
||||
remember: false, // 记住我
|
||||
superUser: false, // 超级管理员
|
||||
userName: '', // 用户名
|
||||
avatar: '', // 头像
|
||||
originalPath: null, // 原始路径
|
||||
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: {
|
||||
setToken(state, token: string) {
|
||||
@@ -53,15 +77,19 @@ const authModule: Module<AuthState, RootState> = {
|
||||
setLevel(state, level: number) {
|
||||
state.level = level
|
||||
},
|
||||
setPermissions(state, permissions: object) {
|
||||
state.permissions = permissions
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
login({ commit }, { token, remember, superUser, userName, avatar, level }) {
|
||||
login({ commit }, { token, remember, superUser, userName, avatar, level, permissions }) {
|
||||
commit('setToken', token)
|
||||
commit('setRemember', remember)
|
||||
commit('setSuperUser', superUser)
|
||||
commit('setUserName', userName)
|
||||
commit('setAvatar', avatar)
|
||||
commit('setLevel', level)
|
||||
commit('setPermissions', permissions)
|
||||
},
|
||||
logout({ commit }) {
|
||||
commit('clearToken')
|
||||
@@ -76,6 +104,7 @@ const authModule: Module<AuthState, RootState> = {
|
||||
getAvatar: state => state.avatar,
|
||||
getOriginalPath: state => state.originalPath,
|
||||
getLevel: state => state.level,
|
||||
getPermissions: state => state.permissions,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
214
src/views/user/UserListView.vue
Normal file
214
src/views/user/UserListView.vue
Normal file
@@ -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 QrcodeVue from 'qrcode.vue'
|
||||
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'
|
||||
@@ -13,7 +12,6 @@ const display = useDisplay()
|
||||
|
||||
const isNewPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
const isPasswordVisible = ref(false)
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
|
||||
@@ -22,9 +20,6 @@ const $toast = useToast()
|
||||
|
||||
const refInputEl = ref<HTMLElement>()
|
||||
|
||||
// 新增用户窗口
|
||||
const addUserDialog = ref(false)
|
||||
|
||||
// 开启双重验证窗口
|
||||
const otpDialog = ref(false)
|
||||
|
||||
@@ -37,13 +32,6 @@ const secret = ref('')
|
||||
// 确认双重验证密码
|
||||
const otpPassword = ref('')
|
||||
|
||||
// 新增用户表单
|
||||
const userForm = reactive({
|
||||
name: '',
|
||||
password: '',
|
||||
email: '',
|
||||
})
|
||||
|
||||
// 当前用户信息
|
||||
const accountInfo = ref<User>({
|
||||
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
|
||||
async function getOtpUri() {
|
||||
try {
|
||||
@@ -335,98 +271,7 @@ onMounted(() => {
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</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>
|
||||
<!-- =弹窗 -->
|
||||
<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">
|
||||
Reference in New Issue
Block a user