532 lines
16 KiB
Vue
532 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"
|
||
@input="searchGoods"
|
||
style="width: 300px"
|
||
>
|
||
<template #prefix>
|
||
<el-icon>
|
||
<Search/>
|
||
</el-icon>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-cascader :collapse-tags="true" :props="props" style="width: 100px" :options="allCateList"
|
||
:show-all-levels="false"
|
||
v-model="searchModel.cateId" clearable/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-select
|
||
placeholder="利润分类"
|
||
style="width: 100px"
|
||
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>
|
||
<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 class="default-btn" @click="open_edit(1301,0)">新增中西成药</span>
|
||
<span class="default-btn" @click="open_edit(1302,0)">新增中药饮片</span>
|
||
<span class="default-btn" @click="open_edit(1306,0)">新增医疗器材</span>
|
||
<span class="default-btn" @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="100">
|
||
<template #default="scope">
|
||
<el-tag type="success" v-if="scope.row.saleStatus">售卖中</el-tag>
|
||
<el-tag type="danger" v-else>已停售</el-tag>
|
||
</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-count="pageNum"
|
||
: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 : '',
|
||
saleStatus: status.value ? status.value : null,
|
||
}
|
||
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: pageNum.value,// 当前页码
|
||
pageSize: 1,// 每页显示条数
|
||
}
|
||
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 total = ref(0)
|
||
let pageNum = ref(1)
|
||
let changePage = (value: number) => {
|
||
pageNum.value = value
|
||
init()
|
||
}
|
||
|
||
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
|
||
})
|
||
}
|
||
const props = {multiple: true}
|
||
</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;
|
||
margin-right: 24px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.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-input__inner) {
|
||
height: 42px;
|
||
}
|
||
|
||
: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> |