web/src/views/member/index.vue

639 lines
18 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="btn" @click="openEdit(null)">患者建档</span>
</div>
<div class="total">共{{ totalCount || 0 }}条</div>
<ul class="content">
<el-scrollbar style="height: 100%">
<li v-for="(item,i) in list" :key="item.id" :class="index==i?'active':''" @click="btn(item,i)">
<img class="image"
:src="'/static/images/member/' + (item.sex === 1 ? 'man' : 'women') + '.png'"
alt=""/>
<div class="name">{{ item.name || '-' }}</div>
<span class="level">{{ item.levelName || "-" }}</span>
<span class="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 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="btn" @click="openEdit(listItem.id)">编辑资料</span>
<span class="btn" @click="openGrant()">发放积分</span>
<span class="btn" @click="isExchange=true">兑换积分</span>
<span class="btn" @click="isFlowingWater=true">查看流水</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==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>
<Panel title="消费记录" class="right-list">
<div class="list-content">
<div class="list">
<el-table :data="tableData" :header-cell-style="{ backgroundColor: '#F1F5FB' }" style="width: 100%"
@cell-click="openDetail">
<el-table-column label="单号" prop="code" show-overflow-tooltip>
</el-table-column>
<el-table-column label="创建时间" prop="createDatetime">
<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="80">
<template #default="scope">
{{ scope.row.totalPrice }}元
</template>
</el-table-column>
<el-table-column label="支付方式" width="100">
<template #default="scope">
{{ scope.row.payType ? getPayTypeStr(scope.row.payType) : "" }}
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status==0" type="info">{{ getStatusStr(scope.row.status) }}</el-tag>
<el-tag v-if="scope.row.status==1" type="success">{{ getStatusStr(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="支付时间" prop="payTime">
<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"
:total="total"
:page-size="pageSize"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
</div>
</div>
</Panel>
</div>
</div>
<VipEdit ref="refEdit" @close="init"></VipEdit>
<VipLevelEdit ref="levelEditRef" @close="init"></VipLevelEdit>
<Mask :width="600" :height="400" :is-show="isGrant" @close="isGrant=false" :title="'发放积分'">
<Grant :info="listItem" @close="closeGrant()"></Grant>
</Mask>
<Mask :width="600" :height="400" :is-show="isExchange" @close="isExchange=false" title="兑换积分">
<Exchange :info="listItem" @close="closeExchange()"></Exchange>
</Mask>
<Mask :is-show="isFlowingWater" @close="isFlowingWater = false" title="流水记录">
<FlowingWater :info="listItem"></FlowingWater>
</Mask>
<OrderDetail ref="orderDetailRef"></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/retail/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: 50
};
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; // 或默认值
}
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 = {
page: page.value,
pageSize: pageSize.value,
patientId: id
}
post('charge/list', {query}).then((res: any) => {
tableData.value = res.list
})
}
const openDetail = (row: any, column: any, event: Event) => {
orderDetailRef.value.init(row.code)
}
const statusObj = {
0: "未完成",
1: "已完成",
2: "已取消",
3: "已退款"
}
const payTypeObj = {
0: "微信",
1: "支付宝",
2: "现金",
3: "医保"
}
type StatusKey = 0 | 1 | 2 | 3;
type PayTypeKey = 0 | 1 | 2 | 3;
//修改时间格式化
const formatDate = (isoStr: any) => {
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;
listItem.value = res[0];
});
};
// 分页
const total = ref(0)
const pageSize = ref(10)
const page = ref(1)
const handleCurrentChange = (val: number) => {
page.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})))
</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;
li {
display: flex;
align-items: center;
height: 50px;
line-height: 50px;
padding: 0 24px;
color: #000000;
cursor: pointer;
&:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
&:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
&:hover {
background: rgba(#4D6DE4, 0.5);
}
.image {
width: 20px;
height: 20px;
margin-right: 5px
}
.name {
flex: 2;
}
.level {
flex: 2;
margin-right: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time {
flex: 2;
font-size: 12px;
color: #7a8794;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.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;
justify-content: space-between;
align-items: center;
.detail-top-left {
display: flex;
.image {
width: 60px;
height: 60px;
margin-right: 16px;
}
.detail-top-left-text {
display: flex;
flex-direction: column;
justify-content: center;
.detail-top-left-text-name {
font-weight: bold;
font-size: 24px;
color: #333333;
font-style: normal;
}
.detail-top-left-text-phone {
margin-top: 4px;
font-weight: 500;
font-size: 16px;
color: rgba(34, 42, 57, 0.8);
.age {
margin: 0 10px;
}
}
}
}
.detail-top-right {
display: flex;
height: 42px;
.btn {
flex: 1;
background: #FFFFFF;
border-radius: 6px;
border: 1px solid #4D6DE4;
padding: 10px 12px;
font-weight: 500;
font-size: 16px;
color: #4D6DE4;
font-style: normal;
margin-left: 24px;
cursor: pointer;
&:hover {
color: #FFFFFF;
background: #4D6DE4;
}
}
}
}
.detail-bottom {
margin-top: 24px;
}
}
}
.right-list {
flex: 1;
margin-top: 24px;
display: flex;
flex-direction: column;
min-height: 0;
.list-content {
display: flex;
flex-direction: column;
padding: 0 24px;
height: 100%;
.list {
flex: 1;
display: flex;
flex-direction: column;
}
.bottom {
width: 100%;
height: 60px;
background-color: #FFF;
box-sizing: border-box;
padding: 10px;
position: relative;
border-top: 1px solid #EEE;
.page_btn_list {
position: absolute;
left: 0;
top: 10px;
}
}
}
}
}
}
.el-button, .el-button.is-round {
padding: 0;
}
.col {
margin: 20px 0;
}
.search_box {
width: 700px;
}
</style>