506 lines
16 KiB
Vue
506 lines
16 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">
|
|
<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" ref="content">
|
|
<el-table :data="tableData" style="width: 100%;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"></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-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 :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 {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: 157px;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
.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> |