refactor: component dialog, select input, select zone, tree view
This commit is contained in:
parent
5d292c5a5c
commit
51f41abc4b
4 changed files with 109 additions and 23 deletions
|
|
@ -45,7 +45,7 @@ const currentTab = defineModel<string>('currentTab');
|
||||||
:model-value="modal"
|
:model-value="modal"
|
||||||
@update:model-value="(v) => (modal = beforeClose ? beforeClose() : v)"
|
@update:model-value="(v) => (modal = beforeClose ? beforeClose() : v)"
|
||||||
@before-show="show"
|
@before-show="show"
|
||||||
@hide="hideCloseEvent !== undefined && hideCloseEvent ? '' : close"
|
@hide="hideCloseEvent !== undefined && hideCloseEvent ? '' : close?.()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="surface-1"
|
class="surface-1"
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,23 @@ const props = withDefaults(
|
||||||
option: Record<string, unknown>[];
|
option: Record<string, unknown>[];
|
||||||
optionLabel?: string;
|
optionLabel?: string;
|
||||||
optionValue?: string;
|
optionValue?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
|
||||||
|
hideSelected?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
incremental?: boolean;
|
incremental?: boolean;
|
||||||
|
fillInput?: boolean;
|
||||||
|
|
||||||
rules?: ((value: string) => string | true)[];
|
rules?: ((value: string) => string | true)[];
|
||||||
}>(),
|
}>(),
|
||||||
{ option: () => [], optionLabel: 'label', optionValue: 'value' },
|
{
|
||||||
|
option: () => [],
|
||||||
|
optionLabel: 'label',
|
||||||
|
optionValue: 'value',
|
||||||
|
hideSelected: true,
|
||||||
|
fillInput: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
|
|
@ -53,14 +62,15 @@ watch(
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-select
|
<q-select
|
||||||
|
:placeholder="placeholder"
|
||||||
outlined
|
outlined
|
||||||
:clearable
|
:clearable
|
||||||
use-input
|
use-input
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
hide-selected
|
:hideSelected
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
:fill-input="!!model"
|
:fill-input="fillInput && !!model"
|
||||||
:hide-dropdown-icon="readonly"
|
:hide-dropdown-icon="readonly"
|
||||||
input-debounce="0"
|
input-debounce="0"
|
||||||
:option-value="optionValue"
|
:option-value="optionValue"
|
||||||
|
|
@ -70,7 +80,7 @@ watch(
|
||||||
:readonly
|
:readonly
|
||||||
:label="label"
|
:label="label"
|
||||||
:options="incremental ? option : options"
|
:options="incremental ? option : options"
|
||||||
:for="`select-${id}`"
|
:for="`${id}`"
|
||||||
@filter="
|
@filter="
|
||||||
(val, update) => {
|
(val, update) => {
|
||||||
incremental ? $emit('filter', val, update) : defaultFilter(val, update);
|
incremental ? $emit('filter', val, update) : defaultFilter(val, update);
|
||||||
|
|
@ -88,8 +98,8 @@ watch(
|
||||||
</q-item>
|
</q-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="$slots.name" v-slot:selected-item="scope">
|
<template v-if="$slots.selectedItem" v-slot:selected-item="scope">
|
||||||
<slot name="selected-item" :scope="scope"></slot>
|
<slot name="selectedItem" :scope="scope"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="$slots.option" v-slot:option="scope">
|
<template v-if="$slots.option" v-slot:option="scope">
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,50 @@ const props = withDefaults(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
items: any;
|
items: any;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
borderSearchSection?: boolean;
|
||||||
|
itemClass?: string;
|
||||||
|
noPadding?: boolean;
|
||||||
|
noItemsIcon?: string;
|
||||||
|
noItemsLabel?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
items: () => [],
|
items: () => [],
|
||||||
color: 'var(--brand-1)',
|
color: 'var(--brand-1)',
|
||||||
|
noItemsIcon: 'mdi-close',
|
||||||
|
itemClass: 'col-md-2 col-sm-6 col-12',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function select(item: unknown) {
|
defineExpose({ select });
|
||||||
if (selectedItem.value.includes(item)) {
|
|
||||||
let index = selectedItem.value.indexOf(item);
|
function select(item?: unknown, all?: boolean) {
|
||||||
selectedItem.value.splice(index, 1);
|
if (all) {
|
||||||
} else selectedItem.value.push(item);
|
if (props.items.every((item) => selectedItem.value.includes(item))) {
|
||||||
|
selectedItem.value = selectedItem.value.filter(
|
||||||
|
(item) => !props.items.includes(item),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
props.items.forEach((i) => {
|
||||||
|
const productExists = selectedItem.value.some((item) => item === i);
|
||||||
|
if (!productExists) {
|
||||||
|
selectedItem.value.push(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedItem.value.includes(item)) {
|
||||||
|
const index = selectedItem.value.indexOf(item);
|
||||||
|
selectedItem.value.splice(index, 1);
|
||||||
|
} else selectedItem.value.push(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<section class="full-width column q-pa-md">
|
<section class="full-width column">
|
||||||
<header class="row items-center no-wrap q-mb-md">
|
<header
|
||||||
|
class="row items-center no-wrap q-px-md q-py-sm"
|
||||||
|
:class="{ 'bordered surface-3 ': borderSearchSection }"
|
||||||
|
>
|
||||||
<div class="col"><slot name="top"></slot></div>
|
<div class="col"><slot name="top"></slot></div>
|
||||||
<q-input
|
<q-input
|
||||||
for="input-search"
|
for="input-search"
|
||||||
|
|
@ -40,14 +67,11 @@ function select(item: unknown) {
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</header>
|
</header>
|
||||||
|
<slot name="tab"></slot>
|
||||||
|
|
||||||
<section class="col">
|
<section class="col q-ma-md">
|
||||||
<div class="row q-col-gutter-md">
|
<div v-if="items.length > 0" class="row q-col-gutter-md">
|
||||||
<div
|
<div v-for="(item, i) in items" :key="i" :class="`${itemClass}`">
|
||||||
v-for="(item, i) in items"
|
|
||||||
:key="i"
|
|
||||||
class="col-md-2 col-sm-6 col-12"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="rounded cursor-pointer relative-position"
|
class="rounded cursor-pointer relative-position"
|
||||||
:style="`border: 1px solid ${selectedItem.includes(item) ? color : 'transparent'}`"
|
:style="`border: 1px solid ${selectedItem.includes(item) ? color : 'transparent'}`"
|
||||||
|
|
@ -64,6 +88,14 @@ function select(item: unknown) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="flex justify-center full-height">
|
||||||
|
<span class="col column items-center justify-center app-text-muted">
|
||||||
|
<q-avatar style="background: var(--surface-0)" class="q-mb-md">
|
||||||
|
<q-icon :name="noItemsIcon" />
|
||||||
|
</q-avatar>
|
||||||
|
{{ noItemsLabel || $t('general.noData') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import DeleteButton from '../button/DeleteButton.vue';
|
||||||
|
|
||||||
type Node = {
|
type Node = {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
@ -17,10 +18,14 @@ type Props = {
|
||||||
level?: number;
|
level?: number;
|
||||||
keyTitle?: string;
|
keyTitle?: string;
|
||||||
keySubtitle?: string;
|
keySubtitle?: string;
|
||||||
expandable?: boolean;
|
|
||||||
hideCheckBox?: boolean;
|
|
||||||
iconSize?: string;
|
iconSize?: string;
|
||||||
|
|
||||||
|
hideCheckBox?: boolean;
|
||||||
|
expandable?: boolean;
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
|
deleteable?: boolean;
|
||||||
|
movable?: boolean;
|
||||||
|
|
||||||
selectedNode?: Node[];
|
selectedNode?: Node[];
|
||||||
ancestorNode?: Node[];
|
ancestorNode?: Node[];
|
||||||
decoration?: {
|
decoration?: {
|
||||||
|
|
@ -38,6 +43,9 @@ const emits = defineEmits<{
|
||||||
(e: 'checked'): void;
|
(e: 'checked'): void;
|
||||||
(e: 'select', node: Node, ancestor?: Node[]): void;
|
(e: 'select', node: Node, ancestor?: Node[]): void;
|
||||||
(e: 'open', node: Node, ancestor?: Node[]): void;
|
(e: 'open', node: Node, ancestor?: Node[]): void;
|
||||||
|
(e: 'delete', node: Node): void;
|
||||||
|
(e: 'moveUp', node: Node): void;
|
||||||
|
(e: 'moveDown', node: Node): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dec = props.decoration?.find((v) => v.level === (props.level || 0));
|
const dec = props.decoration?.find((v) => v.level === (props.level || 0));
|
||||||
|
|
@ -90,6 +98,34 @@ function toggleExpand(node: Node, ancestor?: []) {
|
||||||
() => (selectable ? $emit('select', node) : toggleExpand(node))
|
() => (selectable ? $emit('select', node) : toggleExpand(node))
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<div v-if="!level" style="margin-right: var(--size-1)">
|
||||||
|
<q-btn
|
||||||
|
id="btn-up"
|
||||||
|
for="btn-up"
|
||||||
|
icon="mdi-arrow-up"
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
:disable="i === 0"
|
||||||
|
style="color: hsl(var(--text-mute-2))"
|
||||||
|
@click.stop="$emit('moveUp', node)"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
id="btn-down"
|
||||||
|
for="btn-down"
|
||||||
|
icon="mdi-arrow-down"
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
class="q-mx-sm"
|
||||||
|
:disable="i === nodes.length - 1"
|
||||||
|
style="color: hsl(var(--text-mute-2))"
|
||||||
|
@click.stop="$emit('moveDown', node)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="width: var(--size-6); margin-right: var(--size-1)">
|
<div style="width: var(--size-6); margin-right: var(--size-1)">
|
||||||
<div
|
<div
|
||||||
v-if="level !== maxLevel"
|
v-if="level !== maxLevel"
|
||||||
|
|
@ -154,6 +190,13 @@ function toggleExpand(node: Node, ancestor?: []) {
|
||||||
{{ node[keySubtitle || 'subtitle'] || 'No Subtitle' }}
|
{{ node[keySubtitle || 'subtitle'] || 'No Subtitle' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<DeleteButton
|
||||||
|
v-if="!level"
|
||||||
|
class="q-ml-auto"
|
||||||
|
icon-only
|
||||||
|
@click.stop="$emit('delete', node)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -208,6 +251,7 @@ function toggleExpand(node: Node, ancestor?: []) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
<q-separator v-if="!level && i !== nodes.length - 1" class="q-mt-sm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue