@drax/crud-vue 0.4.0
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/package.json +66 -0
- package/src/EntityCrud.ts +124 -0
- package/src/components/Crud.vue +65 -0
- package/src/components/CrudAutocomplete.vue +94 -0
- package/src/components/CrudDialog.vue +36 -0
- package/src/components/CrudForm.vue +66 -0
- package/src/components/CrudFormField.vue +175 -0
- package/src/components/CrudFormList.vue +81 -0
- package/src/components/CrudList.vue +87 -0
- package/src/components/CrudNotify.vue +31 -0
- package/src/components/CrudSearch.vue +17 -0
- package/src/composables/UseCrud.ts +150 -0
- package/src/index.ts +29 -0
- package/src/interfaces/IEntityCrud.ts +33 -0
- package/src/interfaces/TOperation.ts +6 -0
- package/src/stores/UseCrudStore.ts +86 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drax/crud-vue",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.4.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./src/index.ts",
|
|
9
|
+
"module": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "vite",
|
|
16
|
+
"build": "run-p type-check \"build-only {@}\" --",
|
|
17
|
+
"preview": "vite preview",
|
|
18
|
+
"test:unit": "vitest",
|
|
19
|
+
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
|
20
|
+
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
|
21
|
+
"build-only": "vite build",
|
|
22
|
+
"type-check": "vue-tsc --build --force",
|
|
23
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
|
24
|
+
"format": "prettier --write src/"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@drax/common-front": "^0.4.0",
|
|
28
|
+
"@drax/common-share": "^0.4.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"pinia": "^2.2.2",
|
|
32
|
+
"vue": "^3.5.7",
|
|
33
|
+
"vue-i18n": "^9.14.0",
|
|
34
|
+
"vuetify": "^3.7.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@rushstack/eslint-patch": "^1.8.0",
|
|
38
|
+
"@tsconfig/node20": "^20.1.4",
|
|
39
|
+
"@types/jsdom": "^21.1.7",
|
|
40
|
+
"@types/node": "^20.12.5",
|
|
41
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
42
|
+
"@vue/eslint-config-prettier": "^9.0.0",
|
|
43
|
+
"@vue/eslint-config-typescript": "^13.0.0",
|
|
44
|
+
"@vue/test-utils": "^2.4.5",
|
|
45
|
+
"@vue/tsconfig": "^0.5.1",
|
|
46
|
+
"cypress": "^13.7.2",
|
|
47
|
+
"eslint": "^8.57.0",
|
|
48
|
+
"eslint-plugin-cypress": "^2.15.1",
|
|
49
|
+
"eslint-plugin-vue": "^9.23.0",
|
|
50
|
+
"jsdom": "^24.0.0",
|
|
51
|
+
"npm-run-all2": "^6.1.2",
|
|
52
|
+
"pinia": "^2.1.7",
|
|
53
|
+
"pinia-plugin-persistedstate": "^3.2.1",
|
|
54
|
+
"prettier": "^3.2.5",
|
|
55
|
+
"start-server-and-test": "^2.0.3",
|
|
56
|
+
"typescript": "~5.4.0",
|
|
57
|
+
"vite": "^5.4.3",
|
|
58
|
+
"vite-plugin-css-injected-by-js": "^3.5.1",
|
|
59
|
+
"vite-plugin-dts": "^3.9.1",
|
|
60
|
+
"vitest": "^1.4.0",
|
|
61
|
+
"vue": "^3.5.3",
|
|
62
|
+
"vue-tsc": "^2.0.11",
|
|
63
|
+
"vuetify": "^3.7.1"
|
|
64
|
+
},
|
|
65
|
+
"gitHead": "481b302fe72f403abf092806ceca540dd2765dfa"
|
|
66
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type {IDraxCrud} from "@drax/common-share";
|
|
2
|
+
import type {
|
|
3
|
+
IFields,
|
|
4
|
+
ICrudForm,
|
|
5
|
+
ICrudHeaders,
|
|
6
|
+
ICrudPermissions,
|
|
7
|
+
ICrudRules,
|
|
8
|
+
ICrudField
|
|
9
|
+
} from "./interfaces/IEntityCrud";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EntityCrud{
|
|
14
|
+
|
|
15
|
+
name: string = ''
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get instance(){
|
|
21
|
+
throw new Error('EntityCrud instance not found')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
get headers():ICrudHeaders[]{
|
|
26
|
+
return [
|
|
27
|
+
{title: 'ID',key:'_id'},
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get permissions(): ICrudPermissions {
|
|
32
|
+
return {
|
|
33
|
+
manage: 'manage', view: 'view', create: 'create', update: 'update', delete: 'delete'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get provider(): IDraxCrud<any, any, any>{
|
|
38
|
+
throw new Error('provider not implemented')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get fields():IFields{
|
|
42
|
+
return [
|
|
43
|
+
{name: 'id', type: 'string', label: 'ID', default: '' },
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get form():ICrudForm{
|
|
48
|
+
|
|
49
|
+
function objectFields(field:ICrudField){
|
|
50
|
+
let value:any = {}
|
|
51
|
+
if(field.objectFields){
|
|
52
|
+
field.objectFields.forEach(subField => {
|
|
53
|
+
if(subField.type === 'object'){
|
|
54
|
+
value[subField.name] = objectFields(subField)
|
|
55
|
+
}else{
|
|
56
|
+
value[subField.name] = subField.default
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
return value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const form = this.fields.reduce((acc, field) => {
|
|
65
|
+
|
|
66
|
+
let value = null
|
|
67
|
+
if(field.type === 'object'){
|
|
68
|
+
value = objectFields(field)
|
|
69
|
+
} else if(field.default != undefined){
|
|
70
|
+
value = field.default
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {...acc, [field.name]: value }
|
|
74
|
+
}, {})
|
|
75
|
+
|
|
76
|
+
console.log("Form: ", form)
|
|
77
|
+
|
|
78
|
+
return form
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get refs():{ [key: string]: EntityCrud }{
|
|
83
|
+
return {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRef(ref: string):EntityCrud{
|
|
87
|
+
if(!this.refs.hasOwnProperty(ref)) {
|
|
88
|
+
throw new Error("Ref not found: " + ref)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this.refs[ref] as EntityCrud
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get rules(): ICrudRules{
|
|
95
|
+
return {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get rule() {
|
|
99
|
+
return (field:string) => this.rules[field] || []
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get isEditable(){
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get isCreatable(){
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get isDeletable(){
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get dialogFullscreen(){
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default EntityCrud;
|
|
124
|
+
export { EntityCrud }
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from "vue";
|
|
3
|
+
import EntityCrud from "../EntityCrud";
|
|
4
|
+
import CrudList from "./CrudList.vue";
|
|
5
|
+
import CrudForm from "./CrudForm.vue";
|
|
6
|
+
import CrudNotify from "./CrudNotify.vue";
|
|
7
|
+
import CrudDialog from "./CrudDialog.vue";
|
|
8
|
+
import {useCrud} from "../composables/UseCrud";
|
|
9
|
+
|
|
10
|
+
const {entity} = defineProps({
|
|
11
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
onCreate, onEdit, onDelete, onCancel, onSubmit,
|
|
16
|
+
operation, dialog, form, formValid, notify, error, message,
|
|
17
|
+
} = useCrud(entity);
|
|
18
|
+
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<v-container fluid class="mt-5">
|
|
23
|
+
<v-card>
|
|
24
|
+
|
|
25
|
+
<crud-list
|
|
26
|
+
:entity="entity"
|
|
27
|
+
@create="onCreate"
|
|
28
|
+
@edit="onEdit"
|
|
29
|
+
@delete="onDelete"
|
|
30
|
+
>
|
|
31
|
+
<template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
|
|
32
|
+
<slot :name="`item.${header.key}`" v-bind="{item, value}">
|
|
33
|
+
{{ value }}
|
|
34
|
+
</slot>
|
|
35
|
+
</template>
|
|
36
|
+
</crud-list>
|
|
37
|
+
</v-card>
|
|
38
|
+
|
|
39
|
+
<crud-dialog
|
|
40
|
+
v-model="dialog"
|
|
41
|
+
:entity="entity"
|
|
42
|
+
:operation="operation"
|
|
43
|
+
>
|
|
44
|
+
|
|
45
|
+
<slot name="form">
|
|
46
|
+
<crud-form
|
|
47
|
+
v-model="form"
|
|
48
|
+
:entity="entity"
|
|
49
|
+
:error="error"
|
|
50
|
+
:operation="operation"
|
|
51
|
+
:readonly="operation === 'delete'"
|
|
52
|
+
@submit="onSubmit"
|
|
53
|
+
@cancel="onCancel"
|
|
54
|
+
/>
|
|
55
|
+
</slot>
|
|
56
|
+
|
|
57
|
+
</crud-dialog>
|
|
58
|
+
|
|
59
|
+
<crud-notify v-model="notify" :message="message"></crud-notify>
|
|
60
|
+
</v-container>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {debounce} from "@drax/common-front"
|
|
3
|
+
import type { PropType, Ref} from "vue";
|
|
4
|
+
import {ref, onBeforeMount} from "vue";
|
|
5
|
+
import EntityCrud from "../EntityCrud";
|
|
6
|
+
import type {ICrudField} from "@/interfaces/IEntityCrud";
|
|
7
|
+
|
|
8
|
+
const valueModel = defineModel({type: [String, Array], required: false})
|
|
9
|
+
|
|
10
|
+
const {entity, multiple} = defineProps({
|
|
11
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
12
|
+
field: {type: Object as PropType<ICrudField>, required: true},
|
|
13
|
+
multiple: {type: Boolean, default: false},
|
|
14
|
+
chips: {type: Boolean, default: false},
|
|
15
|
+
closableChips: {type: Boolean, default: true},
|
|
16
|
+
clearable: {type: Boolean, default: true},
|
|
17
|
+
label: {type: String},
|
|
18
|
+
itemValue: {type: [String], default: '_id'},
|
|
19
|
+
itemTitle: {type: [String], default: 'name'},
|
|
20
|
+
rules: {type: Array<Function>, default: []},
|
|
21
|
+
errorMessages: {type: Array as PropType<string[]>, default: []},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const loading: Ref<boolean> = ref(false)
|
|
25
|
+
const items: Ref<Array<any>> = ref([])
|
|
26
|
+
|
|
27
|
+
const debouncedSearch = debounce(search, 300)
|
|
28
|
+
|
|
29
|
+
onBeforeMount(async () => {
|
|
30
|
+
if(valueModel.value && valueModel.value.length > 0){
|
|
31
|
+
if(multiple && Array.isArray(valueModel.value) ){
|
|
32
|
+
items.value = valueModel.value
|
|
33
|
+
//await findByIds(valueModel.value)
|
|
34
|
+
}else{
|
|
35
|
+
items.value = [valueModel.value]
|
|
36
|
+
//await findByIds([valueModel.value])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
async function findByIds(ids: Array<string>) {
|
|
43
|
+
try{
|
|
44
|
+
loading.value = true
|
|
45
|
+
items.value = await entity.provider.findByIds(ids)
|
|
46
|
+
}catch (e){
|
|
47
|
+
console.error(e)
|
|
48
|
+
}finally{
|
|
49
|
+
loading.value = false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async function search(value: any) {
|
|
57
|
+
try{
|
|
58
|
+
loading.value = true
|
|
59
|
+
if(!entity.provider.search){
|
|
60
|
+
throw new Error('Provider does not have a search method')
|
|
61
|
+
}
|
|
62
|
+
items.value = await entity.provider.search(value)
|
|
63
|
+
}catch (e){
|
|
64
|
+
console.error(e)
|
|
65
|
+
}finally{
|
|
66
|
+
loading.value = false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<v-autocomplete
|
|
75
|
+
v-model="valueModel"
|
|
76
|
+
:label="label ? label : field.label"
|
|
77
|
+
:placeholder="field.label"
|
|
78
|
+
:items="items"
|
|
79
|
+
:multiple="multiple"
|
|
80
|
+
:chips="chips"
|
|
81
|
+
:closable-chips="closableChips"
|
|
82
|
+
:clearable="clearable"
|
|
83
|
+
:item-value="itemValue"
|
|
84
|
+
:item-title="itemTitle"
|
|
85
|
+
:loading="loading"
|
|
86
|
+
:rules="rules"
|
|
87
|
+
:error-messages="errorMessages"
|
|
88
|
+
@update:search="debouncedSearch"
|
|
89
|
+
></v-autocomplete>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<style scoped>
|
|
93
|
+
|
|
94
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {TOperation} from "../interfaces/TOperation";
|
|
3
|
+
import type {PropType} from "vue";
|
|
4
|
+
import EntityCrud from "../EntityCrud";
|
|
5
|
+
const dialog = defineModel({type: Boolean, default: false})
|
|
6
|
+
|
|
7
|
+
defineProps({
|
|
8
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
9
|
+
operation: {type: String as PropType<TOperation>}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
defineEmits(
|
|
13
|
+
['submit', 'close']
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<v-dialog v-model="dialog" :fullscreen="entity.dialogFullscreen">
|
|
20
|
+
<v-card>
|
|
21
|
+
<v-toolbar>
|
|
22
|
+
<v-toolbar-title>{{entity.name}} {{$te('action.'+operation) ? $t('action.'+operation) : operation}}</v-toolbar-title>
|
|
23
|
+
<v-spacer></v-spacer>
|
|
24
|
+
<v-btn icon @click="dialog = false"><v-icon>mdi-close</v-icon></v-btn>
|
|
25
|
+
</v-toolbar>
|
|
26
|
+
<v-card-text>
|
|
27
|
+
<slot></slot>
|
|
28
|
+
</v-card-text>
|
|
29
|
+
</v-card>
|
|
30
|
+
|
|
31
|
+
</v-dialog>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style scoped>
|
|
35
|
+
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from "vue";
|
|
3
|
+
import {ref} from "vue";
|
|
4
|
+
import EntityCrud from "../EntityCrud";
|
|
5
|
+
import CrudFormField from "./CrudFormField.vue";
|
|
6
|
+
import type {TOperation} from "../interfaces/TOperation";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const valueModel = defineModel({type: [Object]})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const {entity} = defineProps({
|
|
13
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
14
|
+
operation: {type: String as PropType<TOperation>, required: true},
|
|
15
|
+
readonly: {type: Boolean, default: false},
|
|
16
|
+
error: {type: String, required: false},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const valid = ref()
|
|
20
|
+
const formRef = ref()
|
|
21
|
+
|
|
22
|
+
function submit() {
|
|
23
|
+
formRef.value.validate()
|
|
24
|
+
if(valid.value) {
|
|
25
|
+
emit('submit',valueModel.value)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cancel() {
|
|
30
|
+
emit('cancel')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits(['submit', 'cancel'])
|
|
34
|
+
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<v-form v-model="valid" ref="formRef" @submit.prevent >
|
|
39
|
+
<v-card flat>
|
|
40
|
+
<v-card-text v-if="error">
|
|
41
|
+
<v-alert color="error">{{ $te(error) ? $t(error) : error }}</v-alert>
|
|
42
|
+
</v-card-text>
|
|
43
|
+
<v-card-text>
|
|
44
|
+
<template v-for="field in entity.fields" :key="field.name">
|
|
45
|
+
<crud-form-field
|
|
46
|
+
:field="field"
|
|
47
|
+
:entity="entity"
|
|
48
|
+
v-model="valueModel[field.name]"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
51
|
+
</v-card-text>
|
|
52
|
+
|
|
53
|
+
<v-card-actions>
|
|
54
|
+
<v-spacer></v-spacer>
|
|
55
|
+
<v-btn variant="text" color="grey" @click="cancel">{{ $t('action.cancel') }}</v-btn>
|
|
56
|
+
<v-btn variant="flat" color="primary" @click="submit">
|
|
57
|
+
{{ operation ? $t('action.' + operation) : $t('action.sent') }}
|
|
58
|
+
</v-btn>
|
|
59
|
+
</v-card-actions>
|
|
60
|
+
</v-card>
|
|
61
|
+
</v-form>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
|
|
66
|
+
</style>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed} from "vue";
|
|
3
|
+
import type {PropType} from "vue";
|
|
4
|
+
import type {ICrudField} from "../interfaces/IEntityCrud";
|
|
5
|
+
import CrudFormList from "./CrudFormList.vue";
|
|
6
|
+
import CrudAutocomplete from "./CrudAutocomplete.vue";
|
|
7
|
+
import EntityCrud from "@/EntityCrud";
|
|
8
|
+
import {useI18n} from "vue-i18n";
|
|
9
|
+
import {useCrudStore} from "../stores/UseCrudStore";
|
|
10
|
+
import {VDateInput} from 'vuetify/labs/VDateInput'
|
|
11
|
+
const {t, te} = useI18n()
|
|
12
|
+
|
|
13
|
+
const store = useCrudStore()
|
|
14
|
+
|
|
15
|
+
const valueModel = defineModel({type: [String, Number, Boolean, Object, Array], default: false})
|
|
16
|
+
|
|
17
|
+
const {index, entity, field} = defineProps({
|
|
18
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
19
|
+
field: {type: Object as PropType<ICrudField>, required: true},
|
|
20
|
+
readonly: {type: Boolean, default: false},
|
|
21
|
+
index: {type: Number, default: 0},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const name = computed(() => index > 0 ? `${field.name}_${index}` : field.name)
|
|
25
|
+
|
|
26
|
+
const label = computed(() => {
|
|
27
|
+
const i18n = `${entity.name}.fields.${field.name}`
|
|
28
|
+
return te(i18n) ? t(i18n) : field.label
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const rules = computed(() => {
|
|
32
|
+
return entity.rule(field.name) as any
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const inputErrors = computed(() =>
|
|
36
|
+
store.getInputErrors(field.name).map((error: string) => t(te(error) ? t(error) : error))
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
|
|
43
|
+
<div v-if="field && field.type">
|
|
44
|
+
|
|
45
|
+
<v-text-field
|
|
46
|
+
v-if="field.type === 'string'"
|
|
47
|
+
type="text"
|
|
48
|
+
:name="name"
|
|
49
|
+
:label="label"
|
|
50
|
+
v-model="valueModel"
|
|
51
|
+
:readonly="readonly"
|
|
52
|
+
:error-messages="inputErrors"
|
|
53
|
+
:rules="rules"
|
|
54
|
+
>
|
|
55
|
+
</v-text-field>
|
|
56
|
+
|
|
57
|
+
<v-text-field
|
|
58
|
+
v-if="field.type === 'number'"
|
|
59
|
+
type="number"
|
|
60
|
+
:name="name"
|
|
61
|
+
:label="label"
|
|
62
|
+
v-model="valueModel"
|
|
63
|
+
:readonly="readonly"
|
|
64
|
+
:error-messages="inputErrors"
|
|
65
|
+
:rules="rules"
|
|
66
|
+
>
|
|
67
|
+
</v-text-field>
|
|
68
|
+
|
|
69
|
+
<v-checkbox
|
|
70
|
+
v-if="field.type === 'boolean'"
|
|
71
|
+
:name="name"
|
|
72
|
+
:label="label"
|
|
73
|
+
v-model="valueModel"
|
|
74
|
+
:readonly="readonly"
|
|
75
|
+
:error-messages="inputErrors"
|
|
76
|
+
:rules="rules"
|
|
77
|
+
>
|
|
78
|
+
</v-checkbox>
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
<v-date-input
|
|
82
|
+
v-if="field.type === 'date'"
|
|
83
|
+
type="text"
|
|
84
|
+
:name="name"
|
|
85
|
+
:label="label"
|
|
86
|
+
v-model="valueModel"
|
|
87
|
+
:readonly="readonly"
|
|
88
|
+
:error-messages="inputErrors"
|
|
89
|
+
prepend-inner-icon="mdi-calendar"
|
|
90
|
+
prepend-icon=""
|
|
91
|
+
:rules="rules"
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<crud-autocomplete
|
|
95
|
+
v-if="field.type === 'ref'"
|
|
96
|
+
:entity="entity.getRef(field.ref).instance"
|
|
97
|
+
:field="field"
|
|
98
|
+
v-model="valueModel"
|
|
99
|
+
:label="label"
|
|
100
|
+
:error-messages="inputErrors"
|
|
101
|
+
:rules="rules"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<v-card v-if="field.type === 'object'" class="mt-3" variant="flat" border>
|
|
105
|
+
|
|
106
|
+
<v-card-title class="text-h5">{{ field.label }}</v-card-title>
|
|
107
|
+
<v-card-text>
|
|
108
|
+
<crud-form-field
|
|
109
|
+
v-for="oField in field.objectFields"
|
|
110
|
+
:entity="entity"
|
|
111
|
+
:field="oField"
|
|
112
|
+
v-model="valueModel[oField.name]"
|
|
113
|
+
></crud-form-field>
|
|
114
|
+
</v-card-text>
|
|
115
|
+
|
|
116
|
+
</v-card>
|
|
117
|
+
|
|
118
|
+
<v-combobox
|
|
119
|
+
v-if="field.type === 'array.string'"
|
|
120
|
+
type="text"
|
|
121
|
+
:name="name"
|
|
122
|
+
:label="label"
|
|
123
|
+
v-model="valueModel"
|
|
124
|
+
:multiple="true"
|
|
125
|
+
:chips="true"
|
|
126
|
+
:closable-chips="true"
|
|
127
|
+
:clearable="true"
|
|
128
|
+
:readonly="readonly"
|
|
129
|
+
:error-messages="inputErrors"
|
|
130
|
+
>
|
|
131
|
+
</v-combobox>
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
<crud-autocomplete
|
|
135
|
+
v-if="field.type === 'array.ref'"
|
|
136
|
+
:entity="entity.getRef(field.ref).instance"
|
|
137
|
+
:field="field"
|
|
138
|
+
v-model="valueModel"
|
|
139
|
+
:multiple="true"
|
|
140
|
+
:chips="true"
|
|
141
|
+
:clearable="true"
|
|
142
|
+
:label="label"
|
|
143
|
+
:error-messages="inputErrors"
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
<v-combobox
|
|
148
|
+
v-if="field.type === 'array.number'"
|
|
149
|
+
type="number"
|
|
150
|
+
:name="name"
|
|
151
|
+
:label="label"
|
|
152
|
+
v-model="valueModel"
|
|
153
|
+
:multiple="true"
|
|
154
|
+
:chips="true"
|
|
155
|
+
:clearable="true"
|
|
156
|
+
:readonly="readonly"
|
|
157
|
+
:error-messages="inputErrors"
|
|
158
|
+
>
|
|
159
|
+
</v-combobox>
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
<crud-form-list
|
|
163
|
+
v-if="field.type === 'array.object'"
|
|
164
|
+
:entity="entity"
|
|
165
|
+
:field="field"
|
|
166
|
+
v-model="valueModel"
|
|
167
|
+
:readonly="readonly"
|
|
168
|
+
/>
|
|
169
|
+
|
|
170
|
+
</div>
|
|
171
|
+
</template>
|
|
172
|
+
|
|
173
|
+
<style scoped>
|
|
174
|
+
|
|
175
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from "vue";
|
|
3
|
+
import type {ICrudField} from "../interfaces/IEntityCrud";
|
|
4
|
+
import CrudFormField from "./CrudFormField.vue";
|
|
5
|
+
import EntityCrud from "@/EntityCrud";
|
|
6
|
+
|
|
7
|
+
const valueModel = defineModel({type: Array, default: () => []});
|
|
8
|
+
|
|
9
|
+
const {field} = defineProps({
|
|
10
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
11
|
+
field: {type: Object as PropType<ICrudField>, required: true},
|
|
12
|
+
readonly: {type: Boolean, default: false},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function newItem() {
|
|
16
|
+
return field.objectFields ? field.objectFields.reduce((acc, field) => ({...acc, [field.name]: field.default }), {}) : []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getField(key: string):ICrudField|undefined {
|
|
20
|
+
return field.objectFields ? field.objectFields.find(field => field.name === key) : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasField(key: string):boolean {
|
|
24
|
+
return field.objectFields ? field.objectFields.some(field => field.name === key) : false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function addItem() {
|
|
28
|
+
valueModel.value.push(newItem());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function removeItem(index: number) {
|
|
32
|
+
valueModel.value.splice(index, 1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<v-card class="mt-3" variant="flat" border>
|
|
39
|
+
|
|
40
|
+
<v-card-title class="text-h5">{{field.label}}</v-card-title>
|
|
41
|
+
<v-card-text>
|
|
42
|
+
<v-row>
|
|
43
|
+
<v-col cols="12" v-for="(item,index) in valueModel" :key="index" class="text-right">
|
|
44
|
+
<v-row dense align="center">
|
|
45
|
+
<v-col cols="11">
|
|
46
|
+
<template v-for="key in Object.keys(item)" :key="key">
|
|
47
|
+
<crud-form-field
|
|
48
|
+
v-if="hasField(key)"
|
|
49
|
+
:entity="entity"
|
|
50
|
+
:field="getField(key)"
|
|
51
|
+
v-model="valueModel[index][key]"
|
|
52
|
+
:readonly="readonly"
|
|
53
|
+
:index="index"
|
|
54
|
+
/>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
</v-col>
|
|
58
|
+
<v-col cols="1">
|
|
59
|
+
<v-btn v-if="!readonly" icon @click="removeItem(index)" small class="text-red text--darken-3">
|
|
60
|
+
<v-icon>mdi-close</v-icon>
|
|
61
|
+
</v-btn>
|
|
62
|
+
</v-col>
|
|
63
|
+
</v-row>
|
|
64
|
+
<v-divider></v-divider>
|
|
65
|
+
|
|
66
|
+
</v-col>
|
|
67
|
+
<v-btn icon @click="addItem" class="text-blue text--darken-3">
|
|
68
|
+
<v-icon>mdi-plus</v-icon>
|
|
69
|
+
</v-btn>
|
|
70
|
+
|
|
71
|
+
</v-row>
|
|
72
|
+
</v-card-text>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
</v-card>
|
|
76
|
+
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {PropType} from 'vue'
|
|
3
|
+
import {useAuth} from '@drax/identity-vue'
|
|
4
|
+
import EntityCrud from "../EntityCrud";
|
|
5
|
+
import CrudSearch from "./CrudSearch.vue";
|
|
6
|
+
import {useCrud} from "../composables/UseCrud";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
const {t, te} = useI18n()
|
|
9
|
+
const {hasPermission} = useAuth()
|
|
10
|
+
|
|
11
|
+
const {entity} = defineProps({
|
|
12
|
+
entity: {type: Object as PropType<EntityCrud>, required: true},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const {loading, itemsPerPage, page, sortBy, search, totalItems, items,
|
|
16
|
+
loadItems} = useCrud(entity)
|
|
17
|
+
|
|
18
|
+
const actions = [{title: t('action.actions'),key:'actions', sortable: false, align: 'right'}]
|
|
19
|
+
const tHeaders = entity.headers.map(header => ({...header, title: t(`${entity.name}.fields.${header.title}`)}))
|
|
20
|
+
|
|
21
|
+
const headers = [...tHeaders, ...actions]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
defineExpose({
|
|
25
|
+
loadItems
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<v-data-table-server
|
|
32
|
+
class="border"
|
|
33
|
+
v-if="hasPermission(entity.permissions.view)"
|
|
34
|
+
v-model:items-per-page="itemsPerPage"
|
|
35
|
+
:items-per-page-options="[5, 10, 20, 50]"
|
|
36
|
+
v-model:page="page"
|
|
37
|
+
v-model:sort-by="sortBy"
|
|
38
|
+
:headers="headers"
|
|
39
|
+
:items="items"
|
|
40
|
+
:items-length="totalItems"
|
|
41
|
+
:loading="loading"
|
|
42
|
+
:search="search"
|
|
43
|
+
:multi-sort="false"
|
|
44
|
+
item-value="name"
|
|
45
|
+
@update:options="loadItems"
|
|
46
|
+
>
|
|
47
|
+
<template v-slot:top>
|
|
48
|
+
<v-toolbar density="compact" >
|
|
49
|
+
<v-toolbar-title>{{ entity.name }}</v-toolbar-title>
|
|
50
|
+
<v-spacer></v-spacer>
|
|
51
|
+
<v-btn v-if="entity.isCreatable" icon="mdi-plus" class="mr-1" variant="text" color="primary" @click="$emit('create')">
|
|
52
|
+
</v-btn>
|
|
53
|
+
</v-toolbar>
|
|
54
|
+
|
|
55
|
+
<v-card>
|
|
56
|
+
<v-card-text>
|
|
57
|
+
<crud-search v-model="search"></crud-search>
|
|
58
|
+
</v-card-text>
|
|
59
|
+
</v-card>
|
|
60
|
+
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
<template v-for="header in entity.headers" :key="header.key" v-slot:[`item.${header.key}`]="{item, value}">
|
|
65
|
+
<slot :name="`item.${header.key}`" v-bind="{item, value}" >
|
|
66
|
+
{{value}}
|
|
67
|
+
</slot>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
<template v-slot:item.actions="{item}">
|
|
72
|
+
<v-btn v-if="entity.isEditable && hasPermission(entity.permissions.update)"
|
|
73
|
+
icon="mdi-pencil" variant="text" color="primary"
|
|
74
|
+
@click="$emit('edit', item)">
|
|
75
|
+
</v-btn>
|
|
76
|
+
<v-btn v-if="entity.isDeletable && hasPermission(entity.permissions.delete)"
|
|
77
|
+
icon="mdi-delete" class="mr-1" variant="text" color="red"
|
|
78
|
+
@click="$emit('delete', item)">
|
|
79
|
+
</v-btn>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
</v-data-table-server>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<style scoped>
|
|
86
|
+
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
const valueModel = defineModel({type: Boolean, default: false})
|
|
4
|
+
|
|
5
|
+
defineProps({
|
|
6
|
+
message: {type: String},
|
|
7
|
+
color: {type: String, default: 'success' },
|
|
8
|
+
timeout: {type: Number, default: 3000},
|
|
9
|
+
});
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
|
|
14
|
+
<v-snackbar v-model="valueModel" :timeout="timeout" :color="color" >
|
|
15
|
+
{{message}}
|
|
16
|
+
|
|
17
|
+
<template v-slot:actions>
|
|
18
|
+
<v-btn
|
|
19
|
+
icon="mdi-close"
|
|
20
|
+
variant="text"
|
|
21
|
+
@click="valueModel = false"
|
|
22
|
+
>
|
|
23
|
+
</v-btn>
|
|
24
|
+
</template>
|
|
25
|
+
</v-snackbar>
|
|
26
|
+
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<style scoped>
|
|
30
|
+
|
|
31
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const model = defineModel<any>()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<v-text-field v-model="model" hide-details
|
|
7
|
+
density="compact" class="mr-2"
|
|
8
|
+
variant="outlined"
|
|
9
|
+
append-inner-icon="mdi-magnify"
|
|
10
|
+
:label="$t('action.search')"
|
|
11
|
+
single-line clearable @click:clear="() => model = ''"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<style scoped>
|
|
16
|
+
|
|
17
|
+
</style>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import EntityCrud from "../EntityCrud";
|
|
2
|
+
import type {IDraxPaginateResult} from "@drax/common-share";
|
|
3
|
+
import {useCrudStore} from "../stores/UseCrudStore";
|
|
4
|
+
import {computed} from "vue";
|
|
5
|
+
import type {ICrudField} from "@/interfaces/IEntityCrud";
|
|
6
|
+
|
|
7
|
+
export function useCrud(entity: EntityCrud) {
|
|
8
|
+
|
|
9
|
+
const store = useCrudStore()
|
|
10
|
+
|
|
11
|
+
async function loadItems() {
|
|
12
|
+
store.setLoading(true)
|
|
13
|
+
try {
|
|
14
|
+
const r: IDraxPaginateResult<any> = await entity?.provider.paginate({
|
|
15
|
+
page: store.page,
|
|
16
|
+
limit: store.itemsPerPage,
|
|
17
|
+
orderBy: store.sortBy[0]?.key,
|
|
18
|
+
order: store.sortBy[0]?.order,
|
|
19
|
+
search: store.search
|
|
20
|
+
})
|
|
21
|
+
store.setItems(r.items)
|
|
22
|
+
store.setTotalItems(r.total)
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error("Error paginating", e)
|
|
25
|
+
} finally {
|
|
26
|
+
store.setLoading(false)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cast(item: any){
|
|
31
|
+
|
|
32
|
+
entity.fields.filter(field => field.type === 'date')
|
|
33
|
+
.forEach(field => {
|
|
34
|
+
if(field.type === 'date'){
|
|
35
|
+
item[field.name] = new Date(item[field.name])
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return item
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function onCreate() {
|
|
44
|
+
store.setOperation("create")
|
|
45
|
+
store.setForm(entity.form)
|
|
46
|
+
store.setDialog(true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function onEdit(item: object) {
|
|
50
|
+
store.setOperation("edit")
|
|
51
|
+
store.setForm(cast({...item}))
|
|
52
|
+
store.setDialog(true)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function onDelete(item: object) {
|
|
56
|
+
store.setOperation("delete")
|
|
57
|
+
store.setForm(cast({...item}))
|
|
58
|
+
store.setDialog(true)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function onCancel() {
|
|
62
|
+
store.setDialog(false)
|
|
63
|
+
store.setError("")
|
|
64
|
+
store.setInputErrors(null)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onSubmit(formData: any) {
|
|
68
|
+
console.log("formData", formData)
|
|
69
|
+
store.setInputErrors(null)
|
|
70
|
+
switch (store.operation) {
|
|
71
|
+
case "create":
|
|
72
|
+
doCreate(formData)
|
|
73
|
+
break
|
|
74
|
+
case "edit":
|
|
75
|
+
doUpdate(formData)
|
|
76
|
+
break
|
|
77
|
+
case "delete":
|
|
78
|
+
doDelete(formData)
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function doCreate(formData: any) {
|
|
84
|
+
try {
|
|
85
|
+
await entity?.provider.create(formData)
|
|
86
|
+
await loadItems()
|
|
87
|
+
store.setDialog(false)
|
|
88
|
+
store.showMessage("Entity created successfully!")
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
if(e.inputErrors){
|
|
91
|
+
store.setInputErrors(e.inputErrors)
|
|
92
|
+
}
|
|
93
|
+
store.setError(e.message || "An error occurred while creating the entity")
|
|
94
|
+
console.error("Error creating entity", e)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function doUpdate(formData: any) {
|
|
100
|
+
try {
|
|
101
|
+
await entity?.provider.update(formData._id, formData)
|
|
102
|
+
await loadItems()
|
|
103
|
+
store.setDialog(false)
|
|
104
|
+
store.showMessage("Entity updated successfully!")
|
|
105
|
+
} catch (e: any) {
|
|
106
|
+
console.log("inputErrors", e.inputErrors)
|
|
107
|
+
if(e.inputErrors){
|
|
108
|
+
store.setInputErrors(e.inputErrors)
|
|
109
|
+
}
|
|
110
|
+
store.setError(e.message || "An error occurred while updating the entity")
|
|
111
|
+
console.error("Error updating entity", e)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function doDelete(formData: any) {
|
|
117
|
+
try {
|
|
118
|
+
await entity?.provider.delete(formData._id)
|
|
119
|
+
await loadItems()
|
|
120
|
+
store.setDialog(false)
|
|
121
|
+
store.showMessage("Entity deleted successfully!")
|
|
122
|
+
} catch (e: any) {
|
|
123
|
+
store.setError(e.message || "An error occurred while deleting the entity")
|
|
124
|
+
console.error("Error updating entity", e)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const dialog = computed({get(){return store.dialog} , set(value){store.setDialog(value)}})
|
|
130
|
+
const operation = computed({get(){return store.operation} , set(value){store.setOperation(value)}})
|
|
131
|
+
const form = computed({get(){return store.form} , set(value){store.setForm(value)}})
|
|
132
|
+
const formValid = computed({get(){return store.formValid} , set(value){store.setFormValid(value)}})
|
|
133
|
+
const notify = computed({get(){return store.notify} , set(value){store.setNotify(value)}})
|
|
134
|
+
const error = computed({get(){return store.error} , set(value){store.setError(value)}})
|
|
135
|
+
const message = computed({get(){return store.message} , set(value){store.setMessage(value)}})
|
|
136
|
+
const loading = computed({get(){return store.loading} , set(value){store.setLoading(value)}})
|
|
137
|
+
const itemsPerPage = computed({get(){return store.itemsPerPage} , set(value){store.setItemsPerPage(value)}})
|
|
138
|
+
const page = computed({get(){return store.page} , set(value){store.setPage(value)}})
|
|
139
|
+
const sortBy = computed({get(){return store.sortBy} , set(value){store.setSortBy(value)}})
|
|
140
|
+
const search = computed({get(){return store.search} , set(value){store.setSearch(value)}})
|
|
141
|
+
const totalItems = computed({get(){return store.totalItems} , set(value){store.setTotalItems(value)}})
|
|
142
|
+
const items = computed({get(){return store.items} , set(value){store.setItems(value)}})
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
loadItems, onCreate, onEdit, onDelete, onCancel, onSubmit,
|
|
146
|
+
operation, dialog, form, notify, error, message,
|
|
147
|
+
loading, itemsPerPage, page, sortBy, search, totalItems, items
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Crud from "./components/Crud.vue";
|
|
2
|
+
import CrudDialog from "./components/CrudDialog.vue";
|
|
3
|
+
import CrudForm from "./components/CrudForm.vue";
|
|
4
|
+
import CrudFormField from "./components/CrudFormField.vue";
|
|
5
|
+
import CrudFormList from "./components/CrudFormList.vue";
|
|
6
|
+
import CrudList from "./components/CrudList.vue";
|
|
7
|
+
import CrudNotify from "./components/CrudNotify.vue";
|
|
8
|
+
import CrudSearch from "./components/CrudSearch.vue";
|
|
9
|
+
import {useCrudStore} from "./stores/UseCrudStore";
|
|
10
|
+
import {useCrud} from "./composables/UseCrud";
|
|
11
|
+
import {EntityCrud} from "./EntityCrud";
|
|
12
|
+
|
|
13
|
+
import type {IFields, ICrudForm, ICrudHeaders, ICrudPermissions, ICrudRules} from "./interfaces/IEntityCrud";
|
|
14
|
+
export type {IFields, ICrudForm, ICrudHeaders, ICrudPermissions, ICrudRules}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
Crud,
|
|
18
|
+
CrudDialog,
|
|
19
|
+
CrudForm,
|
|
20
|
+
CrudFormField,
|
|
21
|
+
CrudFormList,
|
|
22
|
+
CrudList,
|
|
23
|
+
CrudNotify,
|
|
24
|
+
CrudSearch,
|
|
25
|
+
useCrud,
|
|
26
|
+
useCrudStore,
|
|
27
|
+
EntityCrud
|
|
28
|
+
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface ICrudHeaders {
|
|
2
|
+
title: string
|
|
3
|
+
key: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface ICrudRules {
|
|
7
|
+
[key: string]: Array<Function>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ICrudField {
|
|
11
|
+
name: string
|
|
12
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'ref' | 'array.string' | 'array.number' | 'array.object' | 'array.ref'
|
|
13
|
+
ref?: string
|
|
14
|
+
objectFields?: ICrudField[]
|
|
15
|
+
label: string,
|
|
16
|
+
default: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ICrudForm {
|
|
20
|
+
[key: string]: string | number | boolean | Date | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type IFields = ICrudField[]
|
|
24
|
+
|
|
25
|
+
interface ICrudPermissions {
|
|
26
|
+
manage: string
|
|
27
|
+
view: string
|
|
28
|
+
create: string
|
|
29
|
+
update: string
|
|
30
|
+
delete: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type {ICrudHeaders, ICrudRules, ICrudField, IFields, ICrudForm, ICrudPermissions}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {defineStore} from "pinia";
|
|
2
|
+
import type {TOperation} from "../interfaces/TOperation";
|
|
3
|
+
|
|
4
|
+
export const useCrudStore = defineStore('CrudStore', {
|
|
5
|
+
state: () => (
|
|
6
|
+
{
|
|
7
|
+
operation: null as TOperation,
|
|
8
|
+
dialog: false as boolean,
|
|
9
|
+
form: {} as any,
|
|
10
|
+
formValid: {} as any,
|
|
11
|
+
notify: false as boolean,
|
|
12
|
+
error: '' as string,
|
|
13
|
+
message: '' as string,
|
|
14
|
+
items: [] as any[],
|
|
15
|
+
totalItems: 0 as number,
|
|
16
|
+
itemsPerPage: 5 as number,
|
|
17
|
+
page: 1 as number,
|
|
18
|
+
search: '' as string,
|
|
19
|
+
sortBy: [] as any[],
|
|
20
|
+
loading: false,
|
|
21
|
+
inputErrors: null
|
|
22
|
+
}
|
|
23
|
+
),
|
|
24
|
+
getters:{
|
|
25
|
+
getInputErrors(state: any) {
|
|
26
|
+
return (fieldName:string) => {
|
|
27
|
+
if (state.inputErrors && state.inputErrors[fieldName]) {
|
|
28
|
+
return state.inputErrors[fieldName]
|
|
29
|
+
}
|
|
30
|
+
return []
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
actions: {
|
|
35
|
+
setOperation(operation: TOperation) {
|
|
36
|
+
this.operation = operation
|
|
37
|
+
},
|
|
38
|
+
setDialog(dialog: boolean) {
|
|
39
|
+
this.dialog = dialog
|
|
40
|
+
},
|
|
41
|
+
setForm(form: any) {
|
|
42
|
+
this.form = form
|
|
43
|
+
},
|
|
44
|
+
setFormValid(formValid: any) {
|
|
45
|
+
this.formValid = formValid
|
|
46
|
+
},
|
|
47
|
+
setError(error: string) {
|
|
48
|
+
this.error = error
|
|
49
|
+
},
|
|
50
|
+
showMessage(message: string) {
|
|
51
|
+
this.message = message
|
|
52
|
+
this.notify = true
|
|
53
|
+
},
|
|
54
|
+
setNotify(notify: boolean) {
|
|
55
|
+
this.notify = notify
|
|
56
|
+
},
|
|
57
|
+
setMessage(message: string) {
|
|
58
|
+
this.message = message
|
|
59
|
+
},
|
|
60
|
+
setItems(items: any[]) {
|
|
61
|
+
this.items = items
|
|
62
|
+
},
|
|
63
|
+
setTotalItems(totalItems: number) {
|
|
64
|
+
this.totalItems = totalItems
|
|
65
|
+
},
|
|
66
|
+
setItemsPerPage(itemsPerPage: number) {
|
|
67
|
+
this.itemsPerPage = itemsPerPage
|
|
68
|
+
},
|
|
69
|
+
setPage(page: number) {
|
|
70
|
+
this.page = page
|
|
71
|
+
},
|
|
72
|
+
setSearch(search: string) {
|
|
73
|
+
this.search = search
|
|
74
|
+
},
|
|
75
|
+
setSortBy(sortBy: any[]) {
|
|
76
|
+
this.sortBy = sortBy
|
|
77
|
+
},
|
|
78
|
+
setLoading(loading: boolean) {
|
|
79
|
+
this.loading = loading
|
|
80
|
+
},
|
|
81
|
+
setInputErrors(inputErrors: any) {
|
|
82
|
+
this.inputErrors = inputErrors
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
})
|