mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
51 changed files with 1227 additions and 519 deletions
@ -1,5 +1,11 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const disableBaseLayout = $computed(() => route.path.startsWith('/nc/view') || route.path.startsWith('/nc/form')) |
||||||
|
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<NuxtLayout name="base"> |
<NuxtLayout :name="disableBaseLayout ? false : 'base'"> |
||||||
<NuxtPage /> |
<NuxtPage /> |
||||||
</NuxtLayout> |
</NuxtLayout> |
||||||
</template> |
</template> |
||||||
|
@ -0,0 +1,197 @@ |
|||||||
|
import useVuelidate from '@vuelidate/core' |
||||||
|
import { minLength, required } from '@vuelidate/validators' |
||||||
|
import { message } from 'ant-design-vue' |
||||||
|
import type { ColumnType, FormType, LinkToAnotherRecordType, TableType, ViewType } from 'nocodb-sdk' |
||||||
|
import { ErrorMessages, RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
||||||
|
import type { Ref } from 'vue' |
||||||
|
import { SharedViewPasswordInj } from '~/context' |
||||||
|
import { extractSdkResponseErrorMsg } from '~/utils' |
||||||
|
import { useInjectionState, useMetas } from '#imports' |
||||||
|
|
||||||
|
const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((sharedViewId: string) => { |
||||||
|
const progress = ref(false) |
||||||
|
const notFound = ref(false) |
||||||
|
const submitted = ref(false) |
||||||
|
const passwordDlg = ref(false) |
||||||
|
const password = ref<string | null>(null) |
||||||
|
const secondsRemain = ref(0) |
||||||
|
|
||||||
|
provide(SharedViewPasswordInj, password) |
||||||
|
|
||||||
|
const sharedView = ref<ViewType>() |
||||||
|
const sharedFormView = ref<FormType>() |
||||||
|
const meta = ref<TableType>() |
||||||
|
const columns = ref<(ColumnType & { required?: boolean; show?: boolean })[]>() |
||||||
|
|
||||||
|
const { $api } = useNuxtApp() |
||||||
|
const { metas, setMeta } = useMetas() |
||||||
|
const formState = ref({}) |
||||||
|
|
||||||
|
const { state: additionalState } = useProvideSmartsheetRowStore( |
||||||
|
meta as Ref<TableType>, |
||||||
|
ref({ |
||||||
|
row: formState, |
||||||
|
rowMeta: { new: true }, |
||||||
|
oldRow: {}, |
||||||
|
}), |
||||||
|
) |
||||||
|
|
||||||
|
const formColumns = computed(() => |
||||||
|
columns?.value?.filter((c) => c.show)?.filter((col) => !isVirtualCol(col) || col.uidt === UITypes.LinkToAnotherRecord), |
||||||
|
) |
||||||
|
const loadSharedView = async () => { |
||||||
|
try { |
||||||
|
const viewMeta = await $api.public.sharedViewMetaGet(sharedViewId, { |
||||||
|
headers: { |
||||||
|
'xc-password': password.value, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
passwordDlg.value = false |
||||||
|
|
||||||
|
sharedView.value = viewMeta |
||||||
|
sharedFormView.value = viewMeta.view |
||||||
|
meta.value = viewMeta.model |
||||||
|
columns.value = viewMeta.model?.columns |
||||||
|
|
||||||
|
setMeta(viewMeta.model) |
||||||
|
|
||||||
|
const relatedMetas = { ...viewMeta.relatedMetas } |
||||||
|
Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key])) |
||||||
|
} catch (e: any) { |
||||||
|
if (e.response && e.response.status === 404) { |
||||||
|
notFound.value = true |
||||||
|
} else if ((await extractSdkResponseErrorMsg(e)) === ErrorMessages.INVALID_SHARED_VIEW_PASSWORD) { |
||||||
|
passwordDlg.value = true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const validators = computed(() => { |
||||||
|
const obj: any = { |
||||||
|
localState: {}, |
||||||
|
virtual: {}, |
||||||
|
} |
||||||
|
for (const column of formColumns?.value ?? []) { |
||||||
|
if ( |
||||||
|
!isVirtualCol(column) && |
||||||
|
((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || (column as any).required) |
||||||
|
) { |
||||||
|
obj.localState[column.title!] = { required } |
||||||
|
} else if ( |
||||||
|
column.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
column.colOptions && |
||||||
|
(column.colOptions as LinkToAnotherRecordType).type === RelationTypes.BELONGS_TO |
||||||
|
) { |
||||||
|
const col = columns.value?.find((c) => c.id === (column?.colOptions as LinkToAnotherRecordType)?.fk_child_column_id) |
||||||
|
|
||||||
|
if ((col && col.rqd && !col.cdf) || column.required) { |
||||||
|
if (col) { |
||||||
|
obj.virtual[column.title!] = { required } |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (isVirtualCol(column) && column.required) { |
||||||
|
obj.virtual[column.title!] = { |
||||||
|
minLength: minLength(1), |
||||||
|
required, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return obj |
||||||
|
}) |
||||||
|
|
||||||
|
const v$ = useVuelidate( |
||||||
|
validators, |
||||||
|
computed(() => ({ localState: formState?.value, virtual: additionalState?.value })), |
||||||
|
) |
||||||
|
|
||||||
|
const submitForm = async () => { |
||||||
|
try { |
||||||
|
if (!(await v$.value?.$validate())) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
progress.value = true |
||||||
|
const data: Record<string, any> = { ...(formState?.value ?? {}), ...(additionalState?.value || {}) } |
||||||
|
const attachment: Record<string, any> = {} |
||||||
|
|
||||||
|
for (const col of metas?.value?.[sharedView?.value?.fk_model_id as string]?.columns ?? []) { |
||||||
|
if (col.uidt === UITypes.Attachment) { |
||||||
|
attachment[`_${col.title}`] = data[col.title!] |
||||||
|
delete data[col.title!] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
await $api.public.dataCreate( |
||||||
|
sharedView?.value?.uuid as string, |
||||||
|
{ |
||||||
|
data, |
||||||
|
...attachment, |
||||||
|
}, |
||||||
|
{ |
||||||
|
headers: { |
||||||
|
'xc-password': password.value, |
||||||
|
}, |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
submitted.value = true |
||||||
|
progress.value = false |
||||||
|
|
||||||
|
await message.success(sharedFormView.value?.success_msg || 'Saved successfully.') |
||||||
|
} catch (e: any) { |
||||||
|
console.log(e) |
||||||
|
await message.error(await extractSdkResponseErrorMsg(e)) |
||||||
|
} |
||||||
|
progress.value = false |
||||||
|
} |
||||||
|
|
||||||
|
/** reset form if show_blank_form is true */ |
||||||
|
watch(submitted, (nextVal: boolean) => { |
||||||
|
if (nextVal && sharedFormView.value?.show_blank_form) { |
||||||
|
secondsRemain.value = 5 |
||||||
|
const intvl = setInterval(() => { |
||||||
|
secondsRemain.value = secondsRemain.value - 1 |
||||||
|
if (secondsRemain.value < 0) { |
||||||
|
submitted.value = false |
||||||
|
clearInterval(intvl) |
||||||
|
} |
||||||
|
}, 1000) |
||||||
|
} |
||||||
|
|
||||||
|
/** reset form state and validation */ |
||||||
|
if (!nextVal) { |
||||||
|
additionalState.value = {} |
||||||
|
formState.value = {} |
||||||
|
v$.value?.$reset() |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
return { |
||||||
|
sharedView, |
||||||
|
sharedFormView, |
||||||
|
loadSharedView, |
||||||
|
columns, |
||||||
|
submitForm, |
||||||
|
progress, |
||||||
|
meta, |
||||||
|
validators, |
||||||
|
v$, |
||||||
|
formColumns, |
||||||
|
formState, |
||||||
|
notFound, |
||||||
|
password, |
||||||
|
submitted, |
||||||
|
secondsRemain, |
||||||
|
passwordDlg, |
||||||
|
} |
||||||
|
}, 'expanded-form-store') |
||||||
|
|
||||||
|
export { useProvideSharedFormStore } |
||||||
|
|
||||||
|
export function useSharedFormStoreOrThrow() { |
||||||
|
const sharedFormStore = useSharedFormStore() |
||||||
|
if (sharedFormStore == null) throw new Error('Please call `useProvideSharedFormStore` on the appropriate parent component') |
||||||
|
return sharedFormStore |
||||||
|
} |
Loading…
Reference in new issue