refactor: refs #8463 simplify creating VnDescriptor
This commit is contained in:
parent
54d126f602
commit
4d8fb8eb5b
|
@ -1,12 +1,15 @@
|
|||
<script setup>
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
|
||||
import { onBeforeMount, computed } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
import VnSubToolbar from '../ui/VnSubToolbar.vue';
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: Number, required: false, default: null },
|
||||
dataKey: { type: String, required: true },
|
||||
url: { type: String, default: undefined },
|
||||
idInWhere: { type: Boolean, default: false },
|
||||
|
@ -16,10 +19,13 @@ const props = defineProps({
|
|||
searchDataKey: { type: String, default: undefined },
|
||||
searchbarProps: { type: Object, default: undefined },
|
||||
redirectOnError: { type: Boolean, default: false },
|
||||
visual: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const router = useRouter();
|
||||
const entityId = computed(() => props.id || route?.params?.id);
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: props.url,
|
||||
userFilter: props.filter,
|
||||
|
@ -35,7 +41,7 @@ onBeforeMount(async () => {
|
|||
|
||||
const route = router.currentRoute.value;
|
||||
try {
|
||||
await fetch(route.params.id);
|
||||
await fetch(entityId.value);
|
||||
} catch {
|
||||
const { matched: matches } = route;
|
||||
const { path } = matches.at(-1);
|
||||
|
@ -51,8 +57,7 @@ onBeforeRouteUpdate(async (to, from) => {
|
|||
router.push({ name, params: to.params });
|
||||
}
|
||||
}
|
||||
const id = to.params.id;
|
||||
if (id !== from.params.id) await fetch(id, true);
|
||||
if (entityId.value !== from.params.id) await fetch(entityId.value, true);
|
||||
});
|
||||
|
||||
async function fetch(id, append = false) {
|
||||
|
@ -61,14 +66,17 @@ async function fetch(id, append = false) {
|
|||
else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
|
||||
else arrayData.store.url = props.url.replace(regex, `/${id}`);
|
||||
await arrayData.fetch({ append, updateRouter: false });
|
||||
emit('onFetch', arrayData.store.data);
|
||||
}
|
||||
function hasRouteParam(params, valueToCheck = ':addressId') {
|
||||
return Object.values(params).includes(valueToCheck);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="$route.path" />
|
||||
</div>
|
||||
<span v-if="visual">
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="$route.path" />
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -1,262 +1,38 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnMoreOptions from './VnMoreOptions.vue';
|
||||
import { ref } from 'vue';
|
||||
import VnDescriptor from './VnDescriptor.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subtitle: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
module: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'md-width',
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const entity = ref({});
|
||||
const isLoading = ref(false);
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
function getValueFromPath(path) {
|
||||
if (!path) return;
|
||||
const keys = path.toString().split('.');
|
||||
let current = entity.value;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current[key] === undefined) return undefined;
|
||||
else current = current[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
const iconModule = computed(() => route.matched[1].meta.icon);
|
||||
const toModule = computed(() =>
|
||||
route.matched[1].path.split('/').length > 2
|
||||
? route.matched[1].redirect
|
||||
: route.matched[1].children[0].redirect,
|
||||
);
|
||||
|
||||
function setData(data) {
|
||||
const newData = (Array.isArray(data) ? data[0] : data) ?? {};
|
||||
entity.value = newData;
|
||||
isLoading.value = false;
|
||||
if (newData) emit('onFetch', newData);
|
||||
}
|
||||
const entity = ref();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
{{ id }}
|
||||
<component
|
||||
:is="card"
|
||||
:id
|
||||
:visual="false"
|
||||
@on-fetch="(data) => setData(data)"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<div class="descriptor">
|
||||
<template v-if="entity && !isLoading">
|
||||
<div class="header bg-primary q-pa-sm justify-between">
|
||||
<slot name="header-extra-action"
|
||||
><QBtn
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="md"
|
||||
:icon="iconModule"
|
||||
color="white"
|
||||
class="link"
|
||||
:to="$attrs['to-module'] ?? toModule"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.goToModuleIndex') }}
|
||||
</QTooltip>
|
||||
</QBtn></slot
|
||||
>
|
||||
<QBtn
|
||||
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="md"
|
||||
icon="preview"
|
||||
color="white"
|
||||
class="link"
|
||||
v-if="summary"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.smartCard.openSummary') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
<RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }">
|
||||
<QBtn
|
||||
class="link"
|
||||
color="white"
|
||||
dense
|
||||
flat
|
||||
icon="launch"
|
||||
round
|
||||
size="md"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.cardDescriptor.summary') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</RouterLink>
|
||||
<VnMoreOptions v-if="$slots.menu">
|
||||
<template #menu="{ menuRef }">
|
||||
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
|
||||
</template>
|
||||
</VnMoreOptions>
|
||||
</div>
|
||||
<slot name="before" />
|
||||
<div class="body q-py-sm">
|
||||
<QList dense>
|
||||
<QItemLabel header class="ellipsis text-h5" :lines="1">
|
||||
<div class="title">
|
||||
<span v-if="$props.title" :title="getValueFromPath(title)">
|
||||
{{ getValueFromPath(title) ?? $props.title }}
|
||||
</span>
|
||||
<slot v-else name="description" :entity="entity">
|
||||
<span :title="entity.name">
|
||||
{{ entity.name }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
</QItemLabel>
|
||||
<QItem dense>
|
||||
<QItemLabel class="subtitle" caption>
|
||||
#{{ getValueFromPath(subtitle) ?? entity.id }}
|
||||
</QItemLabel>
|
||||
</QItem>
|
||||
</QList>
|
||||
<div class="list-box q-mt-xs">
|
||||
<slot name="body" :entity="entity" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<slot name="icons" :entity="entity" />
|
||||
</div>
|
||||
<div class="actions justify-center">
|
||||
<slot name="actions" :entity="entity" />
|
||||
</div>
|
||||
<slot name="after" />
|
||||
</template>
|
||||
<!-- Skeleton -->
|
||||
<SkeletonDescriptor v-if="!entity || isLoading" />
|
||||
</div>
|
||||
<QInnerLoading
|
||||
:label="t('globals.pleaseWait')"
|
||||
:showing="isLoading"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.body {
|
||||
background-color: var(--vn-section-color);
|
||||
.text-h5 {
|
||||
font-size: 20px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.q-item {
|
||||
min-height: 20px;
|
||||
|
||||
.link {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.vn-label-value {
|
||||
display: flex;
|
||||
padding: 0px 16px;
|
||||
.label {
|
||||
color: var(--vn-label-color);
|
||||
font-size: 14px;
|
||||
|
||||
&:not(:has(a))::after {
|
||||
content: ':';
|
||||
@on-fetch="
|
||||
(data) => {
|
||||
entity = data;
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
.value {
|
||||
color: var(--vn-text-color);
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
.info {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
span {
|
||||
color: var(--vn-text-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--vn-text-color);
|
||||
font-size: 16px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.list-box {
|
||||
.q-item__label {
|
||||
color: var(--vn-label-color);
|
||||
padding-bottom: 0%;
|
||||
}
|
||||
}
|
||||
.descriptor {
|
||||
width: 256px;
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icons {
|
||||
margin: 0 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.q-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
margin: 0 5px;
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
"
|
||||
/>
|
||||
<VnDescriptor v-model="entity" v-bind="$attrs">
|
||||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
||||
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
|
||||
</template>
|
||||
</VnDescriptor>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
import VnMoreOptions from './VnMoreOptions.vue';
|
||||
|
||||
const entity = defineModel({ type: Object, default: null });
|
||||
const $props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subtitle: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'md-width',
|
||||
},
|
||||
module: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const state = useState();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { copyText } = useClipboard();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const DESCRIPTOR_PROXY = 'DescriptorProxy';
|
||||
const moduleName = ref();
|
||||
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
|
||||
|
||||
function getName() {
|
||||
let name = $props.module;
|
||||
if (name.includes(DESCRIPTOR_PROXY)) {
|
||||
name = name.split(DESCRIPTOR_PROXY)[0];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
const routeName = computed(() => {
|
||||
let routeName = getName();
|
||||
return `${routeName}Summary`;
|
||||
});
|
||||
|
||||
function getValueFromPath(path) {
|
||||
if (!path) return;
|
||||
const keys = path.toString().split('.');
|
||||
let current = entity.value;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current[key] === undefined) return undefined;
|
||||
else current = current[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function copyIdText(id) {
|
||||
copyText(id, {
|
||||
component: {
|
||||
copyValue: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const iconModule = computed(() => {
|
||||
moduleName.value = getName();
|
||||
if (isSameModuleName) {
|
||||
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
|
||||
?.meta?.icon;
|
||||
} else {
|
||||
return route.matched[1].meta.icon;
|
||||
}
|
||||
});
|
||||
|
||||
const toModule = computed(() => {
|
||||
moduleName.value = getName();
|
||||
if (isSameModuleName) {
|
||||
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
|
||||
?.children[0]?.redirect;
|
||||
} else {
|
||||
return route.matched[1].path.split('/').length > 2
|
||||
? route.matched[1].redirect
|
||||
: route.matched[1].children[0].redirect;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="descriptor">
|
||||
<template v-if="entity">
|
||||
<div class="header bg-primary q-pa-sm justify-between">
|
||||
<slot name="header-extra-action">
|
||||
<QBtn
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="md"
|
||||
:icon="iconModule"
|
||||
color="white"
|
||||
class="link"
|
||||
:to="toModule"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.goToModuleIndex') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</slot>
|
||||
<QBtn
|
||||
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="md"
|
||||
icon="preview"
|
||||
color="white"
|
||||
class="link"
|
||||
v-if="summary"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.smartCard.openSummary') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
|
||||
<QBtn
|
||||
class="link"
|
||||
color="white"
|
||||
dense
|
||||
flat
|
||||
icon="launch"
|
||||
round
|
||||
size="md"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.cardDescriptor.summary') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</RouterLink>
|
||||
<VnMoreOptions v-if="$slots.menu">
|
||||
<template #menu="{ menuRef }">
|
||||
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
|
||||
</template>
|
||||
</VnMoreOptions>
|
||||
</div>
|
||||
<slot name="before" />
|
||||
<div class="body q-py-sm">
|
||||
<QList dense>
|
||||
<QItemLabel header class="ellipsis text-h5" :lines="1">
|
||||
<div class="title">
|
||||
<span v-if="$props.title" :title="getValueFromPath(title)">
|
||||
{{ getValueFromPath(title) ?? $props.title }}
|
||||
</span>
|
||||
<slot v-else name="description" :entity="entity">
|
||||
<span :title="entity.name">
|
||||
{{ entity.name }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
</QItemLabel>
|
||||
<QItem>
|
||||
<QItemLabel class="subtitle">
|
||||
#{{ getValueFromPath(subtitle) ?? entity.id }}
|
||||
</QItemLabel>
|
||||
<QBtn
|
||||
round
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
icon="content_copy"
|
||||
color="primary"
|
||||
@click.stop="copyIdText(entity.id)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.copyId') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QItem>
|
||||
</QList>
|
||||
<div class="list-box q-mt-xs">
|
||||
<slot name="body" :entity="entity" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<slot name="icons" :entity="entity" />
|
||||
</div>
|
||||
<div class="actions justify-center" data-cy="descriptor_actions">
|
||||
<slot name="actions" :entity="entity" />
|
||||
</div>
|
||||
<slot name="after" />
|
||||
</template>
|
||||
<SkeletonDescriptor v-if="!entity" />
|
||||
</div>
|
||||
<QInnerLoading :label="t('globals.pleaseWait')" :showing="!entity" color="primary" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.body {
|
||||
background-color: var(--vn-section-color);
|
||||
.text-h5 {
|
||||
font-size: 20px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.q-item {
|
||||
min-height: 20px;
|
||||
|
||||
.link {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.vn-label-value {
|
||||
display: flex;
|
||||
padding: 0px 16px;
|
||||
.label {
|
||||
color: var(--vn-label-color);
|
||||
font-size: 14px;
|
||||
|
||||
&:not(:has(a))::after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
.value {
|
||||
color: var(--vn-text-color);
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
.info {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
span {
|
||||
color: var(--vn-text-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--vn-text-color);
|
||||
font-size: 16px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.list-box {
|
||||
.q-item__label {
|
||||
color: var(--vn-label-color);
|
||||
padding-bottom: 0%;
|
||||
}
|
||||
}
|
||||
.descriptor {
|
||||
width: 256px;
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icons {
|
||||
margin: 0 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.q-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
margin: 0 5px;
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
globals:
|
||||
copyId: Copy ID
|
||||
es:
|
||||
globals:
|
||||
copyId: Copiar ID
|
||||
</i18n>
|
|
@ -4,8 +4,6 @@ import { useRoute } from 'vue-router';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { toCurrency, toDate } from 'src/filters';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import filter from './OrderFilter.js';
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
|
@ -57,14 +55,11 @@ const getConfirmationValue = (isConfirmed) => {
|
|||
/>
|
||||
<CardDescriptorBeta
|
||||
v-bind="$attrs"
|
||||
:id
|
||||
:id="entityId"
|
||||
:card="OrderCard"
|
||||
ref="descriptor"
|
||||
:url="`Orders/${entityId}`"
|
||||
:filter="filter"
|
||||
title="client.name"
|
||||
@on-fetch="setData"
|
||||
data-key="Order"
|
||||
module="Order"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<VnLv
|
||||
|
|
Loading…
Reference in New Issue