商品前台交互更新

This commit is contained in:
SjS 2025-06-04 18:13:16 +08:00
parent ff1a8c2fca
commit 080f6d8fba
3 changed files with 172 additions and 34 deletions

View File

@ -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
}) })

View File

@ -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 {
// SKUID // SKUID
return { return {
...matchedSku, ...matchedSku,
// ID specs: specIds,
specs: sortedValueIds, specValues: specIds
specValues: sortedValueIds
}; };
} else { } else {
// SKU // SKU
return { return {
id: undefined, // SKUID id: undefined, // SKUID
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);
}); });
}, },

View File

@ -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>