mirror of https://github.com/halo-dev/halo
parent
71bda8e956
commit
e5ef896d51
|
@ -34,11 +34,11 @@
|
||||||
"floating-vue": "2.0.0-beta.16",
|
"floating-vue": "2.0.0-beta.16",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
"vue-filepond": "^7.0.3",
|
"vue-filepond": "^7.0.3",
|
||||||
"vue-grid-layout": "3.0.0-beta1",
|
"vue-grid-layout": "3.0.0-beta1",
|
||||||
"vue-router": "^4.0.16",
|
"vue-router": "^4.0.16"
|
||||||
"vue-starport": "^0.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.1.3",
|
"@rushstack/eslint-patch": "^1.1.3",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicLayout } from "@/layouts";
|
import { BasicLayout } from "@/layouts";
|
||||||
import { IconUpload, VButton, VTabbar } from "@halo-dev/components";
|
import { IconUpload, VButton, VTabbar } from "@halo-dev/components";
|
||||||
import { onMounted, provide, ref } from "vue";
|
import { onMounted, provide, ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { Starport } from "vue-starport";
|
|
||||||
import { axiosInstance } from "@/utils/api-client";
|
import { axiosInstance } from "@/utils/api-client";
|
||||||
import type { User } from "@/types/extension";
|
import type { User } from "@/types/extension";
|
||||||
|
|
||||||
|
@ -49,16 +48,24 @@ provide("user", user);
|
||||||
|
|
||||||
const activeTab = ref();
|
const activeTab = ref();
|
||||||
|
|
||||||
const { name: currentRouteName } = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// set default active tab
|
// set default active tab
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
handleFetchUser();
|
handleFetchUser();
|
||||||
const tab = tabs.find((tab) => tab.routeName === currentRouteName);
|
const tab = tabs.find((tab) => tab.routeName === route.name);
|
||||||
activeTab.value = tab ? tab.id : tabs[0].id;
|
activeTab.value = tab ? tab.id : tabs[0].id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
async (newRouteName) => {
|
||||||
|
const tab = tabs.find((tab) => tab.routeName === newRouteName);
|
||||||
|
activeTab.value = tab ? tab.id : tabs[0].id;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const handleTabChange = (id: string) => {
|
const handleTabChange = (id: string) => {
|
||||||
const tab = tabs.find((tab) => tab.id === id);
|
const tab = tabs.find((tab) => tab.id === id);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
|
@ -73,17 +80,13 @@ const handleTabChange = (id: string) => {
|
||||||
<div class="px-4 sm:px-6 lg:px-8">
|
<div class="px-4 sm:px-6 lg:px-8">
|
||||||
<div class="-mt-12 flex items-end space-x-5 sm:-mt-16">
|
<div class="-mt-12 flex items-end space-x-5 sm:-mt-16">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Starport
|
<div class="h-24 w-24 sm:h-32 sm:w-32">
|
||||||
:duration="400"
|
|
||||||
:port="`user-profile-${user?.metadata?.name}`"
|
|
||||||
class="h-24 w-24 sm:h-32 sm:w-32"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
:src="user?.spec?.avatar"
|
:src="user?.spec?.avatar"
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
class="h-full w-full rounded-full ring-4 ring-white drop-shadow-lg"
|
class="h-full w-full rounded-full ring-4 ring-white drop-shadow-lg"
|
||||||
/>
|
/>
|
||||||
</Starport>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mt-6 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-6 sm:pb-1"
|
class="mt-6 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-6 sm:pb-1"
|
||||||
|
|
|
@ -42,6 +42,7 @@ importers:
|
||||||
tailwindcss-safe-area: ^0.2.2
|
tailwindcss-safe-area: ^0.2.2
|
||||||
tailwindcss-themeable: ^1.3.0
|
tailwindcss-themeable: ^1.3.0
|
||||||
typescript: ~4.7.4
|
typescript: ~4.7.4
|
||||||
|
uuid: ^8.3.2
|
||||||
vite: ^2.9.12
|
vite: ^2.9.12
|
||||||
vite-compression-plugin: ^0.0.4
|
vite-compression-plugin: ^0.0.4
|
||||||
vite-plugin-externals: ^0.5.0
|
vite-plugin-externals: ^0.5.0
|
||||||
|
@ -53,7 +54,6 @@ importers:
|
||||||
vue-filepond: ^7.0.3
|
vue-filepond: ^7.0.3
|
||||||
vue-grid-layout: 3.0.0-beta1
|
vue-grid-layout: 3.0.0-beta1
|
||||||
vue-router: ^4.0.16
|
vue-router: ^4.0.16
|
||||||
vue-starport: ^0.3.0
|
|
||||||
vue-tsc: ^0.34.17
|
vue-tsc: ^0.34.17
|
||||||
dependencies:
|
dependencies:
|
||||||
'@halo-dev/admin-api': 1.1.0
|
'@halo-dev/admin-api': 1.1.0
|
||||||
|
@ -65,11 +65,11 @@ importers:
|
||||||
floating-vue: 2.0.0-beta.16_vue@3.2.37
|
floating-vue: 2.0.0-beta.16_vue@3.2.37
|
||||||
lodash.clonedeep: 4.5.0
|
lodash.clonedeep: 4.5.0
|
||||||
pinia: 2.0.14_j6bzmzd4ujpabbp5objtwxyjp4
|
pinia: 2.0.14_j6bzmzd4ujpabbp5objtwxyjp4
|
||||||
|
uuid: 8.3.2
|
||||||
vue: 3.2.37
|
vue: 3.2.37
|
||||||
vue-filepond: 7.0.3_filepond@4.30.4+vue@3.2.37
|
vue-filepond: 7.0.3_filepond@4.30.4+vue@3.2.37
|
||||||
vue-grid-layout: 3.0.0-beta1
|
vue-grid-layout: 3.0.0-beta1
|
||||||
vue-router: 4.0.16_vue@3.2.37
|
vue-router: 4.0.16_vue@3.2.37
|
||||||
vue-starport: 0.3.0
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@rushstack/eslint-patch': 1.1.3
|
'@rushstack/eslint-patch': 1.1.3
|
||||||
'@tailwindcss/aspect-ratio': 0.4.0_tailwindcss@3.1.4
|
'@tailwindcss/aspect-ratio': 0.4.0_tailwindcss@3.1.4
|
||||||
|
@ -6723,7 +6723,6 @@ packages:
|
||||||
/uuid/8.3.2:
|
/uuid/8.3.2:
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/v8-compile-cache/2.3.0:
|
/v8-compile-cache/2.3.0:
|
||||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
||||||
|
@ -7011,15 +7010,6 @@ packages:
|
||||||
'@vue/devtools-api': 6.1.4
|
'@vue/devtools-api': 6.1.4
|
||||||
vue: 3.2.37
|
vue: 3.2.37
|
||||||
|
|
||||||
/vue-starport/0.3.0:
|
|
||||||
resolution: {integrity: sha512-CfwYVxJDFqj7zoDw0TAMdNdpefuTdUH3rtupsadSa1je5Z7S/XwUCdxN0vVjBEEvWh33HmqjdK0IRQMWDlV7VQ==}
|
|
||||||
dependencies:
|
|
||||||
'@vueuse/core': 8.7.5_vue@3.2.37
|
|
||||||
vue: 3.2.37
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@vue/composition-api'
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/vue-tsc/0.34.17_typescript@4.7.4:
|
/vue-tsc/0.34.17_typescript@4.7.4:
|
||||||
resolution: {integrity: sha512-jzUXky44ZLHC4daaJag7FQr3idlPYN719/K1eObGljz5KaS2UnVGTU/XSYCd7d6ampYYg4OsyalbHyJIxV0aEQ==}
|
resolution: {integrity: sha512-jzUXky44ZLHC4daaJag7FQr3idlPYN719/K1eObGljz5KaS2UnVGTU/XSYCd7d6ampYYg4OsyalbHyJIxV0aEQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { RouterView } from "vue-router";
|
import { RouterView } from "vue-router";
|
||||||
import { StarportCarrier } from "vue-starport";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<StarportCarrier>
|
<RouterView />
|
||||||
<RouterView />
|
|
||||||
</StarportCarrier>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { useRoute, useRouter } from "vue-router";
|
||||||
import { roles } from "@/modules/system/roles/roles-mock";
|
import { roles } from "@/modules/system/roles/roles-mock";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { users } from "@/modules/system/users/users-mock";
|
import { users } from "@/modules/system/users/users-mock";
|
||||||
import { Starport } from "vue-starport";
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
@ -128,11 +127,7 @@ const handleRouteToUser = (username: string) => {
|
||||||
<div class="flex items-center px-4 py-4">
|
<div class="flex items-center px-4 py-4">
|
||||||
<div class="flex min-w-0 flex-1 items-center">
|
<div class="flex min-w-0 flex-1 items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<Starport
|
<div class="h-12 w-12">
|
||||||
:duration="400"
|
|
||||||
:port="`user-profile-${user.name}`"
|
|
||||||
class="h-12 w-12"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden rounded border bg-white hover:shadow-sm"
|
class="overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||||
>
|
>
|
||||||
|
@ -142,7 +137,7 @@ const handleRouteToUser = (username: string) => {
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Starport>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4"
|
class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4"
|
||||||
|
|
|
@ -12,12 +12,13 @@ import {
|
||||||
VSpace,
|
VSpace,
|
||||||
VTag,
|
VTag,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
|
import UserCreationModal from "./components/UserCreationModal.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { Starport } from "vue-starport";
|
|
||||||
import { axiosInstance } from "@halo-dev/admin-shared";
|
import { axiosInstance } from "@halo-dev/admin-shared";
|
||||||
import type { User } from "@/types/extension";
|
import type { User } from "@/types/extension";
|
||||||
|
|
||||||
const checkAll = ref(false);
|
const checkAll = ref(false);
|
||||||
|
const creationModal = ref<boolean>(false);
|
||||||
const users = ref<User[]>([]);
|
const users = ref<User[]>([]);
|
||||||
|
|
||||||
const handleFetchUsers = async () => {
|
const handleFetchUsers = async () => {
|
||||||
|
@ -32,6 +33,11 @@ const handleFetchUsers = async () => {
|
||||||
handleFetchUsers();
|
handleFetchUsers();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<UserCreationModal
|
||||||
|
v-model:visible="creationModal"
|
||||||
|
@close="handleFetchUsers"
|
||||||
|
/>
|
||||||
|
|
||||||
<VPageHeader title="用户">
|
<VPageHeader title="用户">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconUserSettings class="mr-2 self-center" />
|
<IconUserSettings class="mr-2 self-center" />
|
||||||
|
@ -44,7 +50,7 @@ handleFetchUsers();
|
||||||
</template>
|
</template>
|
||||||
角色管理
|
角色管理
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="secondary">
|
<VButton type="secondary" @click="creationModal = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -204,17 +210,13 @@ handleFetchUsers();
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.spec.avatar" class="mr-4">
|
<div v-if="user.spec.avatar" class="mr-4">
|
||||||
<Starport
|
<div class="h-12 w-12">
|
||||||
:duration="400"
|
|
||||||
:port="`user-profile-${user.metadata.name}`"
|
|
||||||
class="h-12 w-12"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
:alt="user.spec.displayName"
|
:alt="user.spec.displayName"
|
||||||
:src="user.spec.avatar"
|
:src="user.spec.avatar"
|
||||||
class="h-full w-full overflow-hidden rounded border bg-white hover:shadow-sm"
|
class="h-full w-full overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||||
/>
|
/>
|
||||||
</Starport>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
<script lang="ts" name="UserCreationModal" setup>
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { axiosInstance } from "@halo-dev/admin-shared";
|
||||||
|
import {
|
||||||
|
IconSave,
|
||||||
|
VButton,
|
||||||
|
VInput,
|
||||||
|
VModal,
|
||||||
|
VTextarea,
|
||||||
|
} from "@halo-dev/components";
|
||||||
|
import type { User } from "@/types/extension";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Object as PropType<User | null>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:visible", "close"]);
|
||||||
|
|
||||||
|
interface creationFormState {
|
||||||
|
user: User;
|
||||||
|
saving: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const creationForm = ref<creationFormState>({
|
||||||
|
user: {
|
||||||
|
spec: {
|
||||||
|
displayName: "",
|
||||||
|
avatar: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
password: "",
|
||||||
|
bio: "",
|
||||||
|
disabled: false,
|
||||||
|
loginHistoryLimit: 0,
|
||||||
|
},
|
||||||
|
apiVersion: "v1alpha1",
|
||||||
|
kind: "User",
|
||||||
|
metadata: {
|
||||||
|
name: uuid(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
saving: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isUpdateMode = computed(() => {
|
||||||
|
return !!creationForm.value.user.metadata.creationTimestamp;
|
||||||
|
});
|
||||||
|
|
||||||
|
const creationModalTitle = computed(() => {
|
||||||
|
return isUpdateMode.value ? "编辑用户" : "新增用户";
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleVisibleChange = (visible: boolean) => {
|
||||||
|
emit("update:visible", visible);
|
||||||
|
if (!visible) {
|
||||||
|
emit("close");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateUser = async () => {
|
||||||
|
try {
|
||||||
|
creationForm.value.saving = true;
|
||||||
|
await axiosInstance.post("/api/v1alpha1/users", creationForm.value.user);
|
||||||
|
|
||||||
|
handleVisibleChange(false);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
creationForm.value.saving = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VModal
|
||||||
|
:title="creationModalTitle"
|
||||||
|
:visible="visible"
|
||||||
|
:width="700"
|
||||||
|
@update:visible="handleVisibleChange"
|
||||||
|
>
|
||||||
|
<form>
|
||||||
|
<div class="space-y-6 divide-y-0 sm:divide-y sm:divide-gray-200">
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VInput v-model="creationForm.user.metadata.name"></VInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
昵称
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VInput v-model="creationForm.user.spec.displayName"></VInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-center sm:gap-4 sm:pt-5">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">
|
||||||
|
电子邮箱
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VInput v-model="creationForm.user.spec.email"></VInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
手机号
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VInput v-model="creationForm.user.spec.phone"></VInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
头像
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VInput v-model="creationForm.user.spec.avatar"></VInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:pt-5">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
描述
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:col-span-2 sm:mt-0">
|
||||||
|
<VTextarea v-model="creationForm.user.spec.bio"></VTextarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<template #footer>
|
||||||
|
<VButton
|
||||||
|
:loading="creationForm.saving"
|
||||||
|
type="secondary"
|
||||||
|
@click="handleCreateUser"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconSave class="h-full w-full" />
|
||||||
|
</template>
|
||||||
|
保存
|
||||||
|
</VButton>
|
||||||
|
</template>
|
||||||
|
</VModal>
|
||||||
|
</template>
|
Loading…
Reference in New Issue