Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc-auto-import-costom.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"globals": {
"defineModel": true,
"PropsOptionsMixed": true
}
}
3 changes: 3 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ declare module 'vue' {
export interface GlobalComponents {
404: typeof import('./src/components/404.vue')['default']
Avatar: typeof import('./src/components/Navigation/Avatar.vue')['default']
AvatarUpload: typeof import('./src/components/AvatarUpload/index.vue')['default']
ChangeTheme: typeof import('./src/components/Navigation/ChangeTheme.vue')['default']
Footer: typeof import('./src/components/Footer/index.vue')['default']
FullScreen: typeof import('./src/components/Navigation/FullScreen.vue')['default']
Expand All @@ -34,8 +35,10 @@ declare module 'vue' {
NDropdown: typeof import('naive-ui')['NDropdown']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NList: typeof import('naive-ui')['NList']
Expand Down
Binary file added src/assets/images/member-avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions src/modules/MemberTeam/components/AvatarUpload.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<template>
<div class="avatar-upload-box relative">
<n-avatar
circle
:size="150"
:src="avatarSource"
/>
<n-popover
v-if="!disabled"
ref="refPopover"
trigger="click"
raw
placement="bottom-start"
>
<template #trigger>
<n-button
size="tiny"
dashed
strong
type="primary"
class="absolute left-50% bottom-16px translate-x--50%"
>
<n-icon
class="mr-4px"
:component="IconPencil"
/>
编辑
</n-button>
</template>
<n-space
vertical
justify="center"
class="p-6px dark:bg-#424245"
>
<n-upload
:default-upload="false"
:show-file-list="false"
@change="handleChangeFile"
>
<n-button
text
size="tiny"
>
<template #icon>
<n-icon
:component="IconUploadOutlined"
/>
</template>
上传图片
</n-button>
</n-upload>
<n-button
text
size="tiny"
@click="handleResetDefault()"
>
<template #icon>
<n-icon
:component="IconArrowReset24Regular"
/>
</template>
恢复默认
</n-button>
</n-space>
</n-popover>
</div>
</template>


<script lang="ts" setup>
defineOptions({
name: 'AvatarUpload'
})

import { NSpace, NButton, NIcon, NUpload, NImage } from 'naive-ui'
import type { UploadFileInfo, PopoverInst } from 'naive-ui'

import { Pencil as IconPencil } from '@vicons/tabler'
import { UploadOutlined as IconUploadOutlined } from '@vicons/antd'
import { ArrowReset24Regular as IconArrowReset24Regular } from '@vicons/fluent'

import memberAvatar from '@/assets/images/member-avatar.png'

import * as FileHandler from '@/utils/fileHandler'

const refPopover = ref<PopoverInst>()
const avatarRef = ref<UploadFileInfo>()


const props = defineProps({
disabled: {
type: Boolean,
default: false
}
})
const avatarSource = defineModel<string>()

/**
* 选择文件更新回调
*/
const handleChangeFile = async (options: { fileList: UploadFileInfo[]; }) => {
refPopover.value!.setShow(false)
avatarRef.value = options.fileList[0]

FileHandler.fileToBase64Url(avatarRef.value.file)
.then((url) => {
avatarSource.value = url as string
})
.catch(() => {
avatarSource.value = memberAvatar
})
}

const handleResetDefault = () => {
refPopover.value!.setShow(false)

window.$ModalDialog.create({
title: '恢复默认头像',
closable: true,
content: () => h(
NSpace,
{
align: 'center',
justify: 'center'
},
() => [
'将',
h(NImage, { width: 80, src: avatarSource.value }),
'替换为 ➔',
h(NImage, { width: 80, src: memberAvatar })
]
),
positiveText: '确认恢复',
async onPositiveClick () {
avatarSource.value = memberAvatar
}
})
}


</script>

<style lang="scss" scoped>
.avatar-upload-box {

}
</style>
4 changes: 4 additions & 0 deletions src/modules/MemberTeam/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
PresenceBlocked10Regular
} from '@vicons/fluent'

import memberAvatar from '@/assets/images/member-avatar.png'

// 随机获取数组中某一项
function getRandomValueFromArray<T>(arr: Array<T>): T {
const randomIndex = Math.floor(Math.random() * arr.length)
Expand Down Expand Up @@ -108,6 +110,7 @@ export interface TypeMemberPerson {
username: string
roleId: string
userId: string
avatar: string
email: string
phone: string
rank: string
Expand All @@ -132,6 +135,7 @@ export const memberTeamList = Array.from({ length: 100 }).map((_, index) => {
roleId: roleItem.value,
rank: rankItem.value,
phone: 10000000000 + _index + '',
avatar: memberAvatar,
email: `${userId}@admin.com`,
memberStatus: Math.random() > 0.5 ? 0 : 1
} as TypeMemberPerson
Expand Down
16 changes: 10 additions & 6 deletions src/modules/MemberTeam/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ const renderIcon = (icon: Component, className = '') => {
}


const goToMemberTeamPreview = (row: TypeMemberPerson) => {
router.push({
name: 'MemberTeamPreview',
params: {
datasetId: row.userId
}
}, `成员查看-${row.username}`)
}

const createActionsColumns = (row: TypeMemberPerson) => {
return h(
NDropdown,
Expand All @@ -142,12 +151,7 @@ const createActionsColumns = (row: TypeMemberPerson) => {
icon: renderIcon(IconNotepadPerson24Regular),
props: {
onClick: () => {
router.push({
name: 'MemberTeamPreview',
params: {
datasetId: row.userId
}
}, `成员查看-${row.username}`)
goToMemberTeamPreview(row)
}
}
},
Expand Down
69 changes: 50 additions & 19 deletions src/modules/MemberTeam/pages/preview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,59 @@
<n-card
:bordered="false"
title="成员管理-成员信息查看"
class="bg-transparent"
>
<n-spin :show="loadingForm">
<n-form
ref="refForm"
:model="memberFormModel"
label-placement="top"
label-width="auto"
disabled
:disabled="disabledForm"
>
<n-form-item
v-for="(infoItem, index) in memberInfoMap"
:key="index"
:path="infoItem.path"
:label="infoItem.label"
<n-grid
:x-gap="100"
cols="xs:1 s:1 m:2 l:2"
responsive="screen"
>
<component
:is="infoItem.render"
v-if="infoItem.render"
v-model:value="memberFormModel[infoItem.path]"
/>

<n-input
v-else
v-model:value="memberFormModel[infoItem.path]"
/>
</n-form-item>
<n-form-item>
<n-grid-item>
<n-grid
cols="l:1"
responsive="screen"
>
<n-form-item-gi
v-for="(infoItem, index) in memberInfoMap"
:key="index"
:path="infoItem.path"
:label="infoItem.label"
>
<component
:is="infoItem.render"
v-if="infoItem.render"
v-model:value="memberFormModel[infoItem.path]"
/>

<n-input
v-else
v-model:value="memberFormModel[infoItem.path]"
/>
</n-form-item-gi>
</n-grid>
</n-grid-item>
<n-grid-item>
<n-form-item
path="avatar"
label="成员头像"
>
<AvatarUpload
v-model="memberFormModel.avatar"
:disabled="disabledForm"
/>
</n-form-item>
</n-grid-item>
</n-grid>

<n-form-item v-if="!disabledForm">
<n-button
type="primary"
:loading="loadingSubmit"
Expand All @@ -48,7 +74,8 @@
</template>

<script lang="ts" setup>
import { NRadioGroup, NRadio, NSelect } from 'naive-ui'
import AvatarUpload from '@/modules/MemberTeam/components/AvatarUpload.vue'
import { NRadioGroup, NRadio, NSelect, NButton } from 'naive-ui'
import { sleep } from '@/utils/request'

import type { TypeMemberPerson } from '../data'
Expand All @@ -65,10 +92,14 @@ const route = useRoute()
const router = useRouter()


const disabledForm = ref(true)


const memberFormModel = ref<TypeMemberPerson>({
username: '',
roleId: '',
userId: '',
avatar: '',
email: '',
rank: '',
phone: '',
Expand Down
38 changes: 38 additions & 0 deletions src/utils/fileHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* File 文件流转为 Base64
*/
export const fileToBase64Url = (file): Promise<string | ArrayBuffer | null> => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function () {
resolve(reader.result)
}
reader.onerror = function (error) {
reject(error)
}
})
}

/**
* Base64 转为 File 文件流
*/
// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
export const base64UrlToFile = async (url: string, filename: string, mimeType?: string) => {
if (url.startsWith('data:')) {
const arr = url.split(',')
const mime = arr[0].match(/:(.*?);/)![1]
const bstr = atob(arr[arr.length - 1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while(n--){
u8arr[n] = bstr.charCodeAt(n)
}
const file = new File([u8arr], filename, {type:mime || mimeType})
return Promise.resolve(file)
}

return fetch(url)
.then(res => res.arrayBuffer())
.then(buf => new File([buf], filename,{type:mimeType}))
}
7 changes: 6 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ export default defineConfig(({ mode }) => {
: '/',
plugins: [
UnoCSS(),
vue(),
vue({
script: {
// https://blog.vuejs.org/posts/vue-3-3#definemodel
defineModel: true
}
}),
AutoImport({
include: [
/\.[tj]sx?$/,
Expand Down