Last active
November 28, 2023 05:13
-
-
Save imzhi/39aec6319306fac5ccc4999eec9911c9 to your computer and use it in GitHub Desktop.
vue 实现电商的规格选中效果
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>电商规格</title> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script> | |
<style> | |
.all_div {} | |
.all_item { | |
display: inline-block; | |
padding: 8px 12px; | |
border: 1px solid #eee; | |
margin: 0 15px 15px 0; | |
} | |
.all_spec { | |
display: inline-block; | |
padding: 8px 12px; | |
border: 1px solid #eee; | |
margin: 0 15px 15px 0; | |
cursor: pointer; | |
background-color: antiquewhite; | |
} | |
.all_spec.selected { | |
background-color: chocolate; | |
} | |
.all_spec.disabled { | |
cursor: not-allowed; | |
background-color: #fff; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="app"> | |
<h1>{{message}}</h1> | |
<div> | |
<p>规格</p> | |
<p v-if="name_str">已选:{{name_str}},价格:{{sale}}</p> | |
<div class="all_div" v-for="(attr_item, attr_index) in list_attr" :key="attr_index"> | |
<div> | |
<span class="all_item"> | |
{{attr_item.attr_name}} | |
</span> | |
</div> | |
<div> | |
<span class="all_spec" :class="[opt_item.is_selected ? 'selected' : '', opt_item.is_enabled ? '' : 'disabled']" | |
v-for="(opt_item, opt_index) in list_attr_opt[attr_item.attr_id]" :key="opt_index" | |
@click="selected_item(opt_item, opt_index)"> | |
{{opt_item.opt_name}} | |
</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
const app = new Vue({ | |
el: '#app', | |
data: { | |
message: '测试电商规格', | |
name: '', | |
sale: '', | |
name_str: '', | |
list_attr: [ | |
{attr_id: 1, attr_name: '书香1'}, | |
{attr_id: 2, attr_name: '书香2'}, | |
{attr_id: 3, attr_name: '书香3'}, | |
], | |
list_attr_opt: { | |
1: [ | |
{attr_id: 1, opt_id: 7, opt_name: '选项7'}, | |
{attr_id: 1, opt_id: 8, opt_name: '选项8'}, | |
{attr_id: 1, opt_id: 9, opt_name: '选项9'}, | |
], | |
2: [ | |
{attr_id: 2, opt_id: 10, opt_name: '选项10'}, | |
{attr_id: 2, opt_id: 11, opt_name: '选项11'}, | |
{attr_id: 2, opt_id: 12, opt_name: '选项12'}, | |
], | |
3: [ | |
{attr_id: 3, opt_id: 13, opt_name: '选项13'}, | |
{attr_id: 3, opt_id: 14, opt_name: '选项14'}, | |
{attr_id: 3, opt_id: 15, opt_name: '选项15'}, | |
] | |
}, | |
// ##用来分隔规格组和规格选项 | |
// ;;用来分隔规格组 | |
list_avai: [ | |
{name: '书香1##选项7;;书香2##选项10;;书香3##选项13', code: 'A123456', sale: 12.5}, | |
{name: '书香1##选项7;;书香2##选项12;;书香3##选项15', code: 'A123457', sale: 12.6}, | |
{name: '书香1##选项8;;书香2##选项10;;书香3##选项14', code: 'A123458', sale: 12.7}, | |
{name: '书香1##选项8;;书香2##选项10;;书香3##选项15', code: 'A123459', sale: 12.9}, | |
{name: '书香1##选项8;;书香2##选项10;;书香3##选项13', code: 'A123410', sale: 13}, | |
{name: '书香1##选项8;;书香2##选项12;;书香3##选项15', code: 'A123412', sale: 13.2}, | |
], | |
list_avai_key: {}, | |
list_attr_key: {}, | |
path_map: {}, | |
}, | |
created() { | |
this._calc_list_attr_key() | |
this._calc_list_avai_key() | |
this._calc_path_map() | |
// this._auto_select() | |
this._auto_select_after() | |
}, | |
methods: { | |
selected_item(opt_item, opt_index) { | |
if (!opt_item.is_enabled) { | |
alert('禁止点击') | |
return | |
} | |
const attr_id = opt_item.attr_id | |
if (opt_item.is_selected) { | |
opt_item.is_selected = false | |
this.$set(this.list_attr_opt[attr_id], opt_index, opt_item) | |
} else { | |
const list_attr_opt = _.clone(this.list_attr_opt) | |
const curr_opts = list_attr_opt[attr_id] | |
for (let curr_opt of curr_opts) { | |
curr_opt.is_selected = false | |
if (curr_opt.opt_id == opt_item.opt_id) { | |
curr_opt.is_selected = true | |
} | |
} | |
this.list_attr_opt = list_attr_opt | |
} | |
this._auto_select_after() | |
}, | |
_auto_select() { | |
const list_attr_opt = _.clone(this.list_attr_opt) | |
for (let attr_id in list_attr_opt) { | |
const curr_opts = list_attr_opt[attr_id] | |
for (let curr_opt of curr_opts) { | |
curr_opt.is_enabled = this._auto_select_inner(curr_opt) | |
if (!curr_opt.is_enabled) { | |
console.log('!curr_opt.is_enabled', curr_opt) | |
} | |
} | |
} | |
}, | |
_auto_select_inner(curr_opt) { | |
const list_attr_opt = _.clone(this.list_attr_opt) | |
for (let the_attr_id in list_attr_opt) { | |
if (the_attr_id === curr_opt.attr_id) { | |
continue | |
} | |
const curr_attr = this.list_attr_key[curr_opt.attr_id] | |
const the_curr_attr = this.list_attr_key[the_attr_id] | |
const the_curr_opts = list_attr_opt[the_attr_id] | |
for (let the_curr_opts_item of the_curr_opts) { | |
const arr = [ | |
`${the_curr_attr.attr_name}##${the_curr_opts_item.opt_name}`, | |
`${curr_attr.attr_name}##${curr_opt.opt_name}`, | |
] | |
arr.sort() | |
const key_name = arr.join(';;') | |
if (this.path_map[key_name]) { | |
return true | |
} | |
} | |
} | |
return false | |
}, | |
_auto_select_after() { | |
const selected_opt_arr = [] | |
const selected_attr_id_arr = [] | |
const selected_name_arr = [] | |
const selected_opt_name_arr = [] | |
const list_attr_key = _.clone(this.list_attr_key) | |
const list_attr_opt = _.clone(this.list_attr_opt) | |
for (let attr_id in list_attr_opt) { | |
const curr_opts = list_attr_opt[attr_id] | |
// console.log('curr_opts', curr_opts) | |
for (let curr_opt of curr_opts) { | |
if (!curr_opt.is_selected) { | |
continue | |
} | |
selected_opt_arr.push(curr_opt) | |
selected_attr_id_arr.push(attr_id) | |
selected_name_arr.push(`${list_attr_key[attr_id].attr_name}##${curr_opt.opt_name}`) | |
selected_opt_name_arr.push(curr_opt.opt_name) | |
} | |
} | |
this._auto_select_after_inner(selected_opt_arr, selected_attr_id_arr, selected_name_arr) | |
this.name = '' | |
this.sale = '' | |
this.name_str = '' | |
if (selected_name_arr.length === this.list_attr.length) { | |
const selected_name_arr_clone = _.clone(selected_name_arr) | |
const key_name = selected_name_arr_clone.join(';;') | |
const key_code = this.path_map[key_name] | |
this.name = this.list_avai_key[key_code].name | |
this.sale = this.list_avai_key[key_code].sale | |
this.name_str = selected_opt_name_arr.join(' ') | |
} | |
}, | |
_auto_select_after_inner(selected_opt_arr, selected_attr_id_arr, selected_name_arr) { | |
console.log('selected_opt_arr, selected_attr_id_arr, selected_name_arr', selected_opt_arr, selected_attr_id_arr, selected_name_arr) | |
const list_attr_key = _.clone(this.list_attr_key) | |
const list_attr_opt = _.clone(this.list_attr_opt) | |
for (let the_attr_id in list_attr_opt) { | |
let selected_name_arr_clone = _.clone(selected_name_arr) | |
const find_idx = selected_attr_id_arr.indexOf(the_attr_id) | |
if (find_idx > -1) { | |
selected_name_arr_clone.splice(find_idx, 1) | |
} | |
const the_curr_attr = list_attr_key[the_attr_id] | |
const the_curr_opts = list_attr_opt[the_attr_id] | |
for (let the_curr_opts_item of the_curr_opts) { | |
const selected_name_arr_clone_2 = _.clone(selected_name_arr_clone) | |
selected_name_arr_clone_2.push(`${the_curr_attr.attr_name}##${the_curr_opts_item.opt_name}`) | |
selected_name_arr_clone_2.sort() | |
const key_name = selected_name_arr_clone_2.join(';;') | |
if (this.path_map[key_name]) { | |
the_curr_opts_item.is_enabled = true | |
} else { | |
the_curr_opts_item.is_enabled = false | |
} | |
} | |
} | |
this.list_attr_opt = list_attr_opt | |
}, | |
_calc_list_attr_key() { | |
const list_attr = _.clone(this.list_attr) | |
this.list_attr_key = _.keyBy(list_attr, 'attr_id') | |
}, | |
_calc_list_avai_key() { | |
const list_avai = _.clone(this.list_avai) | |
this.list_avai_key = _.keyBy(list_avai, 'code') | |
}, | |
_calc_path_map() { | |
const list_avai = _.clone(this.list_avai) | |
for (let avai_item of list_avai) { | |
const name_arr = this._split_name(avai_item.name) | |
name_arr.sort() | |
const s_list = bwPowerSet(name_arr) | |
// console.log('s_list', s_list) | |
for (let s_item of s_list) { | |
if (!s_item.length) { | |
continue | |
} | |
const key_name = s_item.join(';;') | |
// console.log('key_name', key_name) | |
if (!this.path_map.hasOwnProperty(key_name)) { | |
this.path_map[key_name] = [avai_item.code] | |
} else { | |
this.path_map[key_name].push(avai_item.code) | |
} | |
} | |
} | |
console.log('this.path_map', this.path_map) | |
}, | |
_split_name(name) { | |
return name.split(';;') | |
} | |
}, | |
}) | |
/** | |
* Find power-set of a set using BITWISE approach. | |
* | |
* @param {*[]} originalSet | |
* @return {*[][]} | |
*/ | |
function bwPowerSet(originalSet) { | |
const subSets = []; | |
// We will have 2^n possible combinations (where n is a length of original set). | |
// It is because for every element of original set we will decide whether to include | |
// it or not (2 options for each set element). | |
const numberOfCombinations = 2 ** originalSet.length; | |
// Each number in binary representation in a range from 0 to 2^n does exactly what we need: | |
// it shows by its bits (0 or 1) whether to include related element from the set or not. | |
// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to | |
// include only "2" to the current set. | |
for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) { | |
const subSet = []; | |
for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) { | |
// Decide whether we need to include current element into the subset or not. | |
if (combinationIndex & (1 << setElementIndex)) { | |
subSet.push(originalSet[setElementIndex]); | |
} | |
} | |
// Add current subset to the list of all subsets. | |
subSets.push(subSet); | |
} | |
return subSets; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment