Skip to content

Instantly share code, notes, and snippets.

@imzhi
Last active November 28, 2023 05:13
Show Gist options
  • Save imzhi/39aec6319306fac5ccc4999eec9911c9 to your computer and use it in GitHub Desktop.
Save imzhi/39aec6319306fac5ccc4999eec9911c9 to your computer and use it in GitHub Desktop.
vue 实现电商的规格选中效果
<!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