cashier_desktop/src/views/home/components/goods.vue

946 lines
26 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="header">
<div class="menus scroll-x">
<div class="item" :class="{ active: categorysActive == index }" v-for="(item, index) in categorys"
:key="item.id" @click="changeCategory(index)">
<el-text>{{ item.name }}</el-text>
</div>
</div>
<el-popover trigger="click" :width="600" title="全部分类" :visible="showPopover">
<template #reference>
<div class="all" @click="showMoreMenu">
<el-icon class="icon" :class="{ active: showPopover }">
<Menu />
</el-icon>
</div>
</template>
<template #default>
<div class="popover_wrap">
<el-button :plain="categorysActive != index" type="primary" v-for="(item, index) in categorys"
:key="item.id" @click="changeCategory(index)">
{{ item.name }}
</el-button>
</div>
</template>
</el-popover>
</div>
<div class="search_wrap">
<el-button :type="showEditor ? 'warning' : ''" @click="showEditorChange">{{ showEditor ? '关闭编辑' : '编辑'
}}</el-button>
<div class="right">
<div class="input">
<el-input placeholder="请输入商品名称查询" v-model="commdityName" clearable @focus="
global.updateData(false)" @blur="global.updateData(true)" @input="inputChange"></el-input>
</div>
<el-button :loading="searchLoading" :icon="Search" @click="searchHandle">搜索</el-button>
</div>
<!-- <el-button :icon="shopListType == 'text' ? 'PictureRounded' : 'PriceTag'"
@click="changeShopListType"></el-button> -->
</div>
<div class="shop_list" :class="{ img: shopListType == 'img' }" v-loading="loading">
<!-- <swiper class="swiper_box" direction="vertical" @slideChange="onSlideChange"> -->
<swiper class="swiper_box" direction="vertical" @slideChange="onSlideChange">
<swiper-slide class="slide_item" v-for="(goods, index) in goodsList" :key="index">
<div class="item_wrap" v-for="item in goods" :key="item.id" @click="showSkuHandle(item)">
<div class="item">
<transition name="el-fade-in">
<div class="more" v-if="item.showMore" @click.stop>
<div class="ul">
<template v-if="categorys[categorysActive].id == '-1'">
<div class="li" @click.stop="showPutawayHandle(item)">上架</div>
</template>
<template v-else>
<div class="li" @click.stop="goodEditor(item, 0)">下架</div>
<div class="li" @click.stop="goodEditor(item, 1)">售罄</div>
<div class="li" @click.stop="goodStockNumberHandle(item)">修改库存</div>
</template>
<div class="li" @click.stop="item.showMore = false">取消</div>
</div>
</div>
</transition>
<div class="dot" v-if="item.orderCount">{{ item.orderCount }}</div>
<div class="cover" v-if="shopListType == 'img'">
<el-image :src="`${item.coverImg}?x-oss-process=image/resize,m_lfit,w_150,h_150`"
class="el_img" fit="cover"></el-image>
<div class="sell_out" v-if="item.isPauseSale == 1">
<img class="sell_out_icon" src="../../../assets/icon_xq.png">
</div>
</div>
<div class="name"><el-text line-clamp="1">{{ item.name }}</el-text></div>
<div class="item_empty" v-if="shopListType == 'text'"></div>
<div class="price">
<el-text>¥{{ item.lowPrice }}</el-text>
<div class="show_more_btn" v-if="showEditor">
<el-icon>
<MoreFilled />
</el-icon>
</div>
</div>
</div>
</div>
</swiper-slide>
</swiper>
</div>
<div class="empty">
<el-empty description="空空如也~" v-if="!goodsList.length" />
</div>
<!-- 选择规格 -->
<skuModal ref="skuModalRef" @success="skuConfirm" />
<!-- 编辑商品 -->
<el-dialog v-model="showGoodEditor" :title="`${goodEditorEmun[goodEditorType]}商品`">
<div class="dialog">
<div class="el-popover__title content">
确定要{{ `${goodEditorEmun[goodEditorType]}商品:${goodEditorItem.name}` }}
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showGoodEditor = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="goodEditorLoading"
@click="goodEditorConfirm">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 修改库存 -->
<el-dialog v-model="showGoodsEditorStock" title="修改库存" width="400px">
<div class="dialog">
<el-form>
<el-form-item label="库存">
<div>
<el-input-number v-model="goodsEditorStockNumber" :min="0"></el-input-number>
<div class="tips">修改前库存{{ goodsEditorStockItem.stockNumber }}</div>
</div>
</el-form-item>
</el-form>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showGoodsEditorStock = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="goodsEditorStockLoading"
@click="goodsEditorStockConfirm">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 关闭售罄 -->
<el-dialog v-model="showCloseSell" title="关闭售罄">
<div class="dialog">
<div class="el-popover__title content">
确定要将{{ `【${goodEditorItem.name}】` }}关闭售罄吗?
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showCloseSell = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="closeSellLoading"
@click="closeSellHandle">确认</el-button>
</div>
</div>
</div>
</el-dialog>
<!-- 上架商品 -->
<el-dialog v-model="showPutaway" title="上架商品">
<div class="dialog">
<div class="el-popover__title content">
确定要上架商品:{{ `${goodEditorItem.name}` }}吗?
</div>
<div class="footer_wrap">
<div class="btn">
<el-button style="width: 100%;" @click="showPutaway = false">取消</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="showPutawayLoading"
@click="putawayHandle">确认</el-button>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import _ from 'lodash'
import useStorage from "@/utils/useStorage";
import skuModal from '@/components/skuModal.vue'
import { queryCategory, queryNewCommodityInfo, queryProductSku, productStatus, productStock } from '@/api/product'
import { useUser } from "@/store/user.js"
import { Swiper, SwiperSlide } from 'swiper/vue'
import "swiper/swiper-bundle.css";
import { useGlobal } from '@/store/global.js'
const global = useGlobal()
const store = useUser()
const props = defineProps({
masterId: {
type: String,
default: ''
}
})
const emit = defineEmits(['success', 'loading'])
const skuModalRef = ref(null)
const shopListType = ref('img')
const categorys = ref([])
const categorysActive = ref(0)
const commdityName = ref('')
const loading = ref(false)
const goodsList = ref([])
const goodsPage = ref(1)
const goodsPageSize = ref(12)
const finish = ref(false) // 是否加载完
const currentGoodsIndex = ref(0)
const loopMax = ref(0)
const loopTimer = ref(null)
const showPopover = ref(false)
const inputChange = _.debounce(function () {
searchHandle()
}, 500)
// 搜索
const searchLoading = ref(false)
function searchHandle() {
searchLoading.value = true
goodsList.value = []
goodsPage.value = 1
finish.value = false
currentGoodsIndex.value = 0
loopMax.value = 0
loopTimer.value = null
updataGoods()
}
// 确认选择规格回调
function skuConfirm(params) {
emit('loading')
emit('success', params)
}
// 显示全部分类
function showMoreMenu() {
showPopover.value = !showPopover.value
}
// 显示/隐藏编辑
function showEditorChange() {
if (showEditor.value) {
showEditor.value = false
goodsList.value.map(item => {
item.map(val => {
val.showMore = false
})
})
} else {
showEditor.value = true
}
}
// 显示sku
function showSkuHandle(item) {
if (showEditor.value) {
if (item.isPauseSale == 1) {
goodEditorItem.value = item
showCloseSell.value = true
} else {
goodsList.value.map(item => {
item.map(val => {
val.showMore = false
})
})
item.showMore = true
}
} else {
if (item.isPauseSale == 1) {
ElMessage({
type: 'error',
message: '该商品已售罄',
showClose: true,
})
return
}
if (categorys.value[categorysActive.value].id == '-1') {
ElMessage({
type: 'error',
message: '该商品已下架,请上架后操作',
showClose: true,
})
return
}
if (item.typeEnum == 'sku') {
// 多规格
skuModalRef.value.show({ ...item })
} else {
// 单规格
loading.value = true
emit('loading')
queryProductSkuAjax(item)
}
}
}
// 通过选中的商品规格查询价格
const queryProductSkuAjax = _.throttle(async function (goods) {
try {
const res = await queryProductSku({
shopId: store.userInfo.shopId,
productId: goods.id,
spec_tag: ''
})
loading.value = false
emit('success', res)
} catch (error) {
console.log(error)
}
}, 1500)
// 切换商品显示模式
function changeShopListType() {
if (shopListType.value == 'text') {
shopListType.value = 'img'
} else {
shopListType.value = 'text'
}
useStorage.set('shopListType', shopListType.value)
}
// 从本地更新商品显示模式
function localUpdateShopListType() {
return new Promise((resolve, reject) => {
let type = useStorage.get('shopListType')
if (type != null) {
shopListType.value = type
}
resolve()
})
}
// 切换分类
function changeCategory(index) {
showPopover.value = false
categorysActive.value = index
useStorage.set('categorysActive', index)
goodsList.value = []
goodsPage.value = 1
finish.value = false
currentGoodsIndex.value = 0
loopMax.value = 0
clearInterval(loopTimer.value)
loopTimer.value = null
updataGoods()
}
// 从本地更新已选择的选项
function updateCategoryActive() {
return new Promise((resolve, reject) => {
let index = useStorage.get('categorysActive')
if (index != null) {
categorysActive.value = index
}
resolve()
})
}
// 查询分类信息
async function queryCategoryAjax() {
try {
const res = await queryCategory({
shopId: store.userInfo.shopId,
page: 1,
pageSize: 100
})
categorys.value = res.list
categorys.value.unshift({
name: '全部',
id: ''
})
categorys.value.push({
name: '已下架',
id: '-1'
})
} catch (error) {
console.log(error)
}
}
// 查询商品信息
async function productqueryCommodityInfoAjax() {
try {
// loading.value = true
const res = await queryNewCommodityInfo({
shopId: store.userInfo.shopId,
categoryId: categorys.value[categorysActive.value].id,
commdityName: commdityName.value,
page: goodsPage.value,
pageSize: goodsPageSize.value,
masterId: props.masterId,
tableId: global.tableInfo.qrcode || '',
})
if (res.list.length < goodsPageSize.value) {
finish.value = true
}
// loading.value = false
// if (res.pages > 2 && loopTimer.value == null) {
// // 启动循环任务
// // loopMax.value = parseInt(res.total / goodsPageSize.value)
// loopGetGoods()
// }
// if (goodsPage.value >= res.pages) {
// clearInterval(loopTimer.value)
// loopTimer.value = null
// }
res.list.map((val, index) => {
val.showMore = false
})
return res
} catch (error) {
loading.value = false
console.log(error)
}
}
// 循环获取商品
function loopGetGoods() {
loopTimer.value = setInterval(async () => {
goodsPage.value++
const res = await productqueryCommodityInfoAjax()
goodsList.value.push(res.list)
}, 1000)
}
// 更新商品数据
async function updateData() {
updataGoods()
}
// 更新商品数据
async function updataGoods() {
if (!goodsList.value.length) {
const res = await productqueryCommodityInfoAjax()
goodsList.value.push(res.list)
if (!res.isLastPage) {
goodsPage.value++
const res2 = await productqueryCommodityInfoAjax()
goodsList.value.push(res2.list)
}
searchLoading.value = false
} else {
goodsPage.value = currentGoodsIndex.value + 1
// console.log('更新第二页数据', goodsPage.value);
const res = await productqueryCommodityInfoAjax()
goodsList.value[currentGoodsIndex.value] = res.list
searchLoading.value = false
}
}
// 轮播图开始滑动
const onSlideChange = _.debounce(async function (e) {
if (e.activeIndex == e.previousIndex) return
if (e.activeIndex > e.previousIndex) {
// console.log('向下滑动');
{
goodsPage.value++
const res = await productqueryCommodityInfoAjax()
res.list.length && goodsList.value.push(res.list)
}
{
goodsPage.value++
const res = await productqueryCommodityInfoAjax()
res.list.length && goodsList.value.push(res.list)
}
// goodsList.value.shift()
} else {
// console.log('向上滑动');
// goodsPage.value--
// const res = await productqueryCommodityInfoAjax()
// goodsList.value.unshift(res)
// goodsList.value.pop()
}
currentGoodsIndex.value = e.activeIndex
}, 500)
// 订单已结算,清楚商品所有数字
function clearDot() {
goodsList.value.map(item => {
item.map(val => {
val.orderCount = 0
})
})
}
const showEditor = ref(false)
const goodEditorType = ref(0) // 0 上下架 1售罄
const goodEditorItem = ref({})
const goodEditorLoading = ref(false)
const showGoodEditor = ref(false)
const goodEditorEmun = ref({
0: '下架',
1: '售罄'
})
// 编辑商品
function goodEditor(item, t) {
goodEditorItem.value = item
if (item.isPauseSale == 1) {
} else {
goodEditorType.value = t
showGoodEditor.value = true
}
}
// 关闭售罄
const showCloseSell = ref(false)
const closeSellLoading = ref(false)
async function closeSellHandle() {
try {
closeSellLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: 1,
state: 0
})
closeSellLoading.value = false
showCloseSell.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 上架商品
const showPutaway = ref(false)
const showPutawayLoading = ref(false)
function showPutawayHandle(item) {
goodEditorItem.value = item
showPutaway.value = true
}
async function putawayHandle(item) {
try {
showPutawayLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: 0,
state: 1
})
showPutawayLoading.value = false
showPutaway.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 确认操作
async function goodEditorConfirm() {
try {
goodEditorLoading.value = true
const res = await productStatus({
shopId: store.userInfo.shopId,
productId: goodEditorItem.value.id,
type: goodEditorType.value,
state: goodEditorType.value == 0 ? 0 : 1
})
goodEditorLoading.value = false
showGoodEditor.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
// 显示修改库存
const goodsEditorStockItem = ref(0)
const showGoodsEditorStock = ref(false)
const goodsEditorStockNumber = ref(0)
const goodsEditorStockLoading = ref(false)
function goodStockNumberHandle(item) {
// if (item.isDistribute == 0 && item.typeEnum == 'sku') {
// ElMessage({
// type: 'warning',
// message: '未开启共享库存无法修改',
// showClose: true,
// })
// } else {
// goodsEditorStockItem.value = item
// goodsEditorStockNumber.value = item.stockNumber
// showGoodsEditorStock.value = true
// }
goodsEditorStockItem.value = item
goodsEditorStockNumber.value = item.stockNumber
showGoodsEditorStock.value = true
}
// 确认修改库存
async function goodsEditorStockConfirm() {
try {
goodsEditorStockLoading.value = true
const res = await productStock({
shopId: store.userInfo.shopId,
productId: goodsEditorStockItem.value.id,
stock: goodsEditorStockNumber.value
})
goodsEditorStockLoading.value = false
showGoodsEditorStock.value = false
ElMessage({
type: 'success',
message: '操作成功',
showClose: true,
})
updateData()
} catch (error) {
console.log(error);
}
}
defineExpose({
updateData,
clearDot
})
onMounted(async () => {
localUpdateShopListType()
await updateCategoryActive()
await queryCategoryAjax()
})
</script>
<style scoped lang="scss">
.swiper_box {
height: 100%;
.slide_item {
width: 100%;
height: 100%;
}
}
.loading_wrap {
display: flex;
justify-content: center;
padding: 20px 0;
color: #999;
.loading {
display: flex;
align-items: center;
.icon {
font-size: 20px;
}
}
}
.popover_wrap {
display: flex;
flex-wrap: wrap;
gap: var(--el-font-size-base);
padding: var(--el-font-size-base) 0;
:deep(.el-button) {
margin-left: 0;
}
}
.header {
display: flex;
height: var(--el-component-size-large);
justify-content: space-between;
border-bottom: 1px solid #ececec;
}
.menus {
flex: 1;
display: flex;
padding: 0 10px;
overflow-x: auto;
width: 100px;
.item {
padding: 0 10px;
display: flex;
align-items: center;
position: relative;
white-space: nowrap;
span {
font-size: var(--el-font-size-base);
}
&.active {
&::after {
content: "";
width: 70%;
height: 2px;
border-radius: 4px;
position: absolute;
bottom: 0;
left: 15%;
background-color: var(--primary-color);
}
span {
color: #333;
}
}
}
}
.all {
width: calc(var(--el-component-size-large) + 10px);
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-shrink: 0;
&::before {
content: "";
height: 60%;
border-left: 1px solid #ececec;
position: absolute;
left: 0;
top: 20%;
}
.icon {
color: #555;
font-size: 20px;
transition: all .2s ease-in-out;
&.active {
transform: rotate(45deg)
}
}
}
.search_wrap {
display: flex;
justify-content: space-between;
padding: var(--el-font-size-base);
.right {
display: flex;
gap: 10px;
}
}
.shop_list {
// max-height: calc(100vh - 40px - 80px - 40px);
height: calc(100vh - 40px - 80px - 28px);
// overflow-y: auto;
// display: flex;
// flex-wrap: wrap;
padding: 0 10px;
&.img {
.item_wrap {
width: 25%;
}
}
.item_wrap {
float: left;
width: 20%;
height: 33.333%;
padding: 0 5px;
padding-bottom: 10px;
.item {
height: 100%;
border: 1px solid #ececec;
border-radius: 10px;
overflow: hidden;
position: relative;
.more {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, .6);
backdrop-filter: blur(2px);
border-radius: 10px;
.ul {
height: 100%;
padding: 20px;
display: flex;
justify-content: center;
flex-direction: column;
.li {
display: flex;
align-items: center;
color: #fff;
padding: 8px 0;
&:last-child {
border-top: 1px solid rgba(255 255 255 / 20%);
color: #ececec;
}
}
}
}
&:hover {
cursor: pointer;
}
.dot {
padding: 0 8px;
background-color: var(--el-color-danger);
color: #fff;
border-radius: 20px;
position: absolute;
top: 4px;
right: 4px;
z-index: 1;
font-size: 12px;
}
.cover {
width: 100%;
height: 60%;
position: relative;
.el_img {
width: 100%;
height: 100%;
background-color: #efefef;
position: absolute;
top: 0;
left: 0;
}
.sell_out {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
// backdrop-filter: blur(2px);
z-index: 10;
.sell_out_icon {
$size: 60px;
width: $size;
height: $size;
object-fit: cover;
}
}
}
.name {
padding: 0 10px;
height: 20%;
display: flex;
align-items: center;
// margin-top: 20px;
span {
font-weight: bold;
}
}
.item_empty {
height: 30px;
}
.price {
height: 20%;
padding: 6px 10px;
background-color: var(--primary-color);
position: relative;
span {
color: #fff;
font-weight: bold;
}
.show_more_btn {
width: 40px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
right: 0;
z-index: 1;
color: #fff;
}
}
}
}
}
.dialog {
.content {
padding-bottom: 20px;
}
.footer_wrap {
display: flex;
gap: 20px;
.btn {
flex: 1;
}
}
}
</style>