mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-12 16:01:35 +08:00
fix usercard
This commit is contained in:
@@ -6,6 +6,7 @@ import avatar1 from '@images/avatars/avatar-1.png'
|
|||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import { useConfirm } from 'vuetify-use-dialog'
|
import { useConfirm } from 'vuetify-use-dialog'
|
||||||
import { hasPermission } from '@/@core/utils/permission'
|
import { hasPermission } from '@/@core/utils/permission'
|
||||||
|
import UserAddEditDialog from '@/components/dialog/UserAddEditDialog.vue'
|
||||||
|
|
||||||
// 定义输入变量
|
// 定义输入变量
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -24,6 +25,9 @@ const emit = defineEmits(['remove'])
|
|||||||
// 确认框
|
// 确认框
|
||||||
const createConfirm = useConfirm()
|
const createConfirm = useConfirm()
|
||||||
|
|
||||||
|
// 用户信息弹窗
|
||||||
|
const userEditDialog = ref(false)
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -54,7 +58,7 @@ async function removeUser() {
|
|||||||
content: `删除用户 ${props.user?.name} 的所有数据,是否确认?`,
|
content: `删除用户 ${props.user?.name} 的所有数据,是否确认?`,
|
||||||
})
|
})
|
||||||
if (!isConfirmed) return
|
if (!isConfirmed) return
|
||||||
const result: { [key: string]: any } = await api.delete(`user/${props.user.id}`)
|
const result: { [key: string]: any } = await api.delete(`user/${props.user.name}`)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success('用户删除成功')
|
$toast.success('用户删除成功')
|
||||||
emit('remove')
|
emit('remove')
|
||||||
@@ -66,9 +70,14 @@ async function removeUser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
function editUser() {
|
||||||
|
userEditDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 计算是否有用户编辑权限
|
// 计算是否有用户编辑权限
|
||||||
const canEditUser = computed(() => {
|
const canEditUser = computed(() => {
|
||||||
if (props.user.is_superuser) return true
|
if (store.state.auth.superUser) return true
|
||||||
return hasPermission('admin') || hasPermission('usermanage')
|
return hasPermission('admin') || hasPermission('usermanage')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -78,6 +87,12 @@ const canManageUser = computed(() => {
|
|||||||
return canEditUser
|
return canEditUser
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 用户重新完成时
|
||||||
|
function onUserUpdate() {
|
||||||
|
userEditDialog.value = false
|
||||||
|
emit('save')
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchSubscriptions()
|
fetchSubscriptions()
|
||||||
})
|
})
|
||||||
@@ -135,9 +150,17 @@ onMounted(() => {
|
|||||||
</VList>
|
</VList>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardText class="flex flex-row justify-center">
|
<VCardText class="flex flex-row justify-center">
|
||||||
<VBtn v-if="canEditUser" color="primary" class="me-4">编辑</VBtn>
|
<VBtn v-if="canEditUser" color="primary" class="me-4" @click="editUser">编辑</VBtn>
|
||||||
<VBtn v-if="canEditUser" color="info" class="me-4" variant="outlined"> 权限 </VBtn>
|
|
||||||
<VBtn v-if="canManageUser" color="error" variant="outlined" @click="removeUser"> 删除 </VBtn>
|
<VBtn v-if="canManageUser" color="error" variant="outlined" @click="removeUser"> 删除 </VBtn>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
<!-- 用户编辑弹窗 -->
|
||||||
|
<UserAddEditDialog
|
||||||
|
v-if="userEditDialog"
|
||||||
|
v-model="userEditDialog"
|
||||||
|
:username="props.user?.name"
|
||||||
|
oper="edit"
|
||||||
|
@save="onUserUpdate"
|
||||||
|
@close="userEditDialog = false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -54,11 +54,6 @@ const priorityItems = ref(
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监控输入参数
|
|
||||||
watchEffect(async () => {
|
|
||||||
if (props.siteid) fetchSiteInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 查询站点信息
|
// 查询站点信息
|
||||||
async function fetchSiteInfo() {
|
async function fetchSiteInfo() {
|
||||||
try {
|
try {
|
||||||
@@ -124,6 +119,12 @@ async function updateSiteInfo() {
|
|||||||
}
|
}
|
||||||
doneNProgress()
|
doneNProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.oper !== 'add') {
|
||||||
|
fetchSiteInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,2 +1,270 @@
|
|||||||
<script setup lang="ts"></script>
|
<script lang="ts" setup>
|
||||||
<template></template>
|
import { useToast } from 'vue-toast-notification'
|
||||||
|
import type { User } from '@/api/types'
|
||||||
|
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||||
|
import api from '@/api'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
|
|
||||||
|
// 显示器宽度
|
||||||
|
const display = useDisplay()
|
||||||
|
|
||||||
|
const refInputEl = ref<HTMLElement>()
|
||||||
|
const isNewPasswordVisible = ref(false)
|
||||||
|
const isConfirmPasswordVisible = ref(false)
|
||||||
|
const newPassword = ref('')
|
||||||
|
const confirmPassword = ref('')
|
||||||
|
|
||||||
|
// 输入参数
|
||||||
|
const props = defineProps({
|
||||||
|
username: String,
|
||||||
|
oper: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 注册事件
|
||||||
|
const emit = defineEmits(['save', 'close'])
|
||||||
|
|
||||||
|
// 用户编辑表单数据
|
||||||
|
const userForm = ref<User>({
|
||||||
|
id: 0,
|
||||||
|
name: props.username ?? '',
|
||||||
|
password: '',
|
||||||
|
email: '',
|
||||||
|
is_active: false,
|
||||||
|
is_superuser: false,
|
||||||
|
avatar: '',
|
||||||
|
is_otp: false,
|
||||||
|
permissions: {},
|
||||||
|
settings: {
|
||||||
|
wechat_userid: null,
|
||||||
|
telegram_userid: null,
|
||||||
|
slack_userid: null,
|
||||||
|
vocechat_userid: null,
|
||||||
|
synologychat_userid: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// changeAvatar function
|
||||||
|
function changeAvatar(file: Event) {
|
||||||
|
const fileReader = new FileReader()
|
||||||
|
const { files } = file.target as HTMLInputElement
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
fileReader.readAsDataURL(files[0])
|
||||||
|
fileReader.onload = () => {
|
||||||
|
if (typeof fileReader.result === 'string') {
|
||||||
|
userForm.value.avatar = fileReader.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset avatar image
|
||||||
|
function resetAvatar() {
|
||||||
|
userForm.value.avatar = avatar1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提示框
|
||||||
|
const $toast = useToast()
|
||||||
|
|
||||||
|
// 状态下拉项
|
||||||
|
const statusItems = [
|
||||||
|
{ title: '激活', value: 1 },
|
||||||
|
{ title: '已停用', value: 0 },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 查询用户信息
|
||||||
|
async function fetchUserInfo() {
|
||||||
|
try {
|
||||||
|
userForm.value = await api.get(`user/${props.username}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API 新增用户
|
||||||
|
async function addUser() {
|
||||||
|
if (!userForm.value?.name || !newPassword.value) return
|
||||||
|
if (newPassword.value || confirmPassword.value) {
|
||||||
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
|
$toast.error('两次输入的密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userForm.value.password = newPassword.value
|
||||||
|
}
|
||||||
|
startNProgress()
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: string } = await api.post('user/', userForm.value)
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success('新增用户成功')
|
||||||
|
emit('save')
|
||||||
|
} else {
|
||||||
|
$toast.error(`新增用户失败:${result.message}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
doneNProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API更新用户信息
|
||||||
|
async function updateUser() {
|
||||||
|
if (newPassword.value || confirmPassword.value) {
|
||||||
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
|
$toast.error('两次输入的密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userForm.value.password = newPassword.value
|
||||||
|
}
|
||||||
|
startNProgress()
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.put('user/', userForm.value)
|
||||||
|
if (result.success) {
|
||||||
|
$toast.success(`${userForm.value?.name} 更新成功!`)
|
||||||
|
emit('save')
|
||||||
|
} else {
|
||||||
|
$toast.error(`${userForm.value?.name} 更新失败:${result.message}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
$toast.error(`${userForm.value?.name} 更新失败!`)
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
doneNProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户状态转换,true/false转换为1/0
|
||||||
|
const userStatus = computed({
|
||||||
|
get: () => (userForm.value.is_active ? 1 : 0),
|
||||||
|
set: (value: number) => {
|
||||||
|
userForm.value.is_active = value === 1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.oper !== 'add') {
|
||||||
|
fetchUserInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VDialog scrollable :close-on-back="false" persistent eager max-width="50rem" :fullscreen="!display.mdAndUp.value">
|
||||||
|
<VCard
|
||||||
|
:title="`${props.oper === 'add' ? '新增' : '编辑'}用户${props.oper !== 'add' ? ` - ${userForm.name}` : ''}`"
|
||||||
|
class="rounded-t"
|
||||||
|
>
|
||||||
|
<DialogCloseBtn @click="emit('close')" />
|
||||||
|
<VDivider />
|
||||||
|
<VCardText class="d-flex">
|
||||||
|
<!-- 👉 Avatar -->
|
||||||
|
<VAvatar rounded="lg" size="100" class="me-6" :image="userForm.avatar" />
|
||||||
|
|
||||||
|
<!-- 👉 Upload Photo -->
|
||||||
|
<form class="d-flex flex-column justify-center gap-5">
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<VBtn color="primary" @click="refInputEl?.click()">
|
||||||
|
<VIcon icon="mdi-cloud-upload-outline" />
|
||||||
|
<span v-if="display.mdAndUp.value" class="ms-2">上传头像</span>
|
||||||
|
</VBtn>
|
||||||
|
|
||||||
|
<input ref="refInputEl" type="file" name="file" accept=".jpeg,.png,.jpg,GIF" hidden @input="changeAvatar" />
|
||||||
|
|
||||||
|
<VBtn type="reset" color="error" variant="tonal" @click="resetAvatar">
|
||||||
|
<VIcon icon="mdi-refresh" />
|
||||||
|
<span v-if="display.mdAndUp.value" class="ms-2">重置</span>
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-body-1 mb-0">允许 JPG、GIF 或 PNG 格式, 最大尺寸 800K。</p>
|
||||||
|
</form>
|
||||||
|
</VCardText>
|
||||||
|
<VCardText>
|
||||||
|
<VForm @submit.prevent="() => {}" class="mt-3">
|
||||||
|
<VRow>
|
||||||
|
<VCol md="6" cols="12">
|
||||||
|
<VTextField v-model="userForm.name" density="comfortable" label="用户名" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.email" density="comfortable" label="邮箱" type="email" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="newPassword"
|
||||||
|
density="comfortable"
|
||||||
|
:type="isNewPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
|
label="密码"
|
||||||
|
autocomplete=""
|
||||||
|
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<!-- 👉 confirm password -->
|
||||||
|
<VTextField
|
||||||
|
v-model="confirmPassword"
|
||||||
|
density="comfortable"
|
||||||
|
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||||
|
:append-inner-icon="isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
||||||
|
label="确认密码"
|
||||||
|
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VSelect
|
||||||
|
v-model="userStatus"
|
||||||
|
:items="statusItems"
|
||||||
|
item-text="title"
|
||||||
|
item-value="value"
|
||||||
|
label="状态"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
<VRow>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.settings.wechat_userid" density="comfortable" label="微信用户" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.settings.telegram_userid" density="comfortable" label="Telegram用户" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.settings.slack_userid" density="comfortable" label="Slack用户" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField v-model="userForm.settings.vocechat_userid" density="comfortable" label="VoceChat用户" />
|
||||||
|
</VCol>
|
||||||
|
<VCol cols="12" md="6">
|
||||||
|
<VTextField
|
||||||
|
v-model="userForm.settings.synologychat_userid"
|
||||||
|
density="comfortable"
|
||||||
|
label="SynologyChat用户"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
|
</VRow>
|
||||||
|
</VForm>
|
||||||
|
</VCardText>
|
||||||
|
<VCardActions class="pt-3">
|
||||||
|
<VSpacer />
|
||||||
|
<VBtn
|
||||||
|
v-if="props.oper === 'add'"
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
@click="addUser"
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
class="px-5"
|
||||||
|
>
|
||||||
|
新增
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
v-else
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
@click="updateUser"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
class="px-5"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</VBtn>
|
||||||
|
</VCardActions>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ async function loadAllUsers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户新增完成
|
||||||
|
const onUserAdd = () => {
|
||||||
|
addUserDialog.value = false
|
||||||
|
loadAllUsers()
|
||||||
|
}
|
||||||
|
|
||||||
// 加载当前用户数据
|
// 加载当前用户数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadAllUsers()
|
loadAllUsers()
|
||||||
@@ -53,7 +59,7 @@ onActivated(() => {
|
|||||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||||
|
|
||||||
<div v-if="allUsers.length > 0" class="grid gap-3 grid-user-card items-start">
|
<div v-if="allUsers.length > 0" class="grid gap-3 grid-user-card items-start">
|
||||||
<UserCard v-for="user in allUsers" :user="user" />
|
<UserCard v-for="user in allUsers" :user="user" @remove="loadAllUsers" @save="loadAllUsers" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NoDataFound
|
<NoDataFound
|
||||||
@@ -75,5 +81,14 @@ onActivated(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 弹窗 -->
|
<!-- 弹窗 -->
|
||||||
<UserAddEditDialog v-model="addUserDialog" max-width="50rem" persistent z-index="1010" />
|
<UserAddEditDialog
|
||||||
|
v-if="addUserDialog"
|
||||||
|
v-model="addUserDialog"
|
||||||
|
oper="add"
|
||||||
|
max-width="50rem"
|
||||||
|
persistent
|
||||||
|
z-index="1010"
|
||||||
|
@save="onUserAdd"
|
||||||
|
@close="addUserDialog = false"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user