581 lines
20 KiB
Vue
Raw Normal View History

2025-06-14 10:30:10 +08:00
<template>
<div class="pageBox">
<div class="searchBox">
<div class="row">
<span class="span">商品</span>
<div class="right">
<el-select v-model="goodsIds" placeholder="请选择" clearable filterable class="wid100" multiple collapse-tags>
2025-06-14 10:30:10 +08:00
<el-option v-for="it in goodsList" :key="it.id" :label="it.title" :value="it.id" />
</el-select>
</div>
</div>
<div class="row">
<span class="span">商品品牌</span>
<div class="right">
<el-select v-model="brandIds" placeholder="请选择" clearable class="wid100" filterable multiple collapse-tags>
<el-option v-for="it in brandList" :key="it.id" :label="it.name" :value="it.id" />
</el-select>
</div>
</div>
2025-06-14 10:30:10 +08:00
<div class="row">
<span class="span">规格名称</span>
<div class="right"><el-input v-model="filter.title" class="wid100" clearable></el-input></div>
</div>
<div class="row">
<span class="span">规格编码</span>
<div class="right"><el-input v-model="filter.sku_code" class="wid100" clearable></el-input></div>
</div>
<div class="row">
<span class="span">仓库</span>
<div class="right">
<el-select v-model="filter.warehouse_id" placeholder="请选择" clearable class="wid100" filterable>
<el-option v-for="it in warehouseList" :key="it.id" :label="it.name" :value="it.id" />
</el-select>
</div>
</div>
<div class="row">
<span class="span">可售库存</span>
<div class="right">
<el-input v-model="filter.min_available_inventory" class="wid100" clearable></el-input>&nbsp;~&nbsp;
<el-input v-model="filter.max_available_inventory" class="wid100" clearable></el-input>
</div>
</div>
2025-06-14 10:30:10 +08:00
<div class="row">
<span class="span">状态</span>
<div class="right">
<el-select v-model="filter.status" placeholder="请选择" clearable class="wid100">
<el-option label="启用" :value="1" />
<el-option label="不启用" :value="0" />
</el-select>
</div>
</div>
<div class="row">
<span class="span"></span>
<div class="right"><el-button type="primary" @click="handleSearch"><el-icon><Search /></el-icon>&nbsp;筛选</el-button></div>
</div>
</div>
<el-card shadow="never">
<div class="opaBox">
<el-button type="primary" @click="handleAdd"><el-icon><Plus /></el-icon>&nbsp;新增</el-button>
2025-08-29 17:25:42 +08:00
<el-button type="primary" v-if="role" :disabled="!chooseList.length" @click="lte_stock = 0, showStock = true">设置预警库存</el-button>
2025-10-31 14:48:27 +08:00
<el-button type="warning" @click="handleExport" :loading="exportLoading"><span class="iconfont icon-daochu"></span>&nbsp;导出</el-button>
2025-06-14 10:30:10 +08:00
</div>
2025-08-29 17:25:42 +08:00
<el-table :data="tableList" style="width: 100%" border v-loading="loading" @select="select"
ref="multipleTable" @select-all="selectAll">
<el-table-column type="selection" width="55" />
2025-06-14 10:30:10 +08:00
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="goods.title" label="商品名称" align="center" />
<el-table-column prop="goods.goods_code" label="商品货号" align="center" />
<el-table-column prop="title" label="规格名称" align="center" />
<el-table-column prop="sku_code" label="规格编码" align="center" />
2025-08-15 15:46:51 +08:00
<el-table-column label="发货模式" align="center">
<template #default="scope">
<div v-if="scope.row.warehouse">
<span v-if="scope.row.warehouse.ship_mode == 'express'">一件代发</span>
<span v-else>自营仓发</span>
</div>
</template>
</el-table-column>
2025-06-14 10:30:10 +08:00
<el-table-column prop="actual_inventory" label="实际库存" align="center" />
<el-table-column prop="total_lock_num" label="总锁定库存" align="center" />
2025-08-15 13:34:27 +08:00
<el-table-column prop="first_stage_stock" label="一阶段库存" align="center" />
<el-table-column prop="second_stage_stock" label="二阶段库存" align="center" />
<el-table-column prop="third_stage_stock" label="三阶段库存" align="center" />
2025-06-14 10:30:10 +08:00
<el-table-column prop="lock_in_stock" label="运营锁定库存" align="center" />
<el-table-column prop="after_sale_stock" label="售后锁定库存" align="center" />
<el-table-column prop="available_inventory" label="可售库存" align="center" />
2025-08-29 17:25:42 +08:00
<el-table-column prop="lte_stock" label="预警库存" align="center" />
<el-table-column prop="goods_cost" label="商品成本" align="center" />
<el-table-column prop="freight_cost" label="运费成本" align="center" />
2025-06-14 10:30:10 +08:00
<el-table-column prop="warehouse.name" label="仓库" align="center" />
<el-table-column label="状态" align="center">
<template #default="scope">
<span>{{ scope.row.status ? '启用' : '不启用' }}</span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="添加时间" align="center" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button type="primary" circle @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon></el-button>
2025-06-20 11:04:10 +08:00
<el-tooltip effect="dark" content="上传商品到聚水潭" placement="top">
<el-button type="primary" circle @click="handleUpload(scope.row)"><el-icon><Upload /></el-icon></el-button>
</el-tooltip>
2025-06-14 10:30:10 +08:00
</template>
</el-table-column>
</el-table>
<div class="page-pagination">
<el-pagination
:current-page="page"
background
layout="prev, pager, next, sizes, total"
:total="total"
:page-sizes="[10, 50, 100]"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</div>
</el-card>
2025-08-29 17:25:42 +08:00
<el-dialog v-model="showUpdate" width="600px" title="新增">
2025-06-14 10:30:10 +08:00
<el-form label-position="right" label-width="110px">
<el-form-item label="所属商品:">
2025-08-29 17:25:42 +08:00
<el-select v-model="itemInfo.goods_id" placeholder="请选择" clearable filterable disabled>
2025-06-14 10:30:10 +08:00
<el-option v-for="it in goodsList" :key="it.id" :label="it.title" :value="it.id" />
</el-select>
</el-form-item>
<el-form-item label="规格名称:">
2025-08-29 17:25:42 +08:00
<el-input v-model="itemInfo.title" clearable disabled></el-input>
2025-06-14 10:30:10 +08:00
</el-form-item>
<el-form-item label="规格编码:">
2025-08-29 17:25:42 +08:00
<el-input v-model="itemInfo.sku_code" clearable disabled></el-input>
2025-06-14 10:30:10 +08:00
</el-form-item>
2025-08-29 17:25:42 +08:00
<el-form-item label="赠品:">
<el-radio-group v-model="itemInfo.gift" disabled>
<el-radio :label="0">不是</el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
2025-06-14 10:30:10 +08:00
</el-form-item>
<el-form-item label="总锁定库存:">
<el-input v-model="itemInfo.total_lock_num" disabled></el-input>
</el-form-item>
2025-08-29 17:25:42 +08:00
<el-form-item label="预警库存:">
<el-input v-model="itemInfo.lte_stock" clearable></el-input>
</el-form-item>
2025-08-15 13:34:27 +08:00
<el-form-item label="一阶段库存:">
<el-input v-model="itemInfo.first_stage_stock" clearable></el-input>
</el-form-item>
<el-form-item label="二阶段库存:">
<el-input v-model="itemInfo.second_stage_stock" clearable></el-input>
</el-form-item>
<el-form-item label="三阶段库存:">
<el-input v-model="itemInfo.third_stage_stock" clearable></el-input>
</el-form-item>
2025-06-14 10:30:10 +08:00
<el-form-item label="运营锁定库存:">
<el-input v-model="itemInfo.lock_in_stock" clearable></el-input>
</el-form-item>
<el-form-item label="售后锁定库存:">
2025-08-29 17:25:42 +08:00
<el-input v-model="itemInfo.after_sale_stock" clearable disabled></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showUpdate = false">取消</el-button>
<el-button type="primary" @click="commitUpdate()" :loading="opa_loading">确定</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="showDialog" width="600px" :title="opaType == 'add' ? '新增' : '编辑' ">
<el-form label-position="right" label-width="110px">
<el-form-item label="所属商品:">
<el-select v-model="itemInfo.goods_id" placeholder="请选择" clearable filterable>
<el-option v-for="it in goodsList" :key="it.id" :label="it.title" :value="it.id" />
</el-select>
</el-form-item>
<el-form-item label="规格名称:">
<el-input v-model="itemInfo.title" clearable></el-input>
</el-form-item>
<el-form-item label="规格编码:">
<el-input v-model="itemInfo.sku_code" clearable></el-input>
</el-form-item>
<el-form-item label="仓库:">
<el-select v-model="itemInfo.warehouse_id" placeholder="请选择" clearable filterable>
<el-option v-for="it in warehouseList" :key="it.id" :label="it.name" :value="it.id" />
</el-select>
</el-form-item>
<el-form-item label="售后锁定库存:">
<el-input v-model="itemInfo.after_sale_stock" clearable></el-input>
</el-form-item>
2025-09-12 18:07:25 +08:00
<el-form-item label="运费类型:">
<el-radio-group v-model="itemInfo.freight_type">
<el-radio :label="1">有运费</el-radio>
<el-radio :label="3">无运费</el-radio>
<el-radio :label="2">后补</el-radio>
</el-radio-group>
</el-form-item>
2025-08-29 17:25:42 +08:00
<el-form-item label="商品成本:">
<el-input v-model="itemInfo.goods_cost" clearable></el-input>
</el-form-item>
<el-form-item label="运费成本:">
<el-input v-model="itemInfo.freight_cost" clearable></el-input>
2025-06-14 10:30:10 +08:00
</el-form-item>
<el-form-item label="状态:">
2025-08-29 17:25:42 +08:00
<el-radio-group v-model="itemInfo.status">
2025-06-14 10:30:10 +08:00
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">不启用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="赠品:">
2025-08-29 17:25:42 +08:00
<el-radio-group v-model="itemInfo.gift">
2025-06-14 10:30:10 +08:00
<el-radio :label="0">不是</el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序:">
2025-08-29 17:25:42 +08:00
<el-input-number v-model="itemInfo.sort" :min="0" />
2025-06-14 10:30:10 +08:00
</el-form-item>
2025-08-29 17:25:42 +08:00
2025-06-14 10:30:10 +08:00
</el-form>
<template #footer>
2025-08-29 17:25:42 +08:00
<span class="dialog-footer">
2025-06-14 10:30:10 +08:00
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="commitOpa()" :loading="opa_loading">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 预览图片 -->
<el-dialog v-model="picVisible" center width="800px">
<img :src="dialogImageUrl" style="max-width: 700px; margin: 0 auto;display: block;" />
</el-dialog>
2025-08-29 17:25:42 +08:00
<el-dialog v-model="showStock" width="600px" title="设置预警库存">
<el-form label-position="right" label-width="110px">
<el-form-item label="预警库存:">
<el-input-number v-model="lte_stock" clearable></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showStock = false">取消</el-button>
<el-button type="primary" @click="commitStock()">确定</el-button>
</span>
</template>
</el-dialog>
2025-06-14 10:30:10 +08:00
</div>
</template>
<script>
import { onMounted, reactive, toRefs } from "vue"
import { get, post } from "@/api/request"
2025-06-20 11:04:10 +08:00
import { Search, Plus, Edit, ZoomIn, Delete, Upload } from '@element-plus/icons'
import { ElMessage, ElMessageBox } from 'element-plus'
2025-06-14 10:30:10 +08:00
import { parseErrors } from 'components/common'
export default {
components: {
2025-06-20 11:04:10 +08:00
Search, Plus, Edit, ZoomIn, Delete, Upload
2025-06-14 10:30:10 +08:00
},
setup() {
const data = reactive({
filter: {},
tableList: [],
page: 1,
pageSize: 10,
total: 0,
loading: false,
opaType: '',
showDialog: false,
opa_loading: false,
warehouseList: [],
goodsList: [],
itemInfo: {},
role: localStorage.getItem('roleName') == '爆品运营',
goodsIds: [],
brandIds: [],
2025-08-29 17:25:42 +08:00
brandList: [],
chooseList: [],
lte_stock: 0,
showStock: false,
2025-10-31 14:48:27 +08:00
showUpdate: false,
exportLoading: false
2025-06-14 10:30:10 +08:00
})
function handleSearch() {
data.page = 1
fetchData()
}
const fetchData = () => {
data.loading = true
let params = {
page: data.page,
pageSize: data.pageSize,
service_id: data.service_id,
...data.filter,
brand_ids: data.brandIds.join(','),
goods_ids: data.goodsIds.join(',')
2025-06-14 10:30:10 +08:00
}
get(`/api/goods-skus`, params).then((res) => {
data.tableList = res.data
data.total = res.meta.total
data.loading = false
}).catch(() => {
data.loading = false
})
}
2025-10-31 14:48:27 +08:00
const handleExport = () => {
data.exportLoading = true
let params = {
page: data.page,
pageSize: data.pageSize,
service_id: data.service_id,
...data.filter,
brand_ids: data.brandIds.join(','),
goods_ids: data.goodsIds.join(','),
export: 1
}
get(`/api/goods-skus`, params, 'blob').then((res) => {
downLoadXls(res)
ElMessage({ type: "success", message: "导出成功!" })
data.exportLoading = false
}).catch(() => {
data.exportLoading = false
})
}
function downLoadXls(response) {
const content = response
const blob = new Blob([content])
const fileName = `商品规格.xlsx`
if ('download' in document.createElement('a')) {
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
} else {
navigator.msSaveBlob(blob, fileName)
}
}
2025-06-14 10:30:10 +08:00
function handleCurrentChange(e) {
data.page = e
fetchData()
}
function handleSizeChange(e) {
data.page = 1
data.pageSize = e
fetchData()
}
function handleAdd() {
data.opaType = 'add'
data.itemInfo = {
status: 1,
sort: 0,
2025-09-12 18:07:25 +08:00
gift: 0,
freight_type: 1
2025-06-14 10:30:10 +08:00
}
data.showDialog = true
}
function handleEdit(item) {
data.opaType = 'edit'
data.itemInfo = JSON.parse(JSON.stringify(item))
2025-08-29 17:25:42 +08:00
if(data.role) {
data.showUpdate = true
} else {
data.showDialog = true
}
2025-06-14 10:30:10 +08:00
}
2025-06-20 11:04:10 +08:00
function handleUpload(row) {
ElMessageBox.confirm('确定操作上传吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
post(`/api/goods/sku/jstUpload/${row.id}`, {}, 'PUT').then(() => {
ElMessage({ type: 'success', message: '操作成功' })
})
})
}
2025-06-14 10:30:10 +08:00
function commitOpa() {
2025-09-12 18:07:25 +08:00
if(!data.itemInfo.goods_cost || data.itemInfo.goods_cost * 1 == 0) {
return ElMessage({ type: 'info', message: '商品成本必填' })
}
2025-06-14 10:30:10 +08:00
data.opa_loading = true
let params = {
...data.itemInfo
}
if(data.opaType == 'add') {
post(`/api/goods-skus`, params).then(() => {
data.page = 1
fetchData()
ElMessage({ type: 'success', message: '新增成功' })
data.showDialog = false
data.opa_loading = false
}).catch((err) => {
data.opa_loading = false
parseErrors(err.response.data.errors)
})
} else {
2025-08-29 17:25:42 +08:00
post(`/api/goods-skus/${data.itemInfo.id}`, params, 'PUT').then(() => {
fetchData()
ElMessage({ type: 'success', message: '编辑成功' })
data.showDialog = false
data.opa_loading = false
}).catch((err) => {
data.opa_loading = false
parseErrors(err.response.data.errors)
})
2025-06-14 10:30:10 +08:00
}
}
2025-08-29 17:25:42 +08:00
function commitUpdate() {
data.opa_loading = true
let params = {
lock_in_stock: data.itemInfo.lock_in_stock,
first_stage_stock: data.itemInfo.first_stage_stock,
second_stage_stock: data.itemInfo.second_stage_stock,
third_stage_stock: data.itemInfo.third_stage_stock,
lte_stock: data.itemInfo.lte_stock,
goods_cost: data.itemInfo.goods_cost,
freight_cost: data.itemInfo.freight_cost
}
post(`/api/goods/sku/lockInStock/${data.itemInfo.id}`, params, 'PUT').then(() => {
fetchData()
ElMessage({ type: 'success', message: '编辑成功' })
data.showUpdate = false
data.opa_loading = false
}).catch((err) => {
data.opa_loading = false
parseErrors(err.response.data.errors)
})
}
2025-06-14 10:30:10 +08:00
function getWarehouseList() {
get(`/api/all/warehouses`).then((res) => {
data.warehouseList = res.data
})
}
function getGoodsList() {
get(`/api/all/goods`).then((res) => {
data.goodsList = res.data
})
}
function getBrandList() {
get(`/api/all/brands`).then((res) => {
data.brandList = res.data
})
}
2025-08-29 17:25:42 +08:00
function select(e) {
data.chooseList = e
}
function selectAll(e) {
data.chooseList = e
}
function commitStock() {
let ids = []
data.chooseList.forEach((it) => {
ids.push(it.id)
})
let params = {
ids: ids,
lte_stock: data.lte_stock
}
post('/api/goods/sku/batchUpdateLetStock', params, 'PUT').then((res) => {
ElMessage({ type: 'success', message: '操作成功' })
data.chooseList = []
data.showStock = false
fetchData()
})
}
2025-06-14 10:30:10 +08:00
onMounted(() => {
fetchData()
getWarehouseList()
getGoodsList()
getBrandList()
2025-06-14 10:30:10 +08:00
})
return {
...toRefs(data),
handleSearch,
handleCurrentChange,
handleSizeChange,
2025-10-31 14:48:27 +08:00
handleExport,
2025-06-14 10:30:10 +08:00
fetchData,
handleAdd,
handleEdit,
2025-06-20 11:04:10 +08:00
handleUpload,
2025-06-14 10:30:10 +08:00
commitOpa,
2025-08-29 17:25:42 +08:00
commitUpdate,
2025-06-14 10:30:10 +08:00
getWarehouseList,
getGoodsList,
2025-08-29 17:25:42 +08:00
getBrandList,
select,
selectAll,
commitStock
2025-06-14 10:30:10 +08:00
}
}
}
</script>
<style lang="scss" scoped>
.searchBox{
display: flex;
flex-wrap: wrap;
background-color: #fff;
padding: 15px 0 0 0;
border-radius: 4px;
margin-bottom: 15px;
.row{
display: flex;
align-items: center;
width: 20%;
box-sizing: border-box;
margin-bottom: 15px;
&.row1{
width: 25%;
}
.span{
display: block;
width: 80px;
font-size: 14px;
text-align: right;
box-sizing: border-box;
}
.right{
width: calc(100% - 100px);
display: flex;
align-items: center;
2025-06-14 10:30:10 +08:00
}
.wid100{
width: 100%;
}
}
}
.opaBox{
margin-bottom: 15px;
}
.imgBox{
display: flex;
flex-wrap: wrap;
.el-image{
width: 60px;
height: 60px;
border-radius: 4px;
margin-right: 10px;
}
}
.skuBox{
border: 1px solid #e5e5e5;
border-radius: 5px;
padding: 15px 0;
margin-bottom: 15px;
background-color: #f3f3f3;
.tit{
padding-left: 40px;
font-weight: 600;
font-size: 15px;
margin-bottom: 15px;
}
}
</style>