RegisterForm.vue 43 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
<template>
  <div class="register-form">
    <el-form
      :model="form"
      :rules="rules"
      ref="regFormRef"
      label-width="0"
      class="register-form-content"
    >
      <el-form-item prop="email" required>
        <el-input
          v-model="form.email"
          :placeholder="$t('register.emailPlaceholder')"
          clearable
          size="large"
        >
          <template #prefix>
            <span class="required-asterisk">*</span>
            <img src="/youxiang.svg" alt="email" class="input-icon-svg" />
          </template>
        </el-input>
      </el-form-item>

      <el-form-item prop="password" required>
        <el-input
          v-model="form.password"
          :placeholder="$t('register.passwordPlaceholder')"
          show-password
          clearable
          size="large"
        >
          <template #prefix>
            <span class="required-asterisk">*</span>
            <img src="/mima.svg" alt="password" class="input-icon-svg" />
          </template>
        </el-input>
      </el-form-item>

      <el-form-item prop="inviteCode">
        <el-input
          v-model="form.inviteCode"
          :placeholder="$t('register.invitePlaceholder')"
          :disabled="isInviteCodeFromRoute"
          clearable
          size="large"
        >
          <template #prefix>
            <img
              src="/yaoqingma.svg"
              alt="inviteCode"
              class="input-icon-svg invite-icon-aligned"
            />
          </template>
        </el-input>
      </el-form-item>

      <div class="agreement-section">
        <el-checkbox v-model="form.agreeTerms" />
        <div class="agreement-text">
          <span>{{ $t("register.agreeTerms") }}&nbsp;</span>
          <el-link
            type="primary"
            class="agreement-link"
            @click="showAgreementDialog = true"
            >{{ $t("register.userAgreement") }}</el-link
          >&nbsp;<span>{{ $t("register.and") }}</span
          >&nbsp;
          <el-link
            type="primary"
            class="agreement-link"
            @click="showPrivacyDialog = true"
            >{{ $t("register.privacyPolicy") }}</el-link
          >
        </div>
      </div>

      <el-form-item class="submit-section">
        <el-button
          type="primary"
          @click="onSubmit"
          :loading="authStore.loading"
          size="medium"
          class="submit-btn"
        >
          {{ $t("register.createBtn") }}
        </el-button>
      </el-form-item>

      <el-divider>
        {{ $t("register.haveAccount")
        }}<el-link
          type="primary"
          @click="emit('switchToLogin')"
          style="color: #1e6fff"
        >
          {{ $t("register.signIn") }}
        </el-link>
      </el-divider>
    </el-form>

    <!-- 成功弹窗:不再跳转 go-activate -->
    <el-dialog
      v-model="showSuccess"
      :title="$t('register.successTitle')"
      width="400px"
      :show-close="false"
      class="success-dialog"
      append-to-body
    >
      <div class="success-content">
        <el-icon class="success-icon" :size="64" color="#67c23a">
          <SuccessFilled />
        </el-icon>
        <p class="success-message">
          {{ $t("register.verificationEmailSent") }} <b>{{ form.email }}</b
          ><br />
          {{ $t("register.pleaseCheckEmail") }}
        </p>
      </div>
    </el-dialog>

    <!-- 用户协议弹窗 -->
    <el-dialog
      v-model="showAgreementDialog"
      :title="$t('register.userServiceAgreement')"
      width="600px"
      class="agreement-dialog"
      append-to-body
      :close-on-click-modal="false"
    >
      <div class="agreement-dialog-content">
        <div
          class="agreement-text-content"
          v-html="formattedAgreementText"
        ></div>
      </div>
      <template #footer>
        <div class="agreement-dialog-footer">
          <el-checkbox v-model="agreementChecked">
            {{ $t("register.readAndAgree")
            }}<el-link type="primary" @click.prevent style="margin: 0 4px"
              >【{{ $t("register.userAgreement") }}】</el-link
            >
          </el-checkbox>
          <el-button type="primary" @click="handleAgreementConfirm">
            {{ $t("register.confirm") }}
          </el-button>
        </div>
      </template>
    </el-dialog>

    <!-- 隐私政策弹窗 -->
    <el-dialog
      v-model="showPrivacyDialog"
      :title="$t('register.privacyPolicy')"
      width="600px"
      class="agreement-dialog"
      append-to-body
      :close-on-click-modal="false"
    >
      <div class="agreement-dialog-content">
        <div class="agreement-text-content" v-html="formattedPrivacyText"></div>
      </div>
      <template #footer>
        <div class="agreement-dialog-footer">
          <el-checkbox v-model="privacyChecked">
            {{ $t("register.readAndAgree")
            }}<el-link type="primary" @click.prevent style="margin: 0 4px"
              >【{{ $t("register.privacyPolicy") }}】</el-link
            >
          </el-checkbox>
          <el-button type="primary" @click="handlePrivacyConfirm">
            {{ $t("register.confirm") }}
          </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { SuccessFilled } from "@element-plus/icons-vue";
import { useAuthStore } from "@/stores/auth";
import { useRouter, useRoute } from "vue-router";

const { t } = useI18n();
const authStore = useAuthStore();
const router = useRouter();
const route = useRoute();

const emit = defineEmits<{
  (e: "switchToLogin"): void;
}>();

const regFormRef = ref<FormInstance>();
const showSuccess = ref(false);
const showAgreementDialog = ref(false);
const agreementChecked = ref(false);
const showPrivacyDialog = ref(false);
const privacyChecked = ref(false);
const isInviteCodeFromRoute = ref(false);

// 协议内容
const agreementContent = `
更新日期:2025年11月28日  
更新日期:2025年11月28日  
1. 导言
欢迎您使用北京连心医疗科技有限公司(以下简称"连心医疗"或"我们")提供的"领医智能体"服务(以下简称"本服务")。
本服务是一款基于人工智能技术的医疗健康信息辅助平台,旨在通过互动问答、报告解读等形式,为您提供信息参考和辅助工具。本《用户服务协议》(以下简称"本协议")是您与连心医疗之间就注册、登录、使用本服务所订立的具有法律效力的协议。
在您开始使用本服务之前,请您(以下简称"用户"或"您")务必审慎阅读、充分理解本协议各条款内容,特别是以加粗、下划线等形式显示的关于免除或限制责任、医疗风险、法律适用和争议解决的条款。如果您不同意本协议的任何内容,或者无法准确理解该等条款的含义,请不要进行后续操作,并应立即停止访问或使用本服务。您的注册、登录、使用等行为即视为您已阅读、理解并完全接受本协议的全部内容,并同意接受其约束。
2. 服务说明
2.1 本服务依托于生成式人工智能技术,主要功能包括但不限于:医学健康知识问答、检验报告智能解读、健康资讯推送以及相关的健康管理辅助功能。
2.2 本服务的核心功能需要您注册并登录账号后方可使用。
3. 重要提示:非医疗诊断性质与风险承担
3.1 【非医疗诊断声明】
您在此明确知悉并完全理解,"领医智能体"是一款人工智能健康信息辅助工具,其所有输出内容(包括但不限于文本、分析、解读、建议等,以下简称"AI生成内容")均是基于算法和已有数据模型生成的初步、参考性信息。本服务无法也绝不旨在替代执业医师或其他合格医疗专业人员的专业判断、面对面诊断、治疗或医学建议。我们明确声明不通过本服务提供任何形式的医疗诊断、治疗方案、处方开具、预后判断或紧急医疗服务。您与"领医智能体"之间的交互不构成任何形式的医患关系。
3.2 【用户责任与风险自知】
3.2.1 鉴于医学的极端复杂性、高度专业性和个体差异性,AI生成内容可能存在不准确、不完整、过时或不适用于您个人特定健康状况的风险。您必须清醒地认识到,完全依赖此类信息可能导致误判或延误必要的诊断和治疗。
3.2.2 对于任何与您或他人健康相关的问题,尤其是出现急性、严重、持续性或令人担忧的症状时,您必须立即咨询执业医师或前往正规医疗机构就诊。您明确承诺,不会将AI生成内容作为您决定是否采取或不采取任何医疗、健康相关行动(如用药、手术、改变生活方式、中止治疗等)的唯一或决定性依据。
3.2.3 您根据AI生成内容所作出的任何判断、决策或行动,所带来的全部直接或间接后果、风险和责任,均由您自行承担。连心医疗及其关联方在此范围内免除全部责任。
3.3 【检验报告解读特别约定】
3.3.1 您知悉并同意,您主动上传的检验报告、检查单等文件包含您的个人敏感信息(属于个人健康生理信息)。上传行为即表示您授权我们为向您提供解读服务之目的,在必要范围内处理该信息。
3.3.2 我们不对报告解读的准确性、完整性和临床符合率作出任何明示或默示的担保。报告解读结果仅为对检验指标的初步分析和健康科普,旨在帮助您理解报告内容,绝不代表最终的临床诊断。所有检验报告的最终解释权必须归属于出具该报告的医疗机构及您的执业医师。
4. 账号注册与安全
4.1 您需要按照本服务的指引注册账号,并设置符合安全要求的密码。您应提供真实、准确、完整的注册信息,并及时更新。
4.2 您注册的账号仅限于您本人使用。您应对您的账号和密码的安全承担全部责任,并对通过该账号进行的所有活动(包括但不限于信息输入、内容生成等)承担全部法律责任。
4.3 如发现任何未经授权的账号使用行为,您应立即通知我们。我们有权在怀疑账号被非注册人使用时,暂停或终止向该账号提供服务。
5. 用户行为规范
您承诺在使用本服务时,严格遵守中华人民共和国相关法律法规,并保证不得实施以下行为:
5.1 输入、上传、生成、传播任何违反国家法律法规、社会主义制度、中国共产党领导、国家利益、公民合法权益、社会公共秩序、道德风尚及信息真实性等"七条底线"的内容。
5.2 输入、上传、生成、传播任何侵犯他人知识产权、名誉权、隐私权、肖像权等合法权益的内容。
5.3 输入、上传、生成、传播任何涉及淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的内容。
5.4 输入、上传、生成、传播任何含有虚假、骚扰、侮辱、诽谤、恐吓或庸俗淫秽内容的信息。
5.5 从事任何危害计算机网络安全的行为,包括但不限于:使用恶意程序、非法侵入网络、干扰网络正常功能、窃取网络数据等。
5.6 利用本服务进行任何不利于连心医疗或可能对本服务正常运行造成不合理负荷的行为。
5.7 特别禁止: 将本服务用于任何医疗急症。如遇急症,您必须立即拨打急救电话(如120)或前往最近医院的急诊科。
6. 知识产权
6.1 连心医疗是本服务相关的软件、技术、程序、网站、文字、图片、图形、音频、视频、标识、版面设计等内容的合法知识产权权利人或被许可人。未经连心医疗明确书面授权,您不得对前述内容进行复制、修改、租赁、出售、传播或创建衍生作品。
6.2 您通过本服务输入的内容("您的输入"),其相关知识产权仍归您或原始权利人所有。您授予连心医疗一项全球范围内、免费的、不可撤销的许可,允许我们为提供服务之目的(包括但不限于模型训练、服务优化)使用、处理您的输入。
6.3 由本服务基于您的输入而生成的内容("AI输出"),在您遵守本协议全部条款的前提下,我们授予您一项非独占的、有限的使用许可,您可基于个人非商业目的使用。但您不得声称对AI输出拥有绝对所有权,并不得将其用于任何非法目的或进行恶意知识产权抢注、诉讼等行为。
7. 隐私与个人信息保护
我们高度重视保护您的个人信息,尤其是健康敏感信息。关于我们如何收集、使用、存储、共享和保护您的个人信息,请您详细阅读并理解我们另行制定的《隐私政策》。该政策是本协议不可分割的一部分。
8. 服务的变更、中断与终止
8.1 为提升用户体验和服务安全性,我们有权不定期对服务进行更新、升级、调整或中断,并在可行情况下予以公告。
8.2 我们保留根据自身商业决策、法律法规要求或不可抗力等因素,随时暂停、中止或终止向您提供部分或全部服务的权利,无需事先通知您,也无需承担任何责任。
8.3 您有权通过我们提供的流程申请注销您的账号。账号注销后,我们将依据《隐私政策》处理您的个人信息。
9. 免责声明
9.1 【服务"按现状"提供】 本服务是在现有技术和条件所能达到的现状下提供的。连心医疗尽最大努力提供服务,但无法保证服务永不中断、绝对安全或完全无错误。
9.2 【第三方内容】 本服务可能包含或链接至第三方提供的信息或服务。该等内容均由第三方负责,我们不对其真实性、准确性、合法性负责,也不代表我们赞同其观点。
9.3 【不可抗力】 对于因不可抗力(如战争、地震、洪水、火灾、政府行为、网络攻击、病毒入侵等)或非我们可控原因造成的服务中断、数据丢失或其他损失,我们在法律允许的范围内免除责任。
9.4 【责任限制】 在法律允许的最大范围内,连心医疗及其关联方、董事、员工对您因使用或无法使用本服务而产生的任何间接性、后果性、惩戒性、偶然性、特殊或惩罚性的损害赔偿(包括但不限于利润损失、数据丢失、商誉损害、人身伤害或其他无形损失),不承担任何责任。我们对您的全部直接赔偿责任总额,不应超过您就使用本服务而向我们直接支付的费用(如有)。
10. 违约处理
若您违反本协议的任何规定,我们有权独立判断并根据情节轻重,采取包括但不限于以下一种或多种措施:警告、限制或停止部分功能、暂停服务、终止服务、永久关闭账号,并保留追究您法律责任的权利。
11. 协议的变更与通知
我们有权在必要时修改本协议条款。更新后的协议将在我们的官方网站(https://linkmed.cc/)或本软件内显著位置公布,并注明更新日期。若您在本协议内容变更后继续使用本服务,即表示您已充分阅读、理解并接受修订后的协议,并同意受其约束。
12. 法律适用与争议解决
12.1 本协议的订立、效力、解释、履行及争议的解决,均适用中华人民共和国法律(不包括其冲突法规则)。
12.2 因本协议引起的或与本协议有关的任何争议,双方应首先通过友好协商解决。如果协商不成,任何一方均有权将争议提交至北京连心医疗科技有限公司所在地有管辖权的人民法院通过诉讼解决。
13. 其他
13.1 本协议构成您与我们之间就本服务所达成的完整协议,并取代您与我们之间先前就本服务所达成的任何口头或书面约定。
13.2 如果本协议中的任何条款因任何原因被有管辖权的法院认定为无效或不可执行,则该条款应在不影响其他条款效力的前提下从本协议中移除,其余条款继续完全有效。
13.3 我们未行使或执行本协议项下的任何权利或规定,不构成对该权利或规定的放弃。
联系我们
如果您对本协议或本服务有任何疑问、意见或建议,请通过以下方式与我们联系:
●   公司名称: 北京连心医疗科技有限公司
●   官方网址: https://linkmed.cc/
●   联系电话: 18515088735`;

// 格式化协议文本,将换行转换为HTML
const formattedAgreementText = computed(() => {
  return agreementContent
    .split("\n")
    .map((line) => {
      // 处理空行
      if (!line.trim()) {
        return "<br>";
      }
      // 处理标题(数字开头的行)
      if (/^\d+\./.test(line.trim())) {
        return `<p style="font-weight: bold; margin: 12px 0 8px 0;">${line}</p>`;
      }
      // 处理子标题(如 2.1, 3.2.1 等)
      if (/^\d+\.\d+/.test(line.trim())) {
        return `<p style="font-weight: 600; margin: 10px 0 6px 0;">${line}</p>`;
      }
      // 处理加粗标记的内容
      if (line.includes("【") && line.includes("】")) {
        return `<p style="font-weight: bold; margin: 8px 0 4px 0;">${line}</p>`;
      }
      // 普通段落
      return `<p style="margin: 6px 0; line-height: 1.6;">${line}</p>`;
    })
    .join("");
});

// 隐私政策内容
const privacyContent = `
更新日期:2025年11月28日  
更新日期:2025年11月28日  

北京连心医疗科技有限公司(以下简称"我们""连心医疗"或"LinkMed")作为https://linkmed.cc/ 网站(以下简称"本网站")的运营者,深知医疗健康领域个人信息及敏感数据对您的重要性。本隐私政策专为本网站定制,将详细说明我们在您使用本网站账号注册、登录及关联医疗服务过程中,如何收集、使用、存储、保护您的个人信息,以及您享有的信息管理权利。请您在访问、使用本网站前仔细阅读并理解本政策,您使用本网站即视为同意我们按照本政策处理您的个人信息。
一、定义与适用范围
1.1 核心定义
● LinkMed:指北京连心医疗科技有限公司运营的医疗健康服务体系,本网站(https://linkmed.cc/)是其中用于账号认证的核心入口,主要提供账号注册、登录、身份核验等基础服务,为后续使用"领医智能体"等医疗相关功能提供身份支持。
● 个人信息:指以电子或其他方式记录的,能够单独或与其他信息结合识别特定自然人身份或反映其活动情况的信息,如姓名、手机号码、身份证号、医疗执业资质信息等。
● 敏感个人信息:指一旦泄露或非法使用,易导致人身、财产安全受损或人格尊严受侵害的信息,包括身份证件号码、医疗执业证编号、生物识别信息、精准位置信息等,本网站处理的敏感信息将以下划线标注。
1.2 适用范围
本政策仅适用于本网站(https://linkmed.cc/)的所有功能及服务,包括但不限于账号注册、手机号登录、第三方授权登录、身份核验等;若您通过本网站跳转至其他关联平台(如"领医智能体"服务页面),将适用该平台对应的隐私政策,我们会在跳转时通过页面提示告知您。
二、我们如何收集和使用您的个人信息
我们仅为实现本网站的账号认证及基础服务目的,收集必要的个人信息,不超出业务必需范围额外收集;非必要信息您可自主选择是否提供,且不影响基础登录功能使用。
2.1 账号注册与登录场景
本网站作为LinkMed体系的账号认证入口,需通过信息收集完成身份核验,保障医疗服务的合规性与安全性,具体收集信息如下:

| 功能模块               | 收集的个人信息                                                                 | 收集目的                                                                 | 是否必要                                 |
|------------------------|--------------------------------------------------------------------------------|--------------------------------------------------------------------------|------------------------------------------|
| 手机号注册             | 手机号码、短信验证码                                                           | 完成网络实名制认证,创建 LinkMed 账号,用于后续登录及身份核验             | 否(可以邮箱)                           |
| 密码设置               | 账号密码(加密存储)                                                           | 保障账号安全,防止未授权访问                                             | 是                                       |
| 医疗身份核验(如适用) | 姓名、身份证号、医师资格证 / 执业证编号(仅针对医疗从业者用户)                 | 验证医疗从业者身份真实性,为后续使用专业医疗服务(如病例分析、诊疗辅助)提供资质支持 | 否(仅非医疗用户可跳过,医疗从业者使用专业功能需提供) |
| 第三方授权登录(如适用) | 授权平台(如医疗机构内部系统)返回的公开信息(姓名、所属机构、账号标识)       | 简化登录流程,实现跨平台身份同步                                         | 否(可选择其他注册登录)                 |

2.2 账号安全与运营场景
为保障您的账号安全、维护本网站稳定运行,我们会自动收集以下非身份标识信息,无需您主动提供:
● 设备信息:包括设备型号、操作系统版本、设备标识符(如Android ID、OAID,仅用于设备唯一性识别,不关联身份信息)、浏览器类型及版本,用于识别异常登录设备,防范账号盗用风险。
● 日志信息:包括您访问本网站的IP地址、访问时间、页面浏览记录、操作行为(如"点击注册""获取验证码"),用于排查系统故障、优化页面加载速度,及追溯违规操作(如多次输错密码)。
● 安全验证信息:包括登录时的短信验证码、操作时的弹窗确认记录,用于验证操作行为的真实性,防止账号被非法操作。

2.3 征得授权同意的例外
根据《个人信息保护法》等法律法规,以下情形中,我们收集、使用您的个人信息无需事先征得您的同意:
1. 与履行法律法规规定的义务相关的(如向监管部门报送医疗从业者资质核验信息);
2. 与公共安全、公共卫生直接相关的(如配合疫情防控核查账号关联的医疗服务记录);
3. 出于维护您或他人的生命、财产安全,但难以获得本人授权的(如账号异常时冻结操作所需的设备日志);
4. 用于维护本网站安全稳定运行所必需的(如发现并处置恶意注册、黑客攻击行为);
5. 法律法规规定的其他情形。
三、我们如何使用Cookie及同类技术
为优化您的访问体验、保障账号登录安全,本网站会使用Cookie技术,具体用途如下:
1. 身份识别与登录状态保持:通过Cookie记录您的账号登录状态(如"保持登录"选项勾选后),避免您每次访问时重复输入账号密码,Cookie仅存储加密后的登录标识,不包含明文密码。
2. 安全防护:设置安全类Cookie,识别异常登录行为(如异地登录、频繁切换设备),当检测到风险时触发二次验证(如短信验证码),保护账号安全。
3. Cookie管理:您可通过浏览器设置关闭或清除Cookie(如Chrome浏览器:设置-隐私和安全-Cookie及其他网站数据-阻止所有Cookie);但关闭Cookie后,您可能无法使用"保持登录"功能,且每次访问需重新输入账号信息,部分页面加载可能异常。
四、个人信息的共享、转移与公开
我们严格控制个人信息的对外流转,仅在以下限定场景中共享、转移或公开您的信息,且均遵循"最小必要"原则:
4.1 共享场景
● 医疗资质核验合作方:若您需验证医疗从业者身份,我们会将您提供的姓名、执业证编号共享给合法持有医疗资质数据库的合作方(如地方卫健委授权的信息核验平台),仅用于资质真实性验证,合作方不得留存或用于其他目的,我们会与合作方签署数据保密协议。
● 云服务提供商:本网站的服务器存储、数据传输依赖合规的云服务提供商(如国内具备医疗数据存储资质的服务商),我们会将您的账号信息(加密后)、日志信息委托其存储,云服务提供商仅能按照我们的指令处理数据,不得擅自使用。
4.2 转移场景
若发生企业合并、收购、资产转让等情形,您的个人信息可能随业务资产一并转移,我们会要求继受方继续履行本隐私政策的义务;若继受方变更数据处理目的,需重新征得您的书面同意。
4.3 公开场景
我们不会主动公开您的个人信息,仅在以下情形中可公开:
1. 获得您的明确同意后(如您授权本网站公开您的医疗资质信息用于行业认证);
2. 法律法规要求公开的(如配合司法机关调查,提供涉案账号的注册信息);
3. 对违规账号进行处罚公告时,仅公开账号标识(如脱敏后的手机号"138******78"),不披露完整个人信息。
五、个人信息的存储与保护
5.1 存储规则
1. 存储地点:您的个人信息均存储在中华人民共和国境内的服务器上,我们不会将其传输至境外;如需跨境传输(如涉及国际医疗合作),会事先征得您的同意,并符合国家跨境数据传输的法律法规要求。
2. 存储期限:
   - 账号基础信息(手机号、加密密码):保留至您主动注销账号之日;
   - 医疗资质信息(如执业证编号):保留至您的资质有效期届满或您注销账号之日;
   - 设备信息、日志信息:仅保留6个月(超出期限后自动匿名化处理,无法关联身份);
   - 若法律法规要求延长存储期限(如医疗监管部门要求留存资质核验记录3年),我们会按照法定期限留存,到期后立即删除或匿名化。
六、您的个人信息权利
您对自己的个人信息享有查阅、更正、删除、撤回授权、注销账号等权利,具体操作方式如下:
1. 查阅与更正:登录本网站后,进入"账号中心"(或通过本网站跳转至LinkMed主平台"我的-个人信息"),可查阅您的手机号、医疗资质信息;如需更正(如手机号变更、资质更新),可提交修改申请并上传证明材料,我们会在1个工作日内审核处理。
2. 删除信息:您可申请删除非必要信息(如已过期的医疗资质扫描件),通过本网站"联系客服"通道提交申请,我们会在3个工作日内完成删除并告知您结果;账号基础信息(手机号)仅在注销账号后可删除。
3. 撤回授权:若您曾授权我们共享信息给第三方(如医疗资质核验平台),可通过"账号中心-授权管理"撤回授权,撤回后我们会立即通知第三方停止使用您的信息。
4. 账号注销:您可通过本网站"账号中心-安全设置-注销账号"提交申请,注销前需完成身份验证(如短信验证码+身份证号核验);账号注销后,我们会在15个工作日内删除您的所有个人信息(法律法规要求留存的除外),且注销不可逆。
5. 投诉与反馈:若您认为您的个人信息权利受到侵害,可通过以下方式联系我们:
   - 电话:18515088735(工作日9:00-18:00);
   - 邮寄公司:北京连心医疗科技有限公司(运营部收)。
   我们会在15个工作日内核实并回复您的投诉或反馈。
七、未成年人保护
本网站及LinkMed相关服务主要面向成年人及医疗从业者,不主动向未满18周岁的未成年人提供服务。若未成年人需使用本网站(如实习医疗人员),需在监护人的指导下注册账号,并由监护人同意本隐私政策;监护人可联系我们查询、更正或删除未成年人的个人信息,我们会积极配合提供必要支持。
八、隐私政策的修订与通知
1. 若法律法规更新、本网站功能调整(如新增第三方登录方式),或我们的信息处理规则发生变更,我们会修订本隐私政策,并在本网站首页显著位置公示修订后的版本,同时通过短信或账号站内信告知您修订内容;修订后的政策自公示之日起7日后生效,若您在生效后继续使用本网站,视为同意修订后的政策。
2. 重大修订(如扩大个人信息收集范围、变更共享规则)会通过本网站弹窗强制提示您阅读,您需确认同意后才可继续使用服务。
九、联系我们
若您对本隐私政策有任何疑问,或需要协助管理个人信息,可通过以下方式联系我们:
● 官方网址:https://linkmed.cc/(可通过"帮助中心"提交问题);
● 联系电话:18515088735(工作日9:00-18:00);
我们会在15个工作日内对您的咨询、反馈给予正式回复。`;

// 将 markdown 表格转换为 HTML 表格
const parseMarkdownTable = (text: string): string => {
  const lines = text.split("\n");
  let result: string[] = [];
  let inTable = false;
  let tableRows: string[][] = [];
  let tableHeaders: string[] = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]?.trim() || "";

    // 检测表格开始(包含 | 的行)
    if (line.startsWith("|") && line.endsWith("|")) {
      if (!inTable) {
        inTable = true;
        tableRows = [];
      }

      // 跳过分隔行(如 |---|---|)
      if (/^\|[\s\-:]+\|/.test(line)) {
        continue;
      }

      // 解析表格行
      const cells = line
        .split("|")
        .map((cell) => cell.trim())
        .filter((cell) => cell.length > 0);

      if (cells.length > 0) {
        if (tableHeaders.length === 0) {
          tableHeaders = cells;
        } else {
          tableRows.push(cells);
        }
      }
    } else {
      // 表格结束,生成 HTML 表格
      if (inTable && tableHeaders.length > 0) {
        let tableHtml = '<table class="privacy-table">';
        // 表头
        tableHtml += "<thead><tr>";
        tableHeaders.forEach((header) => {
          tableHtml += `<th>${header}</th>`;
        });
        tableHtml += "</tr></thead>";
        // 表体
        tableHtml += "<tbody>";
        tableRows.forEach((row: string[]) => {
          tableHtml += "<tr>";
          row.forEach((cell: string) => {
            tableHtml += `<td>${cell || ""}</td>`;
          });
          tableHtml += "</tr>";
        });
        tableHtml += "</tbody></table>";
        result.push(tableHtml);
        inTable = false;
        tableHeaders = [];
        tableRows = [];
      }

      // 处理非表格行
      if (line.trim()) {
        result.push(line);
      } else {
        result.push("");
      }
    }
  }

  // 处理文件末尾的表格
  if (inTable && tableHeaders.length > 0) {
    let tableHtml = '<table class="privacy-table">';
    tableHtml += "<thead><tr>";
    tableHeaders.forEach((header) => {
      tableHtml += `<th>${header}</th>`;
    });
    tableHtml += "</tr></thead>";
    tableHtml += "<tbody>";
    tableRows.forEach((row: string[]) => {
      tableHtml += "<tr>";
      row.forEach((cell: string) => {
        tableHtml += `<td>${cell || ""}</td>`;
      });
      tableHtml += "</tr>";
    });
    tableHtml += "</tbody></table>";
    result.push(tableHtml);
  }

  return result.join("\n");
};

// 格式化隐私政策文本,将换行转换为HTML,并处理表格
const formattedPrivacyText = computed(() => {
  // 先解析表格
  const textWithTables = parseMarkdownTable(privacyContent);

  return textWithTables
    .split("\n")
    .map((line) => {
      // 如果已经是表格 HTML,直接返回
      if (line.includes("<table")) {
        return line;
      }

      // 处理空行
      if (!line.trim()) {
        return "<br>";
      }

      // 处理标题(中文数字开头的行,如一、二、等)
      if (/^[一二三四五六七八九十]+、/.test(line.trim())) {
        return `<p style="font-weight: bold; margin: 16px 0 10px 0; font-size: 16px;">${line}</p>`;
      }

      // 处理子标题(如 1.1, 2.1 等)
      if (/^\d+\.\d+/.test(line.trim())) {
        return `<p style="font-weight: 600; margin: 12px 0 8px 0;">${line}</p>`;
      }

      // 处理列表项(以 ● 开头)
      if (line.trim().startsWith("●")) {
        return `<p style="margin: 6px 0; line-height: 1.6; padding-left: 20px;">${line}</p>`;
      }

      // 处理加粗标记的内容
      if (line.includes("【") && line.includes("】")) {
        return `<p style="font-weight: bold; margin: 8px 0 4px 0;">${line}</p>`;
      }

      // 普通段落
      return `<p style="margin: 6px 0; line-height: 1.6;">${line}</p>`;
    })
    .join("");
});

// 处理协议确认
const handleAgreementConfirm = () => {
  if (agreementChecked.value) {
    form.agreeTerms = true;
    showAgreementDialog.value = false;
    // watch 会自动将 agreementChecked 和 privacyChecked 同步为 true
  } else {
    ElMessage.warning(t("register.pleaseReadAndAgreeAgreement"));
  }
};

// 处理隐私政策确认
const handlePrivacyConfirm = () => {
  if (privacyChecked.value) {
    form.agreeTerms = true;
    showPrivacyDialog.value = false;
    // watch 会自动将 agreementChecked 和 privacyChecked 同步为 true
  } else {
    ElMessage.warning(t("register.pleaseReadAndAgreePrivacy"));
  }
};

// 加载协议内容
onMounted(async () => {
  // 从路由查询参数中获取邀请码
  const refCode = route.query.ref as string;
  if (refCode) {
    form.inviteCode = refCode;
    isInviteCodeFromRoute.value = true;
  }
});

const form = reactive({
  email: "",
  password: "",
  inviteCode: "",
  agreeTerms: false,
});

// 监听 form.agreeTerms 的变化,同步更新 agreementChecked 和 privacyChecked
watch(
  () => form.agreeTerms,
  (newValue) => {
    agreementChecked.value = newValue;
    privacyChecked.value = newValue;
  },
);

const rules = computed<FormRules>(() => ({
  email: [
    { required: true, message: "请输入邮箱", trigger: "blur" },
    {
      validator: (_rule: any, value: string, callback: any) => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (value && !emailRegex.test(value)) {
          callback(new Error("邮箱格式不正确"));
        } else {
          callback();
        }
      },
      trigger: ["blur", "change"],
    },
  ],
  password: [
    { required: true, message: "请输入密码", trigger: "blur" },
    { min: 6, max: 20, message: "密码长度至少6位,最多20位", trigger: "blur" },
  ],
}));

const onSubmit = async () => {
  // 表单校验:校验不通过则不发送接口请求
  if (!regFormRef.value) {
    return;
  }

  try {
    // 执行表单校验
    await regFormRef.value.validate();
  } catch (error: any) {
    // 校验失败,提取第一个错误信息并提示用户
    let errorMessage = "请检查表单输入";

    if (error && typeof error === "object" && error.fields) {
      // Element Plus 校验失败时,error.fields 包含所有字段的错误信息
      const fields = error.fields;
      const fieldKeys = Object.keys(fields);

      // 获取第一个有错误的字段
      for (const fieldKey of fieldKeys) {
        const fieldErrors = fields[fieldKey];
        if (Array.isArray(fieldErrors) && fieldErrors.length > 0) {
          const firstError = fieldErrors[0];
          if (firstError && firstError.message) {
            errorMessage = firstError.message;
            break;
          }
        }
      }
    }

    ElMessage.error(errorMessage);
    // 校验失败,直接返回,不发送请求
    return;
  }

  // 检查是否同意协议
  if (!form.agreeTerms) {
    ElMessage.error("请阅读并同意用户协议和隐私政策");
    return;
  }

  // 所有校验通过后,才发送注册请求
  try {
    const result = await authStore.register({
      email: form.email,
      password: form.password,
      realName: form.email,
      inviteCode: form.inviteCode || undefined,
    });

    if (result.success) {
      ElMessage.success(
        t("goActivateV2.sendSuccess_link") ||
          "激活邮件已发送,请前往邮箱点击链接完成激活!",
      );
      emit("switchToLogin");
    } else {
      ElMessage.error(t("register.registerFailed"));
    }
  } catch (error) {
    console.error("注册处理错误:", error);
    ElMessage.error(t("register.registerFailed"));
  }
};
</script>

<style scoped lang="scss">
.register-form {
  width: 100%;
  max-width: 400px;
  margin: 0 auto;
  padding: 0;

  :deep(.el-input__prefix) {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;

    .required-asterisk {
      color: #f56c6c;
      font-size: 14px;
      font-weight: bold;
      margin-left: 10px;
      margin-right: 2px;
      line-height: 1;
    }

    .input-icon-svg {
      margin-right: 6px;
      display: block;
    }
    .invite-icon-aligned {
      margin-left: 18px;
    }
  }
  :deep(.el-input--large .el-input__wrapper) {
    padding: 2px 15px 2px 2px !important;
  }

  :deep(.el-input.is-focus .el-input__prefix .input-icon-svg) {
    opacity: 1;
  }
}

.register-form-content {
  background: transparent;
  border-radius: 16px;
  padding: 0;
}

.el-form-item {
  margin-bottom: 20px;
}

.el-input {
  border-radius: 12px;

  :deep(.el-input__inner) {
    height: 38px;
    font-size: 16px;
    border-radius: 12px;
    background: #ffffff !important;
    color: #606266 !important;
    transition: all 0.3s ease;

    &::placeholder {
      color: #c0c4cc;
      font-size: 16px;
    }
  }

  :deep(.el-input__prefix) {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;

    .required-asterisk {
      color: #f56c6c;
      font-size: 14px;
      font-weight: bold;
      margin-left: 10px;
      margin-right: 2px;
      line-height: 1;
    }

    .input-icon-svg {
      margin-right: 6px;
      display: block;
    }
  }

  :deep(.el-input.is-focus .el-input__prefix .input-icon-svg) {
    opacity: 1;
  }

  :deep(.el-input__suffix) {
    color: #909399;
  }
}

.input-icon-svg {
  width: 18px;
  height: 18px;
  object-fit: contain;
  flex-shrink: 0;
  opacity: 0.6;
  transition: opacity 0.3s ease;
  vertical-align: middle;
}

.agreement-section {
  margin: 8px 0;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 16px;
}

.agreement-section :deep(.el-checkbox) {
  display: flex;
  align-items: flex-start;
  line-height: 1.5;
  margin-top: 0;
}

.agreement-section :deep(.el-checkbox__input) {
  margin-top: 2px;
  flex-shrink: 0;
}

.agreement-section :deep(.el-checkbox__label) {
  display: none;
}

.agreement-text {
  font-size: 14px;
  color: #606266;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  line-height: 1.5;
  flex: 1;
  margin-top: 0;
}

.agreement-link {
  font-size: 14px;
  text-decoration: none;
}

.submit-section {
  margin-top: 10px;
  margin-bottom: 24px;
}

.submit-btn {
  width: 100%;
  height: 44px;
  font-size: 16px;
  font-weight: 500;
  border-radius: 12px;
  background: linear-gradient(135deg, #3399ff 0%, #00c9ff 100%);
  border: none;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(51, 153, 255, 0.3);

  &:hover {
    box-shadow: 0 4px 12px rgba(51, 153, 255, 0.4);
  }

  &:active {
    box-shadow: 0 1px 4px rgba(51, 153, 255, 0.3);
  }
}

.success-dialog {
  :deep(.el-dialog) {
    border-radius: 16px;
    background: var(--color-card-bg);
    color: var(--color-text);
  }
}

.success-content {
  text-align: center;
  padding: 20px 0;
}

.success-icon {
  font-size: 64px;
  color: #67c23a;
  margin-bottom: 16px;
}

.success-message {
  font-size: 16px;
  color: var(--color-text);
  line-height: 1.6;
  margin: 0;
}

/* 用户协议弹窗样式 */
.agreement-dialog {
  :deep(.el-dialog) {
    border-radius: 16px;
    background: #ffffff;
    color: #606266;
    display: flex;
    flex-direction: column;
    height: 600px;
    max-height: 80vh;
    margin: 0 auto;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }

  :deep(.el-dialog__wrapper) {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  :deep(.el-dialog__header) {
    padding: 20px 24px;
    border-bottom: 1px solid #ebeef5;
    flex-shrink: 0;
  }

  :deep(.el-dialog__title) {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
  }

  :deep(.el-dialog__body) {
    padding: 20px 24px;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
  }

  :deep(.el-dialog__footer) {
    padding: 16px 24px;
    border-top: 1px solid #ebeef5;
    flex-shrink: 0;
  }
}

.agreement-dialog-content {
  max-height: 500px;
  overflow-y: auto;
  padding-right: 8px;

  &::-webkit-scrollbar {
    width: 6px;
  }

  &::-webkit-scrollbar-track {
    background: #f5f5f5;
    border-radius: 3px;
  }

  &::-webkit-scrollbar-thumb {
    background: #c0c4cc;
    border-radius: 3px;

    &:hover {
      background: #a0a4aa;
    }
  }
}

.agreement-text-content {
  font-size: 14px;
  line-height: 1.8;
  color: #606266;

  :deep(p) {
    margin: 6px 0;
    line-height: 1.8;
  }

  :deep(p:first-child) {
    margin-top: 0;
  }
}

.agreement-dialog-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;

  :deep(.el-checkbox) {
    flex: 1;
  }

  :deep(.el-button) {
    margin-left: 16px;
  }
}

/* 隐私政策表格样式 */
:deep(.privacy-table) {
  width: 100%;
  border-collapse: collapse;
  margin: 16px 0;
  font-size: 14px;
  border: 1px solid #ebeef5;

  thead {
    background-color: #f5f7fa;

    th {
      padding: 12px 16px;
      text-align: left;
      font-weight: 600;
      color: #303133;
      border-bottom: 2px solid #ebeef5;
      border-right: 1px solid #ebeef5;

      &:last-child {
        border-right: none;
      }
    }
  }

  tbody {
    tr {
      border-bottom: 1px solid #ebeef5;

      &:hover {
        background-color: #fafafa;
      }

      &:last-child {
        border-bottom: none;
      }
    }

    td {
      padding: 12px 16px;
      color: #606266;
      line-height: 1.6;
      border-right: 1px solid #ebeef5;
      vertical-align: top;

      &:last-child {
        border-right: none;
      }
    }
  }
}

/* 响应式设计 */
@media (max-width: 540px) {
  .register-form {
    :deep(.el-input__inner) {
      height: 52px !important;
      font-size: 17px !important;

      &::placeholder {
        font-size: 16px;
      }
    }

    :deep(.el-form-item) {
      margin-bottom: 20px;
    }
  }

  .submit-btn {
    height: 54px !important;
    font-size: 19px !important;
    margin-top: 12px;
  }

  .agreement-text {
    font-size: 14px;
    line-height: 1.6;
  }

  .agreement-link {
    font-size: 14px;
  }

  :deep(.el-divider__text) {
    font-size: 16px;
  }
}

@media (max-width: 320px) {
  .register-form {
    padding: 16px;
  }

  .register-form-content {
    padding: 24px;
  }

  .el-input {
    :deep(.el-input__inner) {
      height: 44px;
      font-size: 15px;
      padding-left: 50px;
    }
  }

  .submit-btn {
    height: 44px;
    font-size: 15px;
  }
}
</style>