mirror of https://github.com/halo-dev/halo-admin
parent
efad1d8268
commit
ce03f4c923
|
@ -34,11 +34,11 @@
|
|||
"floating-vue": "2.0.0-beta.16",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"pinia": "^2.0.14",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.2.37",
|
||||
"vue-filepond": "^7.0.3",
|
||||
"vue-grid-layout": "3.0.0-beta1",
|
||||
"vue-router": "^4.0.16",
|
||||
"vue-starport": "^0.3.0"
|
||||
"vue-router": "^4.0.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.1.3",
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts" setup>
|
||||
import { BasicLayout } from "@/layouts";
|
||||
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 { Starport } from "vue-starport";
|
||||
import { axiosInstance } from "@/utils/api-client";
|
||||
import type { User } from "@/types/extension";
|
||||
|
||||
|
@ -49,16 +48,24 @@ provide("user", user);
|
|||
|
||||
const activeTab = ref();
|
||||
|
||||
const { name: currentRouteName } = useRoute();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// set default active tab
|
||||
onMounted(() => {
|
||||
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;
|
||||
});
|
||||
|
||||
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 tab = tabs.find((tab) => tab.id === id);
|
||||
if (tab) {
|
||||
|
@ -73,17 +80,13 @@ const handleTabChange = (id: string) => {
|
|||
<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="flex">
|
||||
<Starport
|
||||
:duration="400"
|
||||
:port="`user-profile-${user?.metadata?.name}`"
|
||||
class="h-24 w-24 sm:h-32 sm:w-32"
|
||||
>
|
||||
<div class="h-24 w-24 sm:h-32 sm:w-32">
|
||||
<img
|
||||
:src="user?.spec?.avatar"
|
||||
alt="Avatar"
|
||||
class="h-full w-full rounded-full ring-4 ring-white drop-shadow-lg"
|
||||
/>
|
||||
</Starport>
|
||||
</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"
|
||||
|
|
|
@ -42,6 +42,7 @@ importers:
|
|||
tailwindcss-safe-area: ^0.2.2
|
||||
tailwindcss-themeable: ^1.3.0
|
||||
typescript: ~4.7.4
|
||||
uuid: ^8.3.2
|
||||
vite: ^2.9.12
|
||||
vite-compression-plugin: ^0.0.4
|
||||
vite-plugin-externals: ^0.5.0
|
||||
|
@ -53,7 +54,6 @@ importers:
|
|||
vue-filepond: ^7.0.3
|
||||
vue-grid-layout: 3.0.0-beta1
|
||||
vue-router: ^4.0.16
|
||||
vue-starport: ^0.3.0
|
||||
vue-tsc: ^0.34.17
|
||||
dependencies:
|
||||
'@halo-dev/admin-api': 1.1.0
|
||||
|
@ -65,11 +65,11 @@ importers:
|
|||
floating-vue: 2.0.0-beta.16_vue@3.2.37
|
||||
lodash.clonedeep: 4.5.0
|
||||
pinia: 2.0.14_j6bzmzd4ujpabbp5objtwxyjp4
|
||||
uuid: 8.3.2
|
||||
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-router: 4.0.16_vue@3.2.37
|
||||
vue-starport: 0.3.0
|
||||
devDependencies:
|
||||
'@rushstack/eslint-patch': 1.1.3
|
||||
'@tailwindcss/aspect-ratio': 0.4.0_tailwindcss@3.1.4
|
||||
|
@ -6723,7 +6723,6 @@ packages:
|
|||
/uuid/8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/v8-compile-cache/2.3.0:
|
||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
||||
|
@ -7011,15 +7010,6 @@ packages:
|
|||
'@vue/devtools-api': 6.1.4
|
||||
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:
|
||||
resolution: {integrity: sha512-jzUXky44ZLHC4daaJag7FQr3idlPYN719/K1eObGljz5KaS2UnVGTU/XSYCd7d6ampYYg4OsyalbHyJIxV0aEQ==}
|
||||
hasBin: true
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { RouterView } from "vue-router";
|
||||
import { StarportCarrier } from "vue-starport";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StarportCarrier>
|
||||
<RouterView />
|
||||
</StarportCarrier>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -13,7 +13,6 @@ import { useRoute, useRouter } from "vue-router";
|
|||
import { roles } from "@/modules/system/roles/roles-mock";
|
||||
import { ref } from "vue";
|
||||
import { users } from "@/modules/system/users/users-mock";
|
||||
import { Starport } from "vue-starport";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
|
@ -128,11 +127,7 @@ const handleRouteToUser = (username: string) => {
|
|||
<div class="flex items-center px-4 py-4">
|
||||
<div class="flex min-w-0 flex-1 items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<Starport
|
||||
:duration="400"
|
||||
:port="`user-profile-${user.name}`"
|
||||
class="h-12 w-12"
|
||||
>
|
||||
<div class="h-12 w-12">
|
||||
<div
|
||||
class="overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||
>
|
||||
|
@ -142,7 +137,7 @@ const handleRouteToUser = (username: string) => {
|
|||
class="h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</Starport>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4"
|
||||
|
|
|
@ -12,12 +12,13 @@ import {
|
|||
VSpace,
|
||||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import UserCreationModal from "./components/UserCreationModal.vue";
|
||||
import { ref } from "vue";
|
||||
import { Starport } from "vue-starport";
|
||||
import { axiosInstance } from "@halo-dev/admin-shared";
|
||||
import type { User } from "@/types/extension";
|
||||
|
||||
const checkAll = ref(false);
|
||||
const creationModal = ref<boolean>(false);
|
||||
const users = ref<User[]>([]);
|
||||
|
||||
const handleFetchUsers = async () => {
|
||||
|
@ -32,6 +33,11 @@ const handleFetchUsers = async () => {
|
|||
handleFetchUsers();
|
||||
</script>
|
||||
<template>
|
||||
<UserCreationModal
|
||||
v-model:visible="creationModal"
|
||||
@close="handleFetchUsers"
|
||||
/>
|
||||
|
||||
<VPageHeader title="用户">
|
||||
<template #icon>
|
||||
<IconUserSettings class="mr-2 self-center" />
|
||||
|
@ -44,7 +50,7 @@ handleFetchUsers();
|
|||
</template>
|
||||
角色管理
|
||||
</VButton>
|
||||
<VButton type="secondary">
|
||||
<VButton type="secondary" @click="creationModal = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
|
@ -204,17 +210,13 @@ handleFetchUsers();
|
|||
/>
|
||||
</div>
|
||||
<div v-if="user.spec.avatar" class="mr-4">
|
||||
<Starport
|
||||
:duration="400"
|
||||
:port="`user-profile-${user.metadata.name}`"
|
||||
class="h-12 w-12"
|
||||
>
|
||||
<div class="h-12 w-12">
|
||||
<img
|
||||
:alt="user.spec.displayName"
|
||||
:src="user.spec.avatar"
|
||||
class="h-full w-full overflow-hidden rounded border bg-white hover:shadow-sm"
|
||||
/>
|
||||
</Starport>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<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