Files
management/src/views/product/index.vue

687 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<div class="head-container">
<div class="flex">
<el-input v-model="query.name" size="small" clearable placeholder="请输入商品名称" @keyup.enter.native="getTableData"
style="width: 200px;" />
<el-select v-model="query.categoryId" placeholder="请选择商品分类">
<el-option :label="item.name" :value="item.id" v-for="item in categorys" :key="item.id" />
</el-select>
<el-select v-model="query.typeEnum" placeholder="请选择商品规格">
<el-option :label="item.label" :value="item.typeEnum" v-for="item in typeEnums" :key="item.typeEnum" />
</el-select>
<el-date-picker v-model="query.createdAt" type="daterange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
<el-button type="primary" @click="queryHandle">查询</el-button>
<el-button @click="resetHandle">重置</el-button>
<el-button @click="showStockWarningHandle">库存预警{{ warnLine }}</el-button>
</div>
</div>
<div class="head-container">
<div class="header_wrap">
<!-- <router-link :to="{ name: 'add_shop' }" > -->
<el-button type="primary" icon="el-icon-plus" @click="toPath('/product/add_shop')">添加商品</el-button>
<!-- </router-link> -->
<el-select v-model="tableData.sort" placeholder="排序" @change="getTableData">
<el-option value="createdAt,desc" label="创建时间"></el-option>
<el-option value="stockNumber,asc" label="数量由低到高"></el-option>
</el-select>
</div>
</div>
<div class="head-container">
<div class="data_wrap">
<div class="item">
<div class="icon">
<i class="el-icon-pie-chart"></i>
</div>
<div class="info">
<div class="row">
<span>商品种数</span>
<!-- @click="showStockHistory('stockNumber')" -->
<span>{{ tableData.total || 0 }}</span>
</div>
</div>
</div>
<div class="item">
<div class="icon">
<i class="el-icon-sort-up"></i>
</div>
<div class="info">
<div class="row">
<span>增加数量:</span>
<span class="link" @click="showStockHistory('addCountNumber')">{{ countInfo.addCountNumber || 0 }}</span>
</div>
<div class="row">
<div class="row">
<span>手动增加:</span>
<span class="link" @click="showStockHistory('addNumber')">{{ countInfo.addNumber || 0 }}</span>
</div>
<div class="line"></div>
<div class="row">
<span>退货</span>
<span class="link" @click="showStockHistory('refundNumber')">{{ countInfo.refundNumber || 0 }}</span>
</div>
</div>
</div>
</div>
<div class="item">
<div class="icon">
<i class="el-icon-sort-down"></i>
</div>
<div class="info">
<div class="row">
<span>减少数量:</span>
<span class="link" @click="showStockHistory('subCountNumber')">{{ countInfo.subCountNumber || 0 }}</span>
</div>
<div class="row">
<div class="row">
<span>手动减少:</span>
<span class="link" @click="showStockHistory('subNumber')">{{ countInfo.subNumber || 0 }}</span>
</div>
<div class="line"></div>
<div class="row">
<span>销售量</span>
<span class="link" @click="showStockHistory('saleNumber')">{{ countInfo.saleNumber || 0 }}</span>
</div>
<div class="line"></div>
<div class="row">
<span>报损</span>
<span class="link" @click="showStockHistory('lossNumber')">{{ countInfo.lossNumber || 0 }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="head-container" id="table_drag">
<el-table ref="table" :data="tableData.data" v-loading="tableData.loading" row-key="key"
:tree-props="{ children: 'skuList', hasChildren: 'hasChildren' }">
<el-table-column width="50px"></el-table-column>
<el-table-column label="商品信息">
<template v-slot="scope">
<div class="shop_info">
<el-image :src="scope.row.coverImg"
style="width: 50px;height: 50px;border-radius: 4px;background-color: #efefef;"
v-if="scope.row.coverImg">
<div class="img_error" slot="error">
<i class="icon el-icon-document-delete"></i>
</div>
</el-image>
<div class="info">
<span>{{ scope.row.name }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="售价">
<template v-slot="scope">
<el-button type="text" v-if="scope.row.typeEnum == '多规格' && scope.row.skuList.length">
{{ scope.row.lowPrice }}
</el-button>
<el-button type="text" v-else @click="changePrice('salePrice', scope.row,)">
{{ scope.row.lowPrice }}
<i class="el-icon-edit"></i>
</el-button>
</template>
</el-table-column>
<el-table-column label="商品规格" prop="typeEnum"></el-table-column>
<el-table-column label="库存">
<template v-slot="scope">
<el-button type="text" v-if="scope.row.typeEnum" @click="changePrice('stockNumber', scope.row,)">
{{ scope.row.stockNumber }}
<i class="el-icon-edit"></i>
</el-button>
</template>
</el-table-column>
<el-table-column label="耗材信息" width="200">
<template v-slot="scope">
<div class="cons_wrap" v-if="scope.row.typeEnum">
<div v-if="scope.row.conInfos && scope.row.conInfos.length">
<el-button type="text" @click="showBindCons(scope.row)">
<span class="cons_span">{{ scope.row.conInfos | conInfosFilter }}</span>
<i class="el-icon-edit"></i>
</el-button>
</div>
<el-button v-else type="text" @click="showBindCons(scope.row)">
绑定
<i class="el-icon-edit"></i>
</el-button>
</div>
</template>
</el-table-column>
<el-table-column label="上架">
<template v-slot="scope">
<!-- <el-button type="text" icon="el-icon-edit" v-if="scope.row.isShowCash">收银端</el-button>
<el-button type="text" icon="el-icon-edit" v-if="scope.row.isShowMall">小程序</el-button>
<el-button type="text" icon="el-icon-edit"
v-if="!scope.row.isShowCash && !scope.row.isShowMall">未上架</el-button> -->
<el-switch v-model="scope.row.isGrounding" :active-value="1" :inactive-value="0"
@change="changeGrounding($event, scope.row, 'grounding')"></el-switch>
</template>
</el-table-column>
<el-table-column label="售罄">
<template v-slot="scope">
<!-- <el-button type="text" icon="el-icon-edit" v-if="scope.row.isShowCash">收银端</el-button>
<el-button type="text" icon="el-icon-edit" v-if="scope.row.isShowMall">小程序</el-button>
<el-button type="text" icon="el-icon-edit"
v-if="!scope.row.isShowCash && !scope.row.isShowMall">未上架</el-button> -->
<el-switch v-model="scope.row.isPauseSale" :active-value="1" :inactive-value="0"
@change="changeGrounding($event, scope.row, 'pauseSale')"></el-switch>
</template>
</el-table-column>
<!-- <el-table-column label="排序" prop="sort" sortable />
<el-table-column label="更新时间" prop="createdAt" width="150">
<template v-slot="scope">
{{ dayjs(scope.row.createdAt).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column> -->
<!-- <el-table-column label="设为热门" prop="createdAt">
<template v-slot="scope">
<el-switch v-model="scope.row.isHot" :active-value="1" :inactive-value="0"
@change="changeHot($event, scope.row)"></el-switch>
</template>
</el-table-column> -->
<el-table-column label="操作" width="150">
<template v-slot="scope">
<!-- <el-button type="text" icon="el-icon-rank" v-if="isPcBowser">排序</el-button> -->
<!-- <router-link :to="{ path: '/product/add_shop', query: { goods_id: scope.row.id } }"
style="margin-left: 20px !important;">
<el-button type="text" icon="el-icon-edit">编辑</el-button>
</router-link> -->
<div style="display: flex;gap: 10px;">
<el-button type="text" icon="el-icon-edit" @click="toPath('/product/add_shop', scope.row)"
v-if="scope.row.typeEnum">编辑</el-button>
<el-popconfirm title="确定删除吗?" @confirm="delTableHandle([scope.row.id])">
<el-button type="text" icon="el-icon-delete" slot="reference">删除</el-button>
</el-popconfirm>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="head-container">
<el-pagination :total="tableData.total" @size-change="handleSizeChange" :current-page="tableData.page + 1"
:page-size="tableData.size" @current-change="paginationChange"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div>
<!-- 绑定耗材 -->
<BindCons ref="bindCons" @refundChange="refundChange" />
<!-- 商品库存记录 -->
<StockHistory ref="stockHistory" :query="query" />
<!-- 编辑售价库存 -->
<el-dialog :title="editorEumn[editorForm.key]" :visible.sync="editorVisable" :show-close="false" width="300px">
<el-form :model="editorForm">
<el-form-item>
<el-input-number v-model="editorForm.value" :min="0" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editorVisable = false"> </el-button>
<el-button type="primary" :loading="editorFormLoading" @click="editorConfirmChange"> </el-button>
</span>
</el-dialog>
<!-- 库存预警值 -->
<el-dialog title="修改库存预警" :visible.sync="showStockWarning" :show-close="false" width="300px">
<el-form :model="editorForm">
<el-form-item label="库存预警">
<el-input-number v-model="warnLineValue" :min="0" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="showStockWarning = false"> </el-button>
<el-button type="primary" :loading="warnLineLoading" @click="stockWarnLineConfirm"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Sortable from 'sortablejs'
import dayjs from 'dayjs'
import settings from '@/settings'
import BindCons from './components/bindCons.vue'
import StockHistory from './components/stockHistory.vue'
import { tbProductListV2, tbShopCategoryGet, tbProductDelete, tbProductIsHot, upProSort, updateProductData, tbProductStockDetailStockCount, stockWarnLine } from '@/api/shop'
import { hasPermission } from '@/utils/limits.js'
export default {
name: 'product',
components: {
BindCons,
StockHistory
},
data() {
return {
dayjs,
query: {
productId: '',
name: '',
categoryId: '',
typeEnum: '',
createdAt: []
},
categorys: [],
typeEnums: settings.typeEnum,
tableData: {
sort: 'createdAt,desc',
data: [],
page: 0,
size: 30,
loading: false,
total: 0
},
editorEumn: {
grounding: '上下架',
pauseSale: '商品',
stockNumber: '库存数量',
salePrice: '售价'
},
editorVisable: false,
editorFormLoading: false,
editorForm: {
shopId: localStorage.getItem('shopId'),
isSku: '',
id: '',
key: '',
value: ''
},
countInfo: {},
showStockWarning: false, // 显示库存预警值
warnLineLoading: false,
warnLine: 0,
warnLineValue: 0
}
},
filters: {
conInfosFilter(arr) {
let newarr = arr.map(item => item.conName)
return newarr.join('、')
}
},
async mounted() {
if (this.$route.query.productId) {
this.query.productId = this.$route.query.productId
}
await this.tbShopCategoryGet()
await this.getTableData()
if (this.isPcBowser) {
this.$nextTick(() => {
this.tableDrag()
})
}
this.tbProductStockDetailStockCount()
},
methods: {
// 是否允许修改商品
async toPath(path, row) {
let res = await hasPermission('允许修改商品');
if (!res) { return; }
let query = {};
if (row) {
query.goods_id = row.id;
}
this.$router.push({ path: path, query: query })
},
// 显示修改商品警告线
showStockWarningHandle() {
this.showStockWarning = true
this.warnLineValue = this.warnLine
},
// 修改商品警告线
async stockWarnLineConfirm() {
try {
this.warnLineLoading = true
const res = await stockWarnLine({
shopId: localStorage.getItem('shopId'),
warnLine: this.warnLineValue
})
this.warnLineLoading = false
this.showStockWarning = false
this.$message.success('修改成功')
this.getTableData()
} catch (error) {
this.warnLineLoading = false
console.log(error);
}
},
// 显示库存记录
showStockHistory(key) {
this.$refs.stockHistory.show(key)
},
// 库存统计列表 统计
async tbProductStockDetailStockCount() {
try {
const res = await tbProductStockDetailStockCount({
shopId: localStorage.getItem('shopId'),
productName: this.query.name,
categoryId: this.query.categoryId,
startTime: this.query.createdAt[0] || '',
endTime: this.query.createdAt[1] || ''
})
this.countInfo = res
} catch (error) {
console.log(error);
}
},
// 编辑退款是否退回库存
refundChange(e) {
let row = this.tableData.data.find(item => item.id == e.id)
if (row.isRefundStock != e.isRefundStock) {
this.editorForm.key = 'refundStock'
this.editorForm.id = e.id
this.editorForm.isSku = !e.typeEnum
this.editorForm.value = e.isRefundStock
this.editorConfirmChange()
}
this.getTableData()
},
async changeGrounding(event, row, key) {
let text;
if (key == 'grounding') { text = "允许上下架商品" }
if (key == 'pauseSale') { text = "允许售罄商品" }
let res = await hasPermission(text);
if (!res) {
if (key == 'grounding') { row.isGrounding = (event == 0 ? 1 : 0); }
if (key == 'pauseSale') { row.isPauseSale = (event == 0 ? 1 : 0); }
return;
}
this.editorForm.key = key
this.editorForm.id = row.id
this.editorForm.isSku = !row.typeEnum
this.editorForm.value = event
this.editorConfirmChange()
},
// 确认修改
async editorConfirmChange() {
try {
this.editorFormLoading = true
const res = await updateProductData([this.editorForm])
this.editorFormLoading = false
// this.$message.success('修改成功')
this.editorVisable = false
this.getTableData()
} catch (error) {
console.log(error);
this.editorFormLoading = false
}
},
// 修改库存
async changePrice(type, row) {
let res = await hasPermission('允许修改商品库存');
if (!res) { return; }
this.editorVisable = true
this.editorForm.key = type
this.editorForm.id = row.id
this.editorForm.isSku = !row.typeEnum
switch (type) {
case 'salePrice':
// 修改售价
this.editorForm.value = row.lowPrice
break;
case 'stockNumber':
// 修改库存
this.editorForm.value = row.stockNumber
break;
default:
break;
}
},
// 显示绑定耗材
async showBindCons(item) {
let res = await hasPermission('允许修改商品');
if (!res) { return; }
// console.log(item);
this.$refs.bindCons.show(item)
},
// 选择分类
queryHandle() {
localStorage.setItem('shopIndexQuery', JSON.stringify(this.query))
this.getTableData()
this.tbProductStockDetailStockCount()
},
//表格拖拽
tableDrag() {
const el = document.querySelector('#table_drag .el-table__body-wrapper tbody')
new Sortable(el, {
animation: 150,
onEnd: async e => {
// console.log('拖拽结束===', e);
if (e.oldIndex == e.newIndex) return
let oid = this.tableData.data[e.oldIndex].id
let nid = this.tableData.data[e.newIndex].id
let ids = this.tableData.data.map(item => item.id)
try {
await upProSort({
strId: oid,
endId: nid,
ids: ids
})
await this.getTableData()
} catch (error) {
console.log(error);
}
}
});
},
// 设置热门
async changeHot(e, row) {
try {
this.tableData.loading = true
await tbProductIsHot({
isHot: e,
id: row.id
})
this.getTableData()
} catch (error) {
console.log(error);
}
},
// 重置查询
resetHandle() {
this.query.name = ''
this.query.categoryId = ''
this.query.typeEnum = ''
this.query.productId = ''
this.query.createdAt = []
this.tableData.page = 0
localStorage.setItem('shopIndexQuery', JSON.stringify(this.query))
this.getTableData()
this.tbProductStockDetailStockCount()
},
// 分页回调
paginationChange(e) {
this.tableData.page = e - 1
this.getTableData()
},
// 获取商品列表
async getTableData() {
try {
let localQuery = JSON.parse(localStorage.getItem('shopIndexQuery'))
if (localQuery != null && localQuery.hasOwnProperty('productId')) {
// this.query = localQuery
}
this.tableData.loading = true
// console.log(this.query, '调试2')
const res = await tbProductListV2({
page: this.tableData.page,
size: this.tableData.size,
name: this.query.name,
categoryId: this.query.categoryId,
id: this.query.productId,
type: this.query.typeEnum,
shopId: localStorage.getItem('shopId'),
sort: this.tableData.sort,
createdAt: this.query.createdAt
})
this.warnLine = res.warnLine
res.content.map(item => {
item.key = item.id
item.skuList.map(val => {
val.key = `${item.id}-${val.id}`
})
})
this.tableData.loading = false
this.tableData.data = res.content
this.tableData.total = res.totalElements
} catch (error) {
console.log(error)
}
},
handleSizeChange(val) {
this.tableData.size = val
this.getTableData()
},
// 获取商品分类列表
async tbShopCategoryGet() {
try {
const res = await tbShopCategoryGet({
shopId: localStorage.getItem('shopId'),
page: 0,
size: 100,
sort: 'id'
})
this.categorys = res.content
} catch (error) {
console.log(error)
}
},
// 删除商品
async delTableHandle(ids) {
let res = await hasPermission('允许修改商品');
if (!res) { return; }
try {
await tbProductDelete(ids)
this.getTableData()
} catch (error) {
console.log(error)
}
},
}
}
</script>
<style scoped lang="scss">
.cons_span {
white-space: break-spaces;
}
.header_wrap {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.data_wrap {
display: flex;
justify-content: space-between;
padding: 0 60px;
.item {
display: flex;
align-items: center;
background-color: #F4F9FF;
height: 80px;
padding: 20px;
.icon {
width: 37px;
height: 37px;
border-radius: 50%;
background-color: #D4E9FE;
display: flex;
align-items: center;
justify-content: center;
color: #3F9EFF;
font-size: 20px;
}
.info {
flex: 1;
display: flex;
gap: 14px;
flex-direction: column;
padding-left: 14px;
.row {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
.line {
margin: 0 14px;
height: 20px;
border-left: 1px solid #DDDFE6;
}
span {
&.link {
color: #3F9EFF;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&:first-child {
color: #666;
}
}
}
}
}
}
.cons_wrap {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.handle {
font-size: 18px;
color: #999;
&:hover {
cursor: grab;
}
}
.shop_info {
display: flex;
.info {
flex: 1;
padding-left: 8px;
display: flex;
flex-direction: column;
.tag_wrap {
display: flex;
}
}
}
</style>