Последняя версия с сервера прошлого разработчика
This commit is contained in:
339
nova/resources/js/components/Form/FileField.vue
Executable file
339
nova/resources/js/components/Form/FileField.vue
Executable file
@@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<default-field
|
||||
:field="field"
|
||||
:errors="errors"
|
||||
:full-width-content="true"
|
||||
:show-help-text="!isReadonly && showHelpText"
|
||||
>
|
||||
<template slot="field">
|
||||
<div v-if="hasValue" :class="{ 'mb-6': !isReadonly }">
|
||||
<template v-if="shouldShowLoader">
|
||||
<ImageLoader
|
||||
:src="imageUrl"
|
||||
:maxWidth="maxWidth"
|
||||
:rounded="field.rounded"
|
||||
@missing="value => (missing = value)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="field.value && !imageUrl">
|
||||
<card
|
||||
class="flex item-center relative border border-lg border-50 overflow-hidden p-4"
|
||||
>
|
||||
<span class="truncate mr-3"> {{ field.value }} </span>
|
||||
|
||||
<DeleteButton
|
||||
:dusk="field.attribute + '-internal-delete-link'"
|
||||
class="ml-auto"
|
||||
v-if="shouldShowRemoveButton"
|
||||
@click="confirmRemoval"
|
||||
/>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<p
|
||||
v-if="imageUrl && !isReadonly"
|
||||
class="mt-3 flex items-center text-sm"
|
||||
>
|
||||
<DeleteButton
|
||||
:dusk="field.attribute + '-delete-link'"
|
||||
v-if="shouldShowRemoveButton"
|
||||
@click="confirmRemoval"
|
||||
>
|
||||
<span class="class ml-2 mt-1"> {{ __('Delete') }} </span>
|
||||
</DeleteButton>
|
||||
</p>
|
||||
|
||||
<portal to="modals">
|
||||
<confirm-upload-removal-modal
|
||||
v-if="removeModalOpen"
|
||||
@confirm="removeFile"
|
||||
@close="closeRemoveModal"
|
||||
/>
|
||||
</portal>
|
||||
</div>
|
||||
|
||||
<p v-if="!hasValue && isReadonly" class="pt-2 text-sm text-90">
|
||||
{{ __('This file field is read-only.') }}
|
||||
</p>
|
||||
|
||||
<span
|
||||
v-if="shouldShowField"
|
||||
class="form-file mr-4"
|
||||
:class="{ 'opacity-75': isReadonly }"
|
||||
>
|
||||
<input
|
||||
ref="fileField"
|
||||
:dusk="field.attribute"
|
||||
class="form-file-input select-none"
|
||||
type="file"
|
||||
:id="idAttr"
|
||||
name="name"
|
||||
@change="fileChange"
|
||||
:disabled="isReadonly || uploading"
|
||||
:accept="field.acceptedTypes"
|
||||
/>
|
||||
<label
|
||||
:for="labelFor"
|
||||
class="form-file-btn btn btn-default btn-primary select-none"
|
||||
>
|
||||
<span v-if="uploading"
|
||||
>{{ __('Uploading') }} ({{ uploadProgress }}%)</span
|
||||
>
|
||||
<span v-else>{{ __('Choose File') }}</span>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
<span v-if="shouldShowField" class="text-90 text-sm select-none">
|
||||
{{ currentLabel }}
|
||||
</span>
|
||||
|
||||
<p v-if="hasError" class="text-xs mt-2 text-danger">{{ firstError }}</p>
|
||||
</template>
|
||||
</default-field>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImageLoader from '@/components/ImageLoader'
|
||||
import DeleteButton from '@/components/DeleteButton'
|
||||
import { FormField, HandlesValidationErrors, Errors } from 'laravel-nova'
|
||||
import Vapor from 'laravel-vapor'
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'resourceId',
|
||||
'relatedResourceName',
|
||||
'relatedResourceId',
|
||||
'viaRelationship',
|
||||
],
|
||||
|
||||
mixins: [HandlesValidationErrors, FormField],
|
||||
|
||||
components: { DeleteButton, ImageLoader },
|
||||
|
||||
data: () => ({
|
||||
file: null,
|
||||
fileName: '',
|
||||
removeModalOpen: false,
|
||||
missing: false,
|
||||
deleted: false,
|
||||
uploadErrors: new Errors(),
|
||||
vaporFile: {
|
||||
key: '',
|
||||
uuid: '',
|
||||
filename: '',
|
||||
extension: '',
|
||||
},
|
||||
uploading: false,
|
||||
uploadProgress: 0,
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
this.field.fill = formData => {
|
||||
let attribute = this.field.attribute
|
||||
|
||||
if (this.file && !this.isVaporField) {
|
||||
formData.append(attribute, this.file, this.fileName)
|
||||
}
|
||||
|
||||
if (this.file && this.isVaporField) {
|
||||
formData.append(attribute, this.fileName)
|
||||
formData.append('vaporFile[' + attribute + '][key]', this.vaporFile.key)
|
||||
formData.append(
|
||||
'vaporFile[' + attribute + '][uuid]',
|
||||
this.vaporFile.uuid
|
||||
)
|
||||
formData.append(
|
||||
'vaporFile[' + attribute + '][filename]',
|
||||
this.vaporFile.filename
|
||||
)
|
||||
formData.append(
|
||||
'vaporFile[' + attribute + '][extension]',
|
||||
this.vaporFile.extension
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Respond to the file change
|
||||
*/
|
||||
fileChange(event) {
|
||||
let path = event.target.value
|
||||
let fileName = path.match(/[^\\/]*$/)[0]
|
||||
this.fileName = fileName
|
||||
let extension = fileName.split('.').pop()
|
||||
this.file = this.$refs.fileField.files[0]
|
||||
|
||||
if (this.isVaporField) {
|
||||
this.uploading = true
|
||||
this.$emit('file-upload-started')
|
||||
|
||||
Vapor.store(this.$refs.fileField.files[0], {
|
||||
progress: progress => {
|
||||
this.uploadProgress = Math.round(progress * 100)
|
||||
},
|
||||
}).then(response => {
|
||||
this.vaporFile.key = response.key
|
||||
this.vaporFile.uuid = response.uuid
|
||||
this.vaporFile.filename = fileName
|
||||
this.vaporFile.extension = extension
|
||||
this.uploading = false
|
||||
this.uploadProgress = 0
|
||||
this.$emit('file-upload-finished')
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm removal of the linked file
|
||||
*/
|
||||
confirmRemoval() {
|
||||
this.removeModalOpen = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the upload removal modal
|
||||
*/
|
||||
closeRemoveModal() {
|
||||
this.removeModalOpen = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the linked file from storage
|
||||
*/
|
||||
async removeFile() {
|
||||
this.uploadErrors = new Errors()
|
||||
|
||||
const {
|
||||
resourceName,
|
||||
resourceId,
|
||||
relatedResourceName,
|
||||
relatedResourceId,
|
||||
viaRelationship,
|
||||
} = this
|
||||
const attribute = this.field.attribute
|
||||
|
||||
const uri =
|
||||
this.viaRelationship &&
|
||||
this.relatedResourceName &&
|
||||
this.relatedResourceId
|
||||
? `/nova-api/${resourceName}/${resourceId}/${relatedResourceName}/${relatedResourceId}/field/${attribute}?viaRelationship=${viaRelationship}`
|
||||
: `/nova-api/${resourceName}/${resourceId}/field/${attribute}`
|
||||
|
||||
try {
|
||||
await Nova.request().delete(uri)
|
||||
this.closeRemoveModal()
|
||||
this.deleted = true
|
||||
this.$emit('file-deleted')
|
||||
Nova.success(this.__('The file was deleted!'))
|
||||
} catch (error) {
|
||||
this.closeRemoveModal()
|
||||
|
||||
if (error.response.status == 422) {
|
||||
this.uploadErrors = new Errors(error.response.data.errors)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if the field has an upload error.
|
||||
*/
|
||||
hasError() {
|
||||
return this.uploadErrors.has(this.fieldAttribute)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the first error for the field.
|
||||
*/
|
||||
firstError() {
|
||||
if (this.hasError) {
|
||||
return this.uploadErrors.first(this.fieldAttribute)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The current label of the file field.
|
||||
*/
|
||||
currentLabel() {
|
||||
return this.fileName || this.__('no file selected')
|
||||
},
|
||||
|
||||
/**
|
||||
* The ID attribute to use for the file field.
|
||||
*/
|
||||
idAttr() {
|
||||
return this.labelFor
|
||||
},
|
||||
|
||||
/**
|
||||
* The label attribute to use for the file field.
|
||||
*/
|
||||
labelFor() {
|
||||
let name = this.resourceName
|
||||
|
||||
if (this.relatedResourceName) {
|
||||
name += '-' + this.relatedResourceName
|
||||
}
|
||||
|
||||
return `file-${name}-${this.field.attribute}`
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the field has a value.
|
||||
*/
|
||||
hasValue() {
|
||||
return (
|
||||
Boolean(this.field.value || this.imageUrl) &&
|
||||
!Boolean(this.deleted) &&
|
||||
!Boolean(this.missing)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the field should show the loader component.
|
||||
*/
|
||||
shouldShowLoader() {
|
||||
return !Boolean(this.deleted) && Boolean(this.imageUrl)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the file field input should be shown.
|
||||
*/
|
||||
shouldShowField() {
|
||||
return Boolean(!this.isReadonly)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the field should show the remove button.
|
||||
*/
|
||||
shouldShowRemoveButton() {
|
||||
return Boolean(this.field.deletable && !this.isReadonly)
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the preview or thumbnail URL for the field.
|
||||
*/
|
||||
imageUrl() {
|
||||
return this.field.previewUrl || this.field.thumbnailUrl
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the maximum width of the field.
|
||||
*/
|
||||
maxWidth() {
|
||||
return this.field.maxWidth || 320
|
||||
},
|
||||
|
||||
/**
|
||||
* Determing if the field is a Vapor field.
|
||||
*/
|
||||
isVaporField() {
|
||||
return this.field.component == 'vapor-file-field'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user