add user page

This commit is contained in:
jxxghp
2023-07-09 16:14:15 +08:00
parent 4bcbcdf6d9
commit 20c801a159
4 changed files with 270 additions and 367 deletions

View File

@@ -414,3 +414,15 @@ export interface Context {
// 种子信息
torrent_info: TorrentInfo,
}
// 用户信息
export interface User {
id: number,
name: string,
password: string,
email: string,
is_active: boolean,
is_superuser: boolean,
avatar: string,
};

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import AccountSettingAccount from "@/views/account-setting/AccountSettingAccount.vue";
import AccountSettingSecurity from "@/views/account-setting/AccountSettingSecurity.vue";
import AccountSettingSystem from "@/views/account-setting/AccountSettingSystem.vue";
import { useRoute } from "vue-router";
@@ -10,9 +9,8 @@ const activeTab = ref(route.params.tab);
// tabs
const tabs = [
{ title: "户", icon: "mdi-account-outline", tab: "account" },
{ title: "安全", icon: "mdi-lock-open-outline", tab: "security" },
{ title: "系统", icon: "mdi-bell-outline", tab: "system" },
{ title: "户", icon: "mdi-account-outline", tab: "account" },
{ title: "系统", icon: "mdi-cog", tab: "system" },
];
</script>
@@ -32,11 +30,6 @@ const tabs = [
<AccountSettingAccount />
</VWindowItem>
<!-- Security -->
<VWindowItem value="security">
<AccountSettingSecurity />
</VWindowItem>
<!-- System -->
<VWindowItem value="system">
<AccountSettingSystem />

View File

@@ -1,30 +1,43 @@
<script lang="ts" setup>
import { requiredValidator } from "@/@validators";
import api from "@/api";
import { User } from "@/api/types";
import avatar1 from "@images/avatars/avatar-1.png";
import { useToast } from "vue-toast-notification";
const isNewPasswordVisible = ref(false);
const isConfirmPasswordVisible = ref(false);
const isPasswordVisible = ref(false);
const newPassword = ref("");
const confirmPassword = ref("");
const accountData = {
avatarImg: avatar1,
firstName: "john",
lastName: "Doe",
email: "johnDoe@example.com",
org: "ThemeSelection",
phone: "+1 (917) 543-9876",
address: "123 Main St, New York, NY 10001",
state: "New York",
zip: "10001",
country: "USA",
language: "English",
timezone: "(GMT-11:00) International Date Line West",
currency: "USD",
};
// 提示框
const $toast = useToast();
const refInputEl = ref<HTMLElement>();
const accountDataLocal = ref(structuredClone(accountData));
const isAccountDeactivated = ref(false);
// 新增用户窗口
const addUserDialog = ref(false);
const resetForm = () => {
accountDataLocal.value = structuredClone(accountData);
};
// 新增用户表单
const userForm = reactive({
name: "",
password: "",
email: "",
});
// 当前用户信息
const accountInfo = ref<User>({
id: 0,
name: "",
password: "",
email: "",
is_active: false,
is_superuser: false,
avatar: "",
});
// 所有用户信息
const allUsers = ref<User[]>([]);
// changeAvatar function
const changeAvatar = (file: Event) => {
@@ -35,72 +48,110 @@ const changeAvatar = (file: Event) => {
fileReader.readAsDataURL(files[0]);
fileReader.onload = () => {
if (typeof fileReader.result === "string")
accountDataLocal.value.avatarImg = fileReader.result;
accountInfo.value.avatar = fileReader.result;
};
}
};
// reset avatar image
const resetAvatar = () => {
accountDataLocal.value.avatarImg = accountData.avatarImg;
accountInfo.value.avatar = avatar1;
};
const timezones = [
"(GMT-11:00) International Date Line West",
"(GMT-11:00) Midway Island",
"(GMT-10:00) Hawaii",
"(GMT-09:00) Alaska",
"(GMT-08:00) Pacific Time (US & Canada)",
"(GMT-08:00) Tijuana",
"(GMT-07:00) Arizona",
"(GMT-07:00) Chihuahua",
"(GMT-07:00) La Paz",
"(GMT-07:00) Mazatlan",
"(GMT-07:00) Mountain Time (US & Canada)",
"(GMT-06:00) Central America",
"(GMT-06:00) Central Time (US & Canada)",
"(GMT-06:00) Guadalajara",
"(GMT-06:00) Mexico City",
"(GMT-06:00) Monterrey",
"(GMT-06:00) Saskatchewan",
"(GMT-05:00) Bogota",
"(GMT-05:00) Eastern Time (US & Canada)",
"(GMT-05:00) Indiana (East)",
"(GMT-05:00) Lima",
"(GMT-05:00) Quito",
"(GMT-04:00) Atlantic Time (Canada)",
"(GMT-04:00) Caracas",
"(GMT-04:00) La Paz",
"(GMT-04:00) Santiago",
"(GMT-03:30) Newfoundland",
"(GMT-03:00) Brasilia",
"(GMT-03:00) Buenos Aires",
"(GMT-03:00) Georgetown",
"(GMT-03:00) Greenland",
"(GMT-02:00) Mid-Atlantic",
"(GMT-01:00) Azores",
"(GMT-01:00) Cape Verde Is.",
"(GMT+00:00) Casablanca",
"(GMT+00:00) Dublin",
"(GMT+00:00) Edinburgh",
"(GMT+00:00) Lisbon",
"(GMT+00:00) London",
];
// 调用API加载当前用户数据
const loadAccountInfo = async () => {
try {
const user: User = await api.get(`user/current`);
accountInfo.value = user;
if (!accountInfo.value.avatar) accountInfo.value.avatar = avatar1;
} catch (error) {
console.log(error);
}
};
const currencies = [
"USD",
"EUR",
"GBP",
"AUD",
"BRL",
"CAD",
"CNY",
"CZK",
"DKK",
"HKD",
"HUF",
"INR",
];
// 保存用户信息
const saveAccountInfo = async () => {
if (newPassword.value || confirmPassword.value) {
if (newPassword.value !== confirmPassword.value) {
$toast.error("两次输入的密码不一致");
return;
}
accountInfo.value.password = newPassword.value;
}
try {
const result: { [key: string]: any } = await api.put(`user`, accountInfo.value);
if (result.success) {
$toast.success("用户信息保存成功!");
} else {
$toast.error(`用户信息保存失败:${result.message}`);
}
} catch (error) {
console.log(error);
}
};
// 调用API查询所有用户
const loadAllUsers = async () => {
try {
const result: User[] = await api.get(`/user`);
allUsers.value = result;
} catch (error) {
console.log(error);
}
};
// 删除用户
const deleteUser = async (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);
}
};
// 冻结用户
const deactivateUser = async (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);
}
};
// 新增用户
const addUser = async () => {
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>
@@ -109,12 +160,7 @@ const currencies = [
<VCard title="个人信息">
<VCardText class="d-flex">
<!-- 👉 Avatar -->
<VAvatar
rounded="lg"
size="100"
class="me-6"
:image="accountDataLocal.avatarImg"
/>
<VAvatar rounded="lg" size="100" class="me-6" :image="accountInfo.avatar" />
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-5">
@@ -139,7 +185,7 @@ const currencies = [
</VBtn>
</div>
<p class="text-body-1 mb-0">允许 JPG, GIF PNG 格式 最大尽寸 800K</p>
<p class="text-body-1 mb-0">允许 JPGGIF PNG 格式 最大尽寸 800K</p>
</form>
</VCardText>
@@ -149,100 +195,47 @@ const currencies = [
<!-- 👉 Form -->
<VForm class="mt-6">
<VRow>
<!-- 👉 First Name -->
<!-- 👉 Name -->
<VCol md="6" cols="12">
<VTextField v-model="accountDataLocal.firstName" label="First Name" />
</VCol>
<!-- 👉 Last Name -->
<VCol md="6" cols="12">
<VTextField v-model="accountDataLocal.lastName" label="Last Name" />
<VTextField readonly v-model="accountInfo.name" label="用户名" />
</VCol>
<!-- 👉 Email -->
<VCol cols="12" md="6">
<VTextField v-model="accountInfo.email" label="邮箱" type="email" />
</VCol>
<VCol cols="12" md="6">
<!-- 👉 new password -->
<VTextField
v-model="accountDataLocal.email"
label="E-mail"
type="email"
v-model="newPassword"
:type="isNewPasswordVisible ? 'text' : 'password'"
:append-inner-icon="
isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="新密码"
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
/>
</VCol>
<!-- 👉 Organization -->
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.org" label="Organization" />
</VCol>
<!-- 👉 Phone -->
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.phone" label="Phone Number" />
</VCol>
<!-- 👉 Address -->
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.address" label="Address" />
</VCol>
<!-- 👉 State -->
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.state" label="State" />
</VCol>
<!-- 👉 Zip Code -->
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.zip" label="Zip Code" />
</VCol>
<!-- 👉 Country -->
<VCol cols="12" md="6">
<VSelect
v-model="accountDataLocal.country"
label="Country"
:items="['USA', 'Canada', 'UK', 'India', 'Australia']"
/>
</VCol>
<!-- 👉 Language -->
<VCol cols="12" md="6">
<VSelect
v-model="accountDataLocal.language"
label="Language"
:items="['English', 'Spanish', 'Arabic', 'Hindi', 'Urdu']"
/>
</VCol>
<!-- 👉 Timezone -->
<VCol cols="12" md="6">
<VSelect
v-model="accountDataLocal.timezone"
label="Timezone"
:items="timezones"
:menu-props="{ maxHeight: 200 }"
/>
</VCol>
<!-- 👉 Currency -->
<VCol cols="12" md="6">
<VSelect
v-model="accountDataLocal.currency"
label="Currency"
:items="currencies"
:menu-props="{ maxHeight: 200 }"
<!-- 👉 confirm password -->
<VTextField
v-model="confirmPassword"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="确认新密码"
@click:append-inner="
isConfirmPasswordVisible = !isConfirmPasswordVisible
"
/>
</VCol>
<!-- 👉 Form Actions -->
<VCol cols="12" class="d-flex flex-wrap gap-4">
<VBtn>保存</VBtn>
<VBtn
color="secondary"
variant="tonal"
type="reset"
@click.prevent="resetForm"
>
重置
</VBtn>
<VBtn @click="saveAccountInfo">保存</VBtn>
</VCol>
</VRow>
</VForm>
@@ -250,19 +243,107 @@ const currencies = [
</VCard>
</VCol>
<VCol cols="12">
<!-- 👉 Deactivate Account -->
<VCard title="删除账户">
<VCardText>
<div>
<VCheckbox v-model="isAccountDeactivated" label="我确认删除我的账户信息" />
</div>
<VBtn :disabled="!isAccountDeactivated" color="error" class="mt-3">
删除账户
</VBtn>
</VCardText>
<VCol cols="12" v-if="accountInfo.is_superuser">
<!-- 👉 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"></th>
</tr>
</thead>
<tbody>
<tr v-for="user in allUsers" :key="user.name">
<td>
{{ user.name }}
</td>
<td>{{ user.email }}</td>
<td>
<VChip color="success" text-color="white" v-if="user.is_active"
>激活</VChip
>
<VChip color="error" text-color="white" v-else>冻结</VChip>
</td>
<td>{{ user.is_superuser ? "是" : "否" }}</td>
<td>
<IconBtn v-show="!user.is_superuser">
<VIcon icon="mdi-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem variant="plain" @click.stop="deactivateUser(user)">
<template #prepend>
<VIcon icon="mdi-lock"></VIcon>
</template>
<VListItemTitle>{{
user.is_active ? "冻结" : "解冻"
}}</VListItemTitle>
</VListItem>
<VListItem
variant="plain"
base-color="error"
@click.stop="deleteUser(user)"
>
<template #prepend>
<VIcon icon="mdi-delete"></VIcon>
</template>
<VListItemTitle>删除</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</IconBtn>
</td>
</tr>
</tbody>
</VTable>
</VCard>
</VCol>
</VRow>
<!-- 站点编辑弹窗 -->
<VDialog v-model="addUserDialog" max-width="800" persistent>
<!-- 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" label="邮箱" />
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardActions>
<VBtn @click="addUserDialog = false"> 取消 </VBtn>
<VSpacer />
<VBtn @click="addUser"> 确定 </VBtn>
</VCardActions>
</VCard>
</VDialog>
</template>

View File

@@ -1,183 +0,0 @@
<script lang="ts" setup>
const isCurrentPasswordVisible = ref(false);
const isNewPasswordVisible = ref(false);
const isConfirmPasswordVisible = ref(false);
const currentPassword = ref("12345678");
const newPassword = ref("87654321");
const confirmPassword = ref("87654321");
const recentDevices = [
{
browser: "Chrome on Windows",
device: "HP Spectre 360",
location: "New York, NY",
recentActivity: "28 Apr 2022, 18:20",
deviceIcon: { icon: "mdi-microsoft-windows", color: "primary" },
},
{
browser: "Chrome on iPhone",
device: "iPhone 12x",
location: "Los Angeles, CA",
recentActivity: "20 Apr 2022, 10:20",
deviceIcon: { icon: "mdi-cellphone", color: "error" },
},
{
browser: "Chrome on Android",
device: "Oneplus 9 Pro",
location: "San Francisco, CA",
recentActivity: "16 Apr 2022, 04:20",
deviceIcon: { icon: "mdi-android", color: "success" },
},
{
browser: "Chrome on MacOS",
device: "Apple iMac",
location: "New York, NY",
recentActivity: "28 Apr 2022, 18:20",
deviceIcon: { icon: "mdi-apple", color: "secondary" },
},
{
browser: "Chrome on Windows",
device: "HP Spectre 360",
location: "Los Angeles, CA",
recentActivity: "20 Apr 2022, 10:20",
deviceIcon: { icon: "mdi-microsoft-windows", color: "primary" },
},
{
browser: "Chrome on Android",
device: "Oneplus 9 Pro",
location: "San Francisco, CA",
recentActivity: "16 Apr 2022, 04:20",
deviceIcon: { icon: "mdi-android", color: "success" },
},
];
</script>
<template>
<VRow>
<!-- SECTION: Change Password -->
<VCol cols="12">
<VCard title="修改密码">
<VForm>
<VCardText>
<!-- 👉 Current Password -->
<VRow class="mb-3">
<VCol cols="12" md="6">
<!-- 👉 current password -->
<VTextField
v-model="currentPassword"
:type="isCurrentPasswordVisible ? 'text' : 'password'"
:append-inner-icon="
isCurrentPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="原密码"
@click:append-inner="
isCurrentPasswordVisible = !isCurrentPasswordVisible
"
/>
</VCol>
</VRow>
<!-- 👉 New Password -->
<VRow>
<VCol cols="12" md="6">
<!-- 👉 new password -->
<VTextField
v-model="newPassword"
:type="isNewPasswordVisible ? 'text' : 'password'"
:append-inner-icon="
isNewPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="新密码"
@click:append-inner="isNewPasswordVisible = !isNewPasswordVisible"
/>
</VCol>
<VCol cols="12" md="6">
<!-- 👉 confirm password -->
<VTextField
v-model="confirmPassword"
:type="isConfirmPasswordVisible ? 'text' : 'password'"
:append-inner-icon="
isConfirmPasswordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
"
label="确认新密码"
@click:append-inner="
isConfirmPasswordVisible = !isConfirmPasswordVisible
"
/>
</VCol>
</VRow>
</VCardText>
<!-- 👉 Action Buttons -->
<VCardText class="d-flex flex-wrap gap-4">
<VBtn> 保存 </VBtn>
<VBtn type="reset" color="secondary" variant="tonal"> 重置 </VBtn>
</VCardText>
</VForm>
</VCard>
</VCol>
<!-- !SECTION -->
<VCol cols="12">
<!-- SECTION: Create an API key -->
<VCard title="安全密钥">
<VRow>
<!-- 👉 Choose API Key -->
<VCol cols="12" md="5" order-md="0" order="1">
<VCardText>
<VForm @submit.prevent="() => {}">
<VRow>
<!-- 👉 Name the API Key -->
<VCol cols="12">
<VTextField label="Name the API key" />
</VCol>
<!-- 👉 Create Key Button -->
<VCol cols="12">
<VBtn type="submit" block> 保存 </VBtn>
</VCol>
</VRow>
</VForm>
</VCardText>
</VCol>
</VRow>
</VCard>
<!-- !SECTION -->
</VCol>
<!-- SECTION Recent Devices -->
<VCol cols="12">
<!-- 👉 Table -->
<VCard title="最近活跃设备">
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">浏览器</th>
<th scope="col">设备</th>
<th scope="col">地点</th>
<th scope="col">最后活动时间</th>
</tr>
</thead>
<tbody>
<tr v-for="device in recentDevices" :key="device.recentActivity">
<td>
<VIcon
start
:icon="device.deviceIcon.icon"
:color="device.deviceIcon.color"
/>
{{ device.browser }}
</td>
<td>{{ device.device }}</td>
<td>{{ device.location }}</td>
<td>{{ device.recentActivity }}</td>
</tr>
</tbody>
</VTable>
</VCard>
</VCol>
<!-- !SECTION -->
</VRow>
</template>