635 lines
19 KiB
Vue
635 lines
19 KiB
Vue
<template>
|
|
<div class="container-wrapper">
|
|
<div class="left">
|
|
<Panel title="患者列表">
|
|
<div class="panel-content" style="height: 100%;display: flex;flex-direction: column;padding: 0 24px 10px">
|
|
<div class="search">
|
|
<div class="input">
|
|
<el-input v-model="input3" style="width:100%;height: 100%" placeholder="搜索患者"
|
|
@keydown.enter="searchVip">
|
|
<template #prefix>
|
|
<el-icon class="el-input__icon">
|
|
<Search/>
|
|
</el-icon>
|
|
</template>
|
|
</el-input>
|
|
</div>
|
|
<span class="default-btn" @click="openEdit(null)" style="margin-left: 24px">患者建档</span>
|
|
</div>
|
|
<div class="total">共{{ totalCount || 0 }}条</div>
|
|
<ul class="content">
|
|
<el-scrollbar style="height: 100%">
|
|
<li class="list-item" v-for="(item,i) in list" :key="item.id" :class="index==i?'active':''"
|
|
@click="btn(item,i)">
|
|
<span class="item-avatar">
|
|
<img class="image"
|
|
:src="'/static/images/member/' + (item.sex === 1 ? 'man' : 'women') + '.png'"
|
|
alt=""/>
|
|
</span>
|
|
<span class="item-name">{{ item.name || '-' }}</span>
|
|
<span class="item-level">{{ item.levelName || "-" }}</span>
|
|
<span class="item-time">{{ formatListTime(item.lastVisitTime) }}</span>
|
|
</li>
|
|
</el-scrollbar>
|
|
</ul>
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
<div class="right">
|
|
<Panel title="患者信息" class="right-top">
|
|
<div class="detail">
|
|
<div class="detail-top">
|
|
<div class="detail-top-left">
|
|
<img class="image"
|
|
:src="listItem.sex==1?'/static/images/member/man.png':listItem.sex==2?'/static/images/member/women.png':'-'"
|
|
alt="">
|
|
<div class="detail-top-left-text">
|
|
<div class="detail-top-left-text-name">
|
|
<span class="name" style="margin-right: 16px">{{ listItem.name || '-' }}</span>
|
|
<el-tag type="success">{{ listItem.levelName || '-' }}</el-tag>
|
|
</div>
|
|
<div class="detail-top-left-text-phone">
|
|
<span class="age">{{ listItem.age ? listItem.age + '岁' : '-' }}</span>
|
|
<span>{{ listItem.phone || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="detail-top-right">
|
|
<span class="default-btn" @click="openEdit(listItem.id)">编辑资料</span>
|
|
<span class="default-btn" @click="openGrant()" style="margin: 0 24px">发放积分</span>
|
|
<span class="default-btn" @click="isExchange=true">兑换积分</span>
|
|
<span class="default-btn" @click="openFlowingWater" style="margin-left: 24px">查看流水</span>
|
|
<!-- <span class="btn" @click="openLevelEdit(listItem)">患者等级编辑</span>-->
|
|
</div>
|
|
</div>
|
|
<div class="detail-bottom">
|
|
<el-descriptions
|
|
title=""
|
|
:column="3"
|
|
border
|
|
>
|
|
<el-descriptions-item label="生日">{{ listItem.birthday || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="民族">
|
|
{{ antysList.find((item: any) => item.id == listItem.nation)?.name || "-" }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="就诊时间">{{
|
|
formatListTime(listItem.lastVisitTime) || "-"
|
|
}}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="证件类型">
|
|
{{ certTypeList.find((item: any) => item.id == Number(listItem.certType))?.name }}
|
|
</el-descriptions-item>
|
|
>
|
|
<el-descriptions-item label="证件号码">{{ listItem.certNo || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="积分"><span>{{ listItem.integralBalance }}</span>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="地址">
|
|
{{ areaName }}{{ areaName ? '/' + listItem.address : listItem.address || "-" }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="既往史">{{ listItem.beforeMedicalHistory || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="过敏史">{{ listItem.allergyHistory || "-" }}</el-descriptions-item>
|
|
<el-descriptions-item label="备注">{{ listItem.remark || "-" }}</el-descriptions-item>
|
|
|
|
</el-descriptions>
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
<div class="right-list">
|
|
<Panel title="消费记录">
|
|
<div class="panel-content" style="display: flex;flex-direction: column;height: 100%;padding: 0 24px;">
|
|
<div class="list">
|
|
<el-table :data="tableData" :header-cell-style="{ backgroundColor: '#F1F5FB' }"
|
|
style="width: 100%;height: 100%"
|
|
@cell-click="openDetail">
|
|
<el-table-column label="单号" prop="code" show-overflow-tooltip>
|
|
</el-table-column>
|
|
<el-table-column label="创建时间" prop="createDatetime" show-overflow-tooltip>
|
|
<template #default="scope">
|
|
{{ formatDate(scope.row.createDatetime) }}
|
|
</template>
|
|
</el-table-column>
|
|
<!-- <el-table-column label="应收" prop="preTotalPrice" width="80">-->
|
|
<!-- <template #default="scope">-->
|
|
<!-- {{ scope.row.preTotalPrice }}元-->
|
|
<!-- </template>-->
|
|
<!-- </el-table-column>-->
|
|
<el-table-column label="实收" prop="totalPrice" width="100" show-overflow-tooltip>
|
|
<template #default="scope">
|
|
{{ scope.row.totalPrice }}元
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="支付方式" width="100" show-overflow-tooltip>
|
|
<template #default="scope">
|
|
{{ scope.row.payType ? getPayTypeStr(scope.row.payType) : "" }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="状态" width="100" show-overflow-tooltip>
|
|
<template #default="scope">
|
|
<el-tag v-if="scope.row.status==0 || scope.row.status==2 " type="info">
|
|
{{ getStatusStr(scope.row.status) }}
|
|
</el-tag>
|
|
<el-tag v-if="scope.row.status==1" type="success">{{ getStatusStr(scope.row.status) }}</el-tag>
|
|
<el-tag v-if="scope.row.status==3" type="danger">{{ getStatusStr(scope.row.status) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="支付时间" prop="payTime" show-overflow-tooltip>
|
|
<template #default="scope">
|
|
{{ formatDate(scope.row.payTime) }}
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
<div class="bottom">
|
|
<div class="page_btn_list">
|
|
<el-pagination
|
|
background
|
|
layout="prev, pager, next"
|
|
:current-page="pageNum"
|
|
:page-size="20"
|
|
:total="total"
|
|
@current-change="handleCurrentChange"
|
|
>
|
|
</el-pagination>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Panel>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<VipEdit ref="refEdit" @close="init"></VipEdit>
|
|
<VipLevelEdit ref="levelEditRef" @close="init"></VipLevelEdit>
|
|
<Grant v-model="isGrant" :info="listItem" @close="closeGrant()"></Grant>
|
|
<Exchange v-model="isExchange" :info="listItem" @close="closeExchange()"></Exchange>
|
|
<FlowingWater v-model="isFlowingWater" ref="flowingWaterRef" :info="listItem"></FlowingWater>
|
|
<OrderDetail ref="orderDetailRef" @updateOrderList="getChargeList(listItem.id)"></OrderDetail>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import {ref, onMounted, nextTick} from 'vue'
|
|
import {Search} from "@element-plus/icons-vue";
|
|
import {post} from "@/utils/request.ts";
|
|
import Mask from "@/components/common/Mask.vue";
|
|
import Grant from "@/components/member/memberProfile/Grant.vue";
|
|
import Exchange from "@/components/member/memberProfile/Exchange.vue";
|
|
import FlowingWater from "@/components/member/memberProfile/FlowingWater.vue";
|
|
import OrderDetail from "@/components/charge/OrderDetail.vue";
|
|
import VipEdit from "@/components/member/memberProfile/VipEdit.vue";
|
|
import VipLevelEdit from "@/components/member/memberProfile/VipLevelEdit.vue";
|
|
import Panel from "@/components/common/Panel.vue";
|
|
import {formatListTime} from "@/utils/dateUtils.ts";
|
|
import antys from "@/assets/config/directory/antys.json"
|
|
import area from "@/assets/config/area.json"
|
|
import psnCertTypes from "@/assets/config/directory/psnCertTypes.json"
|
|
|
|
|
|
const isGrant = ref(false)
|
|
const isExchange = ref(false)
|
|
const isFlowingWater = ref(false)
|
|
const input3 = ref('')
|
|
const listItem = ref<any>({
|
|
id: '',
|
|
realName: "",//姓名
|
|
age: 0,//年龄
|
|
createDatetime: "",//创建时间
|
|
sex: "",//性别
|
|
phone: '',//电话
|
|
address: '',//地址
|
|
levelId: '',//患者等级
|
|
idCode: '',//身份证号
|
|
area: '',//地区
|
|
birthday: '',//生日
|
|
exp: '',//积分
|
|
nation: '',//民族
|
|
remark: '',//备注
|
|
source: ''//来源
|
|
})
|
|
const index = ref(0)
|
|
const refEdit = ref();
|
|
|
|
const openEdit = (id: any) => {
|
|
nextTick(() => {
|
|
refEdit.value?.init(id);
|
|
});
|
|
}
|
|
|
|
const list = ref<any>([]);
|
|
const totalCount = ref(0)
|
|
|
|
onMounted(() => {
|
|
init()
|
|
})
|
|
const init = () => {
|
|
isGrant.value = false;
|
|
const query = {
|
|
page: 1,
|
|
pageSize: 100
|
|
};
|
|
post("vip/vip/list", query).then((res: any) => {
|
|
list.value = res.list;
|
|
totalCount.value = res.total_count
|
|
listItem.value = res.list[0];
|
|
// 向每个对象添加新的属性
|
|
list.value.forEach((item: any) => {
|
|
if (!item.levelId) return
|
|
post("vip/vipLevel/get", {levelId: item.levelId}).then((res: any) => {
|
|
item.levelName = res.name;
|
|
});
|
|
});
|
|
getChargeList(listItem.value.id);
|
|
});
|
|
};
|
|
const getVipIntegral = (id: any) => {
|
|
post("vip/vip/get", {id: id}).then((res: any) => {
|
|
listItem.value.integralBalance = res.integralBalance;
|
|
});
|
|
};
|
|
const closeGrant = () => {
|
|
getVipIntegral(listItem.value.id)
|
|
isGrant.value = false
|
|
}
|
|
const closeExchange = () => {
|
|
getVipIntegral(listItem.value.id)
|
|
isExchange.value = false
|
|
}
|
|
const findAreaName = (code: string | null): string => {
|
|
if (!code) return "";
|
|
for (const province of area) {
|
|
if (province.city && Array.isArray(province.city)) {
|
|
for (const city of province.city) {
|
|
if (city.area && Array.isArray(city.area)) {
|
|
const area = city.area.find((d: any) => d.code === code);
|
|
if (area) {
|
|
return `${province.name}/ ${city.name} /${area.name}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const province of area) {
|
|
if (province.city && Array.isArray(province.city)) {
|
|
const city = province.city.find((c: any) => c.code === code);
|
|
if (city) {
|
|
return `${province.name} /${city.name}`;
|
|
}
|
|
}
|
|
}
|
|
const province = area.find((item: any) => item.code === code);
|
|
if (province) {
|
|
return province.name;
|
|
}
|
|
|
|
// 如果都没找到,返回原始 code
|
|
return code;
|
|
};
|
|
const areaName = ref<any>('')
|
|
// 点击列表中的某一项
|
|
const btn = (item: any, i: number) => {
|
|
index.value = i
|
|
listItem.value = item
|
|
// 修复后:
|
|
let areaData = listItem.value.area;
|
|
try {
|
|
// 如果是字符串则尝试解析为对象
|
|
if (typeof areaData === 'string') {
|
|
areaData = JSON.parse(areaData);
|
|
|
|
}
|
|
} catch (error) {
|
|
areaData = null; // 或默认值
|
|
}
|
|
if (areaData != null) {
|
|
areaName.value = findAreaName(areaData[areaData?.length - 1])
|
|
}
|
|
|
|
getChargeList(listItem.value.id)
|
|
}
|
|
//点击发放
|
|
const openGrant = () => {
|
|
isGrant.value = true
|
|
}
|
|
//消费时间
|
|
const tableData = ref([])
|
|
const orderDetailRef = ref<any>();
|
|
const getChargeList = (id: any) => {
|
|
const query = {
|
|
pageNum: pageNum.value,
|
|
pageSize: 20,
|
|
patientId: id
|
|
}
|
|
post('charge/list', {query}).then((res: any) => {
|
|
tableData.value = res.list
|
|
total.value = res.total_count
|
|
})
|
|
}
|
|
const openDetail = (row: any, column: any, event: Event) => {
|
|
orderDetailRef.value.init(row.code)
|
|
}
|
|
const statusObj = {
|
|
0: "未完成",
|
|
1: "已完成",
|
|
2: "已取消",
|
|
3: "已退款"
|
|
}
|
|
const payTypeObj = {
|
|
1: "医保",
|
|
2: "微信",
|
|
3: "支付宝",
|
|
4: "现金",
|
|
5: "其他"
|
|
}
|
|
type StatusKey = 0 | 1 | 2 | 3;
|
|
type PayTypeKey = 1 | 2 | 3 | 4 | 5;
|
|
//修改时间格式化
|
|
const formatDate = (isoStr: any) => {
|
|
if(!isoStr)return ''
|
|
const date = new Date(isoStr);
|
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
|
}
|
|
const getStatusStr = (status: number): string => {
|
|
if (Object.keys(statusObj).includes(String(status))) {
|
|
return statusObj[status as StatusKey] || "未知";
|
|
}
|
|
return "未知";
|
|
};
|
|
const getPayTypeStr = (payType: number): string => {
|
|
if (Object.keys(payTypeObj).includes(String(payType))) {
|
|
return payTypeObj[payType as PayTypeKey] || "未知";
|
|
}
|
|
return "未知";
|
|
};
|
|
|
|
|
|
const searchVip = () => {
|
|
post("vip/vip/list", {
|
|
page: 1,
|
|
pageSize: 50,
|
|
keyword: input3.value,
|
|
}).then((res: any) => {
|
|
|
|
list.value = res.list;
|
|
totalCount.value = res.total_count
|
|
listItem.value = res.list[0];
|
|
console.log(listItem)
|
|
});
|
|
};
|
|
// 分页
|
|
const total = ref(0)
|
|
const pageNum = ref(1)
|
|
const handleCurrentChange = (val: number) => {
|
|
pageNum.value = val
|
|
getChargeList(listItem.value.id)
|
|
};
|
|
const levelEditRef = ref();
|
|
const antysList = ref<any>(Object.entries(antys).map(([id, name]) => ({id, name})))
|
|
const certTypeList = ref<any>(Object.entries(psnCertTypes).map(([id, name]) => ({id, name})))
|
|
const flowingId = ref();
|
|
const flowingWaterRef = ref();
|
|
const openFlowingWater = () => {
|
|
isFlowingWater.value = true
|
|
flowingId.value = listItem.value.id
|
|
nextTick(() => {
|
|
flowingWaterRef.value?.init(flowingId.value)
|
|
})
|
|
}
|
|
|
|
</script>
|
|
<style scoped lang="scss">
|
|
@use "@/assets/scss/base.scss";
|
|
|
|
:deep(.el-scrollbar__view) {
|
|
height: 100%;
|
|
}
|
|
|
|
.container-wrapper {
|
|
display: flex;
|
|
background: none;
|
|
padding: 0 24px;
|
|
height: 100%;
|
|
|
|
.left {
|
|
height: 100%;
|
|
width: 430px;
|
|
margin-right: 24px;
|
|
|
|
.search {
|
|
height: 56px;
|
|
display: flex;
|
|
|
|
.input {
|
|
height: 42px;
|
|
flex: 1;
|
|
}
|
|
|
|
.btn {
|
|
padding: 0 18px;
|
|
color: #4D6DE4;
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
font-style: normal;
|
|
border-radius: 6px;
|
|
border: 1px solid #4D6DE4;
|
|
height: 42px;
|
|
line-height: 42px;
|
|
margin-left: 24px;
|
|
|
|
&:hover {
|
|
background: #4D6DE4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
.total {
|
|
height: 40px;
|
|
border-top: 1px solid #EBEEF5;
|
|
border-bottom: 1px solid #EBEEF5;
|
|
line-height: 40px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
border-radius: 8px;
|
|
border: 1px solid #EAEAEC;
|
|
|
|
.list-item {
|
|
display: flex;
|
|
height: 50px;
|
|
line-height: 50px;
|
|
padding: 0 24px;
|
|
color: #000000;
|
|
cursor: pointer;
|
|
span {
|
|
text-align: center;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
.item-avatar {
|
|
width: 20px;
|
|
height: 100%;
|
|
margin-right: 10px;
|
|
.image {
|
|
margin-top: 15px;
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
}
|
|
.item-name{
|
|
flex: 1;
|
|
min-width: 0;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
text-align: left;
|
|
}
|
|
.item-level{
|
|
width: 50px;
|
|
}
|
|
.item-time{
|
|
width: 50px;
|
|
font-size: 12px;
|
|
color: #7a8794;
|
|
}
|
|
|
|
&:hover {
|
|
background: rgba(#4D6DE4, 0.5);
|
|
}
|
|
}
|
|
|
|
.active {
|
|
background: #4D6DE4;
|
|
color: #fff;
|
|
|
|
.name {
|
|
color: #fff;
|
|
}
|
|
|
|
.level {
|
|
color: #fff;
|
|
}
|
|
|
|
.time {
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.right {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
|
|
.right-top {
|
|
height: 344px;
|
|
|
|
.detail {
|
|
padding: 0 24px;
|
|
|
|
.detail-top {
|
|
height: 60px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.detail-top-left {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
|
|
.image {
|
|
width: 60px;
|
|
height: 60px;
|
|
margin-right: 16px;
|
|
}
|
|
|
|
.detail-top-left-text {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
margin-right: 24px;
|
|
.detail-top-left-text-name {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-weight: bold;
|
|
font-size: 24px;
|
|
color: #333333;
|
|
font-style: normal;
|
|
display: flex;
|
|
.name{
|
|
white-space: nowrap; /* 防止文本换行 */
|
|
overflow: hidden; /* 隐藏溢出的文本 */
|
|
text-overflow: ellipsis; /* 显示省略号 */
|
|
}
|
|
}
|
|
|
|
.detail-top-left-text-phone {
|
|
margin-top: 4px;
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
color: rgba(34, 42, 57, 0.8);
|
|
|
|
.age {
|
|
margin-right: 10px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.detail-top-right {
|
|
width: 552px;
|
|
display: flex;
|
|
height: 42px;
|
|
}
|
|
}
|
|
|
|
.detail-bottom {
|
|
margin-top: 24px;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
.right-list {
|
|
flex: 1;
|
|
margin-top: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
|
|
.list {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
.bottom {
|
|
width: 100%;
|
|
height: 60px;
|
|
background-color: #FFF;
|
|
box-sizing: border-box;
|
|
padding: 10px;
|
|
border-top: 1px solid #EEE;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.el-button, .el-button.is-round {
|
|
padding: 0;
|
|
}
|
|
|
|
|
|
.col {
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.search_box {
|
|
width: 700px;
|
|
}
|
|
|
|
</style> |