437 lines
10 KiB
Vue
437 lines
10 KiB
Vue
<script lang="ts" setup>
|
|
import { Icon } from '@iconify/vue';
|
|
|
|
import AppBox from 'components/app/AppBox.vue';
|
|
import AppCircle from 'components/app/AppCircle.vue';
|
|
import ToggleButton from '../button/ToggleButton.vue';
|
|
|
|
defineProps<{
|
|
data: {
|
|
name: string;
|
|
code: string;
|
|
male?: boolean;
|
|
female?: boolean;
|
|
img?: string;
|
|
detail?: { icon: string; value: string }[];
|
|
};
|
|
tag?: [{ color: string; value: string }];
|
|
disabled?: boolean;
|
|
noHover?: boolean;
|
|
noAction?: boolean;
|
|
noBg?: boolean;
|
|
history?: boolean;
|
|
prefixId: string;
|
|
separateEnter?: boolean;
|
|
}>();
|
|
|
|
defineEmits<{
|
|
(e: 'editProfile'): void;
|
|
(e: 'history'): void;
|
|
(e: 'deleteCard'): void;
|
|
(e: 'updateCard', action: 'FORM' | 'INFO', isEdit?: boolean): void;
|
|
(e: 'enterCard', action: 'FORM' | 'INFO'): void;
|
|
(e: 'viewCard', action: 'FORM' | 'INFO'): void;
|
|
(e: 'toggleStatus', status: boolean): void;
|
|
}>();
|
|
</script>
|
|
|
|
<template>
|
|
<AppBox
|
|
bordered
|
|
no-padding
|
|
class="column person-box"
|
|
:class="{
|
|
'person-box__disabled': disabled,
|
|
'person-box__no-hover': noHover,
|
|
'person-box__no-detail': !data.detail,
|
|
'person-box__no-bg': noBg,
|
|
}"
|
|
@click="$emit('enterCard', 'INFO')"
|
|
>
|
|
<div class="column items-center">
|
|
<!-- kebab menu -->
|
|
<div class="full-width flex no-wrap" v-if="!noAction">
|
|
<div style="margin-right: auto">
|
|
<span
|
|
class="tags"
|
|
v-for="v in tag"
|
|
:key="v.value"
|
|
:class="{ [`tags__${v.color}`]: true }"
|
|
style="text-wrap: nowrap"
|
|
>
|
|
{{ v.value }}
|
|
</span>
|
|
</div>
|
|
<q-btn
|
|
v-if="history"
|
|
:id="`btn-history-${prefixId}`"
|
|
flat
|
|
round
|
|
class="app-text-muted-2"
|
|
padding="xs"
|
|
icon="mdi-history"
|
|
size="sm"
|
|
@click.stop="$emit('history')"
|
|
/>
|
|
|
|
<q-btn
|
|
:id="`btn-dots-${prefixId}`"
|
|
:key="data.code"
|
|
flat
|
|
round
|
|
padding="xs"
|
|
class="app-text-muted-2"
|
|
icon="mdi-dots-vertical"
|
|
size="sm"
|
|
@click.stop
|
|
>
|
|
<q-menu class="bordered">
|
|
<q-list v-close-popup>
|
|
<q-item
|
|
clickable
|
|
dense
|
|
class="row q-py-sm"
|
|
style="white-space: nowrap"
|
|
v-close-popup
|
|
@click.stop="
|
|
separateEnter
|
|
? $emit('viewCard', 'INFO')
|
|
: $emit('enterCard', 'INFO')
|
|
"
|
|
>
|
|
<q-icon
|
|
name="mdi-eye-outline"
|
|
class="col-3"
|
|
size="xs"
|
|
style="color: hsl(var(--green-6-hsl))"
|
|
/>
|
|
<span class="col-9 q-px-md flex items-center">
|
|
{{ $t('viewDetail') }}
|
|
</span>
|
|
</q-item>
|
|
<q-item
|
|
dense
|
|
clickable
|
|
class="row q-py-sm"
|
|
style="white-space: nowrap"
|
|
@click="$emit('updateCard', 'INFO', true)"
|
|
v-close-popup
|
|
>
|
|
<q-icon
|
|
name="mdi-pencil-outline"
|
|
class="col-3"
|
|
size="xs"
|
|
style="color: hsl(var(--cyan-6-hsl))"
|
|
/>
|
|
<span class="col-9 q-px-md flex items-center">
|
|
{{ $t('edit') }}
|
|
</span>
|
|
</q-item>
|
|
<q-item
|
|
dense
|
|
clickable
|
|
@click="$emit('deleteCard')"
|
|
v-close-popup
|
|
>
|
|
<q-icon
|
|
name="mdi-trash-can-outline"
|
|
size="xs"
|
|
class="col-3 app-text-negative"
|
|
/>
|
|
<span class="col-9 q-px-md flex items-center">
|
|
{{ $t('delete') }}
|
|
</span>
|
|
</q-item>
|
|
<q-item dense>
|
|
<q-item-section class="q-py-sm">
|
|
<div class="q-pa-sm surface-2 rounded flex items-center">
|
|
<ToggleButton
|
|
two-way
|
|
:model-value="!disabled"
|
|
@click="$emit('toggleStatus', disabled === false)"
|
|
/>
|
|
<span class="q-pl-md">
|
|
{{
|
|
!disabled ? $t('switchOnLabel') : $t('switchOffLabel')
|
|
}}
|
|
</span>
|
|
</div>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
|
|
<!-- profile -->
|
|
<AppCircle
|
|
bordered
|
|
class="avatar relative-position"
|
|
style="border: 2px solid var(--border-color); overflow: visible"
|
|
@click="$emit('editProfile')"
|
|
>
|
|
<q-img
|
|
v-if="!$slots.img"
|
|
:src="data.img ?? '/no-profile.png'"
|
|
style="
|
|
object-fit: cover;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
"
|
|
>
|
|
<template #error>
|
|
<div
|
|
style="background: none"
|
|
class="full-width full-height items-center justify-center flex"
|
|
>
|
|
<q-img src="/no-profile.png" width="5rem" />
|
|
</div>
|
|
</template>
|
|
</q-img>
|
|
<slot name="img"></slot>
|
|
<q-badge
|
|
class="absolute-bottom-right"
|
|
style="
|
|
border-radius: 50%;
|
|
width: 16px;
|
|
height: 16px;
|
|
z-index: 2;
|
|
background: var(--border-color);
|
|
"
|
|
>
|
|
<q-badge
|
|
class="absolute-center"
|
|
style="
|
|
border-radius: 7px;
|
|
width: 14px;
|
|
height: 14px;
|
|
background: hsl(var(--positive-bg));
|
|
"
|
|
></q-badge>
|
|
<!-- :style="`background: hsl(var(${active ? '--positive-bg' : '--text-mute'}))`" -->
|
|
</q-badge>
|
|
</AppCircle>
|
|
|
|
<!-- name symbol -->
|
|
<span class="items-center text-center row full-width">
|
|
<span class="col ellipsis" style="width: 0">
|
|
{{ data.name }}
|
|
</span>
|
|
<Icon
|
|
v-if="data.male || data.female"
|
|
class="q-pl-xs"
|
|
:class="{
|
|
'symbol-gender': data.male || data.female,
|
|
'symbol-gender__male': !disabled && data.male,
|
|
'symbol-gender__female': !disabled && data.female,
|
|
'symbol-gender__disable': disabled,
|
|
}"
|
|
:icon="`material-symbols:${data.male ? 'male' : 'female'}`"
|
|
width="24px"
|
|
/>
|
|
</span>
|
|
<span style="color: hsl(var(--text-mute)); scale: 0.9">
|
|
{{ data.code || '-' }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- detail -->
|
|
<q-separator v-if="data.detail" class="q-my-sm q-mx-md" />
|
|
|
|
<div class="row" v-for="v in data.detail" :key="v.value" v-if="data.detail">
|
|
<div class="column items-center" style="width: min-content">
|
|
<div class="detail-icon">
|
|
<q-icon size="md" style="scale: 0.5" :name="v.icon" />
|
|
</div>
|
|
</div>
|
|
<div class="col row items-center full-width">
|
|
<span class="ellipsis">{{ v.value || '-' }}</span>
|
|
<q-tooltip
|
|
anchor="bottom left"
|
|
self="bottom left"
|
|
:offset="[10, 20]"
|
|
:delay="500"
|
|
>
|
|
{{ v.value || '-' }}
|
|
</q-tooltip>
|
|
</div>
|
|
</div>
|
|
</AppBox>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.person-box {
|
|
background-color: var(--surface-1);
|
|
transition: 100ms ease-in-out;
|
|
padding: var(--size-2);
|
|
|
|
&.person-box__disabled {
|
|
opacity: 0.5;
|
|
filter: grayscale(0.9);
|
|
|
|
.status-circle {
|
|
background-color: var(--surface-1);
|
|
}
|
|
}
|
|
|
|
&.person-box__no-detail {
|
|
padding-block: 2rem;
|
|
}
|
|
|
|
&.person-box__no-bg {
|
|
background-color: transparent;
|
|
}
|
|
|
|
& .detail-icon {
|
|
color: hsl(var(--text-mute-2));
|
|
background-color: hsla(var(--stone-6-hsl) / 0.15);
|
|
border-radius: 50%;
|
|
scale: 0.8;
|
|
}
|
|
|
|
& .bg-gender {
|
|
color: hsla(var(--_fg));
|
|
background-color: hsl(var(--_bg));
|
|
|
|
&.bg-gender__disable {
|
|
--_bg: var(--stone-6-hsl);
|
|
background-color: hsla(var(--_bg) / 0.3);
|
|
}
|
|
|
|
&.bg-gender__light {
|
|
color: unset;
|
|
background-color: hsla(var(--_bg) / 0.1);
|
|
}
|
|
}
|
|
|
|
& .symbol-gender {
|
|
color: hsla(var(--_fg));
|
|
|
|
&.symbol-gender__disable {
|
|
--_fg: var(--stone-6-hsl);
|
|
}
|
|
|
|
&.symbol-gender__male {
|
|
--_fg: var(--gender-male);
|
|
}
|
|
|
|
&.symbol-gender__female {
|
|
--_fg: var(--gender-female);
|
|
}
|
|
}
|
|
|
|
& .status-circle {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background-color: var(--teal-6);
|
|
border: 2px solid var(--border-color);
|
|
bottom: 0.6rem;
|
|
right: 0.6rem;
|
|
position: absolute;
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
&:not(.person-box__disabled):not(.person-box__no-hover):hover {
|
|
--_hover: hsl(var(--gender-male));
|
|
cursor: pointer;
|
|
box-shadow: var(--shadow-2);
|
|
}
|
|
}
|
|
|
|
.person-container {
|
|
display: grid;
|
|
gap: var(--size-6);
|
|
}
|
|
|
|
.avatar {
|
|
block-size: 5rem;
|
|
/* transform: rotate(45deg); */
|
|
|
|
& .avatar__status {
|
|
content: ' ';
|
|
display: block;
|
|
block-size: 1rem;
|
|
aspect-ratio: 1;
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
right: -0.5rem;
|
|
border: 1px solid var(--border-color);
|
|
top: calc(50% - 0.5rem);
|
|
bottom: calc(50% - 0.5rem);
|
|
background-color: hsla(var(--positive-bg) / 1);
|
|
}
|
|
|
|
/* & :deep(.q-img) {
|
|
transform: rotate(-45deg);
|
|
} */
|
|
}
|
|
|
|
.edit-profile {
|
|
cursor: pointer;
|
|
transition: opacity 0.2s ease;
|
|
|
|
&:hover {
|
|
opacity: 80%;
|
|
}
|
|
}
|
|
|
|
.tags {
|
|
display: inline-block;
|
|
color: hsla(var(--_color) / 1);
|
|
background: hsla(var(--_color) / 0.15);
|
|
border-radius: var(--radius-2);
|
|
padding-inline: var(--size-2);
|
|
}
|
|
|
|
.tags__purple {
|
|
--_color: var(--violet-11-hsl);
|
|
}
|
|
|
|
.tags__pink {
|
|
--_color: var(--pink-6-hsl);
|
|
}
|
|
|
|
.tags__green {
|
|
--_color: var(--teal-10-hsl);
|
|
}
|
|
|
|
.tags__cyan {
|
|
--_color: var(--cyan-7-hsl);
|
|
}
|
|
|
|
.tags__red {
|
|
--_color: var(--red-6-hsl);
|
|
}
|
|
|
|
.tags__yellow {
|
|
--_color: var(--orange-4-hsl);
|
|
}
|
|
|
|
.tags__orange {
|
|
--_color: var(--orange-5-hsl);
|
|
}
|
|
|
|
.tags__magenta {
|
|
--_color: var(--pink-8-hsl);
|
|
}
|
|
|
|
.dark .tags__purple {
|
|
--_color: var(--violet-10-hsl);
|
|
}
|
|
|
|
.dark .tags__green {
|
|
--_color: var(--teal-8-hsl);
|
|
}
|
|
|
|
.dark .tags__orange {
|
|
--_color: var(--orange-6-hsl);
|
|
}
|
|
|
|
.dark .tags__magenta {
|
|
--_color: var(--pink-7-hsl);
|
|
}
|
|
</style>
|