feat(GoodsStock.vue): 商品库存管理升级vue3

This commit is contained in:
郝先瑞 2022-01-10 23:48:19 +08:00
parent 932c78f988
commit d591ddde7c
8 changed files with 578 additions and 457 deletions

View File

@ -17,6 +17,7 @@
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "^2.0.9", "pinia": "^2.0.9",
"screenfull": "^6.0.0", "screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"vue": "^3.2.16", "vue": "^3.2.16",
"vue-router": "^4.0.12" "vue-router": "^4.0.12"
}, },

View File

@ -58,7 +58,6 @@ import {listAttributes, saveAttributeBatch} from "@/api/pms/attribute";
import {computed, reactive, toRefs, watch} from "vue"; import {computed, reactive, toRefs, watch} from "vue";
import {Plus, Check, Delete} from '@element-plus/icons' import {Plus, Check, Delete} from '@element-plus/icons'
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {listRoleMenuIds} from "@api/system/role";
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({ const props = defineProps({

View File

@ -1,32 +1,37 @@
<!--
<template> <template>
<div class="components-container"> <div class="components-container">
<div class="components-container__main"> <div class="components-container__main">
<el-card class="box-card"> <el-card class="box-card">
<div slot="header"> <template #header>
<span>商品属性</span> <span>商品属性</span>
<el-button style="float: right;" type="primary" size="mini" @click="handleAttributeAdd"> <el-button
style="float: right;"
type="success"
:icon="Plus"
size="mini"
@click="handleAdd"
>
添加属性 添加属性
</el-button> </el-button>
</div> </template>
<el-form <el-form
ref="attributeForm" ref="dataForm"
:model="value" :model="modelValue"
:rules="rules" :rules="rules"
size="mini" size="mini"
:inline="true" :inline="true"
> >
<el-table <el-table
:data="value.attrList" :data="modelValue.attrList"
size="mini" size="mini"
highlight-current-row highlight-current-row
border border
> >
<el-table-column property="name" label="属性名称"> <el-table-column property="name" label="属性名称">
<template slot-scope="scope"> <template #default="scope">
<el-form-item <el-form-item
:prop="'attrList[' + scope.$index + '].name'" :prop="'attrList[' + scope.$index + '].name'"
:rules="rules.attribute.name" :rules="rules.attribute.name"
> >
<el-input v-model="scope.row.name"/> <el-input v-model="scope.row.name"/>
</el-form-item> </el-form-item>
@ -34,10 +39,10 @@
</el-table-column> </el-table-column>
<el-table-column property="value" label="属性值"> <el-table-column property="value" label="属性值">
<template slot-scope="scope"> <template #default="scope">
<el-form-item <el-form-item
:prop="'attrList[' + scope.$index + '].value'" :prop="'attrList[' + scope.$index + '].value'"
:rules="rules.attribute.value" :rules="rules.attribute.value"
> >
<el-input v-model="scope.row.value"/> <el-input v-model="scope.row.value"/>
</el-form-item> </el-form-item>
@ -45,9 +50,15 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="150"> <el-table-column label="操作" width="150">
<template slot-scope="scope"> <template #default="scope">
<el-form-item> <el-form-item>
<el-button type="danger" icon="el-icon-minus" circle @click="handleAttributeRemove(scope.$index)"/> <el-button
v-if="scope.$index>0"
type="danger"
icon="Minus"
circle
@click="handleRemove(scope.$index)"
/>
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
@ -63,53 +74,77 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import {listAttribute} from "@/api/pms/attribute"; import {listAttributes} from "@/api/pms/attribute";
import {computed, nextTick, reactive, ref, toRefs, unref, watch} from "vue";
import {ElForm} from "element-plus";
import {Minus, Plus} from '@element-plus/icons'
export default { const emit = defineEmits(['prev', 'next'])
name: "GoodsAttribute", const dataForm = ref(ElForm)
props: {
value: Object const props = defineProps({
}, modelValue: {
watch:{ type: Object,
// default: {}
'value.categoryId':{ }
handler(newVal,oldVal){ })
listAttribute({categoryId: newVal, type: 2}).then(res => {
this.value.attrList = res.data const categoryId = computed(() => props.modelValue.categoryId);
})
} watch(categoryId, (newVal, oldVal) => {
if (newVal) {
// type=2 ()
listAttributes({categoryId: newVal, type: 2}).then(response => {
const attrList = response.data
if (attrList && attrList.length > 0) {
props.modelValue.attrList = attrList
} else {
props.modelValue.attrList = [{}]
}
})
} else {
props.modelValue.attrList = [{}]
}
},
{
immediate: true,
deep: true
} }
}, )
data() {
return { const state = reactive({
rules: { rules: {
attribute: { attribute: {
name: [{required: true, message: '请填写属性名称', trigger: 'blur'}], name: [{required: true, message: '请填写属性名称', trigger: 'blur'}],
value: [{required: true, message: '请填写属性值', trigger: 'blur'}] value: [{required: true, message: '请填写属性值', trigger: 'blur'}]
}
},
}
},
methods: {
handleAttributeAdd: function () {
this.value.attrList.push({})
},
handleAttributeRemove: function (index) {
this.value.attrList.splice(index, 1)
},
handlePrev: function () {
this.$emit('prev')
},
handleNext: function () {
this.$refs["attributeForm"].validate((valid) => {
if (valid) {
this.$emit('next')
}
})
} }
} }
})
const {rules} = toRefs(state)
function handleAdd() {
props.modelValue.attrList.push({})
} }
function handleRemove(index:number) {
props.modelValue.attrList.splice(index, 1)
}
function handlePrev() {
emit('prev')
}
function handleNext() {
const form = unref(dataForm)
form.validate((valid: any) => {
if (valid) {
emit('next')
}
})
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -125,8 +160,8 @@ export default {
right: 20px; right: 20px;
} }
} }
.el-form-item&#45;&#45;mini.el-form-item{
.el-form-item--mini.el-form-item {
margin-top: 18px; margin-top: 18px;
} }
</style> </style>
-->

View File

@ -4,7 +4,7 @@
<el-cascader-panel <el-cascader-panel
ref="categoryRef" ref="categoryRef"
:options="categoryOptions" :options="categoryOptions"
v-model="categoryId" v-model="modelValue.categoryId"
:props="{emitPath:false}" :props="{emitPath:false}"
@change="handleCategoryChange" @change="handleCategoryChange"
@ -40,17 +40,15 @@ const props = defineProps({
const state = reactive({ const state = reactive({
categoryOptions: [], categoryOptions: [],
pathLabels: [], pathLabels: []
categoryId: undefined
}) })
const {categoryOptions, pathLabels, categoryId} = toRefs(state) const {categoryOptions, pathLabels} = toRefs(state)
function loadData() { function loadData() {
listCascadeCategories({}).then(response => { listCascadeCategories({}).then(response => {
state.categoryOptions = response.data state.categoryOptions = response.data
if (props.modelValue.id) { if (props.modelValue.id) {
state.categoryId = props.modelValue.categoryId
nextTick(() => { nextTick(() => {
handleCategoryChange() handleCategoryChange()
}) })
@ -66,7 +64,8 @@ function handleCategoryChange() {
} }
function handleNext() { function handleNext() {
if (!state.categoryId) { console.log('商品属性',props.modelValue.categoryId)
if (!props.modelValue.categoryId) {
ElMessage.warning('请选择商品分类') ElMessage.warning('请选择商品分类')
return false return false
} }

View File

@ -1,24 +1,25 @@
<!--
<template> <template>
<div class="components-container"> <div class="components-container">
<div class="components-container__main"> <div class="components-container__main">
<el-card class="box-card"> <el-card class="box-card">
<div slot="header"> <template #header>
<span>商品规格</span> <span>商品规格</span>
<el-button style="float: right;" type="primary" size="mini" @click="handleSpecAdd"> <el-button style="float: right;" type="primary" size="mini" @click="handleSpecAdd">
添加规格项 添加规格项
</el-button> </el-button>
</div> </template>
<el-form <el-form
size="mini" ref="specFormRef"
ref="specForm" :model="specForm"
:model="specForm" :inline="true"
:inline="true">
<el-table
ref="specTable"
:data="specForm.specList"
row-key="id"
size="mini" size="mini"
>
<el-table
ref="specTableRef"
:data="specForm.specList"
row-key="id"
size="mini"
> >
<el-table-column align="center" width="50"> <el-table-column align="center" width="50">
<template> <template>
@ -26,16 +27,16 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="规格名" width="200"> <el-table-column label="规格名" width="200">
<template slot-scope="scope"> <template #default="scope">
<el-form-item <el-form-item
:prop="'specList[' + scope.$index + '].name'" :prop="'specList[' + scope.$index + '].name'"
:rules="rules.specification.name" :rules="rules.spec.name"
> >
<el-input <el-input
type="text" type="text"
v-model="scope.row.name" v-model="scope.row.name"
size="mini" size="mini"
@input="changeSpec()" @input="handleSpecChange()"
/> />
</el-form-item> </el-form-item>
</template> </template>
@ -48,20 +49,20 @@
<template slot-scope="{row}"> <template slot-scope="{row}">
<div style="margin-right:15px;display: inline-block" v-for="item in row.values"> <div style="margin-right:15px;display: inline-block" v-for="item in row.values">
<el-tag <el-tag
closable closable
:type="colors[row.index%colors.length]" :type="colors[row.index%colors.length]"
@close="handleSpecValueRemove(row.index,item.id)"> @close="handleSpecValueRemove(row.index,item.id)">
{{ item.value }} {{ item.value }}
</el-tag> </el-tag>
<mini-card-upload v-if="row.index==0" style="margin-top: 5px" v-model="item.picUrl"/> <mini-card-upload v-if="row.index==0" style="margin-top: 5px" v-model="item.picUrl"/>
</div> </div>
<el-input <el-input
style="width: 80px;vertical-align: top" style="width: 80px;vertical-align: top"
size="mini" size="mini"
v-if="tagInputs[row.index].visible" v-if="tagInputs[row.index].visible"
v-model="tagInputs[row.index].value" v-model="tagInputs[row.index].value"
@keyup.enter.native="handleSpecValueInput(row.index)" @keyup.enter.native="handleSpecValueInput(row.index)"
@blur="handleSpecValueInput(row.index)"/> @blur="handleSpecValueInput(row.index)"/>
<el-button v-else size="mini" icon="el-icon-plus" style="vertical-align: top" <el-button v-else size="mini" icon="el-icon-plus" style="vertical-align: top"
@click="handleSpecValueAdd(row.index)">添加规格值 @click="handleSpecValueAdd(row.index)">添加规格值
</el-button> </el-button>
@ -69,65 +70,73 @@
</el-table-column> </el-table-column>
<el-table-column width="60" label="操作"> <el-table-column width="60" label="操作">
<template slot-scope="scope"> <template #default="scope">
<el-button <el-button
type="danger" type="danger"
icon="el-icon-delete" icon="el-icon-delete"
size="mini" size="mini"
circle circle
plain plain
@click.stop="handleSpecRemove(scope.$index)"/> @click.stop="handleSpecRemove(scope.$index)"/>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-form> </el-form>
</el-card> </el-card>
<el-card class="box-card"> <el-card class="box-card">
<div slot="header"> <template #header>
<span>商品库存</span> <span>商品库存</span>
</div> </template>
<el-form <el-form
:model="skuForm" ref="skuFormRef"
size="mini" :model="skuForm"
ref="skuForm" size="mini"
:inline="true" :inline="true"
> >
<el-table <el-table
:data="skuForm.skuList" :data="skuForm.skuList"
:span-method="handleCellMerge" :span-method="handleCellMerge"
size="mini" highlight-current-row
fit highlight-current-row border size="mini"
fit
border
> >
<el-table-column <el-table-column
v-for="(title,index) in specTitleList" v-for="(title,index) in specTitles"
align="center" align="center"
:prop="'specValue'+(index+1)" :prop="'specValue'+(index+1)"
:label="title"> :label="title">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="商品编码" label="商品编码"
align="center" align="center"
> >
<template slot-scope="scope"> <template #default="scope">
<el-form-item :prop="'skuList['+scope.$index+'].sn'" :rules="rules.sku.sn"> <el-form-item :prop="'skuList['+scope.$index+'].sn'" :rules="rules.sku.sn">
<el-input v-model="scope.row.sn"/> <el-input v-model="scope.row.sn"/>
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="价格" align="center"> <el-table-column label="价格" align="center">
<template slot-scope="scope"> <template #default="scope">
<el-form-item :prop="'skuList['+scope.$index+'].price'" :rules="rules.sku.price"> <el-form-item :prop="'skuList['+scope.$index+'].price'" :rules="rules.sku.price">
<el-input v-model="scope.row.price"/> <el-input v-model="scope.row.price"/>
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="库存" align="center"> <el-table-column label="库存" align="center">
<template slot-scope="scope"> <template #default="scope">
<el-form-item :prop="'skuList['+scope.$index+'].stock'" :rules="rules.sku.stock"> <el-form-item :prop="'skuList['+scope.$index+'].stock'" :rules="rules.sku.stock">
<el-input v-model="scope.row.stock"/> <el-input v-model="scope.row.stock"/>
</el-form-item> </el-form-item>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-form> </el-form>
</el-card> </el-card>
@ -139,343 +148,420 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import {listAttribute} from "@/api/pms/attribute"; import {listAttributes} from "@/api/pms/attribute";
import MiniCardUpload from '@/components/Upload/MiniCardUpload' import MiniCardUpload from '@/components/Upload/MiniCardUpload.vue'
import Sortable from "sortablejs"; import Sortable from 'sortablejs'
import {addGoods, updateGoods} from "@/api/pms/goods"; import {addGoods, updateGoods} from "@/api/pms/goods";
import {computed, getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs, unref, watch} from "vue";
import {ElMessage, ElTable, ElForm} from "element-plus"
import {useRouter} from "vue-router";
const emit = defineEmits(['prev', 'next'])
export default { const proxy = getCurrentInstance() as any
name: "GoodsStock", const router = useRouter()
components: {MiniCardUpload},
props: { const specTableRef = ref(ElTable)
value: Object const specFormRef = ref(ElForm)
const skuFormRef = ref(ElForm)
const props = defineProps({
modelValue: {
type: Object,
default: {}
}
})
const categoryId = computed(() => props.modelValue.categoryId);
const state = reactive({
specForm: {
specList: [] as Array<any>,
}, },
watch: { skuForm: {
// skuList: []
'value.categoryId': { },
handler(newVal, oldVal) { //
listAttribute({categoryId: newVal, type: 1}).then(res => { specTitles: [],
res.data.forEach(item => { rules: {
console.log('规格项目', item) spec: {
this.specForm.specList.push({ name: [{required: true, message: '请输入规格名称', trigger: 'blur'}],
name: item.name, value: [{required: true, message: '请输入规格值', trigger: 'blur'}]
values: [] },
sku: {
sn: [{required: true, message: '请输入商品编号', trigger: 'blur'}],
price: [{required: true, message: '请输入商品价格', trigger: 'blur'}],
stock: [{required: true, message: '请输入商品库存', trigger: 'blur'}],
}
},
colors: ['', 'success', 'warning', 'danger'],
tagInputs: [{value: undefined, visible: false}], //
loading: undefined
})
const {specForm, skuForm, specTitles, rules, colors, tagInputs, loading} = toRefs(state)
watch(categoryId, (newVal, oldVal) => {
if (newVal) {
// type=1 ()
listAttributes({categoryId: newVal, type: 1}).then(response => {
const specList = response.data
if (specList && specList.length > 0) {
specList.forEach((item: any) => {
state.specForm.specList.push({
name: item.name,
values: []
})
}) })
this.loadData()
})
})
}
}
},
data() {
return {
//
specForm: {
specList: [],
},
skuForm: {
skuList: []
},
specTitleList: [], //
rules: {
specification: {
name: [{required: true, message: '请输入规格名称', trigger: 'blur'}],
value: [{required: true, message: '请输入规格值', trigger: 'blur'}]
},
sku: {
sn: [{required: true, message: '请输入商品编号', trigger: 'blur'}],
price: [{required: true, message: '请输入商品价格', trigger: 'blur'}],
stock: [{required: true, message: '请输入商品库存', trigger: 'blur'}],
}
},
colors: ['', 'success', 'warning', 'danger'],
tagInputs: [{value: undefined, visible: false}], //
loading: undefined
}
},
created() {
if (this.value.id) {
this.loadData()
}
},
methods: {
async loadData() {
this.value.specList.forEach(spec => {
const index = this.specForm.specList.findIndex(item => item.name == spec.name)
if (index > -1) {
this.specForm.specList[index].values.push({id: spec.id, value: spec.value, picUrl: spec.picUrl})
} else {
this.specForm.specList.push({
name: spec.name,
values: [{id: spec.id, value: spec.value, picUrl: spec.picUrl}]
})
}
})
//
for (let i = 0; i < this.specForm.specList.length; i++) {
this.tagInputs.push({'value': undefined, 'visible': false})
}
// SKUID
this.value.skuList.forEach(sku => {
sku.specIdArr = sku.specIds.split('_')
})
this.generateSku()
this.changeSpec()
this.sortSpec()
this.$nextTick(() => {
this.setSort()
})
},
handleSpecAdd: function () {
if (this.specForm.specList.length >= 3) {
this.$message.warning('最多支持3组规格')
return
}
this.specForm.specList.push({})
this.tagInputs.push({'value': undefined, 'visible': false})
this.sortSpec()
},
handleSpecRemove: function (index) {
this.specForm.specList.splice(index, 1)
this.tagInputs.splice(index, 1)
this.generateSku()
this.sortSpec()
this.changeSpec()
},
sortSpec: function () {
this.specForm.specList.forEach((item, index) => {
item.index = index
})
},
handleSpecValueRemove: function (rowIndex, specValueId) {
const specList = JSON.parse(JSON.stringify(this.specForm.specList))
const removeIndex = specList[rowIndex].values.map(item => item.id).indexOf(specValueId)
console.log('removeIndex', removeIndex)
specList[rowIndex].values.splice(removeIndex, 1)
this.specForm.specList = specList
this.changeSpec()
this.generateSku()
},
handleSpecValueInput: function (rowIndex) {
const currSpecValue = this.tagInputs[rowIndex].value
const specValues = this.specForm.specList[rowIndex].values
if (specValues && specValues.length > 0 && specValues.map(item => item.value).includes(currSpecValue)) {
this.$message.warning("规格值重复,请重新输入")
return false
}
if (currSpecValue) {
if (specValues && specValues.length > 0) {
// ID tid_1_1
let maxSpecValueIndex = specValues.filter(item => item.id.includes('tid_')).map(item => item.id.split('_')[2]).reduce((acc, curr) => {
return acc > curr ? acc : curr
}, 0)
console.log('maxSpecValueIndex', maxSpecValueIndex)
this.specForm.specList[rowIndex].values[specValues.length] = {
'value': currSpecValue,
'id': 'tid_' + (rowIndex + 1) + '_' + ++maxSpecValueIndex
}
} else {
this.specForm.specList[rowIndex].values = [{'value': currSpecValue, 'id': 'tid_' + (rowIndex + 1) + '_1'}]
}
}
this.tagInputs[rowIndex].value = undefined
this.tagInputs[rowIndex].visible = false
// SKU
this.generateSku()
},
handleSpecValueAdd: function (rowIndex) {
this.tagInputs[rowIndex].visible = true
},
setSort() {
const el = this.$refs.specTable.$el.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function (dataTransfer) {
dataTransfer.setData('Text', '')
},
onEnd: evt => {
// oldIndex
// newIndex
const targetRow = this.specForm.specList.splice(evt.oldIndex, 1)[0] //
this.specForm.specList.splice(evt.newIndex, 0, targetRow) //
this.generateSku() // sku
this.sortSpec()
this.changeSpec()
}
})
},
generateSku: function () {
// [
// { 'id':1,'name':'','values':[{id:1,value:''},{id:2,value:''},{id:3,value:''}] },
// { 'id':2,'name':'','values':[{id:1,value:'6+128G'},{id:2,value:'8+128G'},{id:3,value:'8G+256G'}] }
// ]
const specList = JSON.parse(JSON.stringify(this.specForm.specList.filter(item => item.values.length > 0))) // SKU
const skuList = specList.reduce((acc, curr) => {
let result = []
acc.forEach(item => {
// curr => { 'id':1,'name':'','values':[{id:1,value:''},{id:2,value:''},{id:3,value:''}] }
curr.values.forEach(v => { // v=>{id:1,value:''}
let temp = Object.assign({}, item)
temp.specValues += v.value + '_' //
temp.specIds += v.id + '|' // ID
result.push(temp)
})
})
return result
}, [{specValues: '', specIds: ''}])
skuList.forEach(item => {
item.specIds = item.specIds.substring(0, item.specIds.length - 1)
item.name = item.specValues.substring(0, item.specIds.length - 1).replaceAll('_', ' ')
const specIdArr = item.specIds.split('|')
const skus = this.value.skuList.filter(sku => sku.specIdArr.equals(specIdArr)) // SKU
if (skus && skus.length > 0) {
const sku = skus[0]
item.id = sku.id
item.sn = sku.sn
item.price = sku.price / 100
item.stock = sku.stock
}
const specValueArr = item.specValues.substring(0, item.specValues.length - 1).split('_') // ['','6+128G','']
specValueArr.forEach((v, i) => {
const key = 'specValue' + (i + 1)
item[key] = v
if (i == 0 && this.specForm.specList.length > 0) {
const valueIndex = this.specForm.specList[0].values.findIndex(specValue => specValue.value == v)
if (valueIndex > -1) {
item.picUrl = this.specForm.specList[0].values[valueIndex].picUrl
}
} }
}) })
}
},
{
immediate: true,
deep: true
}
)
function loadData() {
const goodsId = props.modelValue.id
//
if (goodsId) {
props.modelValue.specList.forEach((specItem: any) => {
const specIndex = state.specForm.specList.findIndex(item => item.name == specItem.name)
if (specIndex > -1) {
state.specForm.specList[specIndex].values.push({
id: specItem.id,
value: specItem.value,
picUrl: specItem.picUrl
})
} else {
state.specForm.specList.push({
name: specItem.name,
values: [{id: specItem.id, value: specItem.value, picUrl: specItem.picUrl}]
})
}
})
//
for (let i = 0; i < state.specForm.specList.length; i++) {
state.tagInputs.push({'value': undefined, 'visible': false})
}
// SKUID
props.modelValue.skuList.forEach((sku: any) => {
sku.specIdArr = sku.specIds.split('_')
})
generateSkuList()
handleSpecChange()
handleSpecReorder()
nextTick(() => {
registerSpecDragSortEvent()
})
}
}
/**
* 生成SKU列表的title
*/
function handleSpecChange() {
const specList = JSON.parse(JSON.stringify(state.specForm.specList))
state.specTitles = specList.map((item: any) => item.name)
}
/**
* 规格列表重排序
*/
function handleSpecReorder() {
state.specForm.specList.forEach((item, index) => {
item.index = index
})
}
/**
* 注册拖拽排序事件
*/
function registerSpecDragSortEvent() {
const el = specTableRef.value.$el.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function (dataTransfer: any) {
dataTransfer.setData('Text', '')
},
onEnd: (evt: any) => {
// oldIndex
// newIndex
const targetRow = state.specForm.specList.splice(evt.oldIndex, 1)[0] //
state.specForm.specList.splice(evt.newIndex, 0, targetRow) //
generateSkuList() // sku
handleSpecChange()
handleSpecReorder()
}
})
}
/**
* 根据商品规格笛卡尔积生成SKU列表
*
* 规格列表
* [
* { 'id':1,'name':'颜色','values':[{id:1,value:'白色'},{id:2,value:'黑色'},{id:3,value:'蓝色'}] },
* { 'id':2,'name':'版本','values':[{id:1,value:'6+128G'},{id:2,value:'8+128G'},{id:3,value:'8G+256G'}] }
* ]
*/
function generateSkuList() {
const specList = JSON.parse(JSON.stringify(state.specForm.specList.filter(item => item.values.length > 0))) // SKU
const skuList = specList.reduce((acc: any, curr: any) => {
let result = [] as any
acc.forEach((item: any) => {
// curr => { 'id':1,'name':'','values':[{id:1,value:''},{id:2,value:''},{id:3,value:''}] }
curr.values.forEach((v: any) => { // v=>{id:1,value:''}
let temp = Object.assign({}, item)
temp.specValues += v.value + '_' //
temp.specIds += v.id + '|' // ID
result.push(temp)
}) })
this.skuForm.skuList = JSON.parse(JSON.stringify(skuList)) })
}, return result
changeSpec: function () { }, [{specValues: '', specIds: ''}])
const specList = JSON.parse(JSON.stringify(this.specForm.specList))
this.specTitleList = specList.map(item => item.name) skuList.forEach((item: any) => {
}, item.specIds = item.specIds.substring(0, item.specIds.length - 1)
/** item.name = item.specValues.substring(0, item.specIds.length - 1).replaceAll('_', ' ')
* 合并规格值单元格 const specIdArr = item.specIds.split('|')
*/ const skus = props.modelValue.skuList.filter((sku: any) =>
handleCellMerge({row, column, rowIndex, columnIndex}) { sku.specIdArr.length === specIdArr.length &&
let mergeRows = [1, 1, 1] // 123 sku.specIdArr.every((a: number) => specIdArr.some((b: number) => a === b)) &&
const specLen = this.specForm.specList.filter(item => item.values && item.values.length > 0).length specIdArr.every((x: number) => sku.specIdArr.some((y: number) => x === y))
if (specLen == 2) { ) // SKU
const values_len_2 = this.specForm.specList[1].values ? this.specForm.specList[1].values.length : 1 // 2
mergeRows = [values_len_2, 1, 1] if (skus && skus.length > 0) {
} else if (specLen == 3) { const sku = skus[0]
const values_len_2 = this.specForm.specList[1].values ? this.specForm.specList[1].values.length : 1 // 2 item.id = sku.id
const values_len_3 = this.specForm.specList[2].values ? this.specForm.specList[2].values.length : 1 // 3 item.sn = sku.sn
mergeRows = [values_len_2 * values_len_3, values_len_3, 1] item.price = sku.price / 100
} item.stock = sku.stock
if (columnIndex == 0) { }
if (rowIndex % mergeRows[0] === 0) { const specValueArr = item.specValues.substring(0, item.specValues.length - 1).split('_') // ['','6+128G','']
return [mergeRows[0], 1]// specValueArr.forEach((v: any, i: any) => {
} else { const key = 'specValue' + (i + 1)
return [0, 0] // item[key] = v
if (i == 0 && state.specForm.specList.length > 0) {
const valueIndex = state.specForm.specList[0].values.findIndex((specValue: any) => specValue.value == v)
if (valueIndex > -1) {
item.picUrl = state.specForm.specList[0].values[valueIndex].picUrl
} }
} }
if (columnIndex == 1) { })
if (rowIndex % mergeRows[1] === 0) { })
return [mergeRows[1], 1]// state.skuForm.skuList = JSON.parse(JSON.stringify(skuList))
} else { }
return [0, 0] //
} /**
* 添加规格
*/
function handleSpecAdd() {
if (state.specForm.specList.length >= 3) {
ElMessage.warning('最多支持3组规格')
return
}
state.specForm.specList.push({})
state.tagInputs.push({'value': undefined, 'visible': false})
handleSpecReorder()
}
/**
* 删除规格
* @param index
*/
function handleSpecRemove(index: number) {
state.specForm.specList.splice(index, 1)
state.tagInputs.splice(index, 1)
generateSkuList()
handleSpecReorder()
handleSpecChange()
}
/**
* 添加规格值
*
* @param specIndex
*/
function handleSpecValueAdd(specIndex: number) {
state.tagInputs[specIndex].visible = true
}
/**
* 删除规格值
*
* @param rowIndex
* @param specValueId
*/
function handleSpecValueRemove(rowIndex: number, specValueId: number) {
const specList = JSON.parse(JSON.stringify(state.specForm.specList))
const removeIndex = specList[rowIndex].values.map((item: any) => item.id).indexOf(specValueId)
specList[rowIndex].values.splice(removeIndex, 1)
state.specForm.specList = specList
handleSpecChange()
handleSpecReorder()
}
/**
* 规格值输入
*/
function handleSpecValueInput(rowIndex: number) {
const currSpecValue = state.tagInputs[rowIndex].value
const specValues = state.specForm.specList[rowIndex].values
if (specValues && specValues.length > 0 && specValues.map((item: any) => item.value).includes(currSpecValue)) {
ElMessage.warning("规格值重复,请重新输入")
return false
}
if (currSpecValue) {
if (specValues && specValues.length > 0) {
// ID tid_1_1
let maxSpecValueIndex = specValues.filter((item: any) => item.id.includes('tid_')).map((item: any) => item.id.split('_')[2]).reduce((acc: any, curr: any) => {
return acc > curr ? acc : curr
}, 0)
console.log('maxSpecValueIndex', maxSpecValueIndex)
state.specForm.specList[rowIndex].values[specValues.length] = {
'value': currSpecValue,
'id': 'tid_' + (rowIndex + 1) + '_' + ++maxSpecValueIndex
} }
}, } else {
handlePrev: function () { state.specForm.specList[rowIndex].values = [{'value': currSpecValue, 'id': 'tid_' + (rowIndex + 1) + '_1'}]
this.$emit('prev') }
}, }
handleSubmit: function () { state.tagInputs[rowIndex].value = undefined
this.$refs.specForm.validate((specValid) => { state.tagInputs[rowIndex].visible = false
if (specValid) { generateSkuList()
this.$refs.skuForm.validate((skuValid) => { }
if (skuValid) {
this.openFullScreen()
let submitGoodsData = Object.assign({}, this.value)
delete submitGoodsData.specList
delete submitGoodsData.skuList
let specList = [] /**
this.specForm.specList.forEach(item => { * 合并规格单元格
item.values.forEach(value => { *
value.name = item.name * @param cellObj 单元格对象
}) */
specList = specList.concat(item.values) function handleCellMerge(cellObj: any) {
}) const {rowIndex, columnIndex} = cellObj
submitGoodsData.specList = specList // let mergeRows = [1, 1, 1] // 123
const specLen = state.specForm.specList.filter(item => item.values && item.values.length > 0).length
submitGoodsData.price *= 100 // if (specLen == 2) {
submitGoodsData.originPrice *= 100 const values_len_2 = state.specForm.specList[1].values ? state.specForm.specList[1].values.length : 1 // 2
mergeRows = [values_len_2, 1, 1]
let skuList = JSON.parse(JSON.stringify(this.skuForm.skuList)) } else if (specLen == 3) {
skuList.map(item => { const values_len_2 = state.specForm.specList[1].values ? state.specForm.specList[1].values.length : 1 // 2
item.price *= 100 const values_len_3 = state.specForm.specList[2].values ? state.specForm.specList[2].values.length : 1 // 3
return item mergeRows = [values_len_2 * values_len_3, values_len_3, 1]
}) }
submitGoodsData.skuList = skuList if (columnIndex == 0) {
console.log('提交数据', submitGoodsData) if (rowIndex % mergeRows[0] === 0) {
const goodsId = this.value.id return [mergeRows[0], 1]//
if (goodsId) { // } else {
updateGoods(goodsId, submitGoodsData).then((res) => { return [0, 0] //
this.$router.push({path: '/pms/goods'}) }
this.$notify.success('修改商品成功') }
this.closeFullScreen() if (columnIndex == 1) {
}, (err) => { if (rowIndex % mergeRows[1] === 0) {
this.closeFullScreen() return [mergeRows[1], 1]//
} } else {
) return [0, 0] //
} else { //
addGoods(submitGoodsData).then(response => {
this.$router.push({path: '/pms/goods'})
this.$notify.success('新增商品成功')
this.closeFullScreen()
}, (err) => {
this.closeFullScreen()
})
}
}
})
}
})
},
openFullScreen: function () {
this.loading = this.$loading({
lock: true,
text: '商品信息提交中,请等待...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
},
closeFullScreen: function () {
if (this.loading) {
this.loading.close()
}
} }
} }
} }
/** /**
* 重写数组equals方法数组元素完全相同不论顺序 * 商品表单提交
* @param target
* @returns {boolean}
*/ */
Array.prototype.equals = function (target) { function submitForm() {
return this.length === target.length && const specForm = unref(specFormRef)
this.every(a => target.some(b => a === b)) && specForm.validate((specValid: boolean) => {
target.every(x => this.some(y => x === y)); if (specValid) {
const skuForm = unref(skuFormRef)
skuForm.validate((skuValid: boolean) => {
if (skuValid) {
openFullScreen()
let submitsData = Object.assign({}, props.modelValue)
delete submitsData.specList
delete submitsData.skuList
let specList = [] as Array<any>
state.specForm.specList.forEach(item => {
item.values.forEach((value: any) => {
value.name = item.name
})
specList = specList.concat(item.values)
})
submitsData.specList = specList //
submitsData.price *= 100 //
submitsData.originPrice *= 100
let skuList = JSON.parse(JSON.stringify(state.skuForm.skuList))
skuList.map((item: any) => {
item.price *= 100
return item
})
submitsData.skuList = skuList
console.log('提交数据', submitsData)
const goodsId = props.modelValue.id
if (goodsId) { //
updateGoods(goodsId, submitsData).then((res) => {
router.push({path: '/pms/good'})
proxy.$notify.success('修改商品成功')
closeFullScreen()
}, (err) => {
closeFullScreen()
}
)
} else { //
addGoods(submitsData).then(response => {
router.push({path: '/pms/good'})
proxy.$notify.success('新增商品成功')
closeFullScreen()
}, (err) => {
closeFullScreen()
})
}
}
})
}
})
} }
function openFullScreen() {
state.loading = proxy.$loading({
lock: true,
text: '商品信息提交中,请等待...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
}
function closeFullScreen() {
if (state.loading) {
(state.loading as any).close()
}
}
function handlePrev() {
emit('prev')
}
function handNext() {
emit('next')
}
onMounted(() => {
loadData()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -496,8 +582,7 @@ Array.prototype.equals = function (target) {
} }
} }
.el-form-item&#45;&#45;mini.el-form-item{ .el-form-item--mini.el-form-item {
margin-top: 18px; margin-top: 18px;
} }
</style> </style>
-->

View File

@ -78,6 +78,7 @@ export default {
methods: { methods: {
loadData() { loadData() {
const goodsId = this.$route.query.goodsId const goodsId = this.$route.query.goodsId
console.log('goodsId',goodsId)
if (goodsId) { if (goodsId) {
getGoodsDetail(goodsId).then(response => { getGoodsDetail(goodsId).then(response => {
this.goods = response.data this.goods = response.data

View File

@ -30,7 +30,7 @@
<el-table <el-table
v-loading="loading" v-loading="loading"
ref="multipleTable" ref="dataTableRef"
:data="pageList" :data="pageList"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@row-click="handleRowClick" @row-click="handleRowClick"
@ -87,12 +87,12 @@
<el-table-column label="操作" width="120"> <el-table-column label="操作" width="120">
<template #default="scope"> <template #default="scope">
<el-button <el-button
@click="handleUpdate(scope.row)"
type="primary" type="primary"
:icon="Edit" :icon="Edit"
size="mini" size="mini"
circle circle
plain plain
@click.stop="handleUpdate(scope.row)"
/> />
<el-button <el-button
type="danger" type="danger"
@ -100,7 +100,7 @@
size="mini" size="mini"
circle circle
plain plain
@click="handleDelete(scope.row)" @click.stop="handleDelete(scope.row)"
/> />
</template> </template>
</el-table-column> </el-table-column>
@ -121,12 +121,16 @@
import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons' import {Search, Plus, Edit, Refresh, Delete} from '@element-plus/icons'
import {listGoodsWithPage, deleteGoods} from '@/api/pms/goods' import {listGoodsWithPage, deleteGoods} from '@/api/pms/goods'
import {listCascadeCategories} from '@/api/pms/category' import {listCascadeCategories} from '@/api/pms/category'
import {reactive, ref, onMounted, toRefs} from 'vue' import {reactive, ref, onMounted, toRefs, unref} from 'vue'
import {ElMessage, ElMessageBox, ElTree} from 'element-plus' import {ElTable, ElMessage, ElMessageBox, ElTree} from 'element-plus'
import {getCurrentInstance} from 'vue' import {getCurrentInstance} from 'vue'
import {moneyFormatter} from '@/utils/filter' import {moneyFormatter} from '@/utils/filter'
const {proxy}: any = getCurrentInstance(); const dataTableRef = ref(ElTable)
import {useRouter} from "vue-router"
const router=useRouter()
const state = reactive({ const state = reactive({
// //
@ -185,18 +189,17 @@ function resetQuery() {
handleQuery() handleQuery()
} }
function handleGoodsView(detail: any) { function handleGoodsView(detail: any) {
state.goodDetail = detail state.goodDetail = detail
state.dialogVisible = true state.dialogVisible = true
} }
function handleAdd() { function handleAdd() {
proxy.$router.push({path: 'goods-detail'}) router.push({path: 'goods-detail'})
} }
function handleUpdate(row: any) { function handleUpdate(row: any) {
proxy.$router.push({path: 'goods-detail', query: {goodsId: row.id,categoryId:row.categoryId}}) router.push({path: 'goods-detail', query: {goodsId: row.id, categoryId: row.categoryId}})
} }
function handleDelete(row: any) { function handleDelete(row: any) {
@ -214,7 +217,7 @@ function handleDelete(row: any) {
} }
function handleRowClick(row: any) { function handleRowClick(row: any) {
proxy.$refs.multipleTable.toggleRowSelection(row); dataTableRef.value.toggleRowSelection(row);
} }
function handleSelectionChange(selection: any) { function handleSelectionChange(selection: any) {

View File

@ -11,10 +11,8 @@
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext", "dom"], "lib": ["esnext", "dom"],
/* Vite */
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@": ["src"],
"@*": ["src/*"], "@*": ["src/*"],
}, },
"extends": "./tsconfig.extends.json", "extends": "./tsconfig.extends.json",