quasar-ui-danx 0.4.9 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/danx.es.js +6509 -6230
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +7 -7
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/index.d.ts +7 -0
- package/index.ts +1 -0
- package/package.json +8 -3
- package/src/components/ActionTable/ActionMenu.vue +26 -31
- package/src/components/ActionTable/ActionTable.vue +4 -1
- package/src/components/ActionTable/Columns/ActionTableColumn.vue +14 -6
- package/src/components/ActionTable/Columns/ActionTableHeaderColumn.vue +63 -42
- package/src/components/ActionTable/Form/ActionForm.vue +55 -0
- package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +11 -5
- package/src/components/ActionTable/Form/Fields/FieldLabel.vue +18 -15
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +1 -0
- package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +44 -15
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +1 -1
- package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +12 -13
- package/src/components/ActionTable/Form/Fields/NumberField.vue +40 -55
- package/src/components/ActionTable/Form/Fields/SelectField.vue +4 -3
- package/src/components/ActionTable/Form/Fields/TextField.vue +31 -12
- package/src/components/ActionTable/Form/RenderedForm.vue +11 -10
- package/src/components/ActionTable/Form/index.ts +1 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +3 -3
- package/src/components/ActionTable/TableSummaryRow.vue +48 -37
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +2 -2
- package/src/components/ActionTable/listControls.ts +3 -2
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +30 -5
- package/src/components/Utility/Files/FilePreview.vue +72 -12
- package/src/components/Utility/Popovers/PopoverMenu.vue +34 -29
- package/src/config/index.ts +2 -1
- package/src/helpers/FileUpload.ts +59 -8
- package/src/helpers/actions.ts +27 -27
- package/src/helpers/download.ts +8 -2
- package/src/helpers/formats.ts +79 -9
- package/src/helpers/multiFileUpload.ts +6 -4
- package/src/helpers/objectStore.ts +14 -17
- package/src/helpers/request.ts +12 -0
- package/src/helpers/singleFileUpload.ts +63 -55
- package/src/helpers/utils.ts +11 -0
- package/src/index.ts +1 -0
- package/src/styles/danx.scss +5 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/quasar-reset.scss +2 -0
- package/src/styles/themes/danx/action-table.scss +24 -13
- package/src/styles/themes/danx/forms.scss +1 -19
- package/src/types/actions.d.ts +13 -4
- package/src/types/controls.d.ts +4 -4
- package/src/types/fields.d.ts +10 -9
- package/src/types/files.d.ts +10 -5
- package/src/types/index.d.ts +0 -1
- package/src/types/requests.d.ts +2 -0
- package/src/types/tables.d.ts +28 -22
- package/src/{vue-plugin.js → vue-plugin.ts} +5 -4
- package/tsconfig.json +1 -0
- package/types/index.d.ts +2 -0
package/index.d.ts
ADDED
package/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./src";
|
package/package.json
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "quasar-ui-danx",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.12",
|
4
4
|
"author": "Dan <dan@flytedesk.com>",
|
5
5
|
"description": "DanX Vue / Quasar component library",
|
6
6
|
"license": "MIT",
|
7
7
|
"type": "module",
|
8
|
-
"main": "dist/danx.
|
9
|
-
"module": "dist/danx.es.js",
|
8
|
+
"main": "./dist/danx.umd.js",
|
9
|
+
"module": "./dist/danx.es.js",
|
10
|
+
"types": "types/index.d.ts",
|
10
11
|
"scripts": {
|
11
12
|
"dev": "cd dev && quasar dev && cd ..",
|
12
13
|
"build": "vite build",
|
@@ -17,6 +18,10 @@
|
|
17
18
|
"type": "git",
|
18
19
|
"url": "https://github.com/flytedan/quasar-ui-danx"
|
19
20
|
},
|
21
|
+
"peerDependencies": {
|
22
|
+
"quasar": "^2.0.0",
|
23
|
+
"vue": "^3.4.21"
|
24
|
+
},
|
20
25
|
"devDependencies": {
|
21
26
|
"@quasar/extras": "^1.16.4",
|
22
27
|
"@types/luxon": "^3.4.2",
|
@@ -3,53 +3,48 @@
|
|
3
3
|
class="px-2 flex dx-action-button"
|
4
4
|
:items="activeActions"
|
5
5
|
:disabled="!hasTarget"
|
6
|
-
:tooltip="!hasTarget ? tooltip :
|
7
|
-
:loading="isSaving || loading"
|
6
|
+
:tooltip="!hasTarget ? tooltip : ''"
|
7
|
+
:loading="isSaving || !!loading"
|
8
8
|
:loading-component="loadingComponent"
|
9
9
|
@action-item="onAction"
|
10
10
|
/>
|
11
11
|
</template>
|
12
|
-
<script setup>
|
12
|
+
<script setup lang="ts">
|
13
13
|
import { computed, ref } from "vue";
|
14
|
+
import { ActionTarget, ResourceAction } from "../../types";
|
14
15
|
import { PopoverMenu } from "../Utility";
|
15
16
|
|
16
|
-
const
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
default: "First select records to perform a batch action"
|
28
|
-
},
|
29
|
-
loading: Boolean,
|
30
|
-
loadingComponent: {
|
31
|
-
type: [Function, Object],
|
32
|
-
default: undefined
|
33
|
-
}
|
17
|
+
const emit = defineEmits(["success"]);
|
18
|
+
const props = withDefaults(defineProps<{
|
19
|
+
actions: ResourceAction[],
|
20
|
+
target?: ActionTarget,
|
21
|
+
tooltip?: string,
|
22
|
+
loading?: boolean,
|
23
|
+
loadingComponent?: any
|
24
|
+
}>(), {
|
25
|
+
target: () => [],
|
26
|
+
tooltip: "First select records to perform a batch action",
|
27
|
+
loadingComponent: undefined
|
34
28
|
});
|
35
29
|
|
36
30
|
const hasTarget = computed(() => Array.isArray(props.target) ? props.target.length > 0 : !!props.target);
|
37
31
|
|
38
32
|
const activeActions = computed(() => props.actions.filter(action => {
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
if (Array.isArray(props.target)) {
|
34
|
+
return action.batchEnabled ? action.batchEnabled(props.target) : true;
|
35
|
+
}
|
42
36
|
|
43
|
-
|
37
|
+
return action.enabled ? action.enabled(props.target) : true;
|
44
38
|
}));
|
45
39
|
|
46
40
|
const isSaving = ref(false);
|
47
41
|
async function onAction(action) {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
42
|
+
if (!action.trigger) {
|
43
|
+
throw new Error("Action must have a trigger function! Make sure you are using useActions() or implement your own trigger function.");
|
44
|
+
}
|
45
|
+
isSaving.value = true;
|
46
|
+
await action.trigger(props.target);
|
47
|
+
isSaving.value = false;
|
48
|
+
emit("success", action);
|
54
49
|
}
|
55
50
|
</script>
|
@@ -31,6 +31,7 @@
|
|
31
31
|
:label="label"
|
32
32
|
:item-count="summary?.count || 0"
|
33
33
|
:selected-count="selectedRows.length"
|
34
|
+
:sticky-colspan="summaryColSpan"
|
34
35
|
:loading="loadingSummary"
|
35
36
|
:summary="summary"
|
36
37
|
:columns="tableColumns"
|
@@ -87,6 +88,7 @@ export interface Props {
|
|
87
88
|
summary: any;
|
88
89
|
columns: TableColumn[];
|
89
90
|
rowsPerPageOptions?: number[];
|
91
|
+
summaryColSpan?: number;
|
90
92
|
}
|
91
93
|
|
92
94
|
const props = withDefaults(defineProps<Props>(), {
|
@@ -94,7 +96,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
94
96
|
pagedItems: null,
|
95
97
|
summary: null,
|
96
98
|
loadingSummary: false,
|
97
|
-
rowsPerPageOptions: () => [10, 25, 50, 100]
|
99
|
+
rowsPerPageOptions: () => [10, 25, 50, 100],
|
100
|
+
summaryColSpan: null
|
98
101
|
});
|
99
102
|
|
100
103
|
const actionTable = ref(null);
|
@@ -3,13 +3,18 @@
|
|
3
3
|
:key="rowProps.key"
|
4
4
|
:props="rowProps"
|
5
5
|
:style="columnStyle"
|
6
|
+
class="dx-column"
|
7
|
+
:class="column.columnClass"
|
6
8
|
>
|
7
9
|
<div :style="columnStyle">
|
8
10
|
<div
|
9
11
|
class="flex items-center flex-nowrap"
|
10
|
-
:class="
|
12
|
+
:class="wrapClass"
|
11
13
|
>
|
12
|
-
<div
|
14
|
+
<div
|
15
|
+
v-if="!column.hideContent"
|
16
|
+
class="flex-grow overflow-hidden"
|
17
|
+
>
|
13
18
|
<a
|
14
19
|
v-if="column.onClick"
|
15
20
|
:class="column.innerClass"
|
@@ -25,6 +30,7 @@
|
|
25
30
|
<div
|
26
31
|
v-else
|
27
32
|
:class="column.innerClass"
|
33
|
+
class="dx-column-text"
|
28
34
|
>
|
29
35
|
<RenderVnode
|
30
36
|
v-if="column.vnode"
|
@@ -42,7 +48,8 @@
|
|
42
48
|
</div>
|
43
49
|
<div
|
44
50
|
v-if="column.actionMenu"
|
45
|
-
class="flex flex-shrink-0
|
51
|
+
class="flex flex-shrink-0"
|
52
|
+
:class="{'ml-2': !column.hideContent}"
|
46
53
|
>
|
47
54
|
<ActionMenu
|
48
55
|
class="dx-column-action-menu"
|
@@ -55,12 +62,13 @@
|
|
55
62
|
</div>
|
56
63
|
</QTd>
|
57
64
|
</template>
|
58
|
-
<script setup>
|
65
|
+
<script setup lang="ts">
|
59
66
|
import { QTd } from "quasar";
|
60
67
|
import { computed } from "vue";
|
61
68
|
import { RenderVnode } from "../../Utility";
|
62
69
|
import ActionMenu from "../ActionMenu";
|
63
70
|
import { TitleColumnFormat } from "./";
|
71
|
+
import { TableColumn } from "./../../../types";
|
64
72
|
|
65
73
|
const props = defineProps({
|
66
74
|
rowProps: {
|
@@ -74,7 +82,7 @@ const props = defineProps({
|
|
74
82
|
});
|
75
83
|
|
76
84
|
const row = computed(() => props.rowProps.row);
|
77
|
-
const column = computed(() => props.rowProps.col);
|
85
|
+
const column = computed<TableColumn>(() => props.rowProps.col);
|
78
86
|
const value = computed(() => props.rowProps.value);
|
79
87
|
const isSaving = computed(() => row.value.isSaving?.value);
|
80
88
|
|
@@ -86,7 +94,7 @@ const columnStyle = computed(() => {
|
|
86
94
|
};
|
87
95
|
});
|
88
96
|
|
89
|
-
const
|
97
|
+
const wrapClass = computed(() => ({
|
90
98
|
[column.value.class || ""]: true,
|
91
99
|
"is-saving": isSaving.value,
|
92
100
|
"justify-end": column.value.align === "right",
|
@@ -3,7 +3,7 @@
|
|
3
3
|
:key="rowProps.key"
|
4
4
|
:props="rowProps"
|
5
5
|
:data-drop-zone="isResizeable && `resize-column-` + column.name"
|
6
|
-
:class="
|
6
|
+
:class="columnClass"
|
7
7
|
:style="columnStyle"
|
8
8
|
>
|
9
9
|
{{ column.label }}
|
@@ -17,67 +17,88 @@
|
|
17
17
|
</HandleDraggable>
|
18
18
|
</QTh>
|
19
19
|
</template>
|
20
|
-
<script setup>
|
20
|
+
<script setup lang="ts">
|
21
21
|
import { QTh } from "quasar";
|
22
|
-
import { computed } from "vue";
|
22
|
+
import { computed, useCssModule } from "vue";
|
23
23
|
import { DragHandleIcon as RowResizeIcon } from "../../../svg";
|
24
|
+
import { TableColumn } from "../../../types";
|
24
25
|
import { HandleDraggable } from "../../DragAndDrop";
|
25
26
|
|
26
27
|
const emit = defineEmits(["update:model-value"]);
|
27
28
|
const props = defineProps({
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
modelValue: {
|
30
|
+
type: Object,
|
31
|
+
required: true
|
32
|
+
},
|
33
|
+
name: {
|
34
|
+
type: String,
|
35
|
+
required: true
|
36
|
+
},
|
37
|
+
rowProps: {
|
38
|
+
type: Object,
|
39
|
+
required: true
|
40
|
+
}
|
40
41
|
});
|
41
42
|
|
42
|
-
const column = computed(() => props.rowProps.col);
|
43
|
+
const column = computed<TableColumn>(() => props.rowProps.col);
|
43
44
|
const isResizeable = computed(() => column.value.resizeable);
|
44
45
|
|
45
46
|
const columnStyle = computed(() => {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
const width = props.settings?.width || column.value.width;
|
48
|
+
return {
|
49
|
+
width: width ? `${width}px` : undefined,
|
50
|
+
minWidth: column.value.minWidth ? `${column.value.minWidth}px` : undefined,
|
51
|
+
...(column.value.headerStyle || {})
|
52
|
+
};
|
52
53
|
});
|
53
54
|
|
55
|
+
const clsModule = useCssModule("cls");
|
56
|
+
const columnClass = computed(() => {
|
57
|
+
const colCls = {
|
58
|
+
[clsModule["handle-drop-zone"]]: isResizeable.value,
|
59
|
+
"dx-column-shrink": column.value.shrink
|
60
|
+
};
|
61
|
+
|
62
|
+
const headerClass = column.value.headerClass;
|
63
|
+
if (headerClass) {
|
64
|
+
if (typeof headerClass === "string") {
|
65
|
+
colCls[headerClass] = true;
|
66
|
+
} else {
|
67
|
+
Object.keys(headerClass).forEach((key) => {
|
68
|
+
colCls[key] = headerClass[key];
|
69
|
+
});
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
return colCls;
|
74
|
+
});
|
54
75
|
|
55
76
|
function onResizeColumn(val) {
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
const settings = {
|
78
|
+
...props.modelValue,
|
79
|
+
[column.value.name]: {
|
80
|
+
width: Math.max(Math.min(val.distance + val.startDropZoneSize, column.value.maxWidth || 500), column.value.minWidth || 80)
|
81
|
+
}
|
82
|
+
};
|
83
|
+
emit("update:model-value", settings);
|
63
84
|
}
|
64
85
|
</script>
|
65
86
|
|
66
87
|
<style lang="scss" module="cls">
|
67
88
|
.handle-drop-zone {
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
.resize-handle {
|
90
|
+
position: absolute;
|
91
|
+
top: 0;
|
92
|
+
right: -.45em;
|
93
|
+
width: .9em;
|
94
|
+
opacity: 0;
|
95
|
+
transition: all .3s;
|
96
|
+
}
|
76
97
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
98
|
+
&:hover {
|
99
|
+
.resize-handle {
|
100
|
+
opacity: 1;
|
101
|
+
}
|
102
|
+
}
|
82
103
|
}
|
83
104
|
</style>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<RenderedForm
|
4
|
+
v-bind="renderedFormProps"
|
5
|
+
v-model:values="input"
|
6
|
+
empty-value=""
|
7
|
+
:saving="action.isApplying"
|
8
|
+
@update:values="action.trigger(target, input)"
|
9
|
+
/>
|
10
|
+
</div>
|
11
|
+
</template>
|
12
|
+
<script setup lang="ts">
|
13
|
+
import { ref, watch } from "vue";
|
14
|
+
import { ActionTargetItem, AnyObject, Form, ResourceAction } from "../../../types";
|
15
|
+
import RenderedForm from "./RenderedForm.vue";
|
16
|
+
|
17
|
+
interface ActionFormProps {
|
18
|
+
action: ResourceAction;
|
19
|
+
target: ActionTargetItem;
|
20
|
+
form: Form;
|
21
|
+
noLabel?: boolean;
|
22
|
+
showName?: boolean;
|
23
|
+
disable?: boolean;
|
24
|
+
readonly?: boolean;
|
25
|
+
clearable?: boolean;
|
26
|
+
fieldClass?: string;
|
27
|
+
savingClass?: string;
|
28
|
+
}
|
29
|
+
|
30
|
+
const props = defineProps<ActionFormProps>();
|
31
|
+
const renderedFormProps = {
|
32
|
+
form: props.form,
|
33
|
+
noLabel: props.noLabel,
|
34
|
+
showName: props.showName,
|
35
|
+
disable: props.disable,
|
36
|
+
readonly: props.readonly,
|
37
|
+
clearable: props.clearable,
|
38
|
+
fieldClass: props.fieldClass,
|
39
|
+
savingClass: props.savingClass
|
40
|
+
};
|
41
|
+
|
42
|
+
const input: AnyObject = ref({ ...props.target });
|
43
|
+
const fieldStatus: AnyObject = {};
|
44
|
+
|
45
|
+
// Only update field values from target changes when the field is not already being saved
|
46
|
+
watch(() => props.target, (target: ActionTargetItem) => {
|
47
|
+
if (!target) return;
|
48
|
+
|
49
|
+
for (let field of props.form.fields) {
|
50
|
+
if (!fieldStatus[field.name]?.isSaving) {
|
51
|
+
input.value[field.name] = target[field.name];
|
52
|
+
}
|
53
|
+
}
|
54
|
+
});
|
55
|
+
</script>
|
@@ -3,12 +3,13 @@
|
|
3
3
|
class="danx-edit-on-click-text-field flex flex-nowrap items-center rounded overflow-ellipsis"
|
4
4
|
:class="{[props.class]: true, 'is-readonly': readonly, 'cursor-pointer': !isEditing && !readonly}"
|
5
5
|
@click="onEdit"
|
6
|
+
@focusout="isEditing = false"
|
6
7
|
>
|
7
8
|
<div
|
8
9
|
ref="editableBox"
|
9
10
|
:contenteditable="!readonly && isEditing"
|
10
11
|
class="flex-grow p-2 rounded outline-none border-none"
|
11
|
-
:class="{[editingClass]: isEditing}"
|
12
|
+
:class="{[editingClass]: isEditing, [inputClass]: true}"
|
12
13
|
@input="text = $event.target.innerText"
|
13
14
|
>
|
14
15
|
{{ text }}
|
@@ -33,15 +34,20 @@
|
|
33
34
|
import { FaSolidCheck as DoneIcon, FaSolidPencil as EditIcon } from "danx-icon";
|
34
35
|
import { nextTick, ref } from "vue";
|
35
36
|
|
36
|
-
export interface
|
37
|
-
class?:
|
38
|
-
editingClass?:
|
37
|
+
export interface EditOnClickTextFieldProps {
|
38
|
+
class?: string;
|
39
|
+
editingClass?: string;
|
40
|
+
inputClass?: string;
|
39
41
|
readonly?: boolean;
|
40
42
|
}
|
41
43
|
|
42
44
|
const editableBox = ref<HTMLElement | null>(null);
|
43
45
|
const text = defineModel({ type: String });
|
44
|
-
const props = defineProps<
|
46
|
+
const props = withDefaults(defineProps<EditOnClickTextFieldProps>(), {
|
47
|
+
class: "hover:bg-slate-300",
|
48
|
+
editingClass: "bg-slate-500",
|
49
|
+
inputClass: "whitespace-normal"
|
50
|
+
});
|
45
51
|
const isEditing = ref(false);
|
46
52
|
function onEdit() {
|
47
53
|
if (props.readonly) return;
|
@@ -1,27 +1,30 @@
|
|
1
1
|
<template>
|
2
|
-
<
|
2
|
+
<div class="dx-field-label">
|
3
3
|
<slot>
|
4
|
-
|
5
|
-
|
4
|
+
<div class="dx-field-label-text">
|
5
|
+
<div class="dx-field-label-label">
|
6
|
+
{{ label }}
|
7
|
+
</div>
|
8
|
+
<div
|
9
|
+
v-if="name"
|
10
|
+
class="dx-field-label-name"
|
11
|
+
>
|
12
|
+
({{ name }})
|
13
|
+
</div>
|
14
|
+
</div>
|
6
15
|
</slot>
|
7
16
|
<span
|
8
|
-
v-if="
|
17
|
+
v-if="required"
|
9
18
|
class="dx-field-required"
|
10
|
-
>{{ requiredLabel }}</span>
|
11
|
-
</
|
19
|
+
>{{ requiredLabel || "*" }}</span>
|
20
|
+
</div>
|
12
21
|
</template>
|
13
22
|
|
14
23
|
<script setup lang="ts">
|
15
|
-
|
16
|
-
import { FormField } from "../../../../types";
|
17
|
-
|
18
|
-
const props = defineProps<{
|
19
|
-
field?: FormField;
|
24
|
+
defineProps<{
|
20
25
|
label?: string;
|
21
|
-
|
26
|
+
name?: string;
|
22
27
|
required?: boolean;
|
28
|
+
requiredLabel?: string;
|
23
29
|
}>();
|
24
|
-
|
25
|
-
const labelText = computed(() => props.label || props.field?.label);
|
26
|
-
const requiredLabel = computed(() => props.field?.required_group || (props.required || props.field?.required ? "*" : ""));
|
27
30
|
</script>
|
@@ -62,6 +62,7 @@ function upload() {
|
|
62
62
|
async function onAttachFiles({ target: { files } }) {
|
63
63
|
emit("uploading", files);
|
64
64
|
let fileUpload = new FileUpload(files)
|
65
|
+
.prepare()
|
65
66
|
.onProgress(({ file, progress }) => {
|
66
67
|
file.progress = progress;
|
67
68
|
emit("file-progress", file);
|
@@ -1,22 +1,51 @@
|
|
1
1
|
<template>
|
2
2
|
<div>
|
3
|
-
<div class="text-xs font-bold">
|
4
|
-
|
5
|
-
|
3
|
+
<div class="text-xs font-bold">
|
4
|
+
{{ label }}
|
5
|
+
</div>
|
6
|
+
<div :class="valueClass">
|
7
|
+
<a
|
8
|
+
v-if="url"
|
9
|
+
target="_blank"
|
10
|
+
:href="url"
|
11
|
+
:class="valueClass"
|
12
|
+
>
|
13
|
+
<slot>{{ formattedValue }}</slot>
|
14
|
+
</a>
|
15
|
+
<template v-else>
|
16
|
+
<slot>{{ formattedValue }}</slot>
|
17
|
+
</template>
|
6
18
|
</div>
|
7
19
|
</div>
|
8
20
|
</template>
|
9
|
-
<script setup>
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
<script setup lang="ts">
|
22
|
+
import { computed } from "vue";
|
23
|
+
import { fBoolean, fNumber } from "../../../../helpers";
|
24
|
+
|
25
|
+
export interface LabelValueBlockProps {
|
26
|
+
label: string;
|
27
|
+
value: string | number | boolean;
|
28
|
+
url?: string;
|
29
|
+
dense?: boolean;
|
30
|
+
nowrap?: boolean;
|
31
|
+
}
|
32
|
+
|
33
|
+
const props = withDefaults(defineProps<LabelValueBlockProps>(), {
|
34
|
+
value: "",
|
35
|
+
url: ""
|
36
|
+
});
|
37
|
+
|
38
|
+
const valueClass = computed(() => ({ "mt-2": !props.dense, "mt-1": props.dense, "text-no-wrap": props.nowrap }));
|
39
|
+
const formattedValue = computed(() => {
|
40
|
+
switch (typeof props.value) {
|
41
|
+
case "boolean":
|
42
|
+
return fBoolean(props.value);
|
43
|
+
|
44
|
+
case "number":
|
45
|
+
return fNumber(props.value);
|
46
|
+
|
47
|
+
default:
|
48
|
+
return props.value || "-";
|
49
|
+
}
|
21
50
|
});
|
22
51
|
</script>
|
@@ -12,7 +12,6 @@
|
|
12
12
|
:field="field"
|
13
13
|
:no-label="!field.label"
|
14
14
|
label-class="text-xs font-bold text-zinc-800"
|
15
|
-
parent-class="tight-label"
|
16
15
|
input-class="!py-0"
|
17
16
|
dense
|
18
17
|
type="textarea"
|
@@ -29,29 +28,29 @@ import TextField from "./TextField";
|
|
29
28
|
|
30
29
|
const emit = defineEmits(["update:model-value"]);
|
31
30
|
const props = defineProps({
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
31
|
+
modelValue: {
|
32
|
+
type: [String, Number, Object],
|
33
|
+
default: ""
|
34
|
+
},
|
35
|
+
field: {
|
36
|
+
type: Object,
|
37
|
+
default: null
|
38
|
+
}
|
40
39
|
});
|
41
40
|
|
42
41
|
const selectedFieldName = ref(props.field.defaultOption);
|
43
42
|
const searchList = computed(() => props.modelValue && props.modelValue[selectedFieldName.value]);
|
44
43
|
const textInput = ref(formatModelValue());
|
45
44
|
function onChange() {
|
46
|
-
|
47
|
-
|
45
|
+
textInput.value = textInput.value?.replace(/\n/g, ",").replace(/,{2,}/g, ",") || "";
|
46
|
+
emit("update:model-value", textInput.value ? { [selectedFieldName.value]: textInput.value.split(",") } : undefined);
|
48
47
|
}
|
49
48
|
|
50
49
|
function formatModelValue() {
|
51
|
-
|
50
|
+
return Array.isArray(searchList.value) ? searchList.value?.join(",") : "";
|
52
51
|
}
|
53
52
|
|
54
53
|
watch(() => props.modelValue, () => {
|
55
|
-
|
54
|
+
textInput.value = formatModelValue();
|
56
55
|
});
|
57
56
|
</script>
|