Image Upload

This commit is contained in:
Julian Krauser 2025-04-29 18:32:08 +02:00
parent 0771b43f56
commit 91ede95530
4 changed files with 117 additions and 34 deletions

View file

@ -9,7 +9,14 @@
<button type="submit" class="!w-fit !h-fit !p-0">
<CheckIcon class="h-5 w-5 cursor-pointer" />
</button>
<button type="reset" class="!w-fit !h-fit !p-0" @click="enableEdit = false">
<button
type="reset"
class="!w-fit !h-fit !p-0"
@click="
enableEdit = false;
$emit('reset');
"
>
<XMarkIcon class="h-5 w-5 cursor-pointer" />
</button>
</div>
@ -48,6 +55,7 @@ export default defineComponent({
required: true,
},
},
emits: ["reset"],
data() {
return {
enableEdit: false as boolean,

View file

@ -1,31 +1,48 @@
<template>
<BaseSetting title="Vereins-Auftritt Einstellungen" :submit-function="submit" v-slot="{ enableEdit }">
<BaseSetting title="Vereins-Auftritt Einstellungen" :submit-function="submit" v-slot="{ enableEdit }" @reset="reset">
<div class="w-full">
<p>Vereins-Icon</p>
<AppIcon v-if="clubSettings['club.icon'] != '' && !overwriteIcon" class="h-10! max-w-full mx-auto" />
<img v-else-if="overwriteIcon" ref="icon_img" class="hidden w-full h-20 object-contain" />
<div
v-else
class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm cursor-pointer"
@click="($refs.icon as HTMLInputElement).click()"
>
Kein eigenes Icon ausgewählt
<div class="flex flex-row gap-2">
<AppIcon v-if="icon != '' && !overwriteIcon" class="h-10! max-w-full mx-auto" />
<div
v-else-if="!overwriteIcon"
class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm"
:class="{ 'cursor-pointer': enableEdit }"
@click="enableEdit ? ($refs.icon as HTMLInputElement).click() : null"
>
Kein eigenes Icon ausgewählt
</div>
<img ref="icon_img" class="hidden w-full h-20 object-contain" />
<XMarkIcon
v-if="enableEdit && (icon != '' || overwriteIcon)"
class="h-5 w-5 cursor-pointer"
@click="resetImage('icon')"
/>
</div>
<input class="hidden!" type="file" ref="icon" accept="image/*" @change="previewImage('icon')" />
</div>
<div class="w-full">
<p>Vereins-Logo</p>
<AppLogo v-if="clubSettings['club.logo'] != '' && !overwriteLogo" class="h-10! max-w-full mx-auto" />
<img v-else-if="overwriteLogo" ref="logo_img" class="hidden w-full h-20 object-contain" />
<div
v-else
class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm cursor-pointer"
@click="($refs.logo as HTMLInputElement).click()"
>
Kein eigenes Logo ausgewählt
<div class="flex flex-row gap-2">
<AppLogo v-if="logo != '' && !overwriteLogo" class="h-10! max-w-full mx-auto" />
<div
v-else-if="!overwriteLogo"
class="flex h-10 w-full border-2 border-gray-300 rounded-md items-center justify-center text-sm"
:class="{ 'cursor-pointer': enableEdit }"
@click="enableEdit ? ($refs.logo as HTMLInputElement).click() : null"
>
Kein eigenes Logo ausgewählt
</div>
<img ref="logo_img" class="hidden w-full h-20 object-contain" />
<XMarkIcon
v-if="enableEdit && (logo != '' || overwriteLogo)"
class="h-5 w-5 cursor-pointer"
@click="resetImage('logo')"
/>
</div>
<input class="hidden!" type="file" ref="logo" accept="image/*" @change="previewImage('logo')" /></div
></BaseSetting>
<input class="hidden!" type="file" ref="logo" accept="image/*" @change="previewImage('logo')" />
</div>
</BaseSetting>
</template>
<script setup lang="ts">
@ -37,12 +54,20 @@ import AppLogo from "@/components/AppLogo.vue";
import { useAbilityStore } from "@/stores/ability";
import type { SettingString } from "@/types/settingTypes";
import BaseSetting from "./BaseSetting.vue";
import { XMarkIcon } from "@heroicons/vue/24/outline";
</script>
<script lang="ts">
export default defineComponent({
watch: {
clubSettings() {
this.reset();
},
},
data() {
return {
logo: "",
icon: "",
overwriteIcon: false as boolean,
overwriteLogo: false as boolean,
};
@ -54,8 +79,35 @@ export default defineComponent({
return this.readByTopic("club");
},
},
mounted() {
this.reset();
},
methods: {
...mapActions(useSettingStore, ["updateSettings", "uploadImage"]),
reset() {
this.icon = this.clubSettings["club.icon"];
this.overwriteIcon = false;
(this.$refs.icon_img as HTMLImageElement).style.display = "none";
(this.$refs.icon as HTMLInputElement).value = "";
this.logo = this.clubSettings["club.logo"];
this.overwriteLogo = false;
(this.$refs.logo_img as HTMLImageElement).style.display = "none";
(this.$refs.logo as HTMLInputElement).value = "";
},
resetImage(inputname: "icon" | "logo") {
if (inputname == "icon") {
this.icon = "";
this.overwriteIcon = false;
(this.$refs.icon_img as HTMLImageElement).style.display = "none";
(this.$refs.icon as HTMLInputElement).value = "";
} else {
this.logo = "";
this.overwriteLogo = false;
(this.$refs.logo_img as HTMLImageElement).style.display = "none";
(this.$refs.logo as HTMLInputElement).value = "";
}
},
previewImage(inputname: "icon" | "logo") {
let input = this.$refs[inputname] as HTMLInputElement;
let previewElement = this.$refs[inputname + "_img"] as HTMLImageElement;
@ -68,6 +120,12 @@ export default defineComponent({
};
reader.readAsDataURL(input.files[0]);
if (inputname == "icon") {
this.overwriteIcon = true;
} else {
this.overwriteLogo = true;
}
} else {
previewElement.src = "";
previewElement.style.display = "none";
@ -76,11 +134,11 @@ export default defineComponent({
submit(e: any) {
return this.uploadImage([
{
key: "club.icon" as SettingString,
key: "club.icon",
value: (this.$refs.icon as HTMLInputElement).files?.[0],
},
{
key: "club.logo" as SettingString,
key: "club.logo",
value: (this.$refs.logo as HTMLInputElement).files?.[0],
},
]);

View file

@ -34,7 +34,7 @@
<label for="secure">Secure-Verbindung (setzen bei Port 465)</label>
</div>
<div class="w-full">
<label for="password">Passwort (optional)</label>
<label for="password">Passwort (optional - leeres Feld setzt Passwort nicht zurück)</label>
<input id="password" type="password" :readonly="!enableEdit" autocomplete="new-password" />
</div>
</BaseSetting>

View file

@ -54,26 +54,43 @@ export const useSettingStore = defineStore("setting", {
key: K,
value: SettingValueMapping[K]
): Promise<AxiosResponse<any, any>> {
return await http.put("/admin/setting", {
setting: key,
value: value,
});
return await http
.put("/admin/setting", {
setting: key,
value: value,
})
.then((res) => {
this.settings[key] = value;
return res;
});
},
async updateSettings<K extends SettingString>(
data: { key: K; value: SettingValueMapping[K] }[]
): Promise<AxiosResponse<any, any>> {
return await http.put("/admin/setting/multi", data);
return await http.put("/admin/setting/multi", data).then((res) => {
for (const element of data) {
this.settings[element.key] = element.value;
}
return res;
});
},
async uploadImage(data: { key: SettingString; value?: File }[]): Promise<AxiosResponse<any, any>> {
async uploadImage(data: { key: "club.logo" | "club.icon"; value?: File }[]): Promise<AxiosResponse<any, any>> {
const formData = new FormData();
for (let entry of data) {
if (entry.value) formData.append(entry.key, entry.value);
}
return await http.put("/admin/setting/images", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
return await http
.put("/admin/setting/images", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
for (const element of data) {
this.settings[element.key] = element.value ? "configured" : "";
}
return res;
});
},
async resetSetting(key: SettingString): Promise<AxiosResponse<any, any>> {
return await http.delete(`/admin/setting/${key}`);