PC configure
by PhilQ
HTML
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<div id="app">
<div
class="part"
v-for="(part, part_i, part_idx) in parts" :key="part_i"
:class="{'folded': folded[part_i]}"
>
<h2 @click="toggle(part_i)">{{ (part_idx+1) }}. {{ part_i }}<span class="selected-part" v-if="folded[part_i]">{{ parts[part_i][ selection[part_i].item ].name }} | {{ euroFormatted(sorted(parts[part_i][ selection[part_i].item ].prices, 'price')[ selection[part_i].price ].price) }}</span></h2>
<table>
<tr>
<td class="select"></td>
<td class="name">Name</td>
<td v-for="(attr, attr_i, attr_idx) in part[0].attributes" :key="attr_i">{{ attr_i }}</td>
</tr>
<tr
v-for="(item, item_i) in sorted(part, 'name')" :key="item_i"
@click="selectPart(part_i, item_i)"
:class="{'selected': selection[part_i].item===item_i}"
>
<td class="select"><input type="radio" :value="item_i" v-model="selection[part_i].item" /></td>
<td class="name">{{ item.name }}</td>
<td class="prices">
<select
v-model="item.selected_price"
@click.stop
@change="selectPrice(part_i, item_i)"
>
<option
v-for="(price, price_i) in sorted(item.prices, 'price')" :key="price_i"
:value="price_i"
:disabled="!price.available"
>{{ euroFormatted(price.price) }} @ {{ price.shop }}</option>
</select>
</td>
<td v-for="(attr, attr_i) in item.attributes" :key="attr_i">{{ attr }}</td>
</tr>
</table>
</div>
<div class="part">
<h2>Total<span class="selected-part">{{ euroFormatted(total) }}</span></h2>
</div>
</div>
SCSS
@mixin no-selection {
user-select: none; /* standard syntax */
-webkit-user-select: none; /* webkit (safari, chrome) browsers */
-moz-user-select: none; /* mozilla browsers */
-khtml-user-select: none; /* webkit (konqueror) browsers */
-ms-user-select: none; /* IE10+ */
}
*, :before, :after { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #20262E;
font-family: Helvetica;
}
#app {
padding: 0 20px;
}
.part {
margin-top: 20px;
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
h2 {
font-weight: bold;
text-transform: uppercase;
@include no-selection;
.selected-part {
float: right;
text-transform: none;
opacity: 60%;
}
}
table {
margin-top: 10px;
width: 100%;
font-size: 0.85rem;
tr:first-child td {
font-size: 60%;
text-transform: uppercase;
color: #888;
@include no-selection;
}
.select {
width: 30px;
text-align: left;
}
.name {
}
tr.selected td {
font-weight: bold;
}
}
&.folded {
table {
display: none;
}
}
}
Vue
new Vue({
el: "#app",
data: {
folded: {
cpu: false,
// ...
},
selection: {
cpu: { item: 0, price: 0 }, // itemIdx, priceIdx
// ...
},
parts: {
cpu: [
{
name: 'CPU Item 1',
attributes: {},
prices: [
{ shop: 'None', price: 10.00, available: true },
{ shop: 'Shopname', price: 0.00, available: true },
],
selected_price: 0,
},
{
name: 'CPU Item 2',
attributes: {},
prices: [
{ shop: 'None', price: 0.00, available: true },
{ shop: 'Shopname', price: 10.00, available: true },
],
selected_price: 0,
},
],
// ...
},
},
computed: {
total: function() {
let total = 0;
for (const k in this.selection) {
let currentPart = this.parts[ k ][ this.selection[ k ].item ];
let priceIdx = currentPart.selected_price;
total += this.sorted(currentPart.prices, 'price')[ priceIdx ].price;
}
return total;
},
},
mounted() {
for (const k in this.selection) {
this.parts[ k ][ this.selection[ k ].item ].selected_price = this.selection[ k ].price;
}
},
methods: {
dotGet: function(t, path) {
return path.split(".").reduce((r, k) => r?.[k], t);
},
sorted: function(items, orderBy = '', orderDirection = 'asc') {
return items
.slice()
.sort((a, b) => {
orderBy = orderBy.length ? orderBy : 'name';
let direction = (orderDirection === 'desc' ? -1 : 1);
let _a = this.dotGet(a, orderBy);
let _b = this.dotGet(b, orderBy);
if (
typeof _a === 'undefined'
|| typeof _b === 'undefined'
) {
return 0;
}
let valA = (typeof _a === 'number' ? _a : _a.toUpperCase());
let valB = (typeof _b === 'number' ? _b : _b.toUpperCase());
if (valA < valB) {
return -1 * direction;
}
if (valA > valB) {
return 1 * direction;
}
return 0;
});
},
euroFormatted:...