web/src/views/inventory/goods.vue

543 lines
16 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="container-wrapper">
<div class="top">
<div class="search">
<div class="left">
<el-form :inline="true" :model="searchModel">
<el-form-item>
<el-input placeholder="名称/首字母/批准文号/条形码/标识码"
clearable
v-model="searchModel.keyword"
style="width: 290px;height: 42px"
@input="searchGoods"
>
<template #prefix>
<el-icon>
<Search/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-cascader style="width: 180px;height: 42px" :options="allCateList" :show-all-levels="false"
v-model="searchModel.cateId" clearable/>
</el-form-item>
<el-form-item>
<el-select
placeholder="利润分类"
style="width: 180px;"
multiple
collapse-tags
collapse-tags-tooltip
v-model="searchModel.curProfitCate"
>
<el-option
v-for="item in profitCategory"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item style="margin-right: 0">
<el-input placeholder="最低毛利率" v-model="searchModel.minInterestRate"
style="width: 100px;height: 42px">
<template #suffix>
<el-icon class="el-input__icon">%</el-icon>
</template>
</el-input>
<el-icon style="color: #ddd">
<SemiSelect/>
</el-icon>
</el-form-item>
<el-form-item>
<el-input placeholder="最高毛利率" v-model="searchModel.maxInterestRate"
style="width: 100px;height: 42px">
<template #suffix>
<el-icon class="el-input__icon">%</el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
</div>
<div class="btn-group">
<div class="left">
<el-checkbox v-model="inventoryNumber" label="不看0库存" size="large" @change="searchGoods"/>
<el-checkbox v-model="status" label="不看已停用" size="large" @change="searchGoods"/>
</div>
<div class="default-btn" @click="resetSearch">
<span class="iconfont icon-RectangleCopy1"></span>
重置
</div>
<div class="default-btn" @click="searchGoods" style="margin-left: 24px">
<span class="iconfont icon-RectangleCopy"></span>
搜索
</div>
</div>
</div>
<div class="addBtn">
<span @click="open_edit(1301,0)">新增中西成药</span>
<span @click="open_edit(1302,0)">新增中药饮片</span>
<span @click="open_edit(1306,0)">新增医疗器材</span>
<span @click="open_edit(0,0)">新增其他商品</span>
</div>
</div>
<div class="content_goods">
<el-table :data="tableData" style="height: 100%;padding: 0 24px;" @row-click="openMack"
:header-cell-style="{ backgroundColor: '#F1F5FB' }">
<el-table-column fixed prop="name" label="名称" width="200" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.name }}{{ scope.row.commonName ? "(" + scope.row.commonName + ")" : "" }}
</template>
</el-table-column>
<el-table-column fixed label="规格" show-overflow-tooltip width="100">
<template #default="scope">
{{ scope.row.minPackagingNumber }}*{{ scope.row.minPackagingUnit }}/{{ scope.row.packagingUnit }}
</template>
</el-table-column>
<el-table-column fixed label="批准文号" width="200">
<template #default="scope">
{{ scope.row.approvalCode }}
</template>
</el-table-column>
<el-table-column fixed label="厂家" prop="producer" width="230" show-overflow-tooltip/>
<el-table-column fixed label="售价" width="120">
<template #default="scope">
¥{{ scope.row.unitPrice.toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="类型" width="200">
<template #default="scope">
{{ getTypeName(scope.row.type) }}<span v-if="scope.row.cateName">/{{ scope.row.cateName }}</span>
</template>
</el-table-column>
<el-table-column label="库存数量" width="150">
<template #default="scope">
{{ scope.row.inventoryWholeNumber }}{{ scope.row.packagingUnit }}
<template v-if="scope.row.inventoryFragmentNumber > 0">
{{ scope.row.inventoryFragmentNumber }}{{ scope.row.minPackagingUnit }}
</template>
</template>
</el-table-column>
<el-table-column label="可售库存" width="150">
<template #default="scope">
{{ scope.row.inventoryWholeNumber }}{{ scope.row.packagingUnit }}
<template v-if="scope.row.inventoryFragmentNumber > 0">
{{ scope.row.inventoryFragmentNumber }}{{ scope.row.minPackagingUnit }}
</template>
</template>
</el-table-column>
<el-table-column label="最近效期" prop="recentlyExpiryDate" width="150"></el-table-column>
<el-table-column label="进价" width="120">
<template #default="scope">
¥{{ scope.row.purchaseUnitPrice.toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="甲乙丙类" width="80">
<template #default="scope">
{{ scope.row.chrgitmLv }}
</template>
</el-table-column>
<el-table-column label="医保限价" prop="hilistPricUplmtAmt" show-overflow-tooltip></el-table-column>
<el-table-column label="限制说明" prop="hilistLmtpricType" show-overflow-tooltip></el-table-column>
<el-table-column label="自付比例" prop="selfpayProp"></el-table-column>
<el-table-column label="毛利率" width="120">
<template #default="scope">
{{ (scope.row.interestRate * 100).toFixed(2) }}%
</template>
</el-table-column>
<el-table-column label="利润分类" width="100">
<template #default="scope">
{{ getProfitCategory(scope.row.interestRate) }}
</template>
</el-table-column>
<el-table-column label="药品成本" prop="costPrice" width="150">
<template #default="scope">
¥{{ scope.row.costPrice.toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="医保码" prop="hilistCode" width="240" show-overflow-tooltip></el-table-column>
<el-table-column label="标签" prop="producer" width="200">
<template #default="scope">
<el-tag class="tags" type="success" v-for="item in getTagsArray(scope.row.tags)">{{ item }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" width="100" show-overflow-tooltip/>
</el-table>
</div>
<div class="bottom">
<div class="statistics">
<div style="margin-right: 40px">总成本:¥{{ statisticsData.totalCost }}</div>
<div style="margin-right: 40px">总售价:¥{{ statisticsData.totalPrice }}</div>
<div style="margin-right: 40px">医保药品:{{ statisticsData.totalSocialCount }}个</div>
<div v-for="item in statisticsData.chrgitmLvInfoList" style="margin-right: 40px">
{{ item.name }}{{ item.ratio }}({{ item.count }})
</div>
</div>
<div class="page_btn_list">
<el-pagination
background
layout="prev, pager, next"
:page-size="pageSize"
:current-page="page"
:total="total"
@current-change="changePage"
/>
</div>
</div>
</div>
<Edit ref="editRef" @close="is_add = false;init()"/>
<Mask :isShow="open" :height="600" @close="open = false" title="编辑">
<el-tabs v-model="activeName" @tab-change="changeTab">
<el-tab-pane label="商品信息" name="first">
<Edit ref="editRef" @close="open = false;init()"/>
</el-tab-pane>
<el-tab-pane label="批次信息" name="second">
<InventoryBatchDetail ref="inventoryBatchDetailRef"></InventoryBatchDetail>
</el-tab-pane>
<el-tab-pane label="进销存统计" name="third">
<InventoryStatistics :id="id" ref="inventoryStatisticsRef"></InventoryStatistics>
</el-tab-pane>
</el-tabs>
</Mask>
</template>
<script lang="ts" setup>
import {nextTick, onMounted, ref} from "vue";
import {ElTabPane} from "element-plus";
import {post} from '@/utils/request.ts';
import Mask from "@/components/common/Mask.vue";
import Edit from "@/components/inventory/goods/Edit.vue";
import InventoryBatchDetail from "@/components/inventory/goods/InventoryBatchDetail.vue";
import InventoryStatistics from "@/components/inventory/goods/InventoryStatistics.vue";
import CloseBtn from "@/components/CloseBtn.vue";
import {Search, SemiSelect, Refresh} from "@element-plus/icons-vue";
const statisticsData = ref<any>({})
const inventoryNumber = ref(false)
const status = ref(false)
const profitCategory = [
{
name: "A类",
range: {min: 0.9, max: 1}
},
{
name: "B类",
range: {min: 0.6, max: 0.9},
},
{
name: "C类",
range: {min: 0.3, max: 0.6},
},
{
name: "D类",
range: {min: 0.1, max: 0.3},
},
{
name: "E类",
range: {min: 0, max: 0.1},
}
]
const typeList = {
"1301": "中西成药",
"1302": "中药饮片",
"1306": "医疗器材",
"0": "其他商品",
}
const getTypeName = (type: number) => {
const typeStr = type + "";
return typeList[typeStr as keyof typeof typeList] || "未知类型";
}
const searchModel = ref({
keyword: "",
cateId: [],
minInterestRate: "",
maxInterestRate: "",
curProfitCate: []
})
const searchGoods = () => {
let cateArray = searchModel.value.cateId || null
const data = {
pageSize: 20,
pageNum: 1,
cateId: cateArray == null ? null : cateArray[1],
keyword: searchModel.value.keyword,
minInterestRate: parseFloat(searchModel.value.minInterestRate) || null, // 转换为数字并处理空值
maxInterestRate: parseFloat(searchModel.value.maxInterestRate) || null, // 转换为数字并处理空值
interestRateIntervalList: [] as { min: number; max: number }[], // 显式声明类型
inventoryNumber: inventoryNumber.value ? 0 : '',
status: status.value ? true : '',
}
searchModel.value.curProfitCate.forEach((item) => {
for (const cate of profitCategory) {
if (item === cate.name) {
data.interestRateIntervalList.push(cate.range)
}
}
})
post("goods/goods/searchDetail", {query: data}).then((res: any) => {
tableData.value = res.list
total.value = res.total_count
})
}
const getTagsArray = (tags: string) => {
if (!tags) {
return []
}
return tags.split(",")
}
const getProfitCategory = (profit: number) => {
for (const item of profitCategory) {
if (profit > item.range.min && profit <= item.range.max) {
return item.name;
}
}
return '';
}
const editRef = ref<InstanceType<typeof Edit>>();
let is_add = ref(false)
onMounted(() => {
init()
})
let init = () => {
const query = {
pageNum: page.value,// 当前页码
pageSize: pageSize.value,// 每页显示条数
}
post("goods/goods/searchDetail", {query: query}).then((res: any) => {
tableData.value = res.list
total.value = res.total_count
})
getAllCate()
getStatisticsData()
}
let open_edit = (type: number, id: number) => {
nextTick(() => {
editRef.value?.init(type, id);
});
}
//分页
let content: any = ref(null);
let pageSize = ref(20)
let total = ref(0)
let page = ref(1)
let changePage = (value: number) => {
page.value = value
const query = {
pageNum: value,
pageSize: pageSize.value,
}
post("goods/goods/searchDetail", {query: query}).then((res: any) => {
tableData.value = res.list
total.value = res.total_count
})
}
interface CateOption {
value: string;
label: string;
children: { value: number; label: string }[];
}
const allCateList = ref<CateOption[]>([]);
const getAllCate = () => {
post("goods/cate/getAllList", null).then((res: any) => {
const options = [];
for (const key in res) {
if (typeList.hasOwnProperty(key)) { // 检查键是否存在
options.push({
value: key,
label: typeList[key as keyof typeof typeList], // 类型断言
children: res[key].map((item: { id: number; name: string }) => ({
value: item.id,
label: item.name
})),
});
}
}
allCateList.value = options;
})
}
const tableData = ref([])
const inventoryBatchDetailRef = ref<any>(null)
const inventoryStatisticsRef = ref<any>(null)
//打开弹窗
const obj = ref<any>({})
const open = ref(false)
const openMack = (row: any) => {
open.value = true
obj.value = row
if (activeName.value === 'first') {
nextTick(() => {
editRef.value?.init(row.type, row.id);
});
} else if (activeName.value === 'second') {
nextTick(() => {
inventoryBatchDetailRef.value?.init(row.id)
})
} else {
id.value = obj.value.id
}
}
const closeMack = () => {
open.value = false
id.value = ""
}
const id = ref<any>("")
const changeTab = (name: string) => {
activeName.value = name
if (activeName.value === 'first') {
nextTick(() => {
editRef.value?.init(obj.value.type, obj.value.id);
});
} else if (activeName.value === 'second') {
nextTick(() => {
inventoryBatchDetailRef.value?.init(obj.value.id)
})
} else {
id.value = obj.value.id
}
}
const activeName = ref('first')
const resetSearch = () => {
searchModel.value = {
keyword: "",
cateId: [],
minInterestRate: "",
maxInterestRate: "",
curProfitCate: []
}
init()
}
/**
* 获取商品统计数据 总成本 总售价 甲乙丙类
*/
const getStatisticsData = () => {
post("statistics/goodsStatistics").then((res: any) => {
statisticsData.value = res
})
}
</script>
<style scoped lang="scss">
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.container-wrapper {
height: 100%;
display: flex;
flex-direction: column;
.top {
height: 144px;
padding: 24px 24px 0;
background: #fff;
display: flex;
flex-direction: column;
.search {
display: flex;
justify-content: space-between;
.left {
flex: 1;
display: flex;
justify-content: space-between;
margin-right: 24px;
.el-form-item {
margin-right: 5px;
}
}
}
.addBtn {
span {
display: inline-block;
width: 120px;
height: 42px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid #4D6DE4;
margin-right: 24px;
font-weight: 500;
font-size: 16px;
color: #4D6DE4;
text-align: center;
line-height: 42px;
cursor: pointer;
&:hover {
background: #4D6DE4;
color: #fff;
border: none;
}
}
}
}
.content_goods {
width: 100%;
flex: 1;
min-height: 0;
}
.bottom {
width: 100%;
height: 60px;
background-color: #FFF;
box-sizing: border-box;
padding: 10px;
position: relative;
border-top: 1px solid #EEE;
display: flex;
justify-content: space-between;
align-items: center;
.statistics {
display: flex;
}
}
}
.tags {
margin-left: 5px;
}
:deep(.el-cascader .el-input) {
height: 42px;
}
:deep(.el-select__wrapper) {
height: 42px;
}
.btn-group {
display: flex;
.btn {
width: 120px;
height: 42px;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid #979797;
display: flex;
justify-content: center;
align-items: center;
margin-left: 24px;
cursor: pointer;
&:hover {
background: #4D6DE4;
color: #fff;
border: none;
}
}
}
</style>