healthCard.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <template>
  2. <view class="container">
  3. <view class="content">
  4. <view class="member_list">
  5. <view class="member_item" v-for="(item, index) in memberList" :key="index">
  6. <view class="member_name">
  7. <view class="name">{{item.memberName}}</view>
  8. <view class="tip child">{{item.isChildren==1?'儿童':'成人'}}</view>
  9. <view class="tip real">{{item.IsRealNameAuth==1?'已实名':'未实名'}}</view>
  10. <view class="tip bind">{{item.Data_1.length>0?'已绑卡':'未绑卡'}}</view>
  11. </view>
  12. <view class="card_info">
  13. <view class="wrap" @click="btnClick($event)" data-type="1" :data-item="item">
  14. <view class="card-face-container">
  15. <image class="card-bg" src="@/static/images/healthCard/cardnewbg.png" alt="" />
  16. <view class="card-top-info">
  17. <view class="card-top-org">福建省卫生健康委员会</view>
  18. <view class="card-top-title">
  19. <image src="@/static/images/healthCard/icon2.png" alt="" />
  20. <text>{{(item.healthCard&&item.base64Code)||(item.healthCard)?'电子健康卡':'院内就诊卡'}}</text>
  21. </view>
  22. </view>
  23. <view class="card-detail-info">
  24. <view class="card-user-info">
  25. <text class="card-user-name">{{item.MemberName}}</text>
  26. <text class="card-user-id">{{item.CertNum}}</text>
  27. </view>
  28. <view :class="['card-qrcode', item.healthCard ? 'bg_fff' : '']" v-if="item.healthCard">
  29. <view v-if="item.cardTypeDiy==16" class="image_box">
  30. <!-- <image class="card-qrcode-logo" src="../../../static/images/healthCard/logo_.png" alt="" /> -->
  31. <cover-image class="card-qrcode-logo" src="@/static/images/healthCard/logo_.png"></cover-image>
  32. <!-- 健康卡code图片-->
  33. <!-- <image :style="{width:codeWidth+'px', height:codeWidth+'px'}" :src="'data:image/png;base64,'+item.base64Code" alt="" /> -->
  34. <canvas class='baseCode' :canvas-id="'canvas'+index" style="width: 154upx; height: 154upx;"></canvas>
  35. </view>
  36. <view v-else>
  37. <!-- canvas -->
  38. <canvas class='baseCode' :canvas-id="'canvas'+index" style="width: 154upx; height: 154upx;"></canvas>
  39. </view>
  40. </view>
  41. <!-- 升级健康卡图片 -->
  42. <view class="card-qrcode noHealthCard" v-if="!item.healthCard" @click.stop="btnClick($event)" data-type="4" :data-item="item">
  43. <image src="@/static/images/healthCard/ksj.png" alt="" />
  44. </view>
  45. </view>
  46. <view class="card-footer">
  47. 中华人民共和国国家卫生健康委员会监制
  48. </view>
  49. </view>
  50. </view>
  51. </view>
  52. <view class="member_default" v-if="false">
  53. <view class="relieve" @click="unbind" :data-item="item">解除绑定</view>
  54. </view>
  55. </view>
  56. </view>
  57. <view class="btns">
  58. <view class="btn add" @click="btnClick($event)" data-type="2">添加</view>
  59. <view class="btn" @click="btnClick($event)" data-type="3">已有健康卡一键关联</view>
  60. <!-- <view class="btn" @click="clearAuth" >清除授权</view> -->
  61. </view>
  62. <view class="dialogContiner mask" v-if="showBtn">
  63. <view class="dialog">
  64. <!-- <view class="title">微信授权</view> -->
  65. <view class="info">获取你的公开信息(昵称,头像,地区,及性别)</view>
  66. <!-- #ifdef MP-WEIXIN -->
  67. <healthCardLogin :encrypt="true" :authLogin="false" @authFail="authFail" @authSucess="authSuccess" :wechatcode="true" :healthcode="true">
  68. <view @click="auth" class="btn-agree">前往授权</view>
  69. </healthCardLogin>
  70. <!-- #endif -->
  71. <!-- #ifndef MP-WEIXIN -->
  72. <view @click="auth" class="btn-agree">前往授权(模拟)</view>
  73. <!-- #endif -->
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. </template>
  79. <script lang="ts" setup>
  80. import { ref, getCurrentInstance } from 'vue';
  81. import { useSmallProgramLogin } from '@/hook';
  82. import { onShow } from '@dcloudio/uni-app';
  83. import { common, QRCode } from '@/utils';
  84. import {
  85. queryMemberCardListByDb_V3,
  86. updateToHealthCard_V2,
  87. delMemberInfo_V3
  88. } from '@/pagesPersonal/service/patientManagement';
  89. const { proxy } = getCurrentInstance() as any;
  90. const app = getApp();
  91. const memberList = ref<any[]>([]);
  92. const number = ref(0);
  93. const codeWidth = ref<string>('0');
  94. const showBtn = ref(false);
  95. let type_pub = "";
  96. let memberInfo_pub: any = {};
  97. onShow(() => {
  98. main();
  99. });
  100. const main = async () => {
  101. common.showLoading('加载中');
  102. codeWidth.value = common.rpxtopx(154).toFixed(0);
  103. let resp = await queryMemberList();
  104. if (!common.isEmpty(resp)) {
  105. // 延迟一点时间确保canvas节点存在
  106. setTimeout(() => {
  107. for (let i = 0; i < resp.length; i++) {
  108. new QRCode(`canvas${i}`, {
  109. text: resp[i].healthCard || resp[i].idCardNo,
  110. width: Number(codeWidth.value) || 144, // Use calculated width
  111. height: Number(codeWidth.value) || 144,
  112. colorDark: "#000000",
  113. colorLight: "#ffffff",
  114. correctLevel: QRCode.CorrectLevel.H,
  115. });
  116. }
  117. }, 200);
  118. }
  119. common.hideLoading();
  120. memberList.value = resp;
  121. number.value = resp.length;
  122. };
  123. const getWechatCode = () => {
  124. return new Promise((resolve) => {
  125. // #ifdef MP-WEIXIN
  126. uni.login({
  127. provider: 'weixin',
  128. success: (res) => {
  129. resolve(res.code);
  130. },
  131. fail: () => {
  132. resolve('');
  133. }
  134. });
  135. // #endif
  136. // #ifndef MP-WEIXIN
  137. resolve('');
  138. // #endif
  139. });
  140. };
  141. const btnClick = async (e: any) => {
  142. let type = e.currentTarget.dataset.type;
  143. let memberInfo = e.currentTarget.dataset.item;
  144. type_pub = type;
  145. memberInfo_pub = memberInfo;
  146. if (type == 2 || type == 3 || type == 4) {
  147. common.showLoading('加载中');
  148. // let wechatCode = await common.getAuthorize((show: boolean) => {
  149. // if(show) {
  150. // showBtn.value = true;
  151. // }
  152. // });
  153. let wechatCode = await getWechatCode();
  154. // 如果没有code,或者需要显式授权(此处根据业务逻辑,如果需要弹窗,则弹窗)
  155. // 假设如果获取不到code或者特定条件下显示弹窗。
  156. // 这里保留原逻辑的意图:如果有code则直接处理,否则显示弹窗让用户点击授权
  157. if (common.isEmpty(wechatCode)) {
  158. showBtn.value = true;
  159. }
  160. common.hideLoading();
  161. if (!common.isEmpty(wechatCode)) {
  162. app.globalData.wechatCode = wechatCode;
  163. if (type == 2 || type == 3) {
  164. handleType(type);
  165. } else if (type == 4) {
  166. let resp = await updateToHealthCard(memberInfo, wechatCode);
  167. common.hideLoading();
  168. if (!common.isEmpty(resp)) {
  169. common.showToast('升级成功', () => {
  170. main();
  171. });
  172. }
  173. }
  174. }
  175. } else if (type == 1) {
  176. app.globalData.cardInfo_x = memberInfo;
  177. uni.navigateTo({
  178. url: '../healthCardQrcode/healthCardQrcode'
  179. });
  180. }
  181. };
  182. const authSuccess = async (data: any) => {
  183. var res = data.detail;
  184. if (!res.result.code) {
  185. return;
  186. }
  187. var wechatCode = res.result.wechatCode;
  188. app.globalData.wechatCode = wechatCode;
  189. showBtn.value = false;
  190. if (type_pub == '2' || type_pub == '3') {
  191. handleType(type_pub);
  192. } else if (type_pub == '4') {
  193. common.showLoading('加载中');
  194. let resp = await updateToHealthCard(memberInfo_pub, app.globalData.wechatCode);
  195. common.hideLoading();
  196. if (!common.isEmpty(resp)) {
  197. common.showToast('升级成功', () => {
  198. main();
  199. });
  200. }
  201. }
  202. };
  203. const authFail = (e: any) => {
  204. console.log('Auth failed', e);
  205. };
  206. const handleType = (type: string) => {
  207. switch (type) {
  208. case "2":
  209. if (number.value >= 5) {
  210. common.showModal('暂时只允许添加5个就诊人,谢谢您的合作。', () => {}, { showCancel: false, confirmColor: '#0099cc' });
  211. return;
  212. }
  213. uni.navigateTo({
  214. url: '../addMember/addMember?pageType=addHealthCard',
  215. });
  216. break;
  217. case "3":
  218. uni.navigateTo({
  219. url: '../healthCardList/healthCardList'
  220. });
  221. break;
  222. }
  223. };
  224. const queryMemberList = async () => {
  225. let list: any[] = [];
  226. let data = uni.getStorageSync('memberList');
  227. if (common.isNotEmpty(data)) {
  228. list = await Promise.all(
  229. data.map(async (item: any) => {
  230. let queryMemberCard = {
  231. memberId: item.memberId,
  232. mobileFormatDesensitization: "false",
  233. memberNameFormatDesensitization: "false",
  234. certNumFormatDesensitization: "false",
  235. cardNoFormatDesensitization: "false"
  236. };
  237. // 使用 Service 接口调用
  238. let respcard = await queryMemberCardListByDb_V3(queryMemberCard);
  239. console.log(respcard);
  240. // 注意:这里假设 service 返回解构后的 respcard.resData,如果 service 返回原 resp,则保留原逻辑
  241. // 根据 rules,service 返回值直接处理。假设返回的是 { resData: ... } 或者直接是 data
  242. // 原代码:respcard.resData.RespCode
  243. // 建议检查 service 定义。为了安全,保留原字段访问
  244. if (respcard && respcard.resData && respcard.resData.RespCode == "10000" && common.isNotEmpty(respcard.resData.Data)) {
  245. let cardDatas = respcard.resData.Data;
  246. let healthCardData = cardDatas.filter((p: any) => p.cardType == 16);
  247. if (common.isNotEmpty(healthCardData)) {
  248. item.cardTypeDiy = '16';
  249. item.CardType = '16';
  250. item.healthCard = healthCardData[0].cardNo;
  251. item.CardNo = healthCardData[0].cardNo;
  252. } else {
  253. item.CardType = '1';
  254. item.CardNo = cardDatas[0].cardNo;
  255. }
  256. }
  257. item.MemberId = item.memberId;
  258. item.MemberName = item.memberName;
  259. item.Mobile = item.mobile;
  260. item.CertNum = item.certNum;
  261. item.IdCardNo = item.idCardNo;
  262. return item;
  263. })
  264. );
  265. }
  266. return list;
  267. };
  268. const updateToHealthCard = async (item: any, code: string) => {
  269. let param = {
  270. MemberId: item.memberId,
  271. WechatCode: code,
  272. DefaultCardNo: item.CardNo,
  273. DefaultCardType: item.CardType
  274. };
  275. // Service 调用
  276. let { resData } = await updateToHealthCard_V2(param);
  277. if (resData && resData.RespCode == 10000) {
  278. return true;
  279. } else {
  280. if (resData) app.toLogin(resData); // 兼容原逻辑
  281. if(resData.RespCode == "401") {
  282. useSmallProgramLogin()
  283. }
  284. return false;
  285. }
  286. };
  287. const unbind = (e: any) => {
  288. let item = e.currentTarget.dataset.item;
  289. common.showModal(`尊敬的用户,是否确定解除【${item.memberName}】的 绑定?该操作无法回退!`, async() => {
  290. let queryData = {
  291. memberId: item.memberId,
  292. accountSn: item.accountSn,
  293. }
  294. let { resData } = await delMemberInfo_V3(queryData)
  295. if (resData.RespCode == 10000) {
  296. // 解绑操作时,判断是否等同与本地存储数据
  297. if (app.globalData.currentUser && item.memberId == app.globalData.currentUser.memberId) {
  298. app.globalData.currentUser = ""
  299. }
  300. common.showModal(`已成功解绑【${item.memberName}】`, async () => {
  301. // 添加成功并且设置默认完成后 重新查询并保存全局就诊人数据信息
  302. await usePreserMember()
  303. main()
  304. })
  305. }
  306. }, {
  307. title:'温馨提示',
  308. cancelText: '取消'
  309. })
  310. }
  311. const auth = () => {
  312. showBtn.value = false;
  313. // 模拟授权成功(非微信环境)
  314. // #ifndef MP-WEIXIN
  315. authSuccess({ detail: { result: { code: true, wechatCode: 'mock-code' } } });
  316. // #endif
  317. }
  318. </script>
  319. <style lang="scss" scoped>
  320. .member_item{
  321. background-color: #fff;
  322. margin-bottom: 30upx;
  323. }
  324. .member_name{
  325. display: flex;
  326. padding: 20upx;
  327. align-items: center;
  328. border-bottom: 1px solid #ebeaea;
  329. }
  330. .name{
  331. min-width: 104upx;
  332. font-size: 32upx;
  333. }
  334. .tip{
  335. color: #fff;
  336. border-radius: 5px;
  337. padding: 2upx 20upx;
  338. margin:0 10upx;
  339. font-size: 22upx;
  340. }
  341. .child{
  342. background-color: #189fa4;
  343. }
  344. .real{
  345. background-color: #f2a000;
  346. }
  347. .bind{
  348. background-color: #59a4ee;
  349. }
  350. .member_default{
  351. padding:15upx 30upx 15upx 70upx;
  352. border-top: 1px solid #ebeaea;
  353. display: flex;
  354. justify-content: flex-end;
  355. align-items: center;
  356. position: relative;
  357. }
  358. .card_info{
  359. padding: 26upx 0;
  360. }
  361. .icon{
  362. margin-right: 10upx;
  363. position: absolute;
  364. height: 16px;
  365. width: 16upx;
  366. line-height: 16px;
  367. top: 0;
  368. bottom: 0;
  369. left: 25upx;
  370. margin: auto 0;
  371. }
  372. .circular{
  373. width: 14px;
  374. height: 14px;
  375. border: 1px solid #ccc;
  376. border-radius: 50%;
  377. padding: 0;
  378. }
  379. .relieve{
  380. color: #4DB371;
  381. border: 1px solid ;
  382. border-radius: 50px;
  383. padding: 8upx 35upx;
  384. }
  385. .wrap {
  386. background: #fff;
  387. width: 100%;
  388. min-height: 100%;
  389. font-family: PingFangSC-Medium;
  390. }
  391. .card-face-container {
  392. height: 350upx;
  393. width: 620upx;
  394. margin: 0 auto;
  395. position: relative;
  396. color: #000000;
  397. }
  398. .card-face-container .card-bg {
  399. height: 100%;
  400. width: 100%;
  401. position: absolute;
  402. }
  403. .card-face-container .card-top-info {
  404. position: absolute;
  405. top: 23upx;
  406. width:100%;
  407. padding: 0 27upx 0 34upx;
  408. display: flex;
  409. justify-content: space-between;
  410. align-items: center;
  411. }
  412. .card-face-container .card-top-info .card-top-org {
  413. font-size: 18upx;
  414. line-height: 25upx;
  415. }
  416. .card-face-container .card-top-info .card-top-title {
  417. display: flex;
  418. align-items: center;
  419. font-size: 30upx;
  420. line-height: 42upx;
  421. color: #2B2B2B;
  422. }
  423. .card-face-container .card-top-info .card-top-title image {
  424. height: 55upx;
  425. width: 55upx;
  426. margin-right: 8upx;
  427. }
  428. .card-face-container .card-detail-info {
  429. position: absolute;
  430. top: 124upx;
  431. width: 100%;
  432. padding: 0 15upx 0 34upx;
  433. display: flex;
  434. justify-content: space-between;
  435. align-items: flex-end;
  436. }
  437. .card-face-container .card-detail-info .card-user-info {
  438. font-size: 36upx;
  439. color: #2B2B2B;
  440. }
  441. .card-face-container .card-detail-info .card-user-info .card-user-name {
  442. height: 50upx;
  443. }
  444. .card-face-container .card-detail-info .card-user-info .card-user-id {
  445. font-size: 30upx;
  446. line-height: 30upx;
  447. display: block;
  448. padding-top: 12upx;
  449. }
  450. .card-face-container .card-detail-info .card-qrcode {
  451. padding: 4upx;
  452. position: relative;
  453. font-size: 0;
  454. }
  455. .bg_fff{
  456. background: #FFFFFF;
  457. }
  458. .card-face-container .card-detail-info .noHealthCard {
  459. background: transparent;
  460. }
  461. .card-face-container .card-detail-info .card-qrcode .card-qrcode-logo {
  462. height: 44upx;
  463. width: 44upx;
  464. position: absolute;
  465. top: 50%;
  466. left: 50%;
  467. transform: translate(-50%, -50%);
  468. z-index: 10;
  469. }
  470. .card-face-container .card-detail-info .card-qrcode image {
  471. height: 154upx;
  472. width: 154upx;
  473. }
  474. .card-face-container .card-footer {
  475. position: absolute;
  476. font-size: 18upx;
  477. bottom: 16upx;
  478. left: 50%;
  479. transform: translateX(-50%);
  480. white-space: nowrap;
  481. letter-spacing: 0;
  482. line-height: 25upx;
  483. }
  484. .btns{
  485. padding: 10upx 0 1px;
  486. }
  487. .btn{
  488. width: 90%;
  489. margin: 30upx auto;
  490. padding: 20upx;
  491. border-radius: 5px;
  492. background-color: #fff;
  493. color: var(--auxiliaryColor);
  494. text-align: center;
  495. font-size: 30upx;
  496. }
  497. .add{
  498. background-color: var(--auxiliaryColor) !important;
  499. color: #fff !important;
  500. }
  501. /* 健康卡授权 */
  502. .mask {
  503. position: fixed;
  504. top: 0;
  505. left: 0;
  506. width: 100%;
  507. height: 100%;
  508. z-index: 100000;
  509. }
  510. .dialogContiner .dialog {
  511. position: absolute;
  512. top: 50%;
  513. left: 50%;
  514. width: 450upx;
  515. transform: translate(-50%, -50%);
  516. background-color: #fff;
  517. box-shadow: 0 -2px 25px 0 rgba(0, 0, 0, 0.15), 0 13px 25px 0 rgba(0, 0, 0, 0.3);
  518. z-index: 100000;
  519. text-align: center;
  520. border-radius: 10upx;
  521. }
  522. .dialogContiner .dialog .btn-agree {
  523. position: relative;
  524. line-height: 100upx;
  525. font-size: 35upx;
  526. color: #4DB371;
  527. }
  528. .dialogContiner .dialog .btn-agree::before {
  529. content: '';
  530. position: absolute;
  531. left: 0;
  532. right: 0;
  533. top: 0;
  534. height: 1px;
  535. transform: scaleY(0.5);
  536. background-color: rgba(234, 234, 234, 1);
  537. }
  538. .dialogContiner .dialog .btn-agree:hover {
  539. background-color: #fff;
  540. }
  541. .dialogContiner .dialog view.title {
  542. font-size: 40upx;
  543. line-height: 100upx;
  544. position: relative;
  545. }
  546. .dialogContiner .dialog view.title::after {
  547. content: '';
  548. position: absolute;
  549. left: 0;
  550. right: 0;
  551. bottom: 0;
  552. height: 1px;
  553. transform: scaleY(0.5);
  554. background-color: rgba(234, 234, 234, 1);
  555. }
  556. .dialogContiner .dialog view.info {
  557. font-size: 26upx;
  558. line-height: 30upx;
  559. color: rgba(22, 23, 42, 0.5);
  560. padding: 40upx;
  561. }
  562. .baseCode{
  563. width: 144upx;
  564. height: 144upx;
  565. z-index: 1;
  566. }
  567. .image_box{
  568. font-size: 0;
  569. }
  570. </style>