feat: display product options
This commit is contained in:
20
spa/src/components/ProductOptions/ProductOptions.vue
Normal file
20
spa/src/components/ProductOptions/ProductOptions.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div v-for="option in options" :key="option.product_option_id" class="mt-3">
|
||||
<OptionRadio v-if="option.type === 'radio'" :modelValue="option"/>
|
||||
<OptionCheckbox v-else-if="option.type === 'checkbox'" :modelValue="option"/>
|
||||
<OptionText v-else-if="option.type === 'text'" :modelValue="option"/>
|
||||
<OptionTextarea v-else-if="option.type === 'textarea'" :modelValue="option"/>
|
||||
<OptionSelect v-else-if="option.type === 'select'" :modelValue="option"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionRadio from "./Types/OptionRadio.vue";
|
||||
import OptionCheckbox from "./Types/OptionCheckbox.vue";
|
||||
import OptionText from "./Types/OptionText.vue";
|
||||
import OptionTextarea from "./Types/OptionTextarea.vue";
|
||||
import OptionSelect from "./Types/OptionSelect.vue";
|
||||
|
||||
const options = defineModel();
|
||||
|
||||
</script>
|
||||
41
spa/src/components/ProductOptions/Types/OptionCheckbox.vue
Normal file
41
spa/src/components/ProductOptions/Types/OptionCheckbox.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div>
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="value in model.values"
|
||||
class="group relative flex items-center justify-center rounded-md border border-gray-300 bg-white p-2 has-checked:border-indigo-600 has-checked:bg-indigo-600 has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-indigo-600 has-disabled:border-gray-400 has-disabled:bg-gray-200 has-disabled:opacity-25">
|
||||
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="value.product_option_value_id"
|
||||
:checked="value.selected"
|
||||
@change="select(value)"
|
||||
class="absolute inset-0 appearance-none focus:outline-none disabled:cursor-not-allowed"
|
||||
/>
|
||||
|
||||
<span class="text-xs font-medium group-has-checked:text-white">
|
||||
{{ value.name }}<span v-if="value.price"> ({{ value.price_prefix }}{{ value.price }})</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</OptionTemplate>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionTemplate from "./OptionTemplate.vue";
|
||||
|
||||
const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function select(toggledValue) {
|
||||
model.value.values.forEach(value => {
|
||||
if (value === toggledValue) {
|
||||
value.selected = !value.selected;
|
||||
}
|
||||
});
|
||||
|
||||
emit('update:modelValue', model.value);
|
||||
}
|
||||
</script>
|
||||
38
spa/src/components/ProductOptions/Types/OptionRadio.vue
Normal file
38
spa/src/components/ProductOptions/Types/OptionRadio.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="value in model.values"
|
||||
class="group relative flex items-center justify-center rounded-md border border-gray-300 bg-base-200 p-2 has-checked:border-indigo-600 has-checked:bg-primary has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-indigo-600 has-disabled:border-gray-400 has-disabled:bg-gray-200 has-disabled:opacity-25">
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
:name="`option-${model.product_option_id}`"
|
||||
:value="value.product_option_value_id"
|
||||
:checked="value.selected"
|
||||
@change="select(value)"
|
||||
class="absolute inset-0 appearance-none focus:outline-none disabled:cursor-not-allowed"
|
||||
/>
|
||||
|
||||
<span class="text-xs font-medium group-has-checked:text-white">
|
||||
{{ value.name }}<span v-if="value.price"> ({{ value.price_prefix }}{{ value.price }})</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</OptionTemplate>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionTemplate from "./OptionTemplate.vue";
|
||||
|
||||
const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function select(selectedValue) {
|
||||
model.value.values.forEach(value => {
|
||||
value.selected = (value === selectedValue);
|
||||
});
|
||||
|
||||
emit('update:modelValue', model);
|
||||
}
|
||||
</script>
|
||||
35
spa/src/components/ProductOptions/Types/OptionSelect.vue
Normal file
35
spa/src/components/ProductOptions/Types/OptionSelect.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<select
|
||||
:name="`option-${model.product_option_id}`"
|
||||
class="select"
|
||||
@change="onChange"
|
||||
>
|
||||
<option
|
||||
v-for="value in model.values"
|
||||
:key="value.product_option_value_id"
|
||||
:value="value.product_option_value_id"
|
||||
:selected="value.selected"
|
||||
>
|
||||
{{ value.name }}<span v-if="value.price"> ({{ value.price_prefix }}{{ value.price }})</span>
|
||||
</option>
|
||||
</select>
|
||||
</OptionTemplate>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionTemplate from "./OptionTemplate.vue";
|
||||
|
||||
const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function onChange(event) {
|
||||
const selectedId = Number(event.target.value);
|
||||
|
||||
model.value.values.forEach(value => {
|
||||
value.selected = (value.product_option_value_id === selectedId);
|
||||
});
|
||||
|
||||
emit('update:modelValue', model.value);
|
||||
}
|
||||
</script>
|
||||
25
spa/src/components/ProductOptions/Types/OptionTemplate.vue
Normal file
25
spa/src/components/ProductOptions/Types/OptionTemplate.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="text-sm mb-2">
|
||||
{{ name }} <span v-if="required" class="text-red-500">*</span>
|
||||
</h3>
|
||||
|
||||
<fieldset>
|
||||
<slot></slot>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
23
spa/src/components/ProductOptions/Types/OptionText.vue
Normal file
23
spa/src/components/ProductOptions/Types/OptionText.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
:placeholder="model.name"
|
||||
:value="model.value"
|
||||
@input="input(model, $event.target.value)"
|
||||
/>
|
||||
</OptionTemplate>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionTemplate from "./OptionTemplate.vue";
|
||||
|
||||
const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function input(model, newValue) {
|
||||
model.value = newValue;
|
||||
emit('update:modelValue', model);
|
||||
}
|
||||
</script>
|
||||
23
spa/src/components/ProductOptions/Types/OptionTextarea.vue
Normal file
23
spa/src/components/ProductOptions/Types/OptionTextarea.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<textarea
|
||||
type="text"
|
||||
class="textarea"
|
||||
:placeholder="model.name"
|
||||
v-text="model.value"
|
||||
@input="input(model, $event.target.value)"
|
||||
/>
|
||||
</OptionTemplate>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import OptionTemplate from "./OptionTemplate.vue";
|
||||
|
||||
const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function input(model, newValue) {
|
||||
model.value = newValue;
|
||||
emit('update:modelValue', model);
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user