mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-07 14:43:25 +08:00
重构用户卡片页面
This commit is contained in:
@@ -9,6 +9,7 @@ import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
||||
// APP
|
||||
const display = useDisplay()
|
||||
const appMode = inject('pwaMode') && display.mdAndDown.value
|
||||
const isMobile = computed(() => display.mobile.value)
|
||||
|
||||
// 是否刷新过
|
||||
const isRefreshed = ref(false)
|
||||
@@ -19,6 +20,9 @@ const loading = ref(false)
|
||||
// 新增用户窗口
|
||||
const addUserDialog = ref(false)
|
||||
|
||||
// 要编辑的用户
|
||||
const userToEdit = ref<User | null>(null)
|
||||
|
||||
// 所有用户信息
|
||||
const allUsers = ref<User[]>([])
|
||||
|
||||
@@ -41,6 +45,11 @@ const onUserAdd = () => {
|
||||
loadAllUsers()
|
||||
}
|
||||
|
||||
// 打开添加用户对话框
|
||||
const openAddUserDialog = () => {
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
// 加载当前用户数据
|
||||
onMounted(() => {
|
||||
loadAllUsers()
|
||||
@@ -54,39 +63,151 @@ onActivated(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
|
||||
<div v-if="allUsers.length > 0" class="grid gap-3 grid-user-card">
|
||||
<UserCard v-for="user in allUsers" :user="user" :users="allUsers" @remove="loadAllUsers" @save="loadAllUsers" />
|
||||
<div class="user-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<VIcon icon="mdi-account-group" size="large" color="primary" class="title-icon" />
|
||||
<h1 class="title-text">用户管理</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载中提示 -->
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
|
||||
<!-- 用户卡片网格 -->
|
||||
<div v-if="allUsers.length > 0 && isRefreshed" class="users-grid">
|
||||
<!-- 普通用户卡片 -->
|
||||
<UserCard
|
||||
v-for="user in allUsers"
|
||||
:key="user.id"
|
||||
:user="user"
|
||||
:users="allUsers"
|
||||
@remove="loadAllUsers"
|
||||
@save="loadAllUsers"
|
||||
/>
|
||||
|
||||
<!-- 添加用户卡片 -->
|
||||
<div class="add-user-card" @click="openAddUserDialog">
|
||||
<div class="add-user-content">
|
||||
<VIcon icon="mdi-account-plus" size="large" color="primary" />
|
||||
<span class="add-user-text">添加用户</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<div v-if="allUsers.length === 0 && isRefreshed">
|
||||
<NoDataFound
|
||||
error-code="404"
|
||||
error-title="没有用户"
|
||||
error-description="点击添加用户卡片添加用户"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户添加弹窗 -->
|
||||
<UserAddEditDialog
|
||||
v-if="addUserDialog"
|
||||
v-model="addUserDialog"
|
||||
oper="add"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
z-index="1010"
|
||||
@save="onUserAdd"
|
||||
@close="addUserDialog = false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NoDataFound
|
||||
v-if="allUsers.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有用户"
|
||||
error-description="点击右下角按钮添加用户"
|
||||
/>
|
||||
|
||||
<VFab
|
||||
v-if="isRefreshed"
|
||||
icon="mdi-plus"
|
||||
location="bottom"
|
||||
size="x-large"
|
||||
fixed
|
||||
app
|
||||
appear
|
||||
@click="addUserDialog = true"
|
||||
:class="{ 'mb-12': appMode }"
|
||||
/>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<UserAddEditDialog
|
||||
v-if="addUserDialog"
|
||||
v-model="addUserDialog"
|
||||
oper="add"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
@save="onUserAdd"
|
||||
@close="addUserDialog = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-list-container {
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--v-theme-on-surface), 0.87);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.users-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.add-user-card {
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
min-height: 160px;
|
||||
background-color: rgba(var(--v-theme-surface), 1);
|
||||
border: 1.5px dashed rgba(var(--v-theme-primary), 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(var(--v-theme-on-surface), 0.05);
|
||||
}
|
||||
|
||||
.add-user-card:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(var(--v-theme-on-surface), 0.1);
|
||||
}
|
||||
|
||||
.add-user-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.add-user-text {
|
||||
font-size: 1.05rem;
|
||||
color: rgba(var(--v-theme-primary), 0.8);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 600px) {
|
||||
.user-list-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.users-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 601px) and (max-width: 960px) {
|
||||
.users-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 961px) {
|
||||
.users-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -57,6 +57,7 @@ const accountInfo = ref<User>({
|
||||
is_otp: false,
|
||||
permissions: {},
|
||||
settings: {},
|
||||
nickname: ''
|
||||
})
|
||||
|
||||
// 二维码信息
|
||||
@@ -102,17 +103,16 @@ function restoreCurrentAvatar() {
|
||||
$toast.success('已还原当前使用头像!')
|
||||
}
|
||||
|
||||
// 调用API,加载当前用户数据
|
||||
async function loadAccountInfo() {
|
||||
// 加载当前用户信息
|
||||
async function fetchUserInfo() {
|
||||
try {
|
||||
const user: User = await api.get('user/current')
|
||||
console.log(user)
|
||||
accountInfo.value = user
|
||||
if (!accountInfo.value.avatar) {
|
||||
accountInfo.value.avatar = avatar1
|
||||
const result: User = await api.get(`user/${userStore.userName}`)
|
||||
if (result) {
|
||||
accountInfo.value = result
|
||||
accountInfo.value.avatar = accountInfo.value.avatar ? accountInfo.value.avatar : avatar1
|
||||
currentUserName.value = accountInfo.value.name
|
||||
currentAvatar.value = accountInfo.value.avatar
|
||||
}
|
||||
currentAvatar.value = accountInfo.value.avatar
|
||||
currentUserName.value = accountInfo.value.name
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -135,13 +135,26 @@ async function saveAccountInfo() {
|
||||
}
|
||||
accountInfo.value.password = newPassword.value
|
||||
}
|
||||
|
||||
// 将nickname保存到settings中,后端可以直接处理JSON对象
|
||||
if (accountInfo.value.nickname) {
|
||||
if (!accountInfo.value.settings) {
|
||||
accountInfo.value.settings = {};
|
||||
}
|
||||
accountInfo.value.settings.nickname = accountInfo.value.nickname;
|
||||
}
|
||||
|
||||
const oldUserName = accountInfo.value.name
|
||||
const oldAvatar = accountInfo.value.avatar
|
||||
accountInfo.value.avatar = currentAvatar.value
|
||||
accountInfo.value.name = currentUserName.value
|
||||
isSaving.value = true
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.put('user/', accountInfo.value)
|
||||
// 创建一个临时对象来保存用户数据,确保所有字段都会发送
|
||||
const userData = { ...accountInfo.value };
|
||||
|
||||
const result: { [key: string]: any } = await api.put('user/', userData)
|
||||
|
||||
if (result.success) {
|
||||
if (oldUserName !== currentUserName.value) {
|
||||
$toast.success(`【${oldUserName}】更名【${currentUserName.value}】,用户信息保存成功!`)
|
||||
@@ -167,7 +180,7 @@ async function saveAccountInfo() {
|
||||
accountInfo.value.avatar = oldAvatar
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log('保存失败:', error)
|
||||
}
|
||||
isSaving.value = false
|
||||
}
|
||||
@@ -230,7 +243,7 @@ async function judgeOtpPassword() {
|
||||
|
||||
// 加载当前用户数据
|
||||
onMounted(() => {
|
||||
loadAccountInfo()
|
||||
fetchUserInfo()
|
||||
})
|
||||
|
||||
// 监听 localStorage 中的用户头像变化
|
||||
@@ -298,8 +311,13 @@ watch(
|
||||
<!-- 👉 Form -->
|
||||
<VForm class="mt-6">
|
||||
<VRow>
|
||||
<VCol md="6" cols="12">
|
||||
<VTextField v-model="currentUserName" density="comfortable" readonly label="用户名" />
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="currentUserName"
|
||||
density="comfortable"
|
||||
readonly
|
||||
label="用户名"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="accountInfo.email" density="comfortable" clearable label="邮箱" type="email" />
|
||||
@@ -311,7 +329,7 @@ watch(
|
||||
:type="isNewPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
clearable
|
||||
label="新密码"
|
||||
label="密码"
|
||||
autocomplete=""
|
||||
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||
/>
|
||||
@@ -324,10 +342,19 @@ watch(
|
||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||
clearable
|
||||
label="确认新密码"
|
||||
label="确认密码"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="accountInfo.nickname"
|
||||
density="comfortable"
|
||||
clearable
|
||||
label="昵称"
|
||||
placeholder="显示昵称,优先于用户名显示"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VDivider class="my-10">
|
||||
|
||||
Reference in New Issue
Block a user