商品前台交互更新
This commit is contained in:
parent
ff1a8c2fca
commit
080f6d8fba
|
@ -44,9 +44,9 @@ export function delGoods(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 商品上架/下架
|
// 商品上架/下架
|
||||||
export function changeStatus(data) {
|
export function changeStatusAndSort(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/bst/goods/changeStatus',
|
url: '/bst/goods/changeStatusAndSort',
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@click="applyBatchSetting"
|
@click="applyBatchSetting"
|
||||||
:disabled="!batchField || !batchValue || selectedSkuRows.length === 0"
|
:disabled="isReadOnly || !batchField || !batchValue || selectedSkuRows.length === 0"
|
||||||
>
|
>
|
||||||
应用设置
|
应用设置
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -210,8 +210,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sku-table">
|
<div class="sku-table">
|
||||||
<el-table :data="form.skuList" style="width: 85%" :row-style="{height: '100px'}"
|
<el-table
|
||||||
@selection-change="handleSelectionChange"
|
ref="skuTable"
|
||||||
|
:data="form.skuList"
|
||||||
|
style="width: 85%"
|
||||||
|
:row-style="{height: '100px'}"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<!-- 动态生成规格列 -->
|
<!-- 动态生成规格列 -->
|
||||||
|
@ -449,6 +453,9 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$message.success(`已批量设置${this.selectedSkuRows.length}个值`);
|
this.$message.success(`已批量设置${this.selectedSkuRows.length}个值`);
|
||||||
|
this.selectedSkuRows = [];
|
||||||
|
this.batchValue = '';
|
||||||
|
this.$refs.skuTable.clearSelection();
|
||||||
},
|
},
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
@ -627,8 +634,7 @@ export default {
|
||||||
// 失败时使用时间戳作为后备方案
|
// 失败时使用时间戳作为后备方案
|
||||||
return Date.now().toString();
|
return Date.now().toString();
|
||||||
}
|
}
|
||||||
},
|
},generateSKU() {
|
||||||
generateSKU() {
|
|
||||||
// 获取当前所有有效的规格值ID
|
// 获取当前所有有效的规格值ID
|
||||||
const currentValueIds = new Set();
|
const currentValueIds = new Set();
|
||||||
this.form.specs.forEach(spec => {
|
this.form.specs.forEach(spec => {
|
||||||
|
@ -654,17 +660,15 @@ export default {
|
||||||
const validSpecValues = (sku.specValues || sku.specs || []).filter(id =>
|
const validSpecValues = (sku.specValues || sku.specs || []).filter(id =>
|
||||||
currentValueIds.has(id)
|
currentValueIds.has(id)
|
||||||
);
|
);
|
||||||
const specKey = [...validSpecValues].sort().join('##');
|
const specKey = validSpecValues.join('##');
|
||||||
validSkuMap.set(specKey, sku);
|
validSkuMap.set(specKey, sku);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建新的SKU列表
|
// 创建新的SKU列表(不使用排序)
|
||||||
this.form.skuList = combinations.map(comb => {
|
this.form.skuList = combinations.map(comb => {
|
||||||
// 提取并排序组合中的规格值ID
|
// 提取组合中的规格值ID(保持原始顺序)
|
||||||
const sortedValueIds = comb
|
const specIds = comb.map(item => item.id);
|
||||||
.map(item => item.id)
|
const specKey = specIds.join('##');
|
||||||
.sort();
|
|
||||||
const specKey = sortedValueIds.join('##');
|
|
||||||
|
|
||||||
// 从映射表中获取对应的SKU
|
// 从映射表中获取对应的SKU
|
||||||
const matchedSku = validSkuMap.get(specKey);
|
const matchedSku = validSkuMap.get(specKey);
|
||||||
|
@ -673,28 +677,29 @@ export default {
|
||||||
// 保留原有SKU(含ID、价格、库存等数据)
|
// 保留原有SKU(含ID、价格、库存等数据)
|
||||||
return {
|
return {
|
||||||
...matchedSku,
|
...matchedSku,
|
||||||
// 更新规格值列表(避免包含无效ID)
|
specs: specIds,
|
||||||
specs: sortedValueIds,
|
specValues: specIds
|
||||||
specValues: sortedValueIds
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 新建SKU
|
// 新建SKU
|
||||||
return {
|
return {
|
||||||
id: undefined, // 新SKU的ID将由后端生成
|
id: undefined, // 新SKU的ID将由后端生成
|
||||||
specs: sortedValueIds,
|
specs: specIds,
|
||||||
specValues: sortedValueIds,
|
specValues: specIds,
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: "0",
|
stock: "0",
|
||||||
image: ''
|
image: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 使用原始顺序更新选中的行
|
||||||
const oldSelectedIds = this.selectedSkuRows.map(row =>
|
const oldSelectedIds = this.selectedSkuRows.map(row =>
|
||||||
row.specs ? row.specs.sort().join('##') : ''
|
row.specs ? row.specs.join('##') : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
this.selectedSkuRows = this.form.skuList.filter(row => {
|
this.selectedSkuRows = this.form.skuList.filter(row => {
|
||||||
const rowKey = row.specs ? row.specs.sort().join('##') : '';
|
const rowKey = row.specs ? row.specs.join('##') : '';
|
||||||
return oldSelectedIds.includes(rowKey);
|
return oldSelectedIds.includes(rowKey);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
|
||||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
|
||||||
|
|
||||||
|
<div class="app-container">
|
||||||
|
<div v-if="showSortEditPanel" class="sort-edit-panel" :style="panelStyle">
|
||||||
|
<el-input
|
||||||
|
v-model="editSortValue"
|
||||||
|
size="mini"
|
||||||
|
@keyup.enter.native="saveSort"
|
||||||
|
ref="sortInput"
|
||||||
|
></el-input>
|
||||||
|
<div class="sort-edit-buttons">
|
||||||
|
<el-button size="mini" @click="cancelEditSort">取消</el-button>
|
||||||
|
<el-button type="primary" size="mini" @click="saveSort">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||||
<el-form-item label="商品名称" prop="name">
|
<el-form-item label="商品名称" prop="name">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
|
@ -119,6 +131,17 @@
|
||||||
<template v-else-if="column.key === 'status'">
|
<template v-else-if="column.key === 'status'">
|
||||||
<dict-tag :value="d.row.status" :options="dict.type.goods_status"/>
|
<dict-tag :value="d.row.status" :options="dict.type.goods_status"/>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.key === 'sort'">
|
||||||
|
<div>
|
||||||
|
{{ d.row.sort }}
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
size="mini"
|
||||||
|
@click="startEditSort(d.row, $event)">
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.key === 'deposit'">
|
<template v-else-if="column.key === 'deposit'">
|
||||||
<dict-tag :value="d.row.deposit" :options="dict.type.goods_deposit"/>
|
<dict-tag :value="d.row.deposit" :options="dict.type.goods_deposit"/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -135,7 +158,7 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
type="text"
|
type="text"
|
||||||
icon="el-icon-top"
|
icon="el-icon-top"
|
||||||
@click="handleUpdate(scope.row)"
|
@click="handleOnline(scope.row)"
|
||||||
v-has-permi="['bst:goods:edit']"
|
v-has-permi="['bst:goods:edit']"
|
||||||
>上架</el-button>
|
>上架</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
|
@ -143,7 +166,7 @@
|
||||||
size="mini"
|
size="mini"
|
||||||
type="text"
|
type="text"
|
||||||
icon="el-icon-bottom"
|
icon="el-icon-bottom"
|
||||||
@click="handleUpdate(scope.row)"
|
@click="handleOffline(scope.row)"
|
||||||
v-has-permi="['bst:goods:edit']"
|
v-has-permi="['bst:goods:edit']"
|
||||||
>下架</el-button>
|
>下架</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
|
@ -183,14 +206,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {listGoods, getGoods, delGoods, addGoods, updateGoods, changeStatus} from "@/api/bst/goods";
|
import {listGoods, getGoods, delGoods, addGoods, updateGoods, changeStatus, changeStatusAndSort} from "@/api/bst/goods";
|
||||||
import { $showColumns } from '@/utils/mixins';
|
import { $showColumns } from '@/utils/mixins';
|
||||||
import FormCol from "@/components/FormCol/index.vue";
|
import FormCol from "@/components/FormCol/index.vue";
|
||||||
|
|
||||||
// 默认排序字段
|
// 默认排序字段
|
||||||
const defaultSort = {
|
const defaultSort = {
|
||||||
prop: "createTime",
|
prop: "sort",
|
||||||
order: "descending"
|
order: "ascending"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -200,6 +223,10 @@ export default {
|
||||||
dicts: ['goods_deposit','goods_status'],
|
dicts: ['goods_deposit','goods_status'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showSortEditPanel: false, // 控制面板显示
|
||||||
|
editSortValue: null, // 编辑中的排序值
|
||||||
|
editingRow: null, // 当前编辑的行数据
|
||||||
|
panelPosition: { top: 0, left: 0 }, // 面板位置
|
||||||
span: 24,
|
span: 24,
|
||||||
// 字段列表
|
// 字段列表
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -211,7 +238,7 @@ export default {
|
||||||
// {key: 'virtualSales', visible: true, label: '虚拟销量', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
// {key: 'virtualSales', visible: true, label: '虚拟销量', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
{key: 'deposit', visible: true, label: '是否可存', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
{key: 'deposit', visible: true, label: '是否可存', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
{key: 'status', visible: true, label: '商品状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
{key: 'status', visible: true, label: '商品状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
{key: 'sort', visible: true, label: '排序', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
{key: 'sort', visible: true, label: '排序', minWidth: null, sortable: 'custom', overflow: false, align: 'center', width: '120'},
|
||||||
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
//{key: 'storeId', visible: true, label: '店铺名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
//{key: 'storeId', visible: true, label: '店铺名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
//{key: 'categoryId', visible: true, label: '分类名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
//{key: 'categoryId', visible: true, label: '分类名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
|
||||||
|
@ -284,19 +311,78 @@ export default {
|
||||||
activated() {
|
activated() {
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
methods: {
|
mounted() {
|
||||||
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('scroll', this.handleScroll);
|
||||||
|
},
|
||||||
|
methods: {startEditSort(row, event) {
|
||||||
|
this.showSortEditPanel = false;
|
||||||
|
this.editingRow = row;
|
||||||
|
this.editSortValue = row.sort;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// 获取触发按钮的位置
|
||||||
|
const buttonRect = event.currentTarget.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 设置面板位置(按钮下方10px处,水平居中)
|
||||||
|
this.panelStyle = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${buttonRect.bottom + 10}px`,
|
||||||
|
left: `${buttonRect.left}px`,
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
zIndex: 2000
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showSortEditPanel = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.sortInput.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 新增方法:取消编辑
|
||||||
|
cancelEditSort() {
|
||||||
|
this.showSortEditPanel = false;
|
||||||
|
this.editingRow = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增方法:保存排序
|
||||||
|
saveSort() {
|
||||||
|
if (!this.editingRow) return;
|
||||||
|
|
||||||
|
// 检查排序值是否合法
|
||||||
|
const sortValue = parseInt(this.editSortValue);
|
||||||
|
if (isNaN(sortValue)) {
|
||||||
|
this.$message.error('排序值必须是数字');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API保存修改
|
||||||
|
changeStatusAndSort({ id: this.editingRow.id, sort: sortValue })
|
||||||
|
.then(response => {
|
||||||
|
this.$modal.msgSuccess("排序更新成功");
|
||||||
|
// 更新当前行的排序值(不刷新整个页面)
|
||||||
|
this.editingRow.sort = sortValue;
|
||||||
|
this.cancelEditSort();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$modal.msgError("更新失败: " + (error.message || error));
|
||||||
|
});
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
// 上架商品
|
// 上架商品
|
||||||
handleOnline(row) {
|
handleOnline(row) {
|
||||||
this.changeGoodsStatus(row, '1', '上架');
|
this.changeStatusAndSort(row, '1', '上架');
|
||||||
},
|
},
|
||||||
|
|
||||||
// 下架商品
|
// 下架商品
|
||||||
handleOffline(row) {
|
handleOffline(row) {
|
||||||
this.changeGoodsStatus(row, '0', '下架');
|
this.changeStatusAndSort(row, '0', '下架');
|
||||||
},
|
},
|
||||||
|
|
||||||
// 通用的状态切换方法
|
// 通用的状态切换方法
|
||||||
async changeGoodsStatus(row, status, actionName) {
|
async changeStatusAndSort(row, status, actionName) {
|
||||||
try {
|
try {
|
||||||
// 确认操作
|
// 确认操作
|
||||||
const confirmMsg = `是否确认${actionName}商品【${row.name}】?`;
|
const confirmMsg = `是否确认${actionName}商品【${row.name}】?`;
|
||||||
|
@ -309,7 +395,7 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 调用更新接口
|
// 调用更新接口
|
||||||
await changeStatus(updateData);
|
await changeStatusAndSort(updateData);
|
||||||
|
|
||||||
// 提示成功
|
// 提示成功
|
||||||
this.$modal.msgSuccess(`${actionName}成功`);
|
this.$modal.msgSuccess(`${actionName}成功`);
|
||||||
|
@ -345,6 +431,10 @@ export default {
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
listGoods(this.queryParams).then(response => {
|
listGoods(this.queryParams).then(response => {
|
||||||
|
response.rows.forEach(item => {
|
||||||
|
// 确保每行都有编辑状态
|
||||||
|
this.$set(item, 'isEditingSort', false)
|
||||||
|
})
|
||||||
this.goodsList = response.rows;
|
this.goodsList = response.rows;
|
||||||
this.total = response.total;
|
this.total = response.total;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -443,3 +533,46 @@ export default {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 新增排序编辑面板样式 */
|
||||||
|
.sort-edit-panel {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-edit-panel .el-input {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-edit-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保容器有相对定位 */
|
||||||
|
.app-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.sort-edit-panel {
|
||||||
|
position: fixed; /* 改为固定定位 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 2000;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user