mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-10-31 22:57:15 +08:00 
			
		
		
		
	refactor: 调整默认头像规则,由基于性别的固定头像调整为基于昵称展示(背景颜色基于昵称计算随机)
This commit is contained in:
		
							
								
								
									
										82
									
								
								src/components/Avatar/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/components/Avatar/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| <template> | ||||
|   <a-avatar v-if="src" :size="size"> | ||||
|     <img :src="src" :alt="alt" /> | ||||
|     <template v-if="trigger" #trigger-icon><slot name="trigger-icon"></slot></template> | ||||
|   </a-avatar> | ||||
|   <a-avatar | ||||
|     v-else-if="name || text" | ||||
|     :size="size" | ||||
|     :style="{ | ||||
|       backgroundColor: avatarColor, | ||||
|     }" | ||||
|   > | ||||
|     <span v-if="name">{{ avatarName }}</span> | ||||
|     <span v-else>{{ text }}</span> | ||||
|     <template v-if="trigger" #trigger-icon><slot name="trigger-icon"></slot></template> | ||||
|   </a-avatar> | ||||
|   <a-avatar v-else :size="size"> | ||||
|     <img :src="Unknown" :alt="alt" /> | ||||
|     <template v-if="trigger" #trigger-icon><slot name="trigger-icon"></slot></template> | ||||
|   </a-avatar> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import Unknown from '@/assets/images/avatar/unknown.png' | ||||
| import * as Regexp from '@/utils/regexp' | ||||
|  | ||||
| defineOptions({ name: 'Avatar' }) | ||||
|  | ||||
| const props = withDefaults(defineProps<Props>(), { | ||||
|   color: '#168CFF', | ||||
|   size: 20, | ||||
|   alt: 'avatar', | ||||
|   trigger: false | ||||
| }) | ||||
|  | ||||
| interface Props { | ||||
|   src?: string | ||||
|   name?: string | ||||
|   text?: string | ||||
|   color?: string | ||||
|   size?: string | number | ||||
|   alt?: string | ||||
|   trigger?: boolean | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 英文开头:取传入字符串的前面两个字符,例如:Charles => Ch | ||||
|  * 超过 4 位:取字符串第一个字符,例如:系统管理员 => 系 | ||||
|  * 其他:取传入字符串的最后两个字符,例如:张三 => 张三;王多鱼:多鱼 | ||||
|  */ | ||||
| const avatarName = computed(() => { | ||||
|   const name = props.name | ||||
|   if (!name) return '' | ||||
|   if (name[0].match(Regexp.OnlyEn)) { | ||||
|     const nameArr = name.split(' ') | ||||
|     if (nameArr.length > 1 && nameArr[1][0].match(Regexp.OnlyEn)) return `${nameArr[0][0]}${nameArr[1][0]}` | ||||
|     return name.substring(0, 2) | ||||
|   } | ||||
|   if (name.length > 4) return name[0] | ||||
|   return name.substring(name.length - 2, name.length) | ||||
| }) | ||||
|  | ||||
| const colors = [ | ||||
|   '#168CFF', | ||||
|   '#7BC616', | ||||
|   '#14C9C9', | ||||
|   '#FF7D00', | ||||
|   '#FFC72E' | ||||
| ] | ||||
| const avatarColor = computed(() => { | ||||
|   const hash = (s) => { | ||||
|     let hash = 0 | ||||
|     for (let i = 0; i < s.length; i++) { | ||||
|       hash = (hash << 5) - hash + s.charCodeAt(i) | ||||
|     } | ||||
|     return Math.abs(hash) | ||||
|   } | ||||
|   return colors[hash(props.name || props.text) % (colors.length)] | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"></style> | ||||
| @@ -1,8 +1,6 @@ | ||||
| <template> | ||||
|   <a-space fill> | ||||
|     <a-avatar :size="24" shape="circle"> | ||||
|       <img :src="props.avatar" alt="avatar" /> | ||||
|     </a-avatar> | ||||
|     <Avatar :src="props.avatar" :name="props.name" :size="24" /> | ||||
|     <a-link v-if="props.isLink" @click="emit('click')"> | ||||
|       <a-typography-paragraph | ||||
|         class="link-text" | ||||
| @@ -15,7 +13,17 @@ | ||||
|         {{ props.name }} | ||||
|       </a-typography-paragraph> | ||||
|     </a-link> | ||||
|     <span v-else>{{ props.name }}</span> | ||||
|     <span v-else> | ||||
|       <a-typography-paragraph | ||||
|         :ellipsis="{ | ||||
|           rows: 1, | ||||
|           showTooltip: true, | ||||
|           css: true, | ||||
|         }" | ||||
|       > | ||||
|         {{ props.name }} | ||||
|       </a-typography-paragraph> | ||||
|     </span> | ||||
|   </a-space> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -48,10 +48,8 @@ | ||||
|       <a-dropdown trigger="hover"> | ||||
|         <a-row align="center" :wrap="false" class="user"> | ||||
|           <!-- 管理员头像 --> | ||||
|           <a-avatar :size="32"> | ||||
|             <img :src="userStore.avatar" alt="avatar" /> | ||||
|           </a-avatar> | ||||
|           <span class="username">{{ userStore.name }}</span> | ||||
|           <Avatar :src="userStore.avatar" :name="userStore.nickname" :size="32" /> | ||||
|           <span class="username">{{ userStore.nickname }}</span> | ||||
|           <icon-down /> | ||||
|         </a-row> | ||||
|         <template #content> | ||||
|   | ||||
| @@ -15,7 +15,6 @@ import { | ||||
| } from '@/apis' | ||||
| import { clearToken, getToken, setToken } from '@/utils/auth' | ||||
| import { resetHasRouteFlag } from '@/router/permission' | ||||
| import getAvatar from '@/utils/avatar' | ||||
|  | ||||
| const storeSetup = () => { | ||||
|   const userInfo = reactive<UserInfo>({ | ||||
| @@ -33,7 +32,8 @@ const storeSetup = () => { | ||||
|     roles: [], | ||||
|     permissions: [] | ||||
|   }) | ||||
|   const name = computed(() => userInfo.nickname) | ||||
|   const nickname = computed(() => userInfo.nickname) | ||||
|   const username = computed(() => userInfo.username) | ||||
|   const avatar = computed(() => userInfo.avatar) | ||||
|  | ||||
|   const token = ref(getToken() || '') | ||||
| @@ -100,7 +100,7 @@ const storeSetup = () => { | ||||
|   const getInfo = async () => { | ||||
|     const res = await getUserInfoApi() | ||||
|     Object.assign(userInfo, res.data) | ||||
|     userInfo.avatar = getAvatar(res.data.avatar, res.data.gender) | ||||
|     userInfo.avatar = res.data.avatar | ||||
|     if (res.data.roles && res.data.roles.length) { | ||||
|       roles.value = res.data.roles | ||||
|       permissions.value = res.data.permissions | ||||
| @@ -109,7 +109,8 @@ const storeSetup = () => { | ||||
|  | ||||
|   return { | ||||
|     userInfo, | ||||
|     name, | ||||
|     nickname, | ||||
|     username, | ||||
|     avatar, | ||||
|     token, | ||||
|     roles, | ||||
|   | ||||
							
								
								
									
										2
									
								
								src/types/auto-imports.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/types/auto-imports.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -82,6 +82,6 @@ declare global { | ||||
| // for type re-export | ||||
| declare global { | ||||
|   // @ts-ignore | ||||
|   export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' | ||||
|   export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' | ||||
|   import('vue') | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,7 @@ export {} | ||||
|  | ||||
| declare module 'vue' { | ||||
|   export interface GlobalComponents { | ||||
|     Avatar: typeof import('./../components/Avatar/index.vue')['default'] | ||||
|     Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] | ||||
|     Chart: typeof import('./../components/Chart/index.vue')['default'] | ||||
|     CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default'] | ||||
|   | ||||
| @@ -2,11 +2,9 @@ | ||||
|   <a-card class="card" :bordered="false"> | ||||
|     <a-row align="center" wrap :gutter="[{ xs: 0, sm: 14, md: 14, lg: 14, xl: 14, xxl: 14 }, 16]" class="content"> | ||||
|       <a-space size="medium"> | ||||
|         <a-avatar :size="68"> | ||||
|           <img :src="userStore.avatar" alt="avatar" /> | ||||
|         </a-avatar> | ||||
|         <Avatar :src="userStore.avatar" :name="userStore.nickname" :size="68" /> | ||||
|         <div class="welcome"> | ||||
|           <p class="hello">{{ goodTimeText() }}!{{ userStore.name }}</p> | ||||
|           <p class="hello">{{ goodTimeText() }}!{{ userStore.nickname }}</p> | ||||
|           <p>北海虽赊,扶摇可接;东隅已逝,桑榆非晚。</p> | ||||
|         </div> | ||||
|       </a-space> | ||||
|   | ||||
| @@ -11,10 +11,9 @@ | ||||
|           :on-before-upload="onBeforeUpload" | ||||
|         > | ||||
|           <template #upload-button> | ||||
|             <a-avatar :size="100"> | ||||
|             <Avatar :src="avatarList[0].url" :name="userStore.nickname" :size="100" trigger> | ||||
|               <template #trigger-icon><icon-camera /></template> | ||||
|               <img v-if="avatarList.length" :src="avatarList[0].url" alt="avatar" /> | ||||
|             </a-avatar> | ||||
|             </Avatar> | ||||
|           </template> | ||||
|         </a-upload> | ||||
|         <div class="name"> | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|           :scroll="{ x: '100%', y: '100%', minWidth: 1500 }" | ||||
|           :pagination="pagination" | ||||
|           :disabled-tools="['size']" | ||||
|           :disabled-column-keys="['username']" | ||||
|           :disabled-column-keys="['nickname']" | ||||
|           @refresh="search" | ||||
|         > | ||||
|           <template #top> | ||||
| @@ -40,9 +40,8 @@ | ||||
|               <template #default>导出</template> | ||||
|             </a-button> | ||||
|           </template> | ||||
|           <template #username="{ record }"> | ||||
|             <GiCellAvatar :avatar="getAvatar(record.avatar, record.gender)" :name="record.username" is-link | ||||
|                           @click="onDetail(record)" /> | ||||
|           <template #nickname="{ record }"> | ||||
|             <GiCellAvatar :avatar="record.avatar" :name="record.nickname" /> | ||||
|           </template> | ||||
|           <template #gender="{ record }"> | ||||
|             <GiCellGender :gender="record.gender" /> | ||||
| @@ -59,6 +58,7 @@ | ||||
|           </template> | ||||
|           <template #action="{ record }"> | ||||
|             <a-space> | ||||
|               <a-link v-permission="['system:user:list']" @click="onDetail(record)">详情</a-link> | ||||
|               <a-link v-permission="['system:user:update']" @click="onUpdate(record)">修改</a-link> | ||||
|               <a-link | ||||
|                 v-permission="['system:user:delete']" | ||||
| @@ -103,7 +103,6 @@ import type { Columns, Options } from '@/components/GiForm' | ||||
| import type { TableInstanceColumns } from '@/components/GiTable/type' | ||||
| import { useDownload, useTable } from '@/hooks' | ||||
| import { isMobile } from '@/utils' | ||||
| import getAvatar from '@/utils/avatar' | ||||
| import has from '@/utils/has' | ||||
| import { DisEnableStatusList } from '@/constant/common' | ||||
|  | ||||
| @@ -170,15 +169,15 @@ const columns: TableInstanceColumns[] = [ | ||||
|     fixed: !isMobile() ? 'left' : undefined | ||||
|   }, | ||||
|   { | ||||
|     title: '用户名', | ||||
|     dataIndex: 'username', | ||||
|     slotName: 'username', | ||||
|     width: 140, | ||||
|     title: '昵称', | ||||
|     dataIndex: 'nickname', | ||||
|     slotName: 'nickname', | ||||
|     minWidth: 140, | ||||
|     ellipsis: true, | ||||
|     tooltip: true, | ||||
|     fixed: !isMobile() ? 'left' : undefined | ||||
|   }, | ||||
|   { title: '昵称', dataIndex: 'nickname', width: 120, ellipsis: true, tooltip: true }, | ||||
|   { title: '用户名', dataIndex: 'username', slotName: 'username', minWidth: 140, ellipsis: true, tooltip: true }, | ||||
|   { title: '状态', slotName: 'status', align: 'center', width: 80 }, | ||||
|   { title: '性别', slotName: 'gender', align: 'center', width: 100 }, | ||||
|   { title: '所属部门', dataIndex: 'deptName', ellipsis: true, tooltip: true, width: 180 }, | ||||
| @@ -194,7 +193,7 @@ const columns: TableInstanceColumns[] = [ | ||||
|   { | ||||
|     title: '操作', | ||||
|     slotName: 'action', | ||||
|     width: 150, | ||||
|     width: 190, | ||||
|     align: 'center', | ||||
|     fixed: !isMobile() ? 'right' : undefined, | ||||
|     show: has.hasPermOr(['system:user:update', 'system:user:delete', 'system:user:resetPwd']) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user