web/src/views/inventory/goods.vue

493 lines
15 KiB
Vue

<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: 200px"
@input="searchGoods"
>
<template #prefix>
<el-icon class="el-input__icon">
<search/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-cascader :options="allCateList" :show-all-levels="false" v-model="searchModel.cateId" clearable/>
</el-form-item>
<el-form-item>
<el-select
placeholder="利润分类"
style="width: 150px"
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">
<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">
<template #suffix>
<el-icon class="el-input__icon">%</el-icon>
</template>
</el-input>
</el-form-item>
</el-form>
<div class="btn">
<el-button type="primary" @click="searchGoods">搜索</el-button>
<el-button type="primary" @click="resetSearch">重置</el-button>
</div>
</div>
<div class="right">
<el-checkbox v-model="inventoryNumber" label="不看0库存" size="large" @change="searchGoods"/>
<el-checkbox v-model="status" label="不看已停用" size="large" @change="searchGoods"/>
</div>
</div>
<div class="addBtn">
<el-button type="primary" :icon="Plus" @click="open_edit(1301,0)">新增中西成药</el-button>
<el-button type="primary" :icon="Plus" @click="open_edit(1302,0)">新增中药饮片</el-button>
<el-button type="primary" :icon="Plus" @click="open_edit(1306,0)">新增医疗器材</el-button>
<el-button type="primary" :icon="Plus" @click="open_edit(0,0)">新增其他商品</el-button>
</div>
</div>
<div class="content_goods" ref="content">
<el-table :data="tableData" style="width: 100%;height: 100%;padding: 0 24px" @row-click="openMack">
<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="100"></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"></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" show-overflow-tooltip>
<template #default="scope">
¥{{ scope.row.costPrice.toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="医保码" prop="hilistCode" 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="150"/>
<el-table-column label="医保类别">
<template #default="scope">
{{ getTypeName(scope.row.type) }}
</template>
</el-table-column>
</el-table>
</div>
<div class="bottom">
<div class="statistics">
<div class="">总成本:{{statisticsData.totalCost}}</div>
<div class="">总售价:{{statisticsData.totalPrice}}</div>
<div class="">医保药品:{{statisticsData.totalSocialCount}}个</div>
<div v-for="item in statisticsData.chrgitmLvInfoList">
{{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 :is-show="open" :top="50" :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 {Plus, SemiSelect} 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: 112px;
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;
}
}
}
}
.content_goods {
width: 100%;
flex: 1;
overflow: hidden;
}
.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: flex-end;
align-items: center;
.statistics{
display: flex;
}
}
}
.tags {
margin-left: 5px;
}
</style>