This commit is contained in:
2026-02-12 19:48:28 +08:00
parent 2584e24fa1
commit 94c0af4039
62 changed files with 9597 additions and 127 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770890482377" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13589" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M986.98 786.887c-49.594 71.882-104.832 137.21-158.639 151.227v2.991h-59.58a148.886 148.886 0 0 0 19.816-2.99c-54.015-14.045-129.199-79.528-178.794-151.54a152.007 152.007 0 0 1-7.801-13.003l-11.547 11.495H252.353c-0.598 70.01-18.595 103.375-78.02 104.025-5.876-0.65-39.555 0.338-45.433 0-67.355 0-84.52-51.44-84.598-130.032a389.08 389.08 0 0 0 0-52.013A291.947 291.947 0 0 1 87.499 392.97a89.748 89.748 0 0 1 1.664-10.844c-41.818-28.608-126.39-99.293-18.985-143.192 30.48 0.364 52.403 0.286 65.172 0.182 34.042-77.109 86.575-154.452 159.133-156.22h372.593c71.31 1.768 123.842 79.008 158.222 156.038 3.875 0 30.584 0.572 77.344 0 117.184 47.904 5.877 127.64-29.102 149.927 0.235 1.56 0.52 3.407 0.52 4.109a485.59 485.59 0 0 1 28.45 54.016v7.957c60.518 16.827 103.507 56.72 104.027 74.404v205.867a138.951 138.951 0 0 1-19.557 51.674zM876.506 316.953c22.755 4.056 49.41-24.784 26.005-26.007-20.934 0.208-25.278 0-26.005 0a135.883 135.883 0 0 0 0 26.005zM96.314 290.946c-0.728 0-5.07 0.156-26.006 0-23.406 1.223 3.251 30.063 26.006 26.005a135.857 135.857 0 0 0 0-26.005z m546.628-156.038H314.508c-85.302 2.133-139.368 125.558-164.958 208.05h657.96c-26.085-82.492-80.698-205.918-164.57-208.05z m182.46 280.063a148.653 148.653 0 0 0-3.667-20H135.61a151.825 151.825 0 0 0-3.563 20C25.42 553.038 88.409 658.597 95.638 669.026a512.507 512.507 0 0 1 0 56.018c0 70.998-12.378 112.01 46.968 112.035 5.2 0.286 13.003-0.597 18.205 0 28.008-1.404 40.57-9.518 39.58-52.012v-52.013h390.044V529.346c0.676-22.547 70.66-82.361 158.64-82.361h85.82a54.77 54.77 0 0 1-9.493-32.014z m129.122 149.042c-0.52-17.892-52.975-65.016-118.875-65.016h-74.325c-65.901 0-118.382 47.124-118.876 65.016V726.55a81.374 81.374 0 0 0 14.51 40.544c37.19 56.852 93.624 108.552 134.09 119.63a105.118 105.118 0 0 1-14.875 2.367h44.575v-2.367c40.465-11.078 81.866-62.65 119.082-119.395a113.05 113.05 0 0 0 14.694-40.779V564.013zM898.22 728.815q-10.169 16.228-16.047 26.397a133.465 133.465 0 0 1-45.719-46.63v58.28h-28.867v-56.953a194.814 194.814 0 0 1-47.748 47.748 227.712 227.712 0 0 0-15.447-24.394 173.775 173.775 0 0 0 48.242-41.22h-42.546V668.87h57.5v-18.204h-44.212v-75.99h119.057v75.99h-45.979v18.204h59.321v23.172h-46.63a121.943 121.943 0 0 0 49.075 36.773z m-42.886-102.387V598.81H790.32v27.618h65.015zM742.573 766.862H716.98v-90.424q-6.814 11.703-14.226 22.34a328.33 328.33 0 0 0-6.71-38.593 358.394 358.394 0 0 0 35.344-94.066l27.229 8.115a498.36 498.36 0 0 1-16.046 48.579v144.049zM512.416 655.035H382.384a26.006 26.006 0 0 1 0-52.013h130.032a26.006 26.006 0 0 1 0 52.013z m0-104.026H408.39a26.006 26.006 0 0 1 0-52.012h104.026a26.006 26.006 0 0 1 0 52.012z m-286.07 78.02a78.02 78.02 0 1 1 78.019-78.02 78.02 78.02 0 0 1-78.018 78.02z m0-104.026a26.006 26.006 0 1 0 26.006 26.006 26.006 26.006 0 0 0-26.005-26.005z" p-id="13590" fill="#1296DB"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770890512305" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15911" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M957.610667 113.777778a56.888889 56.888889 0 0 1 56.888889 56.888889v682.666666a56.888889 56.888889 0 0 1-56.888889 56.888889H85.333333a56.888889 56.888889 0 0 1-56.888889-56.888889V170.666667a56.888889 56.888889 0 0 1 56.888889-56.888889h872.277334zM455.111111 284.387556L295.424 284.444444a56.888889 56.888889 0 0 0-53.248 36.920889L170.666667 512v256a28.444444 28.444444 0 0 0 28.444444 28.444444H227.555556a28.444444 28.444444 0 0 0 28.444444-28.444444V739.555556l199.111111-0.056889V512H231.424l64-170.666667H455.111111V284.387556zM881.777778 568.888889h-341.333334v85.333333h341.333334V568.888889zM298.666667 568.888889a42.666667 42.666667 0 1 1 0 85.333333 42.666667 42.666667 0 0 1 0-85.333333zM881.777778 398.222222h-341.333334v85.333334h341.333334V398.222222z" fill="#1296DB" p-id="15912"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770890450573" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11921" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M872.107 793.6c-1.707-1.707-8.534-6.827-11.947-8.533-22.187-10.24-47.787-3.414-59.733 13.653l-1.707 1.707-6.827 6.826c-6.826 6.827-10.24 10.24-17.066 15.36-3.414 3.414-8.534 6.827-11.947 10.24-3.413 1.707-6.827 3.414-10.24 6.827-10.24 8.533-22.187 15.36-34.133 22.187H716.8C657.067 896 587.093 918.187 512 918.187c-64.853 0-122.88-15.36-177.493-40.96-1.707 0-1.707 0-3.414-1.707-8.533-3.413-17.066-8.533-23.893-13.653-1.707-1.707-3.413-1.707-6.827-3.414-8.533-3.413-15.36-8.533-22.186-15.36-1.707 0-1.707-1.706-3.414-1.706-22.186-17.067-42.666-34.134-59.733-54.614-1.707-1.706-3.413-6.826-6.827-8.533-3.413-6.827-8.533-10.24-11.946-17.067-1.707-3.413-6.827-6.826-8.534-10.24-3.413-6.826-8.533-10.24-10.24-17.066-1.706-3.414-3.413-8.534-8.533-13.654-3.413-6.826-6.827-10.24-10.24-17.066-1.707-6.827-6.827-13.654-8.533-17.067-3.414-8.533-8.534-17.067-10.24-25.6-1.707-6.827-3.414-13.653-6.827-17.067-1.707-6.826-3.413-10.24-3.413-17.066-1.707-6.827-3.414-13.654-3.414-18.774-1.706-6.826-1.706-10.24-3.413-17.066s-1.707-13.654-1.707-22.187c0-3.413-1.706-10.24-1.706-15.36 0-10.24-1.707-22.187-1.707-32.427v-6.826c0-13.654 0-29.014 1.707-40.96 10.24-208.214 180.906-368.64 390.826-368.64 11.947 0 25.6 0 39.254 1.706h6.826c10.24 1.707 23.894 1.707 34.134 3.414 3.413 0 6.826 1.706 10.24 1.706 6.826 1.707 15.36 3.414 22.186 6.827 6.827 1.707 10.24 1.707 17.067 3.413 3.413 1.707 8.533 1.707 11.947 3.414l32.426 13.653c1.707 1.707 6.827 1.707 8.534 3.413 6.826 3.414 11.946 6.827 18.773 10.24 11.947 6.827 25.6 15.36 37.547 23.894 1.706 1.706 6.826 3.413 8.533 6.826 3.413 3.414 10.24 8.534 15.36 13.654 3.413 3.413 8.533 8.533 15.36 13.653 6.827 3.413 10.24 10.24 15.36 15.36 3.413 3.413 8.533 8.533 11.947 15.36 1.706 3.413 6.826 6.827 8.533 10.24 3.413 6.827 10.24 13.653 15.36 22.187 1.707 3.413 6.827 8.533 8.533 13.653s6.827 8.533 8.534 13.653c0 1.707 1.706 3.414 1.706 3.414l6.827 13.653c1.707 6.827 6.827 10.24 8.533 17.067 3.414 8.533 6.827 15.36 10.24 23.893 1.707 3.413 3.414 10.24 6.827 15.36 1.707 6.827 3.413 13.653 6.827 17.067 1.706 3.413 1.706 8.533 3.413 13.653l6.827 32.427c0 3.413 1.706 8.533 1.706 13.653 0 6.827 1.707 13.653 1.707 22.187v61.44c0 6.826 0 10.24-1.707 17.066-1.706 18.774-3.413 37.547-10.24 56.32-8.533 29.014 8.534 58.027 39.254 64.854 27.306 8.533 58.026-8.534 64.853-39.254 10.24-44.373 17.067-87.04 17.067-131.413 0-90.453-23.894-182.613-68.267-257.707C858.453 100.693 689.493 0 510.293 0 228.693 0 0 232.107 0 512c0 281.6 228.693 512 510.293 512 119.467 0 233.814-40.96 324.267-116.053l95.573 95.573c10.24 10.24 23.894 15.36 39.254 15.36s30.72-6.827 40.96-18.773c17.066-18.774 11.946-54.614-6.827-73.387L872.107 793.6z" fill="#1296DB" p-id="11922"></path><path d="M791.893 457.387v-13.654c0-8.533-6.826-15.36-15.36-15.36h-52.906c-1.707 0-5.12 0-6.827 1.707l-23.893-71.68c-6.827-40.96-35.84-59.733-71.68-59.733H399.36c-40.96 0-64.853 27.306-71.68 59.733l-23.893 71.68c-1.707-1.707-5.12-1.707-6.827-1.707h-52.907a15.296 15.296 0 0 0-15.36 15.36v13.654c0 8.533 6.827 15.36 15.36 15.36l20.48 3.413c-6.826 11.947-10.24 27.307-10.24 42.667l-6.826 85.333V716.8c0 13.653 11.946 25.6 25.6 25.6h39.253c13.653 0 25.6-11.947 25.6-25.6v-32.427h344.747V716.8c0 13.653 11.946 25.6 25.6 25.6h39.253c13.653 0 25.6-11.947 25.6-25.6V600.747l-6.827-85.334c0-17.066-3.413-30.72-10.24-42.666l18.774-3.414c10.24 3.414 17.066-3.413 17.066-11.946z m-445.44-42.667l17.067-46.08v-1.707c1.707-8.533 5.12-11.946 11.947-18.773h273.066c5.12 8.533 8.534 8.533 10.24 18.773l17.067 47.787 6.827 25.6c-1.707 18.773-23.894 34.133-42.667 34.133H382.293c-18.773 0-40.96-15.36-42.666-34.133l6.826-25.6zM358.4 636.587c-22.187 0-39.253-18.774-39.253-39.254s17.066-39.253 39.253-39.253 39.253 18.773 39.253 39.253-17.066 39.254-39.253 39.254z m305.493 0c-22.186 0-39.253-18.774-39.253-39.254s17.067-39.253 39.253-39.253 39.254 18.773 39.254 39.253-17.067 39.254-39.254 39.254z" fill="#1296DB" p-id="11923"></path></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770890501887" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14787" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M539.022 133.68l84.073 90.177h66.944c56.66-2.69 95.157 30.894 115.308 94.098l0.605 1.924 35.679 113.968a22.951 22.951 0 0 1 10.457-2.96l0.501-0.014h83.937l0.104 0.035a23.333 23.333 0 0 1 23.366 22.921l0.004 0.453v20.332c0 6.2-2.444 12.148-6.82 16.538a23.367 23.367 0 0 1-16.062 6.866l-0.453 0.005-31.114 5.29a147.152 147.152 0 0 1 15.81 67.23l-0.011 1.061 11.685 134.472a103.546 103.546 0 0 1-0.795 11.86c0.438 2.208 0.697 4.447 0.774 6.695l0.02 0.843v161.823a41.698 41.698 0 0 1-41.155 41.699l-0.57 0.004h-63.645a41.69 41.69 0 0 1-29.48-12.216 41.705 41.705 0 0 1-12.208-28.915l-0.004-0.572v-51.866H238.687v51.866a41.705 41.705 0 0 1-12.211 29.487 41.69 41.69 0 0 1-28.91 12.212l-0.57 0.004H133.35a41.69 41.69 0 0 1-29.48-12.216 41.705 41.705 0 0 1-12.208-28.915l-0.004-0.572V725.474c0.027-2.53 0.281-5.053 0.76-7.538a98.324 98.324 0 0 1-0.746-10.538l-0.014-1.322 11.892-134.437a146.738 146.738 0 0 1 15.29-67.206l0.474-0.947-31.943-5.532a23.38 23.38 0 0 1-23.368-22.921l-0.004-0.454v-20.193a23.389 23.389 0 0 1 6.798-16.618 23.367 23.367 0 0 1 16.12-6.89l0.454-0.005h84.007a23.124 23.124 0 0 1 11.176 3.193l0.44 0.265 30.69-114.452c23.34-74.659 63.32-97.394 119.337-96.069l1.702 0.047h67.74l83.435-89.49a30.138 30.138 0 0 0 14.874 13.398l-70.944 76.092h165.9l-71.23-76.4a30.144 30.144 0 0 0 14.523-13.776zM268.418 632.323c-34.883 0-63.16 28.284-63.16 63.174s28.277 63.173 63.16 63.173c34.882 0 63.16-28.284 63.16-63.173 0-34.89-28.278-63.174-63.16-63.174z m487.893 0c-34.883 0-63.16 28.284-63.16 63.174s28.277 63.173 63.16 63.173c34.882 0 63.16-28.284 63.16-63.173 0-34.89-28.278-63.174-63.16-63.174zM332.154 302.071l-29.59 0.001c-4.363 0-8.211 2.824-9.532 6.959l-0.083 0.272-33.538 116.136a60.042 60.042 0 0 0-2.358 16.658c0 32.829 26.347 59.504 59.05 60.034l0.992 0.008h396.787c6.511 0 12.98-1.06 19.151-3.136 31.114-10.472 47.992-43.95 38.063-75.114l-0.31-0.944L732.4 308.887a10.007 10.007 0 0 0-9.484-6.815l-30.512-0.001v127.234c0 11.053-8.96 20.014-20.014 20.014H352.168c-11.053 0-20.014-8.96-20.014-20.014V302.071z m286.68 2.318c-23.537 5.284-52.237 7.927-86.22 7.927l3.962 11.29c13.81 0 26.659-0.48 38.667-1.201v15.133h-40.108v12.251h40.108v15.855h-50.075v12.13h50.075v20.78c0 2.522-1.681 3.843-4.803 3.843-5.524 0-11.168-0.24-17.172-0.72l2.882 12.13h18.613c8.646 0 13.089-4.084 13.089-12.251v-23.781h45.751v-12.131h-45.751v-15.855h36.745v-12.25h-36.745v-16.215c13.93-1.442 26.298-3.603 37.105-6.366l-6.124-10.57z m-125.448 86.718H384.951v12.611h108.435v-12.611z m-11.048-75.548h-86.22v12.37h86.22v-12.37z m-102.15-73.427c-11.054 0-20.014 8.963-20.014 20.018 0 11.056 8.96 20.019 20.013 20.019 11.054 0 20.014-8.963 20.014-20.019 0-1.96-0.282-3.856-0.807-5.647l-6.241 6.693c-3.7 3.969-9.87 4.255-13.919 0.697l-0.223-0.201-0.001-0.002c-4.042-3.77-4.264-10.103-0.494-14.146l6.389-6.852a20.061 20.061 0 0 0-4.718-0.56z m263.182 0c-1.028 0-2.038 0.078-3.025 0.228l6.698 7.184c3.77 4.043 3.548 10.376-0.494 14.146l-0.001 0.002c-3.969 3.7-10.144 3.553-13.934-0.279l-0.208-0.217-7.708-8.267a19.973 19.973 0 0 0-1.342 7.221c0 11.056 8.96 20.019 20.014 20.019 11.054 0 20.014-8.963 20.014-20.019 0-11.055-8.96-20.018-20.014-20.018zM512.28 95c13.816 0 25.017 11.203 25.017 25.023s-11.201 25.022-25.018 25.022-25.017-11.203-25.017-25.022c0-13.82 11.2-25.023 25.017-25.023z" fill="#1296DB" p-id="14788"></path></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770890471906" class="icon" viewBox="0 0 1160 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13313" xmlns:xlink="http://www.w3.org/1999/xlink" width="226.5625" height="200"><path d="M906.57526518 469.74627082l-205.05486221 79.5364314v206.29761896c0 43.49648592 22.36962133 83.26470163 57.78818844 105.0129446l147.26667377 85.12883674 147.26667379-85.12883674c35.41856711-21.74824297 57.78818845-61.51645867 57.78818844-105.0129446V549.28270222l-205.05486223-79.5364314z m87.61435023 148.50943051l-42.87510756 42.87510756h18.64135112c10.5634323 0 18.64135111 8.07791882 18.64135111 18.64135111s-8.07791882 18.64135111-18.64135111 18.64135111h-45.36062105v23.61237807h45.36062105c10.5634323 0 18.64135111 8.07791882 18.64135111 18.64135112s-8.07791882 18.64135111-18.64135111 18.64135111h-45.36062105v51.57440474c0 10.5634323-8.07791882 18.64135111-18.6413511 18.6413511s-18.64135111-8.07791882-18.64135112-18.6413511v-51.57440474h-45.36062103c-10.5634323 0-18.64135111-8.07791882-18.64135112-18.64135111s8.07791882-18.64135111 18.64135112-18.64135112h45.36062103V698.41351111h-45.36062103c-10.5634323 0-18.64135111-8.07791882-18.64135112-18.64135111s8.07791882-18.64135111 18.64135112-18.64135111h18.64135111l-42.87510756-42.87510756c-7.45654045-7.45654045-7.45654045-19.26272948 0-26.09789155 7.45654045-7.45654045 19.26272948-7.45654045 26.09789156 0l61.51645867 61.51645867 61.51645866-61.51645867c7.45654045-7.45654045 19.26272948-7.45654045 26.09789156 0 8.69929718 6.83516208 8.69929718 18.64135111 1.24275674 26.09789155z" p-id="13314" fill="#1296DB"></path><path d="M330.55751585 146.00813985c70.83713422-19.88410785 143.53840355-29.82616178 218.72518637-29.82616177 74.56540445 0 147.26667378 9.94205392 218.72518637 29.82616177l41.01097244 208.16175407H289.54654341l41.01097244-208.16175407z m-80.15780977 505.18061512c-41.01097245 0-74.56540445-35.41856711-74.56540445-79.53643142 0-43.49648592 33.554432-79.53643141 74.56540445-79.5364314s74.56540445 35.41856711 74.56540444 79.5364314c0 43.49648592-33.554432 79.53643141-74.56540444 79.53643142z m690.97274784-237.98791586c31.69029689 0 55.92405333-25.47651318 55.92405333-59.65232356 0-33.554432-24.23375645-59.65232355-55.92405333-59.65232355h-61.51645867l-41.01097243-168.39353837c-7.45654045-27.96202667-26.09789155-49.71026963-52.19578312-57.16681008-155.34459259-43.49648592-318.767104-43.49648592-474.11169659 0-26.09789155 8.07791882-46.60337778 29.82616178-54.05991822 57.16681008L218.70940918 294.51757037h-42.87510755c-31.69029689 0-55.92405333 25.47651318-55.92405333 59.65232355 0 25.47651318 14.91308089 47.84613452 37.28270222 55.30267497-33.554432 19.88410785-55.92405333 59.65232355-55.92405334 103.14880948v158.45148445c0 37.90408059 24.23375645 67.108864 55.92405334 77.05091793v41.6323508c0 21.74824297 16.777216 39.7682157 37.28270223 39.7682157h111.84810666c20.50548622 0 37.28270222-18.01997275 37.28270222-39.7682157v-39.7682157H642.48945778V510.13586489l252.27961837-96.93502578h46.60337777z" p-id="13315" fill="#1296DB"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770889535119" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10803" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M977.454545 721.454545a46.545455 46.545455 0 0 1 46.219637 41.099637L1024 768V884.363636a139.636364 139.636364 0 0 1-131.444364 139.403637L884.363636 1024h-162.909091a46.545455 46.545455 0 0 1-5.445818-92.765091L721.454545 930.909091H884.363636a46.545455 46.545455 0 0 0 46.219637-41.099636L930.909091 884.363636v-116.363636a46.545455 46.545455 0 0 1 46.545454-46.545455z m-930.90909 0a46.545455 46.545455 0 0 1 46.219636 41.099637L93.090909 768V884.363636a46.545455 46.545455 0 0 0 41.099636 46.219637L139.636364 930.909091h162.909091a46.545455 46.545455 0 0 1 5.445818 92.765091L302.545455 1024H139.636364a139.636364 139.636364 0 0 1-139.403637-131.444364L0 884.363636v-116.363636a46.545455 46.545455 0 0 1 46.545455-46.545455z m744.727272-139.636363a46.545455 46.545455 0 0 1 46.219637 41.099636L837.818182 628.363636v139.636364a46.545455 46.545455 0 0 1-92.765091 5.445818L744.727273 768v-139.636364a46.545455 46.545455 0 0 1 46.545454-46.545454z m-372.363636 0a46.545455 46.545455 0 0 1 46.219636 41.099636L465.454545 628.363636V744.727273a46.545455 46.545455 0 0 1-92.76509 5.445818L372.363636 744.727273v-116.363637a46.545455 46.545455 0 0 1 46.545455-46.545454z m-186.181818 0a46.545455 46.545455 0 0 1 46.219636 41.099636L279.272727 628.363636v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445819L186.181818 721.454545v-93.090909a46.545455 46.545455 0 0 1 46.545455-46.545454z m372.363636 0a46.545455 46.545455 0 0 1 46.219636 41.099636L651.636364 628.363636V698.181818a46.545455 46.545455 0 0 1-92.765091 5.445818L558.545455 698.181818v-69.818182a46.545455 46.545455 0 0 1 46.545454-46.545454z m372.363636-116.363637a46.545455 46.545455 0 0 1 5.445819 92.765091L977.454545 558.545455H46.545455a46.545455 46.545455 0 0 1-5.445819-92.765091L46.545455 465.454545h930.90909zM325.818182 256a46.545455 46.545455 0 0 1 46.219636 41.099636L372.363636 302.545455v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445818L279.272727 395.636364v-93.090909a46.545455 46.545455 0 0 1 46.545455-46.545455zM512 186.181818a46.545455 46.545455 0 0 1 46.219636 41.099637L558.545455 232.727273v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L465.454545 395.636364V232.727273a46.545455 46.545455 0 0 1 46.545455-46.545455z m186.181818 69.818182a46.545455 46.545455 0 0 1 46.219637 41.099636L744.727273 302.545455v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445818L651.636364 395.636364v-93.090909a46.545455 46.545455 0 0 1 46.545454-46.545455zM884.363636 0a139.636364 139.636364 0 0 1 139.403637 131.444364L1024 139.636364v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L930.909091 302.545455V139.636364a46.545455 46.545455 0 0 0-41.099636-46.219637L884.363636 93.090909h-162.909091a46.545455 46.545455 0 0 1-5.445818-92.765091L721.454545 0H884.363636zM302.545455 0a46.545455 46.545455 0 0 1 5.445818 92.765091L302.545455 93.090909H139.636364a46.545455 46.545455 0 0 0-46.219637 41.099636L93.090909 139.636364v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L0 302.545455V139.636364A139.636364 139.636364 0 0 1 131.444364 0.232727L139.636364 0h162.909091z" fill="#1296DB" p-id="10804"></path></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,37 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none"
xmlns="http://www.w3.org/2000/svg">
<!-- 左侧小车(简化线条) -->
<path
d="M4.5 13.2L6.7 9.3C7.2 8.5 8 8 9 8H13"
stroke="#1296DB"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
/>
<rect
x="4"
y="13.2"
width="9"
height="5.5"
rx="2"
stroke="#1296DB"
stroke-width="1.8"
/>
<circle cx="7.2" cy="18.7" r="1.5" fill="#1296DB" />
<!-- 右侧盾牌,表示风控/核验 -->
<path
d="M17.5 6.8L19.8 7.6C20.3 7.8 20.6 8.2 20.6 8.7V13.5C20.6 15.3 19.5 17 18 17.6L17.5 17.8L17 17.6C15.5 17 14.4 15.3 14.4 13.5V8.7C14.4 8.2 14.7 7.8 15.2 7.6L17.5 6.8Z"
stroke="#1296DB"
stroke-width="1.6"
stroke-linejoin="round"
/>
<path
d="M16.4 12.4L17.5 13.5L19 11.7"
stroke="#1296DB"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 935 B

View File

@@ -0,0 +1,45 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none"
xmlns="http://www.w3.org/2000/svg">
<!-- 车身与车顶 -->
<path
d="M5 13L7.3 8.8C7.8 7.9 8.7 7.3 9.7 7.3H16.6C17.6 7.3 18.5 7.9 19 8.8L21.3 13"
stroke="#1296DB"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
/>
<rect
x="4.5"
y="13"
width="15"
height="6"
rx="2.2"
stroke="#1296DB"
stroke-width="1.8"
/>
<!-- 车灯与车窗线 -->
<path
d="M8 10H12.5M14.5 10H18"
stroke="#1296DB"
stroke-width="1.4"
stroke-linecap="round"
/>
<!-- 车轮 -->
<circle cx="8.5" cy="19" r="1.6" fill="#1296DB" />
<circle cx="17" cy="19" r="1.6" fill="#1296DB" />
<!-- 放大镜 -->
<circle
cx="20.5"
cy="20.5"
r="3.1"
stroke="#1296DB"
stroke-width="1.6"
/>
<path
d="M22.7 22.7L24 24"
stroke="#1296DB"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -0,0 +1,40 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none"
xmlns="http://www.w3.org/2000/svg">
<!-- 车身(略微前倾,显得更动感) -->
<path
d="M5 13L7.3 8.8C7.8 7.9 8.7 7.3 9.7 7.3H16.6C17.6 7.3 18.5 7.9 19 8.8L21.3 13"
stroke="#1296DB"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
/>
<rect
x="4.5"
y="13"
width="15"
height="6"
rx="2.2"
stroke="#1296DB"
stroke-width="1.8"
/>
<!-- 车轮 -->
<circle cx="8.5" cy="19" r="1.6" fill="#1296DB" />
<circle cx="17" cy="19" r="1.6" fill="#1296DB" />
<!-- 估值/趋势:车顶上方的折线图 -->
<path
d="M6 9.3L9 8L12 9.1L15.2 7.6L18.5 8.4"
stroke="#1296DB"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17.8 7.3H19.7V9.2"
stroke="#1296DB"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 950 B

View File

@@ -0,0 +1,13 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="6" width="19" height="12" rx="2"
stroke="#1296DB" stroke-width="1.8"/>
<rect x="3.5" y="8" width="17" height="2.4" fill="#1296DB" fill-opacity="0.15"/>
<path d="M5.5 14H9" stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M5.5 16H8" stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="17" cy="15" r="3.2"
stroke="#1296DB" stroke-width="1.6"/>
<path d="M15.7 15.1L16.8 16.2L18.6 14.2"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 674 B

View File

@@ -0,0 +1,16 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="6" width="7" height="12" rx="1.2"
stroke="#1296DB" stroke-width="1.8"/>
<rect x="13" y="9" width="7" height="9" rx="1.2"
stroke="#1296DB" stroke-width="1.8"/>
<path d="M7.5 9H9" stroke="#1296DB" stroke-width="1.4" stroke-linecap="round"/>
<path d="M7.5 12H9" stroke="#1296DB" stroke-width="1.4" stroke-linecap="round"/>
<path d="M7.5 15H9" stroke="#1296DB" stroke-width="1.4" stroke-linecap="round"/>
<circle cx="8" cy="4" r="1.6" fill="#1296DB"/>
<circle cx="18" cy="7" r="1.6" fill="#1296DB"/>
<circle cx="18" cy="4" r="1.6" fill-opacity="0.2" stroke="#1296DB" stroke-width="1.2"/>
<path d="M8 4L18 7" stroke="#1296DB" stroke-width="1.4" stroke-linecap="round"/>
<path d="M8 4L18 4" stroke="#1296DB" stroke-width="1.4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 906 B

View File

@@ -0,0 +1,13 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="4" width="20" height="14" rx="2"
stroke="#1296DB" stroke-width="1.8"/>
<circle cx="8" cy="10" r="2.5"
stroke="#1296DB" stroke-width="1.6"/>
<path d="M5.5 14.5C6.3 13.4 7.1 13 8 13C8.9 13 9.7 13.4 10.5 14.5"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M13.5 9H18" stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M13.5 12H18" stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M13.5 15H17" stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 678 B

View File

@@ -0,0 +1,15 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="7" y="3" width="10" height="18" rx="2"
stroke="#1296DB" stroke-width="1.8"/>
<circle cx="12" cy="18" r="0.9" fill="#1296DB"/>
<path d="M4 10C4.8 8.5 5.8 7.3 7.1 6.4"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M4 13C4.7 11.7 5.6 10.6 6.8 9.7"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M20 10C19.2 8.5 18.2 7.3 16.9 6.4"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
<path d="M20 13C19.3 11.7 18.4 10.6 17.2 9.7"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 712 B

View File

@@ -0,0 +1,15 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M4 19H20" stroke="#1296DB" stroke-width="1.8" stroke-linecap="round"/>
<rect x="5" y="12" width="2.8" height="6"
fill="#1296DB" fill-opacity="0.25" stroke="#1296DB" stroke-width="1.4" rx="0.8"/>
<rect x="10" y="10" width="2.8" height="8"
fill="#1296DB" fill-opacity="0.25" stroke="#1296DB" stroke-width="1.4" rx="0.8"/>
<rect x="15" y="7" width="2.8" height="11"
fill="#1296DB" fill-opacity="0.25" stroke="#1296DB" stroke-width="1.4" rx="0.8"/>
<path d="M6.5 7.5L10.5 6L14.5 7.5L18 5"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.3 5H19V6.7"
stroke="#1296DB" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 850 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770631310508" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5559" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M805.347556 778.666667h-266.695112a53.333333 53.333333 0 1 1-106.666666 0v-122.652445a266.780444 266.780444 0 0 1 8.078222-64.540444A39.822222 39.822222 0 0 1 445.326222 512h17.066667l14.023111-49.123556a53.248 53.248 0 0 1 40.789333-37.632 925.411556 925.411556 0 0 1 154.766223-19.911111 684.913778 684.913778 0 0 1 156.103111 18.318223 53.219556 53.219556 0 0 1 38.826666 37.233777l14.620445 51.2h17.066666a39.822222 39.822222 0 0 1 5.233778 79.473778 266.097778 266.097778 0 0 1 8.078222 64.540445v122.680888a53.333333 53.333333 0 1 1-106.666666 0z m-40.021334-93.326223a39.992889 39.992889 0 1 0 40.021334-40.021333 40.021333 40.021333 0 0 0-40.021334 40.021333z m-266.638222 0a39.992889 39.992889 0 1 0 39.992889-40.021333 39.992889 39.992889 0 0 0-40.021333 40.021333zM531.911111 477.013333l-40.817778 124.16a13.340444 13.340444 0 0 0 12.657778 17.493334h336.412445a13.340444 13.340444 0 0 0 12.657777-17.578667l-41.415111-124.216889a26.680889 26.680889 0 0 0-25.287111-18.204444H557.226667a26.624 26.624 0 0 0-25.315556 18.346666zM123.619556 661.333333A38.314667 38.314667 0 0 1 85.333333 623.018667V230.314667A38.314667 38.314667 0 0 1 123.619556 192h691.399111A38.314667 38.314667 0 0 1 853.333333 230.314667v157.070222a90.737778 90.737778 0 0 0-15.36-5.233778 724.337778 724.337778 0 0 0-165.973333-19.484444 943.189333 943.189333 0 0 0-163.100444 20.764444 95.431111 95.431111 0 0 0-73.386667 67.726222l-5.688889 19.626667a80.753778 80.753778 0 0 0-21.361778 7.509333 140.316444 140.316444 0 0 0-49.521778-55.296 85.333333 85.333333 0 1 0-120.576 0A154.339556 154.339556 0 0 0 170.666667 554.666667a21.333333 21.333333 0 1 0 42.666666 0c0-58.823111 38.257778-106.666667 85.333334-106.666667a86.101333 86.101333 0 0 1 76.600889 60.501333 81.834667 81.834667 0 0 0 16.896 106.552889 300.828444 300.828444 0 0 0-2.844445 40.96v5.319111z m345.713777-341.333333a21.333333 21.333333 0 0 0 21.333334 21.333333h256a21.333333 21.333333 0 1 0 0-42.666666h-256a21.333333 21.333333 0 0 0-21.333334 21.333333z m-70.513777 164.010667l-0.312889 0.256z m9.699555-5.688889zM256 362.666667a42.666667 42.666667 0 1 1 42.666667 42.666666 42.666667 42.666667 0 0 1-42.666667-42.666666z" fill="#1296DB" p-id="5560"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770889535119" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10803" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M977.454545 721.454545a46.545455 46.545455 0 0 1 46.219637 41.099637L1024 768V884.363636a139.636364 139.636364 0 0 1-131.444364 139.403637L884.363636 1024h-162.909091a46.545455 46.545455 0 0 1-5.445818-92.765091L721.454545 930.909091H884.363636a46.545455 46.545455 0 0 0 46.219637-41.099636L930.909091 884.363636v-116.363636a46.545455 46.545455 0 0 1 46.545454-46.545455z m-930.90909 0a46.545455 46.545455 0 0 1 46.219636 41.099637L93.090909 768V884.363636a46.545455 46.545455 0 0 0 41.099636 46.219637L139.636364 930.909091h162.909091a46.545455 46.545455 0 0 1 5.445818 92.765091L302.545455 1024H139.636364a139.636364 139.636364 0 0 1-139.403637-131.444364L0 884.363636v-116.363636a46.545455 46.545455 0 0 1 46.545455-46.545455z m744.727272-139.636363a46.545455 46.545455 0 0 1 46.219637 41.099636L837.818182 628.363636v139.636364a46.545455 46.545455 0 0 1-92.765091 5.445818L744.727273 768v-139.636364a46.545455 46.545455 0 0 1 46.545454-46.545454z m-372.363636 0a46.545455 46.545455 0 0 1 46.219636 41.099636L465.454545 628.363636V744.727273a46.545455 46.545455 0 0 1-92.76509 5.445818L372.363636 744.727273v-116.363637a46.545455 46.545455 0 0 1 46.545455-46.545454z m-186.181818 0a46.545455 46.545455 0 0 1 46.219636 41.099636L279.272727 628.363636v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445819L186.181818 721.454545v-93.090909a46.545455 46.545455 0 0 1 46.545455-46.545454z m372.363636 0a46.545455 46.545455 0 0 1 46.219636 41.099636L651.636364 628.363636V698.181818a46.545455 46.545455 0 0 1-92.765091 5.445818L558.545455 698.181818v-69.818182a46.545455 46.545455 0 0 1 46.545454-46.545454z m372.363636-116.363637a46.545455 46.545455 0 0 1 5.445819 92.765091L977.454545 558.545455H46.545455a46.545455 46.545455 0 0 1-5.445819-92.765091L46.545455 465.454545h930.90909zM325.818182 256a46.545455 46.545455 0 0 1 46.219636 41.099636L372.363636 302.545455v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445818L279.272727 395.636364v-93.090909a46.545455 46.545455 0 0 1 46.545455-46.545455zM512 186.181818a46.545455 46.545455 0 0 1 46.219636 41.099637L558.545455 232.727273v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L465.454545 395.636364V232.727273a46.545455 46.545455 0 0 1 46.545455-46.545455z m186.181818 69.818182a46.545455 46.545455 0 0 1 46.219637 41.099636L744.727273 302.545455v93.090909a46.545455 46.545455 0 0 1-92.765091 5.445818L651.636364 395.636364v-93.090909a46.545455 46.545455 0 0 1 46.545454-46.545455zM884.363636 0a139.636364 139.636364 0 0 1 139.403637 131.444364L1024 139.636364v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L930.909091 302.545455V139.636364a46.545455 46.545455 0 0 0-41.099636-46.219637L884.363636 93.090909h-162.909091a46.545455 46.545455 0 0 1-5.445818-92.765091L721.454545 0H884.363636zM302.545455 0a46.545455 46.545455 0 0 1 5.445818 92.765091L302.545455 93.090909H139.636364a46.545455 46.545455 0 0 0-46.219637 41.099636L93.090909 139.636364v162.909091a46.545455 46.545455 0 0 1-92.765091 5.445818L0 302.545455V139.636364A139.636364 139.636364 0 0 1 131.444364 0.232727L139.636364 0h162.909091z" fill="#1296DB" p-id="10804"></path></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -268,6 +268,11 @@ const featureMap = {
name: "违约失信",
component: defineAsyncComponent(() => import("@/ui/CFLXG3D56.vue")),
},
FLXG3A9B: {
name: "限高被执行人",
component: defineAsyncComponent(() => import("@/ui/CFLX3A9B.vue")),
remark: '限高被执行人模块展示法院公布的限制高消费记录,并同时提示历史失信记录,帮助识别严重履约风险。'
},
FLXG7E8F: {
name: "司法涉诉",
component: defineAsyncComponent(() =>
@@ -288,6 +293,16 @@ const featureMap = {
),
remark: '人企关系加强版提供全面的企业关联分析,包括投资企业记录、高管任职记录和涉诉风险等多维度信息。'
},
QYGL66SL: {
name: "企业司法涉诉",
component: defineAsyncComponent(() => import("@/ui/CQYGL66SL.vue")),
remark: '企业司法涉诉模块展示被查询企业在全国法院公开信息中的民事案件情况,包括案件数量、结案情况、涉案地域与案由分布等,用于评估企业的司法风险。'
},
QYGL2S0W: {
name: "失信被执行人",
component: defineAsyncComponent(() => import("@/ui/CQYGL2S0W.vue")),
remark: '失信被执行人模块展示最高法院公布的失信记录,包括履行情况、执行法院、执行依据等信息,是评估严重违约风险的重要参考。'
},
// 人企关系加强版拆分模块
CQYGL3F8E_Investment: {
name: "投资企业记录",
@@ -329,6 +344,44 @@ const featureMap = {
name: "名下车辆",
component: defineAsyncComponent(() => import("@/ui/CQCXG9P1C.vue")),
},
QCXGGB2Q: {
name: "人车核验简版",
component: defineAsyncComponent(() => import("@/ui/CQCXGGB2Q.vue")),
},
QCXGYTS2: {
name: "人车核验详版",
component: defineAsyncComponent(() => import("@/ui/CQCXGYTS2.vue")),
},
QCXG5F3A: {
name: "名下车辆(车牌)",
component: defineAsyncComponent(() => import("@/ui/QCXG5F3A.vue")),
},
QCXG4D2E: { name: "名下车辆(数量)", component: defineAsyncComponent(() => import("@/ui/CQCXG4D2E.vue")) },
QCXG5U0Z: { name: "车辆静态信息查询", component: defineAsyncComponent(() => import("@/ui/CQCXG5U0Z.vue")) },
QCXG1U4U: { name: "车辆里程记录(混合查询)", component: defineAsyncComponent(() => import("@/ui/CQCXG1U4U.vue")) },
QCXGY7F2: { name: "二手车VIN估值", component: defineAsyncComponent(() => import("@/ui/CQCXGY7F2.vue")) },
QCXG1H7Y: { name: "车辆过户简版查询", component: defineAsyncComponent(() => import("@/ui/CQCXG1H7Y.vue")) },
QCXG4I1Z: { name: "车辆过户详版查询", component: defineAsyncComponent(() => import("@/ui/CQCXG4I1Z.vue")) },
QCXG3Y6B: { name: "车辆维保简版查询", component: defineAsyncComponent(() => import("@/ui/CQCXG3Y6B.vue")) },
QCXG3Z3L: { name: "车辆维保详细版查询", component: defineAsyncComponent(() => import("@/ui/CQCXG3Z3L.vue")) },
QCXGP00W: { name: "车辆出险详版查询", component: defineAsyncComponent(() => import("@/ui/CQCXGP00W.vue")) },
QCXG6B4E: { name: "车辆出险记录核验", component: defineAsyncComponent(() => import("@/ui/CQCXG6B4E.vue")) },
IVYZ9K7F: { name: "公安二要素认证", component: defineAsyncComponent(() => import("@/ui/CIVYZ9K7F.vue")) },
IVYZA1B3: { name: "公安三要素", component: defineAsyncComponent(() => import("@/ui/CIVYZA1B3.vue")) },
IVYZ6M8P: { name: "职业资格证书查询", component: defineAsyncComponent(() => import("@/ui/CIVYZ6M8P.vue")) },
JRZQ8B3C: { name: "个人消费能力等级", component: defineAsyncComponent(() => import("@/ui/JRZQ8B3C/index.vue")) },
YYSY3M8S: { name: "运营商二要素", component: defineAsyncComponent(() => import("@/ui/CYYSY3M8S.vue")) },
YYSYK9R4: { name: "全网手机三要素验证周更", component: defineAsyncComponent(() => import("@/ui/CYYSYK9R4.vue")) },
YYSYF2T7: { name: "号码二次放号", component: defineAsyncComponent(() => import("@/ui/CYYSYF2T7.vue")) },
YYSYK8R3: { name: "手机空号检测", component: defineAsyncComponent(() => import("@/ui/CYYSYK8R3.vue")) },
YYSYS9W1: { name: "手机携号转网", component: defineAsyncComponent(() => import("@/ui/CYYSYS9W1.vue")) },
YYSYE7V5: { name: "手机在网状态", component: defineAsyncComponent(() => import("@/ui/CYYSYE7V5.vue")) },
YYSYP0T4: { name: "手机号码在网时长", component: defineAsyncComponent(() => import("@/ui/CYYSYP0T4.vue")) },
YYSY6F2B: { name: "手机消费区间验证", component: defineAsyncComponent(() => import("@/ui/CYYSY6F2B.vue")) },
YYSY9E4A: { name: "手机号码归属地核验", component: defineAsyncComponent(() => import("@/ui/CYYSY9E4A.vue")) },
QYGL5F6A: { name: "名下企业关联", component: defineAsyncComponent(() => import("@/ui/CQYGL5F6A.vue")) },
JRZQACAB: { name: "银行卡四要素验证(详版)", component: defineAsyncComponent(() => import("@/ui/CJRZQACAB.vue")) },
JRZQ0B6Y: { name: "银行卡黑名单", component: defineAsyncComponent(() => import("@/ui/CJRZQ0B6Y.vue")) },
BehaviorRiskScan: {
name: "风险行为扫描",
component: defineAsyncComponent(() =>
@@ -595,13 +648,22 @@ const featureRiskLevels = {
// 🟡 中风险类 - 权重 5
'QYGL3F8E': 5, // 人企关系加强版
'QCXG7A2B': 5, // 名下车辆
'QCXGGB2Q': 5, // 人车核验简版
'QCXGYTS2': 5, // 人车核验详版
'QCXG5F3A': 5, // 名下车辆(车牌)
'QCXG4D2E': 5, 'QCXG5U0Z': 5, 'QCXG1U4U': 5, 'QCXGY7F2': 5, 'QCXG1H7Y': 5,
'QCXG4I1Z': 5, 'QCXG3Y6B': 5, 'QCXG3Z3L': 5, 'QCXGP00W': 5, 'QCXG6B4E': 5,
'JRZQ09J8': 5, // 收入评估
'IVYZ9K7F': 5, 'IVYZA1B3': 5, 'IVYZ6M8P': 5, 'JRZQ8B3C': 3,
'YYSY3M8S': 5, 'YYSYK9R4': 5, 'YYSYF2T7': 5, 'YYSYK8R3': 5,
'YYSYS9W1': 5, 'YYSYE7V5': 5, 'YYSYP0T4': 5, 'YYSY6F2B': 5,
'YYSY9E4A': 3, 'QYGL5F6A': 5, 'JRZQACAB': 5, 'JRZQ0B6Y': 5,
'QYGL66SL': 10, 'QYGL2S0W': 10, 'FLXG3A9B': 10,
// 🔵 低风险类 - 权重 3
'IVYZ5733': 3, // 婚姻状态
'IVYZ9A2B': 3, // 学历信息
'IVYZ3P9M': 3, // 学历信息查询(实时版)
'JRZQ8B3C': 3, // 个人消费能力
// 📊 复合报告类 - 按子模块动态计算
'DWBG8B4D': 0, // 谛听多维报告(由子模块计算)

View File

@@ -1,15 +1,13 @@
<template>
<div class="inquire-bg min-h-screen relative" :class="isDefaultBackground ? 'pt-12' : 'pt-48'" :style="backgroundStyle">
<div class="inquire-bg min-h-screen relative" :class="isDefaultBackground ? 'pt-12' : 'pt-48'"
:style="backgroundStyle">
<!-- 主要内容区域 - 覆盖背景图片 -->
<div class="min-h-screen relative mx-4 pb-12">
<!-- 产品卡片牌匾效果 - 使用背景图片 -->
<div class="absolute -top-[12px] left-1/2 transform -translate-x-1/2 w-[140px]">
<div class="trapezoid-bg-image flex items-center justify-center" :style="trapezoidBgStyle">
<div class="text-xl whitespace-nowrap" :style="trapezoidTextStyle">{{
featureData.product_name }}</div>
</div>
</div>
<div class="card-container">
<!-- 卡片头部产品名 -->
<div class="card-header text-lg text-center font-bold text-gray-800 mb-4 pb-3 border-b border-gray-200">
{{ featureData.product_name }}
</div>
<!-- 基本信息标题 -->
<div class="mb-6 flex items-center">
<SectionTitle title="基本信息" />
@@ -20,22 +18,201 @@
</div>
<!-- 表单输入区域 -->
<div class="space-y-4 mb-6">
<div class="flex items-center py-3 border-b border-gray-100">
<!-- 姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('name')">
<label for="name" class="w-20 font-medium text-gray-700">姓名</label>
<input v-model="formData.name" id="name" type="text" placeholder="请输入正确的姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<div class="flex items-center py-3 border-b border-gray-100">
<!-- 身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCard')">
<label for="idCard" class="w-20 font-medium text-gray-700">身份证号</label>
<input v-model="formData.idCard" id="idCard" type="text" placeholder="请输入准确的身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<div class="flex items-center py-3 border-b border-gray-100">
<!-- 企业名称 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('entName')">
<label for="entName" class="w-20 font-medium text-gray-700">企业名称</label>
<input v-model="formData.entName" id="entName" type="text" placeholder="请输入企业名称"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 统一社会信用代码 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('entCode')">
<label for="entCode" class="w-32 font-medium text-gray-700">统一社会信用代码</label>
<input v-model="formData.entCode" id="entCode" type="text" placeholder="请输入统一社会信用代码"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻男方姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('nameMan')">
<label for="nameMan" class="w-24 font-medium text-gray-700">男方姓名</label>
<input v-model="formData.nameMan" id="nameMan" type="text" placeholder="请输入男方姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻男方身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCardMan')">
<label for="idCardMan" class="w-28 font-medium text-gray-700">男方身份证号</label>
<input v-model="formData.idCardMan" id="idCardMan" type="text" placeholder="请输入男方身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻女方姓名 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('nameWoman')">
<label for="nameWoman" class="w-24 font-medium text-gray-700">女方姓名</label>
<input v-model="formData.nameWoman" id="nameWoman" type="text" placeholder="请输入女方姓名"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 双人婚姻女方身份证号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('idCardWoman')">
<label for="idCardWoman" class="w-28 font-medium text-gray-700">女方身份证号</label>
<input v-model="formData.idCardWoman" id="idCardWoman" type="text" placeholder="请输入女方身份证号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 手机号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('mobile')">
<label for="mobile" class="w-20 font-medium text-gray-700">手机号</label>
<input v-model="formData.mobile" id="mobile" type="tel" placeholder="请输入手机号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<div class="flex items-center py-3 border-b border-gray-100">
<!-- 车牌号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('carLicense')">
<label for="carLicense" class="w-20 font-medium text-gray-700">车牌号</label>
<input v-model="formData.carLicense" id="carLicense" type="text" placeholder="请输入车牌号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 号牌类型 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('carType')">
<label for="carType" class="w-24 font-medium text-gray-700">号牌类型</label>
<input v-model="formData.carType" id="carType" type="text" placeholder="请输入号牌类型"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 关系类型(名下车辆数量) -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('userType')">
<label for="userType" class="w-28 font-medium text-gray-700">关系类型</label>
<div class="flex-1">
<van-field :model-value="userTypeDisplay" readonly is-link placeholder="请选择关系类型"
input-align="right" @click="showUserTypePicker = true" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showUserTypePicker" round position="bottom">
<van-picker :columns="userTypeOptions" @confirm="onUserTypeConfirm"
@cancel="showUserTypePicker = false" />
</van-popup>
</div>
<!-- 车架号/VIN -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('vinCode')">
<label for="vinCode" class="w-24 font-medium text-gray-700">车架号/VIN</label>
<input v-model="formData.vinCode" id="vinCode" type="text" placeholder="请输入车架号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 行驶证图片 URL(里程混合等)行驶证形状框上传接口返回 URL限制 3MB -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('imageUrl')">
<p class="text-sm text-gray-600 mb-2">上传行驶证照片将生成可访问链接</p>
<input ref="imageUrlFileInputRef" type="file" accept="image/*" class="hidden"
@change="onImageUrlFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="imageUrlFileInputRef?.click()"
@keydown.enter.space.prevent="imageUrlFileInputRef?.click()">
<img v-if="formData.imageUrl" :src="formData.imageUrl" alt="行驶证" class="vlphoto-preview"
@error="onImageUrlError" />
<div v-else class="vlphoto-placeholder">
<span v-if="imageUrlUploading" class="vlphoto-hint">上传中...</span>
<template v-else>
<span class="vlphoto-icon">📄</span>
<span class="vlphoto-hint">点击上传行驶证</span>
</template>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">图片不能超过 3M</p>
</div>
<!-- 车辆所在地区 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('vehicleLocation')">
<label for="vehicleLocation" class="w-28 font-medium text-gray-700">车辆所在地区</label>
<input v-model="formData.vehicleLocation" id="vehicleLocation" type="text" placeholder="必填"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 初次登记日期 -->
<div class="flex items-center py-3 border-b border-gray-100"
v-if="isHasInput('firstRegistrationDate')">
<label for="firstRegistrationDate" class="w-28 font-medium text-gray-700">初次登记日期</label>
<div class="flex-1">
<van-field :model-value="firstRegistrationDateDisplay" readonly is-link placeholder="请选择日期"
input-align="right" @click="openFirstRegistrationDatePicker" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showFirstRegistrationDatePicker" round position="bottom">
<van-date-picker v-model="firstRegistrationDateColumns" title="选择初次登记日期"
:columns-type="['year', 'month']" @confirm="onFirstRegistrationDateConfirm"
@cancel="showFirstRegistrationDatePicker = false" />
</van-popup>
</div>
<!-- 行驶证图片上传(出险详版)行驶证形状框点击上传限制 3MB -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('vlphotoData')">
<p class="text-sm text-gray-600 mb-2">上传行驶证照片</p>
<input ref="vlphotoFileInputRef" type="file" accept="image/*" class="hidden"
@change="onVlphotoFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="vlphotoFileInputRef?.click()"
@keydown.enter.space.prevent="vlphotoFileInputRef?.click()">
<img v-if="vlphotoPreviewUrl" :src="vlphotoPreviewUrl" alt="行驶证" class="vlphoto-preview" />
<div v-else class="vlphoto-placeholder">
<span class="vlphoto-icon">📄</span>
<span class="vlphoto-hint">点击上传行驶证</span>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">图片不能超过 3M</p>
</div>
<!-- 银行卡号 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('bankCard')">
<label for="bankCard" class="w-24 font-medium text-gray-700">银行卡号</label>
<input v-model="formData.bankCard" id="bankCard" type="text" placeholder="请输入银行卡号"
class="flex-1 border-none outline-none" @click="handleInputClick" />
</div>
<!-- 人像图片上传公安三要素人像照片框点击上传限制 500KBjpg/png15~4000px -->
<div class="py-3 border-b border-gray-100" v-if="isHasInput('photoData')">
<p class="text-sm text-gray-600 mb-2">上传人像照片</p>
<input type="file" accept="image/jpeg,image/png" class="hidden" ref="photoDataInputRef"
@change="onPhotoDataFileChange" />
<div class="vlphoto-frame" role="button" tabindex="0" @click="photoDataInputRef?.click()"
@keydown.enter.space.prevent="photoDataInputRef?.click()">
<img v-if="photoDataPreviewUrl" :src="photoDataPreviewUrl" alt="人像照片"
class="vlphoto-preview" />
<div v-else class="vlphoto-placeholder">
<span class="vlphoto-icon">📷</span>
<span class="vlphoto-hint">点击上传人像照片</span>
</div>
</div>
<p class="mt-1 text-xs text-amber-600">
图片格式需为 jpg/png大小不超过 500KB宽高需在 15px ~ 4000px 之间
</p>
</div>
<!-- 是否取得用户授权 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('authorized')">
<label for="authorized" class="w-32 font-medium text-gray-700">用户授权</label>
<div class="flex-1">
<van-field :model-value="authorizedDisplay" readonly is-link placeholder="请选择"
input-align="right" @click="showAuthorizedPicker = true" class="!p-0 !min-h-0" />
</div>
<van-popup v-model:show="showAuthorizedPicker" round position="bottom">
<van-picker :columns="authorizedOptions" @confirm="onAuthorizedConfirm"
@cancel="showAuthorizedPicker = false" />
</van-popup>
</div>
<!-- 验证码 -->
<div class="flex items-center py-3 border-b border-gray-100" v-if="isHasInput('verificationCode')">
<label for="verificationCode" class="w-20 font-medium text-gray-700">验证码</label>
<input v-model="formData.verificationCode" id="verificationCode" placeholder="请输入验证码"
maxlength="6" class="flex-1 border-none outline-none" @click="handleInputClick" />
@@ -276,13 +453,14 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from "vue";
import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
import { aesEncrypt } from "@/utils/crypto";
import { useRoute, useRouter } from "vue-router";
import { useUserStore } from "@/stores/userStore";
import { useDialogStore } from "@/stores/dialogStore";
import { useEnv } from "@/composables/useEnv";
import { showConfirmDialog } from "vant";
import { showConfirmDialog, showToast, DatePicker } from "vant";
import { useInquireForm } from "@/composables/useInquireForm";
import Payment from "@/components/Payment.vue";
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
@@ -357,29 +535,56 @@ const showPayment = ref(false);
const pendingPayment = ref(false);
const queryId = ref(null);
const productBackground = ref('');
const trapezoidBgImage = ref('');
const isDefaultBackground = ref(false);
const isCountingDown = ref(false);
const countdown = ref(60);
const vlphotoFileInputRef = ref(null);
const vlphotoFileName = ref('');
const vlphotoPreviewUrl = ref('');
const photoDataInputRef = ref(null);
const photoDataFileName = ref('');
const photoDataPreviewUrl = ref('');
const imageUrlFileInputRef = ref(null);
const imageUrlUploading = ref(false);
const showUserTypePicker = ref(false);
const showAuthorizedPicker = ref(false);
const showFirstRegistrationDatePicker = ref(false);
// 初次登记日期(年、月)选择的列值,例如 ['2024', '01']
const firstRegistrationDateColumns = ref([]);
// 使用传入的featureData或创建响应式引用
const featureData = computed(() => props.featureData || {});
// 表单数据
const formData = reactive({
name: "",
idCard: "",
mobile: "",
verificationCode: "",
agreeToTerms: false
// 使用通用查询表单 composable根据 feature 自动决定字段)
const { formData, isPhoneNumberValid, isIdCardValid, isHasInput, buildRequestPayload } = useInquireForm(feature);
const userTypeOptions = [
{ text: "ETC开户人", value: "1" },
{ text: "车辆所有人", value: "2" },
{ text: "ETC经办人", value: "3" },
];
const authorizedOptions = [
{ text: "是", value: "1" },
{ text: "否", value: "0" },
];
const userTypeDisplay = computed(() => {
const opt = userTypeOptions.find((o) => o.value === formData.userType);
return opt ? opt.text : "";
});
// 计算属性
const isPhoneNumberValid = computed(() => {
return /^1[3-9]\d{9}$/.test(formData.mobile);
const authorizedDisplay = computed(() => {
const opt = authorizedOptions.find((o) => o.value === formData.authorized);
return opt ? opt.text : "";
});
const isIdCardValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCard));
const firstRegistrationDateDisplay = computed(() => formData.firstRegistrationDate || "");
// 额外字段校验(复用旧项目的规则思路)
const isIdCardManValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCardMan || ""));
const isIdCardWomanValid = computed(() => /^\d{17}[\dX]$/i.test(formData.idCardWoman || ""));
const isCreditCodeValid = computed(() => /^.{18}$/.test(formData.entCode || ""));
const isLoggedIn = computed(() => userStore.isLoggedIn);
@@ -404,57 +609,40 @@ const backgroundStyle = computed(() => {
return {};
});
// 动态加载牌匾背景图片
const loadTrapezoidBackground = async () => {
try {
let bgModule;
if (props.feature === 'marriage') {
bgModule = await import("@/assets/images/report/title_inquire_bg_red.png");
} else if (props.feature === 'homeservice') {
bgModule = await import("@/assets/images/report/title_inquire_bg_green.png");
} else {
bgModule = await import("@/assets/images/report/title_inquire_bg.png");
}
trapezoidBgImage.value = bgModule.default;
} catch (error) {
console.warn(`Failed to load trapezoid background image:`, error);
// 回退到默认图片
try {
const defaultModule = await import("@/assets/images/report/title_inquire_bg.png");
trapezoidBgImage.value = defaultModule.default;
} catch (e) {
console.error('Failed to load default trapezoid background:', e);
}
}
};
// 牌匾背景图片样式
const trapezoidBgStyle = computed(() => {
if (trapezoidBgImage.value) {
return {
backgroundImage: `url(${trapezoidBgImage.value})`,
};
}
return {};
});
// 牌匾文字样式
const trapezoidTextStyle = computed(() => {
// homeservice 和 marriage 使用白色文字
if (props.feature === 'homeservice' || props.feature === 'marriage') {
return {
color: 'white',
};
}
// 其他情况使用默认字体色(不设置 color使用浏览器默认或继承
return {};
});
// 获取功能图标
const getFeatureIcon = (apiId) => {
const iconMap = {
JRZQ4AA8: "/inquire_icons/huankuanyali.svg", // 还款压力
QCXG7A2B: "/inquire_icons/mingxiacheliang.svg", // 名下车辆
QCXGGB2Q: "/inquire_icons/mingxiacheliang.svg", // 人车核验简版
QCXGYTS2: "/inquire_icons/mingxiacheliang.svg", // 人车核验详版
QCXG5F3A: "/inquire_icons/mingxiacheliang.svg", // 名下车辆(车牌)
QCXG4D2E: "/inquire_icons/mingxiacheliang.svg",
QCXG5U0Z: "/inquire_icons/mingxiacheliang.svg",
QCXG1U4U: "/inquire_icons/mingxiacheliang.svg",
QCXGY7F2: "/inquire_icons/mingxiacheliang.svg",
QCXG1H7Y: "/inquire_icons/mingxiacheliang.svg",
QCXG4I1Z: "/inquire_icons/mingxiacheliang.svg",
QCXG3Y6B: "/inquire_icons/mingxiacheliang.svg",
QCXG3Z3L: "/inquire_icons/mingxiacheliang.svg",
QCXGP00W: "/inquire_icons/mingxiacheliang.svg",
QCXG6B4E: "/inquire_icons/mingxiacheliang.svg",
IVYZ9K7F: "/inquire_icons/mingxiacheliang.svg",
IVYZA1B3: "/inquire_icons/mingxiacheliang.svg",
IVYZ6M8P: "/inquire_icons/mingxiacheliang.svg",
JRZQ8B3C: "/inquire_icons/mingxiacheliang.svg",
YYSY3M8S: "/inquire_icons/mingxiacheliang.svg",
YYSYK9R4: "/inquire_icons/mingxiacheliang.svg",
YYSYF2T7: "/inquire_icons/mingxiacheliang.svg",
YYSYK8R3: "/inquire_icons/mingxiacheliang.svg",
YYSYS9W1: "/inquire_icons/mingxiacheliang.svg",
YYSYE7V5: "/inquire_icons/mingxiacheliang.svg",
YYSYP0T4: "/inquire_icons/mingxiacheliang.svg",
YYSY6F2B: "/inquire_icons/mingxiacheliang.svg",
YYSY9E4A: "/inquire_icons/mingxiacheliang.svg",
QYGL5F6A: "/inquire_icons/mingxiacheliang.svg",
JRZQACAB: "/inquire_icons/mingxiacheliang.svg",
JRZQ0B6Y: "/inquire_icons/mingxiacheliang.svg",
BehaviorRiskScan: "/inquire_icons/fengxianxingwei.svg", // 风险行为扫描
IVYZ5733: "/inquire_icons/hunyinzhuangtai.svg", // 婚姻状态
PersonEnterprisePro: "/inquire_icons/renqiguanxi.svg", // 人企关系加强版
@@ -494,10 +682,161 @@ const validateField = (field, value, validationFn, errorMessage) => {
return true;
};
const defaultInput = ["name", "idCard", "mobile", "verificationCode"];
const isHasInput = (input) => {
return defaultInput.includes(input);
};
const MAX_VLPHOTO_SIZE = 3 * 1024 * 1024; // 3MB
function onVlphotoFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
if (file.size > MAX_VLPHOTO_SIZE) {
showToast({ message: '图片不能超过 3M' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
let base64 = dataUrl;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
formData.vlphotoData = base64;
vlphotoFileName.value = file.name;
vlphotoPreviewUrl.value = dataUrl;
};
reader.readAsDataURL(file);
e.target.value = '';
}
const MAX_PHOTO_DATA_SIZE = 500 * 1024; // 500KB 公安三要素
function onPhotoDataFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
// 大小校验
if (file.size > MAX_PHOTO_DATA_SIZE) {
showToast({ message: '人像图片不能超过 500KB' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
if (typeof dataUrl !== 'string') {
showToast({ message: '图片读取失败,请重试' });
e.target.value = '';
return;
}
// 尺寸校验:宽高需在 15px ~ 4000px 之间
const img = new Image();
img.onload = () => {
const w = img.width;
const h = img.height;
if (w < 15 || h < 15 || w > 4000 || h > 4000) {
showToast({ message: '人像图片宽高需在 15px ~ 4000px 之间' });
photoDataFileName.value = '';
photoDataPreviewUrl.value = '';
e.target.value = '';
return;
}
let base64 = dataUrl;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
formData.photoData = base64;
photoDataFileName.value = file.name;
photoDataPreviewUrl.value = dataUrl;
e.target.value = '';
};
img.onerror = () => {
showToast({ message: '图片格式不正确,请选择 jpg 或 png 图片' });
e.target.value = '';
};
img.src = dataUrl;
};
reader.readAsDataURL(file);
}
async function onImageUrlFileChange(e) {
const file = e.target?.files?.[0];
if (!file) return;
if (file.size > MAX_VLPHOTO_SIZE) {
showToast({ message: '图片不能超过 3M' });
e.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = async () => {
let base64 = reader.result;
if (typeof base64 === 'string' && base64.includes(',')) {
base64 = base64.split(',')[1];
}
imageUrlUploading.value = true;
try {
const { data, error } = await useApiFetch('/upload/image')
.post({ image_base64: base64 })
.json();
if (data.value && data.value.code === 200 && data.value.data?.url) {
formData.imageUrl = data.value.data.url;
} else {
showToast({ message: (data.value?.msg || error.value?.message) || '上传失败' });
}
} finally {
imageUrlUploading.value = false;
}
e.target.value = '';
};
reader.readAsDataURL(file);
}
function onImageUrlError() {
formData.imageUrl = '';
}
function openFirstRegistrationDatePicker() {
if (formData.firstRegistrationDate) {
const [y, m] = (formData.firstRegistrationDate || "").split("-");
if (y && m) {
firstRegistrationDateColumns.value = [y, m];
} else {
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1).padStart(2, "0");
firstRegistrationDateColumns.value = [year, month];
}
} else {
const now = new Date();
const year = String(now.getFullYear());
const month = String(now.getMonth() + 1).padStart(2, "0");
firstRegistrationDateColumns.value = [year, month];
}
showFirstRegistrationDatePicker.value = true;
}
function onFirstRegistrationDateConfirm({ selectedValues }) {
const [year, month] = selectedValues || [];
if (year && month) {
formData.firstRegistrationDate = `${year}-${month}`;
}
showFirstRegistrationDatePicker.value = false;
}
function onUserTypeConfirm({ selectedOptions }) {
const option = selectedOptions?.[0];
if (option) {
formData.userType = option.value;
}
showUserTypePicker.value = false;
}
function onAuthorizedConfirm({ selectedOptions }) {
const option = selectedOptions?.[0];
if (option) {
formData.authorized = option.value;
}
showAuthorizedPicker.value = false;
}
// 处理绑定手机号成功的回调
function handleBindSuccess() {
@@ -567,6 +906,84 @@ function handleSubmit() {
(v) => isIdCardValid.value,
"请输入有效的身份证号码"
) ||
!validateField(
"entName",
formData.entName,
(v) => v,
"请输入企业名称"
) ||
!validateField(
"entCode",
formData.entCode,
(v) => isCreditCodeValid.value,
"请输入统一社会信用代码"
) ||
!validateField(
"nameMan",
formData.nameMan,
(v) => v,
"请输入男方姓名"
) ||
!validateField(
"idCardMan",
formData.idCardMan,
(v) => isIdCardManValid.value,
"请输入有效的男方身份证号码"
) ||
!validateField(
"nameWoman",
formData.nameWoman,
(v) => v,
"请输入女方姓名"
) ||
!validateField(
"idCardWoman",
formData.idCardWoman,
(v) => isIdCardWomanValid.value,
"请输入有效的女方身份证号码"
) ||
!validateField(
"carLicense",
formData.carLicense,
(v) => v,
"请输入车牌号"
) ||
!validateField(
"carType",
formData.carType,
(v) => v,
"请输入号牌类型"
) ||
!validateField(
"vinCode",
formData.vinCode,
(v) => v && v.trim(),
"请输入车架号/VIN"
) ||
!validateField(
"imageUrl",
formData.imageUrl,
(v) => props.feature !== "toc_VehicleMileageMixed" || (v && v.trim()),
"请输入行驶证图片链接"
) ||
!validateField(
"vehicleLocation",
formData.vehicleLocation,
(v) => v && v.trim(),
"请输入车辆所在地区"
) ||
!validateField(
"firstRegistrationDate",
formData.firstRegistrationDate,
(v) => v && v.trim() && /^\d{4}-\d{2}$/.test(v.trim()),
"请输入初次登记日期(yyyy-MM)"
) ||
!validateField(
"vlphotoData",
formData.vlphotoData,
(v) => v && v.trim(),
"请提供行驶证图片(base64)"
) ||
!validateField(
"verificationCode",
formData.verificationCode,
@@ -587,12 +1004,8 @@ function handleSubmit() {
}
async function submitRequest() {
const req = {
name: formData.name,
id_card: formData.idCard,
mobile: formData.mobile,
code: formData.verificationCode
};
// 根据当前 feature 与字段配置组装请求体
const req = buildRequestPayload();
const reqStr = JSON.stringify(req);
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
@@ -688,7 +1101,6 @@ const toHistory = () => {
// 生命周期
onMounted(async () => {
await loadBackgroundImage();
await loadTrapezoidBackground();
});
// 加载背景图片
@@ -719,7 +1131,6 @@ onUnmounted(() => {
});
watch(feature, async () => {
await loadBackgroundImage();
await loadTrapezoidBackground();
});
</script>
@@ -752,19 +1163,11 @@ button:active {
transform: translateY(0);
}
/* 梯形背景图片样式 */
.trapezoid-bg-image {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 44px;
}
/* 卡片容器样式 */
.card-container {
background: white;
border-radius: 8px;
padding: 32px 16px;
padding: 16px;
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
}
@@ -792,4 +1195,58 @@ button:active {
border-radius: 50%;
margin-right: 8px;
}
/* 行驶证形状上传框 */
.vlphoto-frame {
width: 100%;
max-width: 320px;
aspect-ratio: 1.55;
margin: 0 auto;
border-radius: 14px;
border: 2px dashed #c9c9c9;
background: linear-gradient(145deg, #f8f9fa 0%, #eef0f2 100%);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
outline: none;
}
.vlphoto-frame:hover {
border-color: var(--color-primary, #2563eb);
background: linear-gradient(145deg, #f0f4ff 0%, #e8eeff 100%);
}
.vlphoto-frame:focus-visible {
border-color: var(--color-primary, #2563eb);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
.vlphoto-preview {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.vlphoto-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
color: #9ca3af;
}
.vlphoto-icon {
font-size: 2.5rem;
line-height: 1;
}
.vlphoto-hint {
font-size: 14px;
color: #6b7280;
}
</style>

View File

@@ -0,0 +1,291 @@
import { reactive, computed } from "vue";
/**
* 通用查询表单字段与规则(可按 feature 扩展)
*
* 目标:
* - 统一管理「这个产品需要哪些输入控件」
* - 暴露统一的 formData / 校验规则 / 字段可见性 / 组装请求体
*
* 使用方式(示例,见 InquireForm.vue
* const { formData, isPhoneNumberValid, isIdCardValid, isHasInput, buildRequestPayload } =
* useInquireForm(feature);
*/
export function useInquireForm(featureRef) {
// 1. 表单字段全集(后续有新字段,只在这里统一扩展)
const formData = reactive({
name: "",
idCard: "",
mobile: "",
verificationCode: "",
// 预留扩展字段位(婚姻、车辆、企业等)
nameMan: "",
idCardMan: "",
nameWoman: "",
idCardWoman: "",
entName: "",
entCode: "",
carLicense: "",
carType: "",
vinCode: "",
certificateNumber: "",
// 车辆类扩展(仅必填项)
userType: "1",
returnUrl: "",
imageUrl: "",
vehicleLocation: "",
firstRegistrationDate: "",
vlphotoData: "",
authorized: "1",
// 核验工具
bankCard: "",
photoData: "",
// 协议
agreeToTerms: false,
});
// 2. 默认输入字段(大部分个人产品共用)
const defaultInput = ["name", "idCard", "mobile", "verificationCode"];
/**
* 3. 不同产品的专用字段配置
* key 为 featureroute 参数 / 产品英文编码)
* value 为该产品需要的字段数组
*
* 先以你当前主要产品为例填入,后续有新产品直接在这里扩展即可。
*/
const productFieldConfig = {
// 婚恋风险(当前与默认字段一致,单独配置方便以后扩展)
marriage: ["name", "idCard", "mobile", "verificationCode"],
// 司法涉诉
toc_PersonalLawsuit: ["name", "idCard", "mobile", "verificationCode"],
// 人企关系加强版:仅身份证号
toc_PersonEnterprisePro: ["idCard"],
// 新企业司法涉诉QYGL66SL仅企业名称其他字段后端自动补齐
toc_EnterpriseLawsuitQYGL66SL: ["entName"],
// 被执行人 / 限高
// 限高被执行人 FLXG3A9B姓名 + 身份证 + 手机号(授权由后端默认传 1前端不展示
toc_LimitHighExecuted: ["name", "idCard", "mobile"],
// 失信被执行人 QYGL2S0W姓名 + 身份证type 后端默认 per
toc_DishonestExecutedPerson: ["name", "idCard"],
// 婚姻状况系列(占位,后续可根据实际字段调整)
toc_PersonalMarriageStatus: [
"name",
"idCard",
"mobile",
"verificationCode",
],
toc_MarriageStatusRegisterTime: [
"name",
"idCard",
"mobile",
"verificationCode",
],
toc_MarriageStatusSupplement: [
"name",
"idCard",
"mobile",
"verificationCode",
],
toc_MarriageStatusVerify: [
"name",
"idCard",
"mobile",
"verificationCode",
],
toc_DualMarriageStatusRegisterTime: [
"nameMan",
"idCardMan",
"nameWoman",
"idCardWoman",
"mobile",
"verificationCode",
],
// 车辆相关(占位,后续可细化)
toc_VehiclesUnderName: ["name", "idCard", "mobile", "verificationCode"],
// 名下车辆(车牌):姓名 + 身份证 + 手机 + 验证码
toc_VehiclesUnderNamePlate: [
"name",
"idCard",
"mobile",
"verificationCode",
],
// 人车核验简版:姓名 + 号牌类型 + 车牌号
toc_PersonVehicleVerification: ["name", "carType", "carLicense"],
// 人车核验详版:姓名 + 号牌类型 + 车牌号(同简版)
toc_PersonVehicleVerificationDetail: ["name", "carType", "carLicense"],
// 名下车辆(数量) QCXG4D2E仅 user_type + id_card
toc_VehiclesUnderNameCount: ["userType", "idCard"],
// 车辆静态信息 QCXG5U0Z仅 vin_code车辆类不要求手机号与验证码
toc_VehicleStaticInfo: ["vinCode"],
// 车辆里程混合 QCXG1U4U仅必填vin_code, image_url回调地址由后端自动生成
toc_VehicleMileageMixed: ["vinCode", "imageUrl"],
// 二手车VIN估值 QCXGY7F2仅必填vin_code, vehicle_location, first_registrationdate
toc_VehicleVinValuation: [
"vinCode",
"vehicleLocation",
"firstRegistrationDate",
],
// 车辆过户简版 QCXG1H7Y必填vin_code + 车牌号)
toc_VehicleTransferSimple: ["vinCode", "carLicense"],
// 车辆过户详版 QCXG4I1Z
toc_VehicleTransferDetail: ["vinCode"],
// 车辆维保简版 QCXG3Y6B仅必填vin_code回调地址由后端自动生成
toc_VehicleMaintenanceSimple: ["vinCode"],
// 车辆维保详细版 QCXG3Z3L仅必填vin_code回调地址由后端自动生成
toc_VehicleMaintenanceDetail: ["vinCode"],
// 车辆出险详版 QCXGP00W仅必填vin_code, vlphoto_data回调地址由后端自动生成
toc_VehicleClaimDetail: ["vinCode", "vlphotoData"],
// 车辆出险记录核验 QCXG6B4E授权由后端默认传 1前端不展示
toc_VehicleClaimVerify: ["vinCode"],
// 核验工具verify feature.md
toc_PoliceTwoFactors: ["mobile", "idCard", "name"],
toc_PoliceThreeFactors: ["photoData", "idCard", "name"],
toc_ProfessionalCertificate: ["idCard", "name"],
toc_PersonalConsumptionCapacityLevel: ["mobile", "idCard", "name"], // 个人消费能力(沿用现有 product_en
toc_OperatorTwoFactors: ["mobile", "name"],
toc_MobileThreeFactors: ["mobile", "idCard", "name"],
toc_NumberRecycle: ["mobile"],
toc_MobileEmptyCheck: ["mobile"],
toc_MobilePortability: ["mobile"],
toc_MobileOnlineStatus: ["mobile"],
toc_MobileOnlineDuration: ["mobile"],
toc_MobileAttribution: ["mobile"],
toc_MobileConsumptionRange: ["mobile", "authorized"],
toc_EnterpriseRelation: ["idCard"],
toc_BankcardFourFactors: ["mobile", "idCard", "bankCard", "name"],
toc_BankcardBlacklist: ["mobile", "idCard", "name", "bankCard"],
};
// 当前 feature 名称
const currentFeature = computed(() => featureRef?.value || featureRef);
/**
* 4. 判断某个字段在当前产品下是否需要显示 / 校验 / 组包
*/
const isHasInput = (field) => {
const key = currentFeature.value;
if (key && productFieldConfig[key]) {
return productFieldConfig[key].includes(field);
}
return defaultInput.includes(field);
};
// 5. 通用校验规则(不直接弹 Toast只返回布尔值UI 再决定提示)
const isPhoneNumberValid = computed(() => {
if (!formData.mobile) return false;
return /^1[3-9]\d{9}$/.test(formData.mobile);
});
const isIdCardValid = computed(() => {
if (!formData.idCard) return false;
return /^\d{17}[\dX]$/i.test(formData.idCard);
});
// 预留:其他字段校验(如车牌、车架号等)后续可加在这里
/**
* 6. 根据当前产品配置组装请求体
* 仅包含当前产品需要的字段;字段名与后端约定对齐。
*/
const buildRequestPayload = () => {
const req = {};
if (isHasInput("name") && formData.name) {
req.name = formData.name;
}
if (isHasInput("idCard") && formData.idCard) {
req.id_card = formData.idCard;
}
if (isHasInput("mobile") && formData.mobile) {
req.mobile = formData.mobile;
}
if (isHasInput("verificationCode") && formData.verificationCode) {
req.code = formData.verificationCode;
}
// 预留字段映射(以后启用时直接在这里补充)
if (isHasInput("nameMan") && formData.nameMan) {
req.name_man = formData.nameMan;
}
if (isHasInput("idCardMan") && formData.idCardMan) {
req.id_card_man = formData.idCardMan;
}
if (isHasInput("nameWoman") && formData.nameWoman) {
req.name_woman = formData.nameWoman;
}
if (isHasInput("idCardWoman") && formData.idCardWoman) {
req.id_card_woman = formData.idCardWoman;
}
if (isHasInput("entName") && formData.entName) {
req.ent_name = formData.entName;
}
if (isHasInput("entCode") && formData.entCode) {
req.ent_code = formData.entCode;
}
if (isHasInput("carLicense") && formData.carLicense) {
req.car_license = formData.carLicense.trim();
}
if (isHasInput("carType") && formData.carType) {
req.car_type = formData.carType;
}
if (isHasInput("vinCode") && formData.vinCode) {
req.vin_code = formData.vinCode.trim();
}
if (isHasInput("certificateNumber") && formData.certificateNumber) {
req.certificate_number = formData.certificateNumber;
}
// 车辆类扩展字段(与后端 types/query.go 对齐)
if (isHasInput("userType") && formData.userType) {
req.user_type = formData.userType;
}
if (isHasInput("returnUrl") && formData.returnUrl) {
req.return_url = formData.returnUrl.trim();
}
if (isHasInput("imageUrl") && formData.imageUrl) {
req.image_url = formData.imageUrl.trim();
}
if (isHasInput("vehicleLocation") && formData.vehicleLocation) {
req.vehicle_location = formData.vehicleLocation.trim();
}
if (
isHasInput("firstRegistrationDate") &&
formData.firstRegistrationDate
) {
req.first_registrationdate = formData.firstRegistrationDate.trim();
}
if (isHasInput("vlphotoData") && formData.vlphotoData) {
req.vlphoto_data = formData.vlphotoData;
}
if (
isHasInput("authorized") &&
formData.authorized !== undefined &&
formData.authorized !== ""
) {
req.authorized = String(formData.authorized);
}
if (isHasInput("bankCard") && formData.bankCard) {
req.bank_card = formData.bankCard.trim();
}
if (isHasInput("photoData") && formData.photoData) {
req.photo_data = formData.photoData;
}
return req;
};
return {
formData,
isPhoneNumberValid,
isIdCardValid,
isHasInput,
buildRequestPayload,
defaultInput,
productFieldConfig,
};
}

View File

@@ -0,0 +1,346 @@
/**
* 查询模块二级分类配置
* 首页点击模块后进入的方格列表选项每个选项对应一个查询类型feature
* banner: 该大类顶部海报图assets/images 下文件名)
*/
export const inquireCategoryConfig = {
/** 司法涉诉 */
lawsuit: {
title: "司法涉诉",
banner: "fxpg_banner.png",
items: [
{
name: "个人司法涉诉",
feature: "toc_PersonalLawsuit",
desc: "民事、刑事、行政、执行、失信、限高等",
icon: "grss_icon.svg",
iconFrom: "category",
},
{
name: "企业司法涉诉",
feature: "toc_EnterpriseLawsuitQYGL66SL",
desc: "企业涉诉与执行信息",
icon: "qyss_icon.svg",
iconFrom: "category",
},
{
name: "人企关系加强版",
feature: "toc_PersonEnterprisePro",
desc: "仅身份证号",
icon: "renqiguanxi.svg",
iconFrom: "category",
},
{
name: "限高被执行人",
feature: "toc_LimitHighExecuted",
desc: "限制高消费被执行人信息",
icon: "xianzhigaoxiaofei.svg",
iconFrom: "category",
},
{
name: "失信被执行人",
feature: "toc_DishonestExecutedPerson",
desc: "失信被执行人信息",
icon: "sjsys_icon.svg",
iconFrom: "category",
},
],
},
/** 婚恋风险 */
marriage: {
title: "婚恋风险",
banner: "hygj_banner.png",
items: [
{
name: "婚恋风险评估",
feature: "marriage",
desc: "多维度婚恋对象风险分析",
icon: "renqiguanxi.svg",
},
],
},
/** 车辆查询 */
vehicle: {
title: "车辆查询",
banner: "car_banner.png",
items: [
{
name: "名下车辆查询",
feature: "toc_VehiclesUnderName",
desc: "个人名下车辆信息",
icon: "mxcl_icon.svg",
iconFrom: "category",
},
{
name: "人车核验(简版)",
feature: "toc_PersonVehicleVerification",
desc: "根据车牌与号牌类型核验车辆与人员是否匹配",
icon: "rchyjb.svg",
iconFrom: "category",
},
{
name: "人车核验(详版)",
feature: "toc_PersonVehicleVerificationDetail",
desc: "提供更丰富的人车匹配详细信息",
icon: "rchyxb.svg",
iconFrom: "category",
},
{
name: "名下车辆(车牌)",
feature: "toc_VehiclesUnderNamePlate",
desc: "按身份证与姓名查询名下车辆信息",
icon: "mxcl_icon.svg",
iconFrom: "category",
},
{
name: "名下车辆(数量)",
feature: "toc_VehiclesUnderNameCount",
desc: "查询名下车辆数量",
icon: "mxcl_icon.svg",
iconFrom: "category",
},
{
name: "车辆静态信息查询",
feature: "toc_VehicleStaticInfo",
desc: "车辆静态信息",
icon: "clxx.svg",
iconFrom: "category",
},
{
name: "车辆里程记录(混合查询)",
feature: "toc_VehicleMileageMixed",
desc: "车辆里程混合查询",
icon: "clcx.svg",
iconFrom: "category",
},
{
name: "二手车VIN估值",
feature: "toc_VehicleVinValuation",
desc: "按VIN查询二手车估值",
icon: "escgz.svg",
iconFrom: "category",
},
{
name: "车辆过户简版查询",
feature: "toc_VehicleTransferSimple",
desc: "车辆过户简版",
icon: "esc.svg",
iconFrom: "category",
},
{
name: "车辆过户详版查询",
feature: "toc_VehicleTransferDetail",
desc: "车辆过户详版",
icon: "esc.svg",
iconFrom: "category",
},
{
name: "车辆维保简版查询",
feature: "toc_VehicleMaintenanceSimple",
desc: "车辆维保简版",
icon: "clxx.svg",
iconFrom: "category",
},
{
name: "车辆维保详细版查询",
feature: "toc_VehicleMaintenanceDetail",
desc: "车辆维保详细版",
icon: "clxx.svg",
iconFrom: "category",
},
{
name: "车辆出险详版查询",
feature: "toc_VehicleClaimDetail",
desc: "车辆出险详版",
icon: "cxcl.svg",
iconFrom: "category",
},
{
name: "车辆出险记录核验",
feature: "toc_VehicleClaimVerify",
desc: "车辆出险记录核验",
icon: "cxcl.svg",
iconFrom: "category",
},
],
},
/** 核验工具 */
verify: {
title: "核验工具",
banner: "hygj_banner.png",
items: [
{
name: "公安二要素认证",
feature: "toc_PoliceTwoFactors",
desc: "手机号+身份证+姓名",
icon: "icon_verify_id.svg",
iconFrom: "category",
},
{
name: "公安三要素",
feature: "toc_PoliceThreeFactors",
desc: "人像图+身份证+姓名",
icon: "icon_verify_id.svg",
iconFrom: "category",
},
{
name: "职业资格证书查询",
feature: "toc_ProfessionalCertificate",
desc: "身份证+姓名",
icon: "icon_verify_id.svg",
iconFrom: "category",
},
{
name: "个人消费能力等级",
feature: "toc_PersonalConsumptionCapacityLevel",
desc: "手机号+身份证+姓名",
icon: "icon_verify_score.svg",
iconFrom: "category",
},
{
name: "运营商二要素",
feature: "toc_OperatorTwoFactors",
desc: "手机号+姓名",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "全网手机三要素验证周更",
feature: "toc_MobileThreeFactors",
desc: "手机号+身份证+姓名",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "号码二次放号",
feature: "toc_NumberRecycle",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机空号检测",
feature: "toc_MobileEmptyCheck",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机携号转网",
feature: "toc_MobilePortability",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机在网状态",
feature: "toc_MobileOnlineStatus",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机号码在网时长",
feature: "toc_MobileOnlineDuration",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机消费区间验证",
feature: "toc_MobileConsumptionRange",
desc: "手机号+授权",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "手机号码归属地核验",
feature: "toc_MobileAttribution",
desc: "手机号",
icon: "icon_verify_operator.svg",
iconFrom: "category",
},
{
name: "名下企业关联",
feature: "toc_EnterpriseRelation",
desc: "法人身份证(选填)",
icon: "icon_verify_company.svg",
iconFrom: "category",
},
{
name: "人企关系加强版",
feature: "toc_PersonEnterprisePro",
desc: "仅身份证号",
icon: "icon_verify_company.svg",
iconFrom: "category",
},
{
name: "银行卡四要素验证(详版)",
feature: "toc_BankcardFourFactors",
desc: "手机+身份证+银行卡+姓名",
icon: "icon_verify_bank.svg",
iconFrom: "category",
},
{
name: "银行卡黑名单(实时)",
feature: "toc_BankcardBlacklist",
desc: "手机+身份证+姓名+银行卡",
icon: "icon_verify_bank.svg",
iconFrom: "category",
},
],
},
/** 婚姻状况 */
marriageStatus: {
title: "婚姻状况",
banner: "hygj_banner.png",
items: [
{
name: "个人婚姻状态",
feature: "toc_PersonalMarriageStatus",
desc: "个人婚姻登记状态",
icon: "zrrsczt_icon.svg",
iconFrom: "category",
},
// {
// name: '婚姻状态查询(登记时间版)',
// feature: 'toc_MarriageStatusRegisterTime',
// desc: '按登记时间查询婚姻状态',
// icon: 'zrrsczt_icon.svg',
// iconFrom: 'category',
// },
// {
// name: '婚姻状态查询(补证版)',
// feature: 'toc_MarriageStatusSupplement',
// desc: '补证版婚姻状态查询',
// icon: 'zrrsczt_icon.svg',
// iconFrom: 'category',
// },
// {
// name: '婚姻状态核验',
// feature: 'toc_MarriageStatusVerify',
// desc: '婚姻状态核验',
// icon: 'zrrsczt_icon.svg',
// iconFrom: 'category',
// },
// {
// name: '双人婚姻状态(登记时间版)',
// feature: 'toc_DualMarriageStatusRegisterTime',
// desc: '双人婚姻状态按登记时间查询',
// icon: 'zrrsczt_icon.svg',
// iconFrom: 'category',
// },
],
},
};
/** 根据 category 获取配置 */
export function getInquireCategoryConfig(category) {
return inquireCategoryConfig[category] || null;
}
/** 根据 category 获取页面标题(用于导航栏) */
export function getInquireCategoryTitle(category) {
const config = inquireCategoryConfig[category];
return config ? config.title : "选择查询";
}

View File

@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import GlobalLayout from '@/layouts/GlobalLayout.vue'
import { getInquireCategoryTitle } from '@/config/inquireCategories'
import HomeLayout from '@/layouts/HomeLayout.vue'
import PageLayout from '@/layouts/PageLayout.vue'
import index from '@/views/index.vue'
@@ -16,13 +17,10 @@ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// 路由切换时的滚动行为
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置(浏览器前进/后退)
if (savedPosition) {
return savedPosition;
} else {
// 否则滚动到顶部
return { top: 0, behavior: "smooth" };
}
return { top: 0 };
},
routes: [
{
@@ -149,6 +147,12 @@ const router = createRouter({
component: () => import('@/views/AgentServiceAgreement.vue'),
meta: { title: '信息技术服务合同' },
},
{
path: '/inquire/category/:category',
name: 'inquireCategory',
component: () => import('@/views/InquireCategory.vue'),
meta: { title: '选择查询' },
},
{
path: '/inquire/:feature',
name: 'inquire',
@@ -361,6 +365,10 @@ NProgress.configure({
// 路由导航守卫
router.beforeEach(async (to, from, next) => {
NProgress.start(); // 启动进度条
// 二级查询分类页:根据 category 动态设置导航栏标题
if (to.name === 'inquireCategory' && to.params.category) {
to.meta.title = getInquireCategoryTitle(to.params.category);
}
const isAuthenticated = localStorage.getItem("token");
const agentStore = useAgentStore();
const userStore = useUserStore();

309
src/ui/CFLX3A9B.vue Normal file
View File

@@ -0,0 +1,309 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] rounded-xl">
<!-- 标题 -->
<div class="flex items-center mb-3 p-4">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/ssfxztgl.png" alt="限高/失信风险" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">限高 / 失信风险</span>
</div>
<!-- 风险整体概览 -->
<div class="px-4 mb-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
<div class="p-3 rounded-lg bg-[#EB3C3C1A] border border-[#EB3C3C4D]">
<div class="text-xs text-gray-600 mb-1">规则最终决策</div>
<div class="text-lg font-semibold" :class="finalDecision === 'Accept' ? 'text-green-600' : 'text-red-600'">
{{ finalDecisionText }}
</div>
<div v-if="finalWeight" class="text-xs text-gray-500 mt-1">
风险权重{{ finalWeight }}
</div>
</div>
<div class="p-3 rounded-lg bg-[#D6943E1A] border border-[#D6943E4D]">
<div class="text-xs text-gray-600 mb-1">失信记录</div>
<div class="text-lg font-semibold text-orange-600">
{{ sxList.length }}
</div>
<div class="text-xs text-gray-500 mt-1">
命中失信被执行人公告
</div>
</div>
<div class="p-3 rounded-lg bg-[#2B79EE1A] border border-[#2B79EE4D]">
<div class="text-xs text-gray-600 mb-1">限高记录</div>
<div class="text-lg font-semibold text-blue-600">
{{ xgList.length }}
</div>
<div class="text-xs text-gray-500 mt-1">
{{ recentLimitDesc }}
</div>
</div>
</div>
</div>
<!-- 失信记录 -->
<div v-if="sxList.length" class="px-4 mb-4">
<LTitle title="失信被执行人记录" />
<div class="space-y-3">
<div v-for="(item, index) in sxList" :key="'sx_' + index" class="case-wrapper">
<div class="bg-white rounded-xl overflow-hidden border px-4 pt-3 border-[#DDDDDD]">
<div class="cursor-pointer relative" @click="toggleExpand('sx', index)">
<div class="flex items-center">
<div class="font-bold text-base text-[#333333] mr-2">
{{ item.casecode || '暂无案号' }}
</div>
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#F9ECEC] text-[#EB3C3C]">
{{ item.datatype || '失信被执行人' }}
</span>
</div>
<div class="pb-2 text-sm">
<span class="text-[#666666]">立案</span>
<span class="text-[#333333]">{{ item.regdate || '-' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#EB3C3C1A] text-[#EB3C3C]">
{{ item.signalDesc || '主体存在失信记录' }}
</span>
<div class="flex items-center text-xs text-gray-500">
<span class="mr-1">
{{ isExpanded('sx', index) ? '收起详情' : '展开详情' }}
</span>
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
:class="{ 'rotate-180': isExpanded('sx', index) }" />
</div>
</div>
</div>
<div class="mt-3 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isExpanded('sx', index),
'max-h-[500px] opacity-100': isExpanded('sx', index),
}">
<div class="border-t border-dashed border-gray-200 pt-3 pb-2 text-xs">
<div class="flex mb-1">
<span class="w-16 text-gray-500">被执行人</span>
<span class="flex-1 text-gray-800">
{{ item.iname || '-' }}
<span v-if="item.sexname || item.age" class="text-gray-500 ml-1">
{{ [item.sexname, item.age && item.age + ''].filter(Boolean).join('') }}
</span>
</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">地域</span>
<span class="flex-1 text-gray-800">{{ item.areaname || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">法院</span>
<span class="flex-1 text-gray-800">{{ item.courtname || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">执行依据</span>
<span class="flex-1 text-gray-800">{{ item.gistcid || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">立案时间</span>
<span class="flex-1 text-gray-800">{{ item.regdate || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">发布日期</span>
<span class="flex-1 text-gray-800">{{ item.publishdate || '-' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 限高记录 -->
<div v-if="xgList.length" class="px-4 mb-4">
<LTitle title="限高被执行人记录" />
<div class="space-y-3">
<div v-for="(item, index) in xgList" :key="'xg_' + index" class="case-wrapper">
<div class="bg-white rounded-xl overflow-hidden border px-4 pt-3 border-[#DDDDDD]">
<div class="cursor-pointer relative" @click="toggleExpand('xg', index)">
<div class="flex items-center">
<div class="font-bold text-base text-[#333333] mr-2">
{{ item.casecode || '暂无案号' }}
</div>
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#F9ECEC] text-[#EB3C3C]">
{{ item.datatype || '限高被执行人' }}
</span>
</div>
<div class="pb-2 text-sm">
<span class="text-[#666666]">立案</span>
<span class="text-[#333333]">{{ item.regdate || '-' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#D6943E1A] text-[#D6943E]">
{{ item.signalDesc || '主体被限制高消费' }}
</span>
<div class="flex items-center text-xs text-gray-500">
<span class="mr-1">
{{ isExpanded('xg', index) ? '收起详情' : '展开详情' }}
</span>
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
:class="{ 'rotate-180': isExpanded('xg', index) }" />
</div>
</div>
</div>
<div class="mt-3 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isExpanded('xg', index),
'max-h-[500px] opacity-100': isExpanded('xg', index),
}">
<div class="border-t border-dashed border-gray-200 pt-3 pb-2 text-xs">
<div class="flex mb-1">
<span class="w-16 text-gray-500">被执行人</span>
<span class="flex-1 text-gray-800">
{{ item.iname || '-' }}
<span v-if="item.sexname || item.age" class="text-gray-500 ml-1">
{{ [item.sexname, item.age && item.age + ''].filter(Boolean).join('') }}
</span>
</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">地域</span>
<span class="flex-1 text-gray-800">{{ item.areaname || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">法院</span>
<span class="flex-1 text-gray-800">{{ item.courtname || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">立案时间</span>
<span class="flex-1 text-gray-800">{{ item.regdate || '-' }}</span>
</div>
<div class="flex mb-1">
<span class="w-16 text-gray-500">发布日期</span>
<span class="flex-1 text-gray-800">{{ item.publishdate || '-' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="!sxList.length && !xgList.length"
class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2"></div>
暂未命中失信或限高被执行人记录
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, watchEffect } from 'vue'
import LTitle from '@/components/LTitle.vue'
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
})
const raw = computed(() => props.data || {})
const finalDecision = computed(() => raw.value.Rule_final_decision || '')
const finalWeight = computed(() => raw.value.Rule_final_weight || '')
const finalDecisionText = computed(() => {
const v = finalDecision.value
if (!v) return '未知'
if (v.toLowerCase() === 'accept') return '通过'
if (v.toLowerCase() === 'reject') return '拒绝'
return v
})
// 解析失信记录el_sx1_*
const sxList = computed(() => {
const d = raw.value
const list = []
if (d.el_sx1_datatype) {
list.push({
datatype: d.el_sx1_datatype,
age: d.el_sx1_age,
areaname: d.el_sx1_areaname,
casecode: d.el_sx1_casecode,
courtname: d.el_sx1_courtname,
gistcid: d.el_sx1_gistcid,
iname: d.el_sx1_iname,
partytypename: d.el_sx1_partytypename,
publishdate: d.el_sx1_publishdate,
regdate: d.el_sx1_regdate,
sexname: d.el_sx1_sexname,
sign: d.el_sx1_sign,
signalDesc: d.el_sx1_signalDesc,
signalRating: d.el_sx1_signalRating,
})
}
return list
})
// 解析限高记录el_xg1_* / el_xg2_* / el_xg3_*
const xgList = computed(() => {
const d = raw.value
const result = []
;[1, 2, 3].forEach((n) => {
const prefix = `el_xg${n}_`
const datatype = d[`${prefix}datatype`]
if (!datatype) return
result.push({
datatype,
age: d[`${prefix}age`],
areaname: d[`${prefix}areaname`],
casecode: d[`${prefix}casecode`],
courtname: d[`${prefix}courtname`],
iname: d[`${prefix}iname`],
publishdate: d[`${prefix}publishdate`],
regdate: d[`${prefix}regdate`],
sexname: d[`${prefix}sexname`],
sign: d[`${prefix}sign`],
signalDesc: d[`${prefix}signalDesc`],
signalRating: d[`${prefix}signalRating`],
})
})
return result
})
const recentLimitDesc = computed(() => {
if (!xgList.value.length) return '暂无限高记录'
const recent = xgList.value[0]
return recent.signalDesc || '主体存在限高被执行人记录'
})
// 展开状态
const expandedMap = ref({})
const keyOf = (type, index) => `${type}_${index}`
const toggleExpand = (type, index) => {
const k = keyOf(type, index)
expandedMap.value[k] = !expandedMap.value[k]
}
const isExpanded = (type, index) => !!expandedMap.value[keyOf(type, index)]
// 上报风险:只要有失信或限高记录即视为有风险
watchEffect(() => {
if (!props.notifyRiskStatus) return
const hasRisk = sxList.value.length > 0 || xgList.value.length > 0
props.notifyRiskStatus(props.apiId || 'FLXG3A9B', props.index || 0, {
hasRisk,
})
})
</script>
<style scoped>
.card {
padding: 0.5rem;
}
.case-wrapper {
width: 100%;
}
</style>

104
src/ui/CIVYZ6M8P.vue Normal file
View File

@@ -0,0 +1,104 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">职业资格证书查询</h3>
<p class="header-desc">展示查询到的职业资格考试及等级信息</p>
</div>
<div v-if="hasData" class="result-section">
<div class="info-row">
<span class="info-label">考试名称</span>
<span class="info-value">{{ examName || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">级别</span>
<span class="info-value">{{ level || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">专业</span>
<span class="info-value">{{ major || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">通过日期</span>
<span class="info-value">{{ passDate || '-' }}</span>
</div>
</div>
<div v-if="hasData && !examName && !major && !level && !passDate" class="empty-tip">
已返回结果但格式不在预期范围内请联系技术支持查看原始数据
</div>
<div v-if="!hasData" class="empty-tip">暂无查询结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
// ED0001: 考试名称, ED0002: 级别, ED0003: 专业, ED0004: 通过年月
const examName = computed(() => props.data?.ED0001 || '');
const level = computed(() => props.data?.ED0002 || '');
const major = computed(() => props.data?.ED0003 || '');
const passDate = computed(() => props.data?.ED0004 || '');
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
background: #f9fafb;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.25rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

227
src/ui/CIVYZ9K7F.vue Normal file
View File

@@ -0,0 +1,227 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">公安二要素认证</h3>
<p class="header-desc">核验姓名与身份证号是否一致</p>
</div>
<div v-if="hasData" class="result-section" :class="resultSectionClass">
<div class="result-main">
<div class="result-label">核验结果</div>
<div class="result-value" :class="resultClass">
{{ resultText }}
</div>
</div>
<div v-if="desc" class="result-desc">
{{ desc }}
</div>
</div>
<div v-if="hasBaseInfo" class="info-block">
<div class="block-title">被核验人信息</div>
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">性别</span>
<span class="info-value">{{ sex || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">生日</span>
<span class="info-value">{{ birthdayDisplay }}</span>
</div>
<div class="info-row">
<span class="info-label">户籍地址</span>
<span class="info-value">{{ address || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const resultCode = computed(() => {
const v = props.data?.result;
if (v === 0 || v === '0') return 0;
if (v === 1 || v === '1') return 1;
if (v === 2 || v === '2') return 2;
return null;
});
const resultText = computed(() => {
switch (resultCode.value) {
case 0:
return '一致';
case 1:
return '不一致';
case 2:
return '无记录';
default:
return '暂无结果';
}
});
const resultClass = computed(() => {
if (resultCode.value === 0) return 'result-ok';
if (resultCode.value === 1) return 'result-bad';
if (resultCode.value === 2) return 'result-unknown';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
if (resultCode.value === 0) return 'result-section ok';
if (resultCode.value === 1) return 'result-section bad';
if (resultCode.value === 2) return 'result-section unknown';
return 'result-section unknown';
});
const desc = computed(() => props.data?.desc || '');
const sex = computed(() => props.data?.sex || '');
const birthday = computed(() => props.data?.birthday || '');
const address = computed(() => props.data?.address || '');
const birthdayDisplay = computed(() => {
const b = birthday.value;
if (!b || b.length !== 8) return b || '-';
const y = b.slice(0, 4);
const m = b.slice(4, 6);
const d = b.slice(6, 8);
return `${y}-${m}-${d}`;
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
const hasBaseInfo = computed(() => sex.value || birthday.value || address.value || props.params?.name);
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-unknown {
color: #6b7280;
}
.result-desc {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

239
src/ui/CIVYZA1B3.vue Normal file
View File

@@ -0,0 +1,239 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">公安三要素</h3>
<p class="header-desc">比对人像与身份证信息是否为同一人</p>
</div>
<div v-if="hasData" class="result-section" :class="scoreLevelClass">
<div class="result-main">
<div class="result-label">系统判断</div>
<div class="result-value" :class="scoreTextClass">
{{ msg || scoreConclusion }}
</div>
</div>
<div class="result-score">
相似度分值<span class="font-semibold">{{ scoreDisplay }}</span>
<span class="text-xs text-gray-500 ml-1">(01越高越相似)</span>
</div>
</div>
<div v-if="hasBaseInfo" class="info-block">
<div class="block-title">被核验人信息</div>
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">性别</span>
<span class="info-value">{{ sex || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">生日</span>
<span class="info-value">{{ birthdayDisplay }}</span>
</div>
<div class="info-row">
<span class="info-label">户籍地址</span>
<span class="info-value">{{ address || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const score = computed(() => {
const v = props.data?.score;
if (typeof v === 'number') return v;
const n = Number(v);
return Number.isFinite(n) ? n : null;
});
const msg = computed(() => props.data?.msg || '');
const incorrect = computed(() => props.data?.incorrect ?? null);
const sex = computed(() => props.data?.sex || '');
const birthday = computed(() => props.data?.birthday || '');
const address = computed(() => props.data?.address || '');
const scoreDisplay = computed(() => {
if (score.value == null) return '-';
return score.value.toFixed(2);
});
const scoreConclusion = computed(() => {
const s = score.value;
if (s == null) return '暂无结论';
if (s < 0.4) return '系统判断为不同人';
if (s < 0.45) return '不能确定是否为同一人';
return '系统判断为同一人';
});
const scoreLevelClass = computed(() => {
const s = score.value;
if (s == null) return 'result-section unknown';
if (s < 0.4) return 'result-section bad';
if (s < 0.45) return 'result-section warn';
return 'result-section ok';
});
const scoreTextClass = computed(() => {
const s = score.value;
if (s == null) return 'result-unknown';
if (s < 0.4) return 'result-bad';
if (s < 0.45) return 'result-warn';
return 'result-ok';
});
const birthdayDisplay = computed(() => {
const b = birthday.value;
if (!b || b.length !== 8) return b || '-';
const y = b.slice(0, 4);
const m = b.slice(4, 6);
const d = b.slice(6, 8);
return `${y}-${m}-${d}`;
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
const hasBaseInfo = computed(() => sex.value || birthday.value || address.value || props.params?.name);
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.warn {
background: #fffbeb;
border-color: #f9731633;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-warn {
color: #d97706;
}
.result-unknown {
color: #6b7280;
}
.result-score {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

235
src/ui/CJRZQ0B6Y.vue Normal file
View File

@@ -0,0 +1,235 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">银行卡黑名单(实时)</h3>
<p class="header-desc">查询银行卡是否命中各类黑名单/风险名单</p>
</div>
<div v-if="hasData" class="result-section">
<div class="risk-summary" :class="summaryClass">
<span class="risk-label">综合结果</span>
<span class="risk-value">
{{ summaryText }}
</span>
</div>
<div class="risk-tags">
<div v-for="item in riskItems" :key="item.key" class="risk-item" :class="item.hit ? 'hit' : 'normal'">
<div class="risk-item-title">{{ item.label }}</div>
<div class="risk-item-value">
{{ item.hit ? '命中' : '未命中' }}
</div>
</div>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验信息</div>
<div class="info-row">
<span class="info-label">持卡人姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">身份证号</span>
<span class="info-value font-mono">{{ maskedIdCard }}</span>
</div>
<div class="info-row">
<span class="info-label">银行卡号</span>
<span class="info-value font-mono">{{ maskedBankCard }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const riskItems = computed(() => {
const d = props.data || {};
const toBool = (v) => v === '1' || v === 1;
return [
{ key: 'badCardHolder', label: '不良持卡人', hit: toBool(d.badCardHolder) },
{ key: 'caseRelated', label: '涉案卡片', hit: toBool(d.caseRelated) },
{ key: 'fraudTrans', label: '交易欺诈卡片', hit: toBool(d.fraudTrans) },
{ key: 'offlineBlack', label: '线下卡号黑名单', hit: toBool(d.offlineBlack) },
{ key: 'onlineBlack', label: '线上卡号黑名单', hit: toBool(d.onlineBlack) },
{ key: 'otherBlack', label: '其他卡号黑名单', hit: toBool(d.otherBlack) },
];
});
const anyHit = computed(() => riskItems.value.some((i) => i.hit));
const summaryText = computed(() => {
if (!hasData.value) return '暂无结果';
if (!anyHit.value) return '未命中任何银行卡黑名单';
return '存在银行卡黑名单或风险记录';
});
const summaryClass = computed(() => {
if (!hasData.value) return 'summary unknown';
if (anyHit.value) return 'summary bad';
return 'summary ok';
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
const maskedIdCard = computed(() => {
const id = props.params?.id_card || '';
if (!id || id.length < 8) return id || '-';
return id.slice(0, 4) + '********' + id.slice(-4);
});
const maskedBankCard = computed(() => {
const card = props.params?.bank_card || '';
if (!card || card.length < 8) return card || '-';
return card.slice(0, 4) + '********' + card.slice(-4);
});
const hasParams = computed(() => {
const p = props.params || {};
return p.name || p.id_card || p.bank_card;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
background: #f9fafb;
}
.risk-summary {
font-size: 0.95rem;
margin-bottom: 0.5rem;
}
.summary.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.summary.bad {
background: #fef2f2;
border-color: #ef444433;
}
.summary.unknown {
background: #f9fafb;
}
.risk-label {
color: #6b7280;
}
.risk-value {
font-weight: 600;
color: #111827;
}
.risk-tags {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
}
.risk-item {
border-radius: 0.5rem;
padding: 0.5rem 0.6rem;
font-size: 0.8rem;
}
.risk-item.hit {
background: #fef2f2;
border: 1px solid #ef4444;
color: #b91c1c;
}
.risk-item.normal {
background: #ecfdf3;
border: 1px solid #22c55e33;
color: #166534;
}
.risk-item-title {
margin-bottom: 0.15rem;
}
.risk-item-value {
font-weight: 600;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

227
src/ui/CJRZQACAB.vue Normal file
View File

@@ -0,0 +1,227 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">银行卡四要素验证详版</h3>
<p class="header-desc">核验姓名身份证号银行卡号与预留手机号是否匹配</p>
</div>
<div v-if="hasData" class="result-section" :class="resultSectionClass">
<div class="result-main">
<div class="result-label">验证结果</div>
<div class="result-value" :class="resultClass">
{{ stateText }}
</div>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验信息</div>
<div class="info-row">
<span class="info-label">持卡人姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">身份证号</span>
<span class="info-value font-mono">{{ maskedIdCard }}</span>
</div>
<div class="info-row">
<span class="info-label">银行卡号</span>
<span class="info-value font-mono">{{ maskedBankCard }}</span>
</div>
<div class="info-row">
<span class="info-label">预留手机号</span>
<span class="info-value font-mono">{{ maskedMobile }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const state = computed(() => props.data?.state || '');
const stateText = computed(() => {
switch (state.value) {
case '1':
return '验证一致';
case '2':
return '认证不一致:此卡已过期或卡号无效';
case '3':
return '认证不一致:认证未通过';
case '4':
return '认证不一致:今日验证次数过多,请明日再试';
case '5':
return '认证不一致:当前提交数量过多,请降低提交频率';
case '6':
return '认证不一致:持卡人信息有误或卡状态异常';
case '7':
return '认证不一致:未开通无卡支付或发卡行不支持该卡验证';
case '8':
return '认证不一致:认证受限';
default:
return '暂无结果';
}
});
const resultClass = computed(() => {
if (state.value === '1') return 'result-ok';
if (!state.value) return 'result-unknown';
return 'result-bad';
});
const resultSectionClass = computed(() => {
if (state.value === '1') return 'result-section ok';
if (!state.value) return 'result-section unknown';
return 'result-section bad';
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
const maskedIdCard = computed(() => {
const id = props.params?.id_card || '';
if (!id || id.length < 8) return id || '-';
return id.slice(0, 4) + '********' + id.slice(-4);
});
const maskedBankCard = computed(() => {
const card = props.params?.bank_card || '';
if (!card || card.length < 8) return card || '-';
return card.slice(0, 4) + '********' + card.slice(-4);
});
const maskedMobile = computed(() => {
const m = props.params?.mobile || props.params?.mobile_no || '';
if (!m || m.length < 7) return m || '-';
return m.slice(0, 3) + '****' + m.slice(-4);
});
const hasParams = computed(() => {
const p = props.params || {};
return p.name || p.id_card || p.bank_card || p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-unknown {
color: #6b7280;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

204
src/ui/CQCXG1H7Y.vue Normal file
View File

@@ -0,0 +1,204 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆过户简版查询</h3>
<p class="header-desc">查看车辆最近是否发生过户及过户次数</p>
</div>
<div class="header-tag" :class="flagClass">
<span class="tag-label">是否过户</span>
<span class="tag-value">{{ flagText }}</span>
</div>
</div>
<template v-if="hasData">
<div class="summary-card" :class="flagClass">
<div class="summary-main">
<div class="summary-title">
最近过户情况
</div>
<div class="summary-flag">{{ flagDetailText }}</div>
</div>
<div class="summary-meta">
<div class="meta-line">
<span class="meta-label">最近过户时间</span>
<span class="meta-value">{{ formattedTransferDate }}</span>
</div>
<div class="meta-line">
<span class="meta-label">累计过户次数</span>
<span class="meta-value strong">{{ data.transferNum || '0' }} </span>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无过户信息</div>
<div class="sub">未查询到车辆过户记录</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
// transferFlag: 0-否1-是(字符串)
const flag = computed(() => props.data?.transferFlag);
const flagText = computed(() => {
if (flag.value === '1') return '已过户';
if (flag.value === '0') return '未过户';
return '未知';
});
const flagDetailText = computed(() => {
if (flag.value === '1') return '该车辆存在过户记录';
if (flag.value === '0') return '该车辆暂无过户记录';
return '未能识别过户状态';
});
const flagClass = computed(() => {
if (flag.value === '1') return 'flag-yes';
if (flag.value === '0') return 'flag-no';
return 'flag-unknown';
});
const formattedTransferDate = computed(() => {
const raw = props.data?.transferDate;
if (!raw) return '-';
if (raw === '近一年内过户') return '近一年内过户';
// 期望格式 yyyyMM
if (raw.length === 6) {
const y = raw.slice(0, 4);
const m = raw.slice(4, 6);
return `${y}${m}`;
}
return raw;
});
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-sky-50 via-blue-50 to-sky-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-sky-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-sky-800 opacity-90;
}
.header-tag {
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm;
}
.header-tag.flag-yes {
@apply text-amber-800;
}
.header-tag.flag-no {
@apply text-emerald-800;
}
.header-tag.flag-unknown {
@apply text-gray-700;
}
.tag-label {
@apply text-sm text-gray-500;
}
.tag-value {
@apply text-xl font-bold leading-none whitespace-nowrap;
}
.summary-card {
@apply rounded-2xl border px-5 py-4 mb-4;
}
.summary-card.flag-yes {
@apply bg-amber-50 border-amber-100;
}
.summary-card.flag-no {
@apply bg-emerald-50 border-emerald-100;
}
.summary-card.flag-unknown {
@apply bg-gray-50 border-gray-200;
}
.summary-main {
@apply flex items-baseline justify-between mb-3;
}
.summary-title {
@apply text-lg font-semibold text-gray-900;
}
.summary-flag {
@apply text-base text-gray-700;
}
.summary-meta {
@apply space-y-2 text-base text-gray-800;
}
.meta-line {
@apply flex items-center gap-2;
}
.meta-label {
@apply text-gray-500;
}
.meta-value {
@apply font-medium;
}
.meta-value.strong {
@apply text-lg font-semibold;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

374
src/ui/CQCXG1U4U.vue Normal file
View File

@@ -0,0 +1,374 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆里程记录混合查询</h3>
<p class="header-desc">综合诊断与维保记录展示车辆里程变化与是否存在调表嫌疑</p>
</div>
</div>
<template v-if="hasData">
<!-- 概览里程是否异常 + 最新里程 -->
<div class="summary-card" :class="suspectClass">
<div class="summary-main">
<div class="summary-left">
<div class="vin-label">VIN</div>
<div class="vin-value font-mono">{{ vin || '-' }}</div>
</div>
<div class="summary-right">
<div class="summary-label">最新里程</div>
<div class="summary-mileage">{{ latestMileageText }}</div>
<div class="summary-sub">最近记录日期{{ latestReportTime || '-' }}</div>
</div>
</div>
<div class="summary-meta">
<div class="meta-line">
<span>里程是否异常</span>
<span class="strong" :class="suspectClass">{{ suspectedText }}</span>
</div>
<div class="meta-line" v-if="imageUrl">
<span>行驶证图片</span>
</div>
<div v-if="imageUrl" class="image-wrap">
<img :src="imageUrl" alt="行驶证图片" class="licence-img" />
</div>
</div>
</div>
<!-- 里程时间轴 -->
<div class="detail-card">
<h4 class="section-title">里程记录时间轴</h4>
<div v-if="mileageList && mileageList.length" class="timeline">
<div v-for="(item, idx) in mileageList" :key="idx" class="timeline-item">
<div class="timeline-left">
<div class="dot-wrap">
<div class="dot" :class="item.mileageStatus === '1' ? 'dot-abnormal' : 'dot-normal'">
</div>
<div v-if="idx !== mileageList.length - 1" class="line"></div>
</div>
</div>
<div class="timeline-content">
<div class="row-main">
<div class="date">{{ formatDate(item.reportTime) }}</div>
<div class="km">{{ formatMileage(item.mileage) }}</div>
</div>
<div class="row-sub">
<span>来源{{ sourceText(item.source) }}</span>
<span v-if="item.mileageStatus === '1'" class="badge-abnormal">异常里程</span>
</div>
</div>
</div>
</div>
<div v-else class="empty-small">
暂无里程记录
</div>
</div>
<!-- 异常里程列表 -->
<div class="detail-card" v-if="adjustList && adjustList.length">
<h4 class="section-title">疑似调表记录</h4>
<div class="adjust-list">
<div v-for="(item, idx) in adjustList" :key="idx" class="adjust-item">
<div class="adjust-time">{{ formatDate(item.reportTime) }}</div>
<div class="adjust-body">
<div>
<div class="adjust-label">调整前</div>
<div class="adjust-value">{{ formatMileage(item.beforeMileage) }}</div>
</div>
<div class="adjust-arrow"></div>
<div>
<div class="adjust-label">调整后</div>
<div class="adjust-value">{{ formatMileage(item.afterMileage) }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无里程数据</div>
<div class="sub">未查询到车辆里程记录或返回数据为空</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
const vin = computed(() => props.data?.vehicleInfo?.vin || '');
const mileageList = computed(() => props.data?.mileageInfo?.mileageList || []);
const adjustList = computed(() => props.data?.mileageInfo?.suspectedAdjustMileageList || []);
const suspectedAdjust = computed(() => props.data?.mileageInfo?.suspectedAdjust);
const imageUrl = computed(() => props.data?.imageUrl || '');
const latestRecord = computed(() => {
const list = mileageList.value;
if (!list || !list.length) return null;
// 默认接口数据已按时间升序,直接取最后一条
return list[list.length - 1];
});
const latestMileageText = computed(() => {
if (!latestRecord.value) return '-';
return formatMileage(latestRecord.value.mileage);
});
const latestReportTime = computed(() => latestRecord.value?.reportTime || '');
const suspectedText = computed(() => {
if (suspectedAdjust.value === 'true') return '存在异常里程行为';
if (suspectedAdjust.value === 'false') return '未发现里程异常';
return '未知';
});
const suspectClass = computed(() => {
if (suspectedAdjust.value === 'true') return 'suspect-yes';
if (suspectedAdjust.value === 'false') return 'suspect-no';
return 'suspect-unknown';
});
const formatMileage = (val) => {
if (!val && val !== 0) return '-';
const num = Number(val);
if (Number.isNaN(num)) return `${val} km`;
// 默认 km按 km 展示
return `${num.toLocaleString()} km`;
};
const sourceText = (source) => {
if (source === '0') return '诊断里程';
if (source === '1') return '维保里程';
return '其他';
};
const formatDate = (val) => {
if (!val) return '-';
// 期望格式 yyyy-MM-dd
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
if (m) {
return `${m[1]}${m[2]}${m[3]}`;
}
return val;
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center mb-4 px-5 py-4 rounded-2xl bg-gradient-to-r from-sky-50 via-blue-50 to-indigo-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-sky-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-sky-800 opacity-90;
}
.summary-card {
@apply rounded-2xl border px-5 py-4 mb-4;
}
.summary-card.suspect-yes {
@apply bg-amber-50 border-amber-100;
}
.summary-card.suspect-no {
@apply bg-emerald-50 border-emerald-100;
}
.summary-card.suspect-unknown {
@apply bg-gray-50 border-gray-200;
}
.summary-main {
@apply flex flex-col mb-3 gap-3;
}
.summary-left {
@apply flex flex-col gap-1;
}
.vin-label {
@apply text-sm text-gray-500;
}
.vin-value {
@apply text-base text-gray-900;
}
.summary-right {
@apply text-left;
}
.summary-label {
@apply text-sm text-gray-500;
}
.summary-mileage {
@apply text-2xl font-bold text-sky-800 mt-1;
}
.summary-sub {
@apply text-sm text-sky-700 opacity-90;
}
.summary-meta {
@apply space-y-1 text-base text-gray-800;
}
.meta-line {
@apply flex flex-wrap items-center gap-2;
}
.meta-line .strong {
@apply font-semibold;
}
.image-wrap {
@apply mt-2 flex justify-center;
}
.licence-img {
@apply rounded-xl border border-gray-200 max-w-full;
max-height: 220px;
object-fit: contain;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-3;
}
.timeline {
@apply mt-2;
}
.timeline-item {
@apply flex mb-3;
}
.timeline-left {
@apply mr-3 flex flex-col items-center;
}
.dot-wrap {
@apply flex flex-col items-stretch;
}
.dot {
@apply w-3 h-3 rounded-full bg-gray-400 self-center;
}
.dot-normal {
@apply bg-emerald-500;
}
.dot-abnormal {
@apply bg-red-500;
}
.line {
@apply flex-1 w-px bg-gray-300 mx-auto;
}
.timeline-content {
@apply flex-1 rounded-2xl border border-gray-100 bg-white px-4 py-3;
}
.row-main {
@apply flex items-baseline justify-between mb-1;
}
.row-main .date {
@apply text-base font-medium text-gray-900;
}
.row-main .km {
@apply text-lg font-semibold text-gray-900;
}
.row-sub {
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
}
.badge-abnormal {
@apply inline-flex items-center px-2 py-0.5 rounded-full bg-red-50 text-red-700 text-xs font-medium;
}
.adjust-list {
@apply space-y-3;
}
.adjust-item {
@apply rounded-2xl border border-amber-100 bg-amber-50/70 px-4 py-3;
}
.adjust-time {
@apply text-sm text-gray-700 mb-2;
}
.adjust-body {
@apply flex items-center justify-between gap-4;
}
.adjust-label {
@apply text-xs text-gray-500 mb-1;
}
.adjust-value {
@apply text-base font-semibold text-gray-900;
}
.adjust-arrow {
@apply text-2xl text-gray-400;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty-small {
@apply text-center py-6 text-sm text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

284
src/ui/CQCXG3Y6B.vue Normal file
View File

@@ -0,0 +1,284 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆维保简版查询</h3>
<p class="header-desc">按时间轴展示维保记录包含保养与更换材料明细</p>
</div>
</div>
<template v-if="hasData">
<!-- 概览VIN + 维保次数 + 最近一次维保 -->
<div class="summary-card">
<div class="summary-main">
<div class="summary-left">
<div class="summary-label">车架号 VIN</div>
<div class="summary-vin font-mono">{{ vin || '-' }}</div>
</div>
<div class="summary-right">
<div class="summary-count">维保记录 {{ totalCount }} </div>
<div class="summary-last" v-if="lastRecord">
最近一次{{ formatDate(lastRecord.lastTime) }} · {{ formatMileage(lastRecord.mileage) }}
</div>
</div>
</div>
</div>
<!-- 维保时间轴 -->
<div class="detail-card">
<h4 class="section-title">维保记录时间轴</h4>
<div v-if="records && records.length" class="timeline">
<div v-for="(item, idx) in records" :key="idx" class="timeline-item">
<div class="timeline-left">
<div class="dot-wrap">
<div class="dot"></div>
<div v-if="idx !== records.length - 1" class="line"></div>
</div>
</div>
<div class="timeline-content">
<div class="row-main">
<div class="date">{{ formatDate(item.lastTime) }}</div>
<div class="km">{{ formatMileage(item.mileage) }}</div>
</div>
<div class="row-sub">
<span class="repair-type">{{ item.repairType || '维保' }}</span>
<span class="vin-small font-mono">VIN: {{ item.vin || vin || '-' }}</span>
</div>
<div v-if="item.details && item.details.length" class="sub-section">
<div class="sub-title">维修项目</div>
<ul class="sub-list">
<li v-for="(d, di) in item.details" :key="di">
<span v-if="d.type" class="tag">{{ d.type }}</span>
<span>{{ d.content }}</span>
</li>
</ul>
</div>
<div v-if="item.materials && item.materials.length" class="sub-section">
<div class="sub-title">使用材料</div>
<ul class="sub-list">
<li v-for="(m, mi) in item.materials" :key="mi">
<span v-if="m.type" class="tag tag-material">{{ m.type }}</span>
<span>{{ m.content }}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div v-else class="empty-small">
暂无维保记录
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无维保数据</div>
<div class="sub">未查询到车辆维保记录或返回数据为空</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
const records = computed(() => Array.isArray(props.data?.record) ? props.data.record : []);
const vin = computed(() => {
if (records.value.length && records.value[0].vin) return records.value[0].vin;
return props.params?.vin_code || '';
});
const totalCount = computed(() => records.value.length || 0);
const lastRecord = computed(() => (records.value.length ? records.value[records.value.length - 1] : null));
const formatDate = (val) => {
if (!val) return '-';
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
if (m) {
return `${m[1]}${m[2]}${m[3]}`;
}
return val;
};
const formatMileage = (val) => {
if (!val && val !== 0) return '-';
const num = Number(val);
if (Number.isNaN(num)) return `${val} km`;
return `${num.toLocaleString()} km`;
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center mb-4 px-5 py-4 rounded-2xl bg-gradient-to-r from-emerald-50 via-sky-50 to-teal-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-emerald-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-emerald-800 opacity-90;
}
.summary-card {
@apply rounded-2xl border border-emerald-100 bg-emerald-50/60 px-5 py-4 mb-4;
}
.summary-main {
@apply flex items-start justify-between mb-3 gap-4;
}
.summary-left {
@apply flex flex-col gap-1;
}
.summary-label {
@apply text-sm text-gray-500;
}
.summary-vin {
@apply text-base text-gray-900;
}
.summary-right {
@apply text-right;
}
.summary-count {
@apply text-base font-semibold text-gray-900;
}
.summary-last {
@apply text-sm text-emerald-800 mt-1;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-3;
}
.timeline {
@apply mt-2;
}
.timeline-item {
@apply flex mb-3;
}
.timeline-left {
@apply mr-3 flex flex-col items-center;
}
.dot-wrap {
@apply flex flex-col items-stretch;
}
.dot {
@apply w-3 h-3 rounded-full bg-emerald-500 self-center;
}
.line {
@apply flex-1 w-px bg-gray-300 mx-auto;
}
.timeline-content {
@apply flex-1 rounded-2xl border border-gray-100 bg-white px-4 py-3;
}
.row-main {
@apply flex items-baseline justify-between mb-1;
}
.row-main .date {
@apply text-base font-medium text-gray-900;
}
.row-main .km {
@apply text-lg font-semibold text-gray-900;
}
.row-sub {
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
}
.repair-type {
@apply font-medium text-gray-800;
}
.vin-small {
@apply text-xs text-gray-500;
}
.sub-section {
@apply mt-3;
}
.sub-title {
@apply text-sm font-semibold text-gray-800 mb-1;
}
.sub-list {
@apply text-sm text-gray-800 space-y-1;
}
.sub-list li {
@apply flex flex-wrap gap-1;
}
.tag {
@apply inline-flex items-center px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-700 text-xs font-medium;
}
.tag-material {
@apply bg-sky-50 text-sky-700;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty-small {
@apply text-center py-6 text-sm text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

288
src/ui/CQCXG3Z3L.vue Normal file
View File

@@ -0,0 +1,288 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆维保详细版查询</h3>
<p class="header-desc">展示品牌车架号等基本信息及每次维保的详细内容</p>
</div>
</div>
<template v-if="hasData">
<!-- 概览品牌 + VIN + 车牌号 + 发动机号 -->
<div class="summary-card">
<div class="summary-main">
<div class="summary-left">
<div class="summary-label">品牌名称</div>
<div class="summary-brand">{{ data.brandName || '未知品牌' }}</div>
</div>
<div class="summary-right">
<div class="summary-label">车架号 VIN</div>
<div class="summary-vin font-mono">{{ data.vin || '-' }}</div>
</div>
</div>
<div class="summary-meta">
<div class="meta-line">
<span>车牌号</span><span class="strong">{{ data.licensePlate || '未提供' }}</span>
<span class="dot"></span>
<span>发动机号</span><span class="strong code">{{ data.engine || '-' }}</span>
</div>
<div class="meta-line" v-if="records.length">
<span>维保记录</span><span class="strong">{{ records.length }} </span>
<span class="dot"></span>
<span>最近一次</span>
<span class="strong">{{ formatDate(records[records.length - 1].date) }}</span>
</div>
</div>
</div>
<!-- 维保时间轴 -->
<div class="detail-card">
<h4 class="section-title">维保记录时间轴</h4>
<div v-if="records && records.length" class="timeline">
<div v-for="(item, idx) in records" :key="idx" class="timeline-item">
<div class="timeline-left">
<div class="dot-wrap">
<div class="dot"></div>
<div v-if="idx !== records.length - 1" class="line"></div>
</div>
</div>
<div class="timeline-content">
<div class="row-main">
<div class="date">{{ formatDate(item.date) }}</div>
<div class="km">{{ formatMileage(item.mileage) }}</div>
</div>
<div class="row-sub">
<span class="repair-type">{{ item.type || '维保' }}</span>
<span class="brand-small" v-if="data.brandName">{{ data.brandName }}</span>
</div>
<div class="sub-section" v-if="item.content">
<div class="sub-title">维修内容</div>
<div class="sub-text">{{ item.content }}</div>
</div>
<div class="sub-section" v-if="item.material">
<div class="sub-title">材料</div>
<div class="sub-text">{{ item.material }}</div>
</div>
<div class="sub-section" v-if="item.remark">
<div class="sub-title">备注</div>
<div class="sub-text">{{ item.remark }}</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-small">
暂无维保记录
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无维保数据</div>
<div class="sub">未查询到车辆维保记录或返回数据为空</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
const records = computed(() => Array.isArray(props.data?.record) ? props.data.record : []);
const data = computed(() => props.data || {});
const formatDate = (val) => {
if (!val) return '-';
const m = String(val).match(/^(\d{4})-(\d{2})-(\d{2})/);
if (m) {
return `${m[1]}${m[2]}${m[3]}`;
}
return val;
};
const formatMileage = (val) => {
if (!val && val !== 0) return '-';
const num = Number(val);
if (Number.isNaN(num)) return `${val} km`;
return `${num.toLocaleString()} km`;
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center mb-4 px-5 py-4 rounded-2xl bg-gradient-to-r from-indigo-50 via-sky-50 to-emerald-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-indigo-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-indigo-800 opacity-90;
}
.summary-card {
@apply rounded-2xl border border-indigo-100 bg-indigo-50/60 px-5 py-4 mb-4;
}
.summary-main {
@apply flex items-start justify-between mb-3 gap-4;
}
.summary-left {
@apply flex flex-col gap-1;
}
.summary-right {
@apply text-right;
}
.summary-label {
@apply text-sm text-gray-500;
}
.summary-brand {
@apply text-lg font-semibold text-gray-900;
}
.summary-vin {
@apply text-base text-gray-900;
}
.summary-meta {
@apply space-y-1 text-base text-gray-800;
}
.meta-line {
@apply flex flex-wrap items-center gap-2;
}
.meta-line .dot {
@apply w-1 h-1 rounded-full bg-gray-400;
}
.strong {
@apply font-semibold;
}
.code {
@apply font-mono tracking-wide;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-3;
}
.timeline {
@apply mt-2;
}
.timeline-item {
@apply flex mb-3;
}
.timeline-left {
@apply mr-3 flex flex-col items-center;
}
.dot-wrap {
@apply flex flex-col items-stretch;
}
.dot {
@apply w-3 h-3 rounded-full bg-indigo-500 self-center;
}
.line {
@apply flex-1 w-px bg-gray-300 mx-auto;
}
.timeline-content {
@apply flex-1 rounded-2xl border border-gray-100 bg-white px-4 py-3;
}
.row-main {
@apply flex items-baseline justify-between mb-1;
}
.row-main .date {
@apply text-base font-medium text-gray-900;
}
.row-main .km {
@apply text-lg font-semibold text-gray-900;
}
.row-sub {
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
}
.repair-type {
@apply font-medium text-gray-800;
}
.brand-small {
@apply text-xs text-gray-500;
}
.sub-section {
@apply mt-3;
}
.sub-title {
@apply text-sm font-semibold text-gray-800 mb-1;
}
.sub-text {
@apply text-sm text-gray-800 whitespace-pre-wrap break-words;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty-small {
@apply text-center py-6 text-sm text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

126
src/ui/CQCXG4D2E.vue Normal file
View File

@@ -0,0 +1,126 @@
<template>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<!-- 车辆总数统计 -->
<div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div>
<div class="text-lg font-semibold text-gray-900">名下车辆(数量)</div>
</div>
</div>
<div class="bg-blue-50 text-blue-700 px-4 py-2 rounded-full text-sm font-medium">
{{ vehicleCount }}
</div>
</div>
<!-- 车辆列表 -->
<div class="space-y-3" v-if="vehicleList && vehicleList.length > 0">
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:bg-blue-50 hover:border-blue-200 transition-colors duration-200"
v-for="(vehicle, index) in vehicleList" :key="index">
<div class="space-y-3">
<div class="text-xl font-bold text-gray-900 font-mono tracking-wider">
{{ vehicle.plateNum }}
</div>
<div class="flex items-center gap-3">
<div class="inline-flex items-center gap-1 px-3 py-1 rounded text-xs font-medium text-white"
:class="getPlateColorClass(vehicle.plateColor)">
<span>🏷</span>
<span>{{ getPlateColorText(vehicle.plateColor) }}</span>
</div>
<div class="text-sm text-gray-600">
<span class="text-gray-500">车辆类型:</span>
<span class="font-medium text-gray-900 ml-1">{{ getVehicleTypeText(vehicle.vehicleType)
}}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 无数据状态 -->
<div class="text-center py-12 text-gray-500" v-else>
<div class="text-4xl mb-3">🚫</div>
<div class="text-lg font-medium mb-1">暂无车辆信息</div>
<div class="text-sm">No vehicle records found</div>
</div>
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: Object,
params: Object,
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const plateColorMap = {
0: '蓝色 - 普通燃油车',
1: '黄色 - 大型车/货车',
2: '黑色 - 外籍车辆/港澳台车',
3: '白色 - 警车/军车/武警车',
4: '渐变绿色 - 新能源汽车',
5: '黄绿双拼色 - 大型新能源汽车',
6: '蓝白渐变色 - 临时牌照',
7: '临时牌照 - 临时行驶车辆',
11: '绿色 - 新能源汽车',
12: '红色 - 教练车/试验车'
};
const vehicleTypeMap = {
1: '一型客车',
2: '二型客车',
3: '三型客车',
4: '四型客车',
11: '一型货车',
12: '二型货车',
13: '三型货车',
14: '四型货车',
15: '五型货车',
16: '六型货车',
21: '一型专项作业车',
22: '二型专项作业车',
23: '三型专项作业车',
24: '四型专项作业车',
25: '五型专项作业车',
26: '六型专项作业车'
};
const vehicleList = computed(() => props.data?.list || []);
const vehicleCount = computed(() => props.data?.vehicleCount || 0);
const getPlateColorText = (plateColor) => {
return plateColorMap[plateColor] || '未知颜色 - 未知类型';
};
const getPlateColorClass = (plateColor) => {
const colorClassMap = {
0: 'bg-blue-500',
1: 'bg-yellow-500',
2: 'bg-gray-800',
3: 'bg-gray-200 text-gray-800',
4: 'bg-green-500',
5: 'bg-gradient-to-r from-yellow-500 to-green-500',
6: 'bg-gradient-to-r from-blue-500 to-white text-blue-800',
7: 'bg-red-500',
11: 'bg-green-500',
12: 'bg-red-500'
};
return colorClassMap[plateColor] || 'bg-gray-500';
};
const getVehicleTypeText = (vehicleType) => {
return vehicleTypeMap[vehicleType] || '未知类型';
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
/* 保持与 CQCXG9P1C 一致的布局风格 */
</style>

256
src/ui/CQCXG4I1Z.vue Normal file
View File

@@ -0,0 +1,256 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆过户详版查询</h3>
<p class="header-desc">按时间轴展示每一次车辆过户的车牌与地区变更情况</p>
</div>
<div class="header-tag" v-if="totalTimes">
<span class="tag-label">总过户次数</span>
<span class="tag-value">{{ totalTimes }} </span>
</div>
</div>
<template v-if="transfers && transfers.length">
<div class="timeline">
<div v-for="(item, index) in transfers" :key="index" class="timeline-item">
<div class="timeline-left">
<div class="dot-wrap">
<div class="dot"></div>
<div v-if="index !== transfers.length - 1" class="line"></div>
</div>
</div>
<div class="timeline-content">
<div class="transfer-header">
<div class="transfer-date">{{ item.changeMonthFormatted }}</div>
<div class="transfer-count"> {{ item.transTimeSum }} 次过户</div>
</div>
<div class="plates-row">
<div class="plate old">
<div class="label">过户前车牌</div>
<div class="value">{{ item.oldCp || '未知' }}</div>
<div class="city" v-if="item.cityBefore">所在城市{{ item.cityBefore }}</div>
</div>
<div class="arrow"></div>
<div class="plate new">
<div class="label">过户后车牌</div>
<div class="value">{{ item.newCp || '未知' }}</div>
<div class="city" v-if="item.cityAfter">所在城市{{ item.cityAfter }}</div>
</div>
</div>
<div class="interval-row">
<span>距上次过户</span>
<span class="strong">{{ item.intervalText }}</span>
</div>
<div class="vin-row">
<span class="vin-label">VIN</span>
<span class="vin-value font-mono">{{ item.vin || '-' }}</span>
</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无过户明细</div>
<div class="sub">未查询到车辆过户明细记录</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const rawList = computed(() => Array.isArray(props.data?.retdata) ? props.data.retdata : []);
const transfers = computed(() =>
rawList.value.map((item) => {
const changeMonth = item.changeMonth;
let changeMonthFormatted = '-';
if (changeMonth === '近一年内过户') {
changeMonthFormatted = '近一年内过户';
} else if (typeof changeMonth === 'string' && changeMonth.length === 6) {
const y = changeMonth.slice(0, 4);
const m = changeMonth.slice(4, 6);
changeMonthFormatted = `${y}${m}`;
} else if (changeMonth) {
changeMonthFormatted = changeMonth;
}
let intervalText = '-';
if (item.transYear || item.transMonth) {
const years = item.transYear ? `${item.transYear}` : '';
const months = item.transMonth ? `${item.transMonth}个月` : '';
intervalText = `${years}${months}` || '-';
}
return {
...item,
changeMonthFormatted,
intervalText,
};
})
);
const totalTimes = computed(() => {
if (!transfers.value.length) return '';
const last = transfers.value[transfers.value.length - 1];
return last.transTimeSum ?? '';
});
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-indigo-50 via-blue-50 to-indigo-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-indigo-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-indigo-800 opacity-90;
}
.header-tag {
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm text-indigo-800;
}
.tag-label {
@apply text-sm text-gray-500;
}
.tag-value {
@apply text-2xl font-bold leading-none whitespace-nowrap;
}
.timeline {
@apply mt-4;
}
.timeline-item {
@apply flex mb-4;
}
.timeline-left {
@apply mr-3 flex flex-col items-center;
}
.dot-wrap {
@apply flex flex-col items-center;
}
.dot {
@apply w-3 h-3 rounded-full bg-indigo-500;
}
.line {
@apply flex-1 w-px bg-gray-300 mt-1;
}
.timeline-content {
@apply flex-1 rounded-2xl border border-gray-100 bg-gray-50/70 px-4 py-3;
}
.transfer-header {
@apply flex items-baseline justify-between mb-2;
}
.transfer-date {
@apply text-lg font-semibold text-gray-900;
}
.transfer-count {
@apply text-sm text-gray-600;
}
.plates-row {
@apply flex items-stretch gap-3 mt-2;
}
.plate {
@apply flex-1 rounded-xl px-3 py-2 border border-gray-200 bg-white;
}
.plate .label {
@apply text-xs text-gray-500 mb-1;
}
.plate .value {
@apply text-lg font-semibold text-gray-900;
}
.plate .city {
@apply text-sm text-gray-600 mt-1;
}
.plate.old {
@apply bg-gray-50;
}
.plate.new {
@apply bg-indigo-50/60 border-indigo-100;
}
.arrow {
@apply flex items-center justify-center text-2xl text-gray-400;
}
.interval-row {
@apply mt-3 text-base text-gray-700;
}
.interval-row .strong {
@apply font-semibold;
}
.vin-row {
@apply mt-2 text-sm text-gray-600;
}
.vin-label {
@apply text-gray-500;
}
.vin-value {
@apply text-base text-gray-900;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

212
src/ui/CQCXG5U0Z.vue Normal file
View File

@@ -0,0 +1,212 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆静态信息查询</h3>
<p class="header-desc">查看车辆生产排放标准及燃料等核心静态信息</p>
</div>
</div>
<template v-if="records && records.length">
<div v-for="(item, idx) in records" :key="idx" class="vehicle-card">
<div class="vehicle-title">
<div>
<div class="vehicle-chip">车辆 {{ idx + 1 }}</div>
<div class="vehicle-model">
{{ item.vType || '未知车型' }}
</div>
</div>
<div class="vehicle-meta">
<span class="badge">{{ item.vFuelType || '燃料未知' }}</span>
</div>
</div>
<div class="field-grid">
<div class="field">
<div class="field-label">发动机号</div>
<div class="field-value code">{{ item.engineNO || '-' }}</div>
</div>
<div class="field">
<div class="field-label">发动机型号</div>
<div class="field-value code">{{ item.engineType || '-' }}</div>
</div>
<div class="field">
<div class="field-label">生产日期</div>
<div class="field-value">{{ item.vScdate || '-' }}</div>
</div>
<div class="field">
<div class="field-label">排放阶段</div>
<div class="field-value">{{ item.dischargeStage || '-' }}</div>
</div>
<div class="field">
<div class="field-label">车辆分类</div>
<div class="field-value">{{ item.vClassification || '-' }}</div>
</div>
<div class="field field-span">
<div class="field-label">生产企业名称</div>
<div class="field-value">{{ item.vManufacturer || '-' }}</div>
</div>
<div class="field field-span">
<div class="field-label">生产厂地址</div>
<div class="field-value">{{ item.vSccdz || '-' }}</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无车辆静态信息</div>
<div class="sub">未查询到车辆静态信息或返回格式不正确</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: [Object, String, Array], default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
// 解析返回的 JSON 字符串,得到数组
const records = computed(() => {
const raw = props.data;
if (!raw) return [];
// data 本身是字符串
if (typeof raw === 'string') {
try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) return parsed;
return [];
} catch {
return [];
}
}
// data 已经是数组
if (Array.isArray(raw)) {
return raw;
}
// data 是对象,里层再包了一层字符串/数组的情况
if (typeof raw === 'object') {
if (Array.isArray(raw.list)) return raw.list;
if (typeof raw.data === 'string') {
try {
const parsed = JSON.parse(raw.data);
if (Array.isArray(parsed)) return parsed;
} catch {
return [];
}
}
}
return [];
});
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply rounded-2xl mb-5 px-5 py-4 bg-gradient-to-r from-blue-50 via-indigo-50 to-blue-50 flex items-center justify-between;
color: #1e40af;
}
.header-title {
@apply text-xl font-semibold m-0;
}
.header-desc {
@apply text-sm mt-2 m-0 opacity-80 text-gray-700;
}
.header-left {
@apply flex flex-col;
}
.header-tag {
@apply inline-flex items-center gap-2 px-3 py-1 rounded-full text-base font-medium bg-white/80 text-blue-700 shadow-sm;
}
.header-tag .dot {
@apply w-2 h-2 rounded-full bg-green-500;
}
.vehicle-card {
@apply mb-4 p-5 rounded-2xl border border-gray-100 bg-gray-50/80;
}
.vehicle-title {
@apply flex items-start justify-between text-base text-gray-700;
}
.vehicle-title .label {
@apply text-base uppercase tracking-wide text-gray-500;
}
.vehicle-title .value {
@apply font-semibold text-lg text-gray-900 mt-1;
}
.vehicle-model {
@apply text-base font-semibold text-gray-900 mt-1;
}
.vehicle-meta {
@apply flex items-center gap-2;
}
.badge {
@apply inline-flex items-center px-3 py-1 rounded-full text-base font-medium bg-blue-100 text-blue-700;
}
.field-label {
@apply text-base text-gray-500 mb-1;
}
.field-value {
@apply text-base text-gray-900;
}
.field-value.code {
@apply font-mono tracking-wide;
}
.field-grid {
@apply grid gap-y-3 gap-x-6 mt-4;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.field-span {
grid-column: 1 / -1;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-base font-medium mb-1;
}
.empty .sub {
@apply text-base;
}
</style>

493
src/ui/CQCXG6B4E.vue Normal file
View File

@@ -0,0 +1,493 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆出险记录核验</h3>
<p class="header-desc">综合车辆出险脱保重大事故等信息评估风险</p>
</div>
</div>
<template v-if="hasData">
<div class="risk-band" :class="riskLevelClass">
<div class="risk-band-label">风险等级</div>
<div class="risk-band-text">{{ riskLevelText }}</div>
</div>
<!-- 顶部车辆与价格概览 -->
<div class="summary-card">
<div class="summary-main">
<div class="summary-left">
<div class="plate" v-if="data.LicensePlate">{{ data.LicensePlate }}</div>
<div class="car-type">{{ data.CarType || '未知车型' }}</div>
</div>
<div class="summary-right">
<div class="summary-label">二手车价格参考</div>
<div class="summary-price">{{ usedCarPriceText }}</div>
<div class="summary-sub">新车购置价{{ newCarPriceText }}</div>
</div>
</div>
<div class="summary-meta">
<div class="meta-line">
<span>燃料</span><span class="strong">{{ data.FuelType || '未知' }}</span>
<span class="dot"></span>
<span>发动机号</span><span class="strong code">{{ data.EngineNumber || '-' }}</span>
</div>
<div class="meta-line">
<span>初登日期</span><span class="strong">{{ data.DebutDate || '-' }}</span>
<span class="dot"></span>
<span>车龄</span><span class="strong">{{ carAgeText }}</span>
</div>
</div>
</div>
<!-- 核心风险指标 -->
<div class="detail-card">
<h4 class="section-title">核心风险指标</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">是否高风险车辆</div>
<div class="field-value" :class="flagClass(data.IfHighriskVehicle === '1')">
{{ yesNoText(data.IfHighriskVehicle, '高风险车辆') }}
</div>
</div>
<div class="field">
<div class="field-label">是否营运车辆</div>
<div class="field-value" :class="flagClass(data.IsOperation === '1')">
{{ yesNoText(data.IsOperation, '营运车辆') }}
</div>
</div>
<div class="field">
<div class="field-label">是否投保车损险</div>
<div class="field-value" :class="flagClass(data.IfCarDamage === '1')">
{{ yesNoText(data.IfCarDamage, '已投保车损险', '未投保车损险') }}
</div>
</div>
<div class="field">
<div class="field-label">是否连续投保</div>
<div class="field-value" :class="flagClass(data.IsConInsure === '1')">
{{ yesNoText(data.IsConInsure, '连续投保', '非连续投保') }}
</div>
</div>
<div class="field">
<div class="field-label">历史是否脱保</div>
<div class="field-value" :class="flagClass(data.IfTuoBao === '1')">
{{ yesNoText(data.IfTuoBao, '有脱保记录', '无脱保记录') }}
</div>
</div>
<div class="field">
<div class="field-label">历史最大脱保时间</div>
<div class="field-value">{{ data.TuoBaoTime || '-' }}</div>
</div>
<div class="field">
<div class="field-label">最高车损险损失比例</div>
<div class="field-value">{{ data.CompensationRatioo || '-' }}</div>
</div>
<div class="field">
<div class="field-label">车损险综合评分</div>
<div class="field-value strong">{{ data.Total || '-' }}</div>
</div>
</div>
</div>
<!-- 出险与事故情况 -->
<div class="detail-card">
<h4 class="section-title">出险与事故情况</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">商业险出险</div>
<div class="field-value">{{ formatDangerCount(data.CommercialPolicyDangerCount, '商业险') }}</div>
</div>
<div class="field">
<div class="field-label">交强险出险</div>
<div class="field-value">{{ formatDangerCount(data.CompulsoryPolicyDangerCount, '交强险') }}</div>
</div>
<div class="field">
<div class="field-label">三者险出险次数</div>
<div class="field-value">{{ formatDangerCount(data.ThreeRisksDangerCount, '三者险') }}</div>
</div>
<div class="field">
<div class="field-label">全损情况</div>
<div class="field-value">{{ totalLossText }}</div>
</div>
<div class="field field-span">
<div class="field-label">重大事故标志</div>
<div class="field-value">{{ formatMajorAccident(data.MajorAccident) }}</div>
</div>
<div class="field">
<div class="field-label">事故次数</div>
<div class="field-value">{{ data.IsMajorAccidentData || '-' }}</div>
</div>
<div class="field">
<div class="field-label">事故等级</div>
<div class="field-value">{{ data.IsMajorAccidentLevel || '-' }}</div>
</div>
<div class="field field-span">
<div class="field-label">损失部位</div>
<div class="field-value">{{ formatLossPart(data.LossPart) }}</div>
</div>
</div>
</div>
<!-- 保单与责任险可保情况 -->
<div class="detail-card">
<h4 class="section-title">保单与责任险承保情况</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">商业险保单倒计时</div>
<div class="field-value">{{ formatPolicyTime(data.CommercialPolicyTime, '商业险') }}</div>
</div>
<div class="field">
<div class="field-label">交强险保单倒计时</div>
<div class="field-value">{{ formatPolicyTime(data.CompulsoryPolicyTime, '交强险') }}</div>
</div>
<div class="field">
<div class="field-label">商业险过户次数</div>
<div class="field-value">{{ formatTransferCount(data.CommercialPolicyTransferCount) }}</div>
</div>
<div class="field">
<div class="field-label">交强险过户次数</div>
<div class="field-value">{{ formatTransferCount(data.CompulsoryPolicyTransferCount) }}</div>
</div>
<div class="field">
<div class="field-label">是否可投保责任险</div>
<div class="field-value" :class="flagClass(data.IsLiabilityAvailable === 'Y')">
{{ ynText(data.IsLiabilityAvailable, '可投保', '不可投保') }}
</div>
</div>
<div class="field">
<div class="field-label">是否可承保延保</div>
<div class="field-value" :class="flagClass(data.IsExtendAvailable === 'Y')">
{{ ynText(data.IsExtendAvailable, '可承保', '不可承保') }}
</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无出险记录</div>
<div class="sub">未查询到车辆出险记录或返回数据为空</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const data = computed(() => props.data || {});
const hasData = computed(() => !!data.value && Object.keys(data.value).length > 0);
const usedCarPriceText = computed(() => {
const v = data.value.UsedCarPrice;
if (!v) return '-';
return `${v}`;
});
const newCarPriceText = computed(() => {
const v = data.value.PurchasePrice;
if (!v) return '-';
return `${v}`;
});
const carAgeText = computed(() => {
const m = data.value.CarAge;
if (!m) return '-';
return `${m} 个月`;
});
const totalLossText = computed(() => {
const v = data.value.TotalLoss;
if (v === '1') return '存在全损记录';
if (v === '0') return '无全损记录';
return '-';
});
// 简单按高风险车辆/重大事故等情况给出一个文字风险等级
const riskLevelText = computed(() => {
if (data.value.IfHighriskVehicle === '1') return '高风险';
if (data.value.IsMajorAccidentLevel && data.value.IsMajorAccidentLevel !== '一般') return '较高风险';
if (data.value.IsMajorAccidentData && data.value.IsMajorAccidentData !== '0') return '有事故记录';
return '风险可控';
});
const riskLevelClass = computed(() => {
const t = riskLevelText.value;
if (t === '高风险') return 'risk-high';
if (t === '较高风险' || t === '有事故记录') return 'risk-mid';
return 'risk-low';
});
const flagClass = (flag) => {
return flag ? 'flag-yes' : 'flag-no';
};
const yesNoText = (val, yesText, noText = '否') => {
if (val === '1') return yesText;
if (val === '0') return noText;
return '-';
};
const ynText = (val, yesText, noText) => {
if (val === 'Y') return yesText;
if (val === 'N') return noText;
return '-';
};
const formatPolicyTime = (val, label) => {
if (!val || val === 'NULL') {
return `当期无${label}保单`;
}
const parts = String(val).split(':');
if (parts.length < 2) return val;
const daysRaw = parts[1];
if (!daysRaw || daysRaw.toLowerCase() === 'null') {
return `${label}保单已过期`;
}
const days = Number(daysRaw);
if (Number.isNaN(days)) return val;
if (days < 0) return `${label}保单已过期`;
return `${label}保单剩余 ${days}`;
};
const formatDangerCount = (val, label) => {
if (!val) return '-';
const parts = String(val).split(':');
const countRaw = parts[1] ?? '';
if (!countRaw || countRaw.toLowerCase() === 'null') {
return `${label}暂无出险记录`;
}
const count = Number(countRaw);
if (Number.isNaN(count)) return val;
if (count === 0) return `${label}暂无出险记录`;
return `${label}出险 ${count}`;
};
const formatTransferCount = (val) => {
if (!val) return '-';
const parts = String(val).split(':');
const countRaw = parts[1] ?? '';
if (!countRaw || countRaw.toLowerCase() === 'null') return '-';
const count = Number(countRaw);
if (Number.isNaN(count)) return val;
return `${count}`;
};
const formatMajorAccident = (val) => {
if (!val) return '-';
const map = {
A: '碰撞',
B: '火自燃',
C: '水淹',
D: '盗抢',
};
const list = [];
String(val)
.split(',')
.forEach((pair) => {
const [k, v] = pair.split(':');
if (v === '1' && map[k]) {
list.push(map[k]);
}
});
if (!list.length) return '无重大事故记录';
return `重大事故类型:${list.join('、')}`;
};
const formatLossPart = (val) => {
if (!val) return '-';
const partMap = {
1: '正前方',
2: '正后方',
3: '顶部',
4: '底部',
5: '前方左侧',
6: '后方左侧',
7: '中间左侧',
8: '前方右侧',
9: '后方右侧',
10: '中间右侧',
11: '内部',
12: '其它',
13: '不详',
};
const items = [];
String(val)
.split(',')
.forEach((pair) => {
const [kRaw, vRaw] = pair.split(':');
const key = Number(kRaw);
const count = Number(vRaw);
if (!Number.isNaN(key) && !Number.isNaN(count) && count > 0) {
const label = partMap[key] || `部位${key}`;
items.push(`${label}${count}`);
}
});
if (!items.length) return '暂无损失部位信息';
return items.join('、');
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center mb-3 px-5 py-4 rounded-2xl bg-gradient-to-r from-rose-50 via-orange-50 to-amber-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-rose-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-rose-800 opacity-90;
}
.risk-band {
@apply mb-4 px-4 py-3 rounded-2xl flex items-center justify-between;
}
.risk-band.risk-high {
@apply bg-red-50 border border-red-100;
}
.risk-band.risk-mid {
@apply bg-amber-50 border border-amber-100;
}
.risk-band.risk-low {
@apply bg-emerald-50 border border-emerald-100;
}
.risk-band-label {
@apply text-sm text-gray-600;
}
.risk-band-text {
@apply text-xl font-bold;
}
.summary-card {
@apply rounded-2xl border border-amber-100 bg-amber-50/60 px-5 py-4 mb-4;
}
.summary-main {
@apply flex items-start justify-between mb-3 gap-4;
}
.summary-left {
@apply flex flex-col gap-2;
}
.plate {
@apply inline-flex items-center px-4 py-2 rounded-full bg-slate-900 text-white text-xl font-semibold tracking-widest;
}
.car-type {
@apply text-lg font-medium text-gray-800;
}
.summary-right {
@apply text-right;
}
.summary-label {
@apply text-sm text-gray-500;
}
.summary-price {
@apply text-2xl font-bold text-amber-800 mt-1;
}
.summary-sub {
@apply text-sm text-amber-700 opacity-90;
}
.summary-meta {
@apply space-y-1 text-base text-gray-800;
}
.meta-line {
@apply flex flex-wrap items-center gap-2;
}
.meta-line .dot {
@apply w-1 h-1 rounded-full bg-gray-400;
}
.strong {
@apply font-semibold;
}
.code {
@apply font-mono tracking-wide;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-3;
}
.field-grid {
@apply grid gap-y-3 gap-x-6;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.field-label {
@apply text-sm text-gray-500 mb-1;
}
.field-value {
@apply text-base text-gray-900;
}
.field-span {
grid-column: 1 / -1;
}
.flag-yes {
@apply text-red-700;
}
.flag-no {
@apply text-emerald-700;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

216
src/ui/CQCXGGB2Q.vue Normal file
View File

@@ -0,0 +1,216 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">人车核验简版</h3>
<p class="header-desc">校验人员姓名与车辆号牌是否匹配</p>
</div>
<div class="result-section" :class="resultSectionClass">
<div class="result-icon-wrap">
<span class="result-icon" :class="iconClass">
{{ iconChar }}
</span>
</div>
<div class="result-label">核验结果</div>
<div class="result-value" :class="resultClass">{{ resultText }}</div>
</div>
<div v-if="hasParams" class="info-rows">
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">车牌号</span>
<span class="info-value font-mono">{{ params?.plate_no || params?.car_license || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">号牌类型</span>
<span class="info-value">{{ params?.carplate_type || params?.car_type || '-' }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
params: {
type: Object,
default: () => ({}),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
// verify_code: 1 一致2 不匹配
const isMatch = computed(() => {
const code = props.data?.verify_code;
if (code === 1) return true;
if (code === 2) return false;
return null; // 无有效数据时
});
const resultText = computed(() => {
if (isMatch.value === true) return '一致';
if (isMatch.value === false) return '不匹配';
return '暂无结果';
});
const resultClass = computed(() => {
if (isMatch.value === true) return 'result-match';
if (isMatch.value === false) return 'result-mismatch';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
if (isMatch.value === true) return 'result-section match';
if (isMatch.value === false) return 'result-section mismatch';
return 'result-section unknown';
});
const iconClass = computed(() => {
if (isMatch.value === true) return 'icon-match';
if (isMatch.value === false) return 'icon-mismatch';
return 'icon-unknown';
});
const iconChar = computed(() => {
if (isMatch.value === true) return '✓';
if (isMatch.value === false) return '✕';
return '?';
});
const hasParams = computed(() => {
const p = props.params || {};
return p.name || p.plate_no || p.car_license || p.carplate_type || p.car_type;
});
// 简版人车核验本身不直接计为负面风险,给满分
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({
riskScore,
});
</script>
<style scoped>
.card {
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
}
.header-box {
@apply rounded-lg mb-4 p-4;
background: linear-gradient(135deg, var(--van-theme-primary) 0%, var(--van-theme-primary-dark, #1565c0) 100%);
color: #fff;
}
.header-title {
@apply text-lg font-semibold m-0;
}
.header-desc {
@apply text-sm mt-1 opacity-90 m-0;
}
.result-section {
@apply rounded-xl p-5 text-center mb-4;
}
.result-section.match {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
border: 1px solid rgba(76, 175, 80, 0.3);
}
.result-section.mismatch {
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
border: 1px solid rgba(244, 67, 54, 0.3);
}
.result-section.unknown {
@apply bg-gray-50 border border-gray-200;
}
.result-icon-wrap {
@apply mb-2;
}
.result-icon {
@apply inline-flex items-center justify-center w-12 h-12 rounded-full text-2xl font-bold text-white;
}
.result-icon.icon-match {
background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.4);
}
.result-icon.icon-mismatch {
background: linear-gradient(135deg, #e53935 0%, #c62828 100%);
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4);
}
.result-icon.icon-unknown {
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
}
.result-label {
@apply text-sm text-gray-500 mb-1;
}
.result-value {
@apply text-xl font-semibold;
}
.result-match {
color: #2e7d32;
}
.result-mismatch {
color: #c62828;
}
.result-unknown {
@apply text-gray-500;
}
.info-rows {
@apply space-y-3 pt-2 border-t border-gray-100;
}
.info-row {
@apply flex items-center text-sm;
}
.info-label {
@apply w-20 text-gray-500 shrink-0;
}
.info-value {
@apply font-medium text-gray-800;
}
</style>

634
src/ui/CQCXGP00W.vue Normal file
View File

@@ -0,0 +1,634 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">车辆出险详版查询</h3>
<p class="header-desc">展示多维出险记录碰撞部位及车况信息辅助评估车辆风险</p>
</div>
</div>
<template v-if="hasData">
<!-- 车辆基本信息 + 碰撞统计 -->
<div class="summary-card">
<div class="summary-main">
<div class="summary-left">
<div class="summary-label">品牌名称</div>
<div class="summary-brand">{{ clxx.brandName || '未知品牌' }}</div>
<div class="summary-subline" v-if="clxx.vehicleStyle">
{{ clxx.vehicleStyle }}
</div>
</div>
<div class="summary-right">
<div class="summary-label">车架号 VIN</div>
<div class="summary-vin font-mono">{{ pzVin || clxxVin || '-' }}</div>
<div class="summary-subline" v-if="clxx.licensePlate">
车牌号{{ clxx.licensePlate }}
</div>
</div>
</div>
<div class="summary-meta">
<div class="meta-line">
<span class="meta-label">事故总次数</span>
<span class="meta-value strong">{{ tjxx.claimCount ?? '-' }}</span>
</div>
<div class="meta-line">
<span class="meta-label">总维修金额</span>
<span class="meta-value strong">{{ tjxx.totalAmount || '-' }}</span>
</div>
<div class="meta-line">
<span class="meta-label">最大单次维修金额</span>
<span class="meta-value strong">{{ tjxx.largestAmount || '-' }}</span>
</div>
<div class="meta-line">
<span class="meta-label">已结案次数</span>
<span class="meta-value strong">{{ tjxx.claimCacCount ?? 0 }} </span>
</div>
<div class="meta-line">
<span class="meta-label">未结案次数</span>
<span class="meta-value strong">{{ tjxx.claimUnCacCount ?? 0 }} </span>
</div>
</div>
</div>
<!-- 碰撞记录时间轴 -->
<div class="detail-card" v-if="pzRecords && pzRecords.length">
<h4 class="section-title">碰撞出险记录</h4>
<div class="timeline">
<div v-for="(rec, idx) in pzRecords" :key="idx" class="timeline-item">
<div class="timeline-content">
<div class="row-main">
<div class="date">{{ rec.date || '-' }}</div>
<div class="amount">{{ formatFen(rec.serviceMoney) }}</div>
</div>
<div class="row-sub">
<span>{{ rec.accidentType || '出险' }}</span>
<span class="status">{{ rec.claimStatus || '-' }}</span>
</div>
<div class="sub-section" v-if="rec.result && rec.result.length">
<div class="sub-title">维修明细</div>
<ul class="sub-list">
<li v-for="(d, di) in rec.result" :key="di">
<span class="tag">{{ dangerTypeText(d.dangerSingleType) }}</span>
<span>{{ d.dangerSingleName }}</span>
<span v-if="d.dangerSingleNum">×{{ d.dangerSingleNum }}</span>
<span v-if="d.dangerSingleMoney" class="money">
{{ formatFen(d.dangerSingleMoney) }}
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 车况排查大类 -->
<div class="detail-card" v-if="ckdlpc">
<h4 class="section-title">车况排查大类</h4>
<div class="ckdlpc-grid">
<div v-for="item in ckdlpcList" :key="item.key" class="ckdlpc-item">
<div class="ckdlpc-name">{{ item.label }}</div>
<div class="ckdlpc-status" :class="ckLevelClass(item.value)">
{{ ckLevelText(item.value) }}
</div>
</div>
</div>
</div>
<!-- 车况明细排查部件所有分组都展示如整体无命中则显示一行暂无车况明细排查记录 -->
<div class="detail-card" v-if="ckpclbGroups && ckpclbGroups.length">
<h4 class="section-title">车况明细排查部件</h4>
<template v-if="hasCkpclbHit">
<div class="ckpclb-grid">
<div v-for="group in ckpclbGroups" :key="group && group.key" v-if="group" class="ckpclb-group">
<div class="ckpclb-title">{{ group.label }}</div>
<div class="ckpclb-tags" v-if="group.items && group.items.length">
<span v-for="(p, pi) in group.items" :key="pi" class="part-tag part-tag-hit">
{{ p.name }}{{ p.type }}
</span>
</div>
<div v-else class="ckpclb-empty">无相关排查记录</div>
</div>
</div>
</template>
<div v-else class="text-sm text-gray-500">暂无车况明细排查记录</div>
</div>
<!-- 车辆损失方位总结所有方位按矩阵展示有受损的高亮显示 -->
<div class="detail-card" v-if="clfwzjMatrix && clfwzjMatrix.length">
<h4 class="section-title">车辆损失方位总结</h4>
<div class="clfwzj-matrix">
<div v-for="(row, ri) in clfwzjMatrix" :key="ri" class="clfwzj-row">
<div v-for="(cell, ci) in row" :key="ci" class="clfwzj-cell">
<div v-if="cell && cell.label"
:class="['pos-box', cell.value === 1 ? 'pos-box-hit' : 'pos-box-normal']">
{{ cell.label }}
</div>
</div>
</div>
</div>
<p class="mt-4 text-sm text-gray-500">红色方位表示该部位存在受损记录灰色表示当前无受损记录</p>
</div>
<!-- 车况信息简要 -->
<div class="detail-card" v-if="ckxx">
<h4 class="section-title">车况信息概览</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">是否火烧</div>
<div class="field-value" :class="flagClass(ckxx.isFire === 1)">
{{ bool01Text(ckxx.isFire) }}
</div>
</div>
<div class="field">
<div class="field-label">是否水淹</div>
<div class="field-value" :class="flagClass(ckxx.isFlood === 1)">
{{ bool01Text(ckxx.isFlood) }}
</div>
</div>
<div class="field">
<div class="field-label">是否偷盗</div>
<div class="field-value" :class="flagClass(ckxx.isTheft === 1)">
{{ bool01Text(ckxx.isTheft) }}
</div>
</div>
<div class="field">
<div class="field-label">是否覆盖件损伤</div>
<div class="field-value" :class="flagClass(ckxx.isPanel === 1)">
{{ bool01Text(ckxx.isPanel) }}
</div>
</div>
<div class="field">
<div class="field-label">是否大额赔偿</div>
<div class="field-value">
{{ largeCostText(ckxx.isLargeCost) }}
</div>
</div>
<div class="field">
<div class="field-label">未结案记录</div>
<div class="field-value">{{ ynUnknown(ckxx.recordIcpending) }}</div>
</div>
<div class="field">
<div class="field-label">注销记录</div>
<div class="field-value">{{ ynUnknown(ckxx.recordIwriteoff) }}</div>
</div>
<div class="field">
<div class="field-label">拒赔记录</div>
<div class="field-value">{{ ynUnknown(ckxx.refusalRecord) }}</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无出险详版数据</div>
<div class="sub">未查询到车辆详细出险记录或返回数据为空</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const retdata = computed(() => props.data?.retdata || {});
const hasData = computed(() => !!retdata.value && Object.keys(retdata.value).length > 0);
const pzlsmx = computed(() => retdata.value.pzlsmx || {});
const pzRecords = computed(() => Array.isArray(pzlsmx.value.records) ? pzlsmx.value.records : []);
const pzVin = computed(() => (pzRecords.value[0]?.vin) || '');
const ckdlpc = computed(() => retdata.value.ckdlpc || null);
const ckxx = computed(() => retdata.value.ckxx || null);
const ckpclb = computed(() => retdata.value.ckpclb || null);
const clxx = computed(() => retdata.value.clxx || {});
const clfwzj = computed(() => retdata.value.clfwzj || null);
const tjxx = computed(() => retdata.value.tjxx || {});
const clxxVin = computed(() => clxx.value.vin || '');
const ckdlpcList = computed(() => {
if (!ckdlpc.value) return [];
const map = [
{ key: 'type1', label: '骨架' },
{ key: 'type2', label: '外观' },
{ key: 'type3', label: '发动机/变速箱' },
{ key: 'type4', label: '火烧' },
{ key: 'type5', label: '水淹' },
{ key: 'type6', label: '气囊' },
{ key: 'type7', label: '加强件' },
];
return map.map((m) => ({
key: m.key,
label: m.label,
value: ckdlpc.value[m.key],
}));
});
const hasCkpclb = computed(() => {
const v = ckpclb.value;
if (!v) return false;
return Object.values(v).some((arr) => Array.isArray(arr) && arr.length > 0);
});
const hasCkpclbHit = computed(() => {
const v = ckpclb.value;
if (!v) return false;
return Object.values(v).some((arr) => Array.isArray(arr) && arr.length > 0);
});
const ckpclbGroups = computed(() => {
if (!ckpclb.value) return [];
const labelMap = {
dp: '底盘悬挂',
fdj: '发动机',
fspj: '附属配件',
gj: '骨架',
hs: '火烧',
jqj: '加强件',
qn: '气囊',
sy: '水淹',
wg: '外观',
};
return Object.entries(ckpclb.value).map(([key, arr]) => ({
key,
label: labelMap[key] || key,
items: Array.isArray(arr) ? arr : [],
}));
});
const hasClfwzj = computed(() => {
if (!clfwzj.value) return false;
return Object.values(clfwzj.value).some((v) => v === 1);
});
const clfwzjMatrix = computed(() => {
const src = clfwzj.value || {};
const val = (key) => (src[key] === 1 ? 1 : 0);
// 按大致方位排布成矩阵,便于理解
return [
[
{ label: '', value: null },
{ label: '正前方', value: val('正前方') },
{ label: '', value: null },
],
[
{ label: '前方左侧', value: val('前方左侧') },
{ label: '顶部', value: val('顶部') },
{ label: '前方右侧', value: val('前方右侧') },
],
[
{ label: '中间左侧', value: val('中间左侧') },
{ label: '内部', value: val('内部') },
{ label: '中间右侧', value: val('中间右侧') },
],
[
{ label: '后方左侧', value: val('后方左侧') },
{ label: '底部', value: val('底部') },
{ label: '后方右侧', value: val('后方右侧') },
],
[
{ label: '', value: null },
{ label: '正后方', value: val('正后方') },
{ label: '其他', value: val('其他') },
],
];
});
const ckLevelText = (v) => {
const num = Number(v);
if (Number.isNaN(num)) return '未知';
if (num === 0) return '正常';
if (num === 1) return '无法确定';
if (num === 2) return '疑似异常';
if (num === 3) return '维保异常';
if (num === 4) return '碰撞异常';
return '未知';
};
const ckLevelClass = (v) => {
const num = Number(v);
if (num === 0) return 'level-ok';
if (num === 1) return 'level-unknown';
if (num === 2) return 'level-suspect';
if (num === 3) return 'level-maintain';
if (num === 4) return 'level-collision';
return 'level-unknown';
};
const formatFen = (val) => {
if (!val && val !== 0) return '-';
const n = Number(val);
if (Number.isNaN(n)) return `${val}`;
const yuan = n / 100;
return `${yuan.toLocaleString()}`;
};
const dangerTypeText = (t) => {
if (t === '1') return '更换';
if (t === '2') return '维修';
if (t === '3') return '材料';
return '其他';
};
const bool01Text = (v) => {
if (v === 1) return '是';
if (v === 0) return '否';
return '未知';
};
const largeCostText = (v) => {
if (v === 0) return '无大额赔偿记录';
if (v === 1) return '有大额赔偿记录';
if (v === 2) return '无法确定是否大额赔偿';
return '未知';
};
const ynUnknown = (v) => {
if (v === '是') return '是';
if (v === '否') return '否';
if (v == null) return '未知';
return v;
};
const flagClass = (flag) => {
return flag ? 'flag-yes' : 'flag-no';
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center mb-4 px-5 py-4 rounded-2xl bg-gradient-to-r from-orange-50 via-amber-50 to-rose-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-orange-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-orange-800 opacity-90;
}
.summary-card {
@apply rounded-2xl border border-amber-100 bg-amber-50/60 px-5 py-4 mb-4;
}
.summary-main {
@apply flex items-start justify-between mb-3 gap-4;
}
.summary-left {
@apply flex flex-col gap-1;
}
.summary-right {
@apply text-right;
}
.summary-label {
@apply text-sm text-gray-500;
}
.summary-brand {
@apply text-lg font-semibold text-gray-900;
}
.summary-vin {
@apply text-base text-gray-900;
}
.summary-subline {
@apply text-sm text-gray-700 mt-1;
}
.summary-meta {
@apply space-y-2 text-base text-gray-800;
}
.meta-line {
@apply flex justify-between items-center;
}
.meta-label {
@apply text-sm text-gray-600;
}
.meta-value {
@apply text-base;
}
.meta-line .dot {
@apply w-1 h-1 rounded-full bg-gray-400;
}
.strong {
@apply font-semibold;
}
.code {
@apply font-mono tracking-wide;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-3;
}
.timeline {
@apply mt-2;
}
.timeline-item {
@apply flex-1 rounded-2xl border border-gray-100 bg-white px-4 py-3;
}
.row-main {
@apply flex items-baseline justify-between mb-1;
}
.row-main .date {
@apply text-base font-medium text-gray-900;
}
.row-main .amount {
@apply text-lg font-semibold text-gray-900;
}
.row-sub {
@apply flex items-center justify-between text-sm text-gray-600 mt-1;
}
.status {
@apply text-xs px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-700 font-medium;
}
.sub-section {
@apply mt-3;
}
.sub-title {
@apply text-sm font-semibold text-gray-800 mb-1;
}
.sub-list {
@apply text-sm text-gray-800 space-y-1;
}
.sub-list li {
@apply flex flex-wrap gap-1;
}
.tag {
@apply inline-flex items-center px-2 py-0.5 rounded-full bg-orange-50 text-orange-700 text-xs font-medium;
}
.money {
@apply text-xs text-gray-500;
}
.ckdlpc-grid {
@apply grid gap-3;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.ckdlpc-item {
@apply p-3 rounded-xl bg-white border border-gray-100;
}
.ckdlpc-name {
@apply text-sm text-gray-600 mb-1;
}
.ckdlpc-status {
@apply text-sm font-semibold;
}
.level-ok {
@apply text-emerald-700;
}
.level-unknown {
@apply text-gray-600;
}
.level-suspect {
@apply text-amber-700;
}
.level-maintain {
@apply text-blue-700;
}
.level-collision {
@apply text-red-700;
}
.ckpclb-grid {
@apply grid gap-3;
}
.ckpclb-group {
@apply p-3 rounded-xl bg-white border border-gray-100;
}
.ckpclb-title {
@apply text-sm font-semibold text-gray-800 mb-2;
}
.ckpclb-tags {
@apply flex flex-wrap gap-2;
}
.part-tag {
@apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium;
}
.part-tag-hit {
@apply bg-sky-50 text-sky-700;
}
.clfwzj-matrix {
@apply grid gap-2;
}
.clfwzj-row {
@apply grid gap-2;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.clfwzj-cell {
@apply flex items-center justify-center;
}
.pos-box {
@apply w-full text-center px-3 py-2 rounded-xl text-xs font-medium;
}
.pos-box-hit {
@apply bg-rose-50 text-rose-700;
}
.pos-box-normal {
@apply bg-gray-100 text-gray-500;
}
.field-grid {
@apply grid gap-y-3 gap-x-6;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.field-label {
@apply text-sm text-gray-500 mb-1;
}
.field-value {
@apply text-base text-gray-900;
}
.flag-yes {
@apply text-red-700;
}
.flag-no {
@apply text-emerald-700;
}
.empty {
@apply text-center py-10 text-gray-500;
}
.empty .icon {
@apply text-3xl mb-2;
}
.empty .title {
@apply text-lg font-medium mb-1;
}
.empty .sub {
@apply text-sm;
}
</style>

228
src/ui/CQCXGY7F2.vue Normal file
View File

@@ -0,0 +1,228 @@
<template>
<div class="card">
<div class="header-box">
<div class="header-left">
<h3 class="header-title">二手车VIN估值</h3>
<p class="header-desc">基于车型排量排放标准等信息给出参考估值</p>
</div>
<div class="header-tag">
<span class="tag-label">估值结果</span>
<span class="tag-value">{{ data.estimatedValue || '-' }}</span>
</div>
</div>
<template v-if="hasData">
<div class="summary-card">
<div class="summary-main">
<div class="summary-price">{{ data.estimatedValue || '-' }}</div>
<div class="summary-sub">参考估值仅供参考实际价格以市场为准</div>
</div>
<div class="summary-meta">
<div class="badge">{{ data.seriesName || '未知车系' }}</div>
<div class="meta-line">
<span>{{ data.manufacturerName || '未知厂商' }}</span>
<span v-if="data.productionDate" class="dot"></span>
<span v-if="data.productionDate">{{ data.productionDate }} 年出厂</span>
</div>
<div class="meta-line meta-small">
<span v-if="data.displacement">排量{{ data.displacement }}</span>
<span v-if="data.transmissionType" class="dot"></span>
<span v-if="data.transmissionType">变速箱{{ data.transmissionType }}</span>
<span v-if="data.emissionStandard" class="dot"></span>
<span v-if="data.emissionStandard">排放{{ data.emissionStandard }}</span>
</div>
</div>
</div>
<div class="detail-card">
<h4 class="section-title">基础信息</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">厂商品牌名称</div>
<div class="field-value">{{ data.manufacturerName || '-' }}</div>
</div>
<div class="field">
<div class="field-label">车系名称</div>
<div class="field-value">{{ data.seriesName || '-' }}</div>
</div>
<div class="field">
<div class="field-label">车型年款</div>
<div class="field-value">{{ data.modelYear || data.productionDate || '-' }}</div>
</div>
<div class="field">
<div class="field-label">座位数</div>
<div class="field-value">{{ data.seatingCapacity || '-' }}</div>
</div>
<div class="field field-span">
<div class="field-label">车型名称</div>
<div class="field-value">{{ data.modelName || '-' }}</div>
</div>
<div class="field field-span">
<div class="field-label">车型指导价</div>
<div class="field-value">{{ data.msrp || '-' }}</div>
</div>
</div>
</div>
<div class="detail-card">
<h4 class="section-title">技术参数</h4>
<div class="field-grid">
<div class="field">
<div class="field-label">排量</div>
<div class="field-value">{{ data.displacement || '-' }}</div>
</div>
<div class="field">
<div class="field-label">变速箱类型</div>
<div class="field-value">{{ data.transmissionType || '-' }}</div>
</div>
<div class="field">
<div class="field-label">排放标准</div>
<div class="field-value">{{ data.emissionStandard || '-' }}</div>
</div>
<div class="field">
<div class="field-label">车身颜色</div>
<div class="field-value">{{ data.color || '-' }}</div>
</div>
<div class="field field-span" v-if="data.seriesGroupName">
<div class="field-label">车系组名</div>
<div class="field-value">{{ data.seriesGroupName }}</div>
</div>
</div>
</div>
</template>
<div v-else class="empty">
<div class="icon"></div>
<div class="title">暂无估值结果</div>
<div class="sub">未查询到有效的估值数据请检查 VIN 与车辆信息是否正确</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => !!props.data && Object.keys(props.data).length > 0);
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-2xl p-6 shadow-sm border border-gray-100;
}
.header-box {
@apply flex items-center justify-between mb-5 px-5 py-4 rounded-2xl bg-gradient-to-r from-amber-50 via-orange-50 to-amber-50;
}
.header-left {
@apply flex flex-col;
}
.header-title {
@apply text-2xl font-semibold m-0 text-amber-900;
}
.header-desc {
@apply text-base mt-3 m-0 text-amber-800 opacity-90;
}
.header-tag {
@apply inline-flex flex-col items-end gap-1 px-3 py-2 rounded-xl bg-white/80 shadow-sm;
}
.tag-label {
@apply text-sm text-gray-500;
}
.tag-value {
@apply text-2xl font-bold text-amber-700 leading-none whitespace-nowrap;
}
.summary-card {
@apply rounded-2xl border border-amber-100 bg-amber-50/60 px-5 py-5 mb-4 flex flex-col gap-4;
}
.summary-main {}
.summary-price {
@apply text-3xl font-extrabold text-amber-800 leading-tight;
}
.summary-sub {
@apply text-sm text-amber-700 opacity-90;
}
.summary-meta {
@apply space-y-2 text-base text-gray-800;
}
.badge {
@apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-800;
}
.meta-line {
@apply flex flex-wrap items-center gap-2 text-sm text-gray-700;
}
.meta-line .dot {
@apply w-1 h-1 rounded-full bg-gray-400;
}
.meta-small {
@apply text-sm text-gray-600;
}
.detail-card {
@apply rounded-2xl border border-gray-100 bg-gray-50/60 px-5 py-4 mb-4;
}
.section-title {
@apply text-base font-semibold text-gray-800 mb-4;
}
.field-grid {
@apply grid gap-y-3 gap-x-6;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.field-label {
@apply text-sm text-gray-500 mb-1.5;
}
.field-value {
@apply text-base text-gray-900;
}
.field-span {
grid-column: 1 / -1;
}
.empty {
@apply text-center py-12 text-gray-500;
}
.empty .icon {
@apply text-4xl mb-3;
}
.empty .title {
@apply text-lg font-medium mb-2;
}
.empty .sub {
@apply text-sm;
}
</style>

250
src/ui/CQCXGYTS2.vue Normal file
View File

@@ -0,0 +1,250 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">人车核验详版</h3>
<p class="header-desc">展示人员与车辆的详细匹配结果及相关说明</p>
</div>
<div class="result-section" :class="resultSectionClass">
<div class="result-icon-wrap">
<span class="result-icon" :class="iconClass">
{{ iconChar }}
</span>
</div>
<div class="result-label">认证结果</div>
<div class="result-value" :class="resultTextClass">{{ resultText }}</div>
<p v-if="resultDesc" class="result-desc">{{ resultDesc }}</p>
</div>
<div v-if="hasParams" class="info-rows">
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">车牌号</span>
<span class="info-value font-mono">{{ params?.plate_no || params?.car_license || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">号牌类型</span>
<span class="info-value">{{ params?.carplate_type || params?.car_type || '-' }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
// status: 0 一致, -1 不一致, -2 非法姓名, -4 无记录
const status = computed(() => {
const s = props.data?.status;
if (s === 0 || s === -1 || s === -2 || s === -4) return s;
return null;
});
const resultText = computed(() => {
const s = status.value;
if (s === 0) return '一致';
if (s === -1) return '不一致';
if (s === -2) return '非法姓名';
if (s === -4) return '无记录';
return '暂无结果';
});
const resultDesc = computed(() => {
const s = status.value;
if (s === -2) return '姓名长度或格式不正确,请核对后重试';
if (s === -4) return '未查询到相关核验记录';
return '';
});
const resultTextClass = computed(() => {
const s = status.value;
if (s === 0) return 'result-match';
if (s === -1) return 'result-mismatch';
if (s === -2) return 'result-invalid';
if (s === -4) return 'result-norecord';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
const s = status.value;
if (s === 0) return 'result-section match';
if (s === -1) return 'result-section mismatch';
if (s === -2) return 'result-section invalid';
if (s === -4) return 'result-section norecord';
return 'result-section unknown';
});
const iconClass = computed(() => {
const s = status.value;
if (s === 0) return 'icon-match';
if (s === -1) return 'icon-mismatch';
if (s === -2) return 'icon-invalid';
if (s === -4) return 'icon-norecord';
return 'icon-unknown';
});
const iconChar = computed(() => {
const s = status.value;
if (s === 0) return '✓';
if (s === -1) return '✕';
if (s === -2) return '!';
if (s === -4) return '—';
return '?';
});
const hasParams = computed(() => {
const p = props.params || {};
return p.name || p.plate_no || p.car_license || p.carplate_type || p.car_type;
});
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
}
.header-box {
@apply rounded-lg mb-4 p-4;
background: linear-gradient(135deg, #5c6bc0 0%, #3949ab 100%);
color: #fff;
}
.header-title {
@apply text-lg font-semibold m-0;
}
.header-desc {
@apply text-sm mt-1 opacity-90 m-0;
}
.result-section {
@apply rounded-xl p-5 text-center mb-4;
}
.result-section.match {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
border: 1px solid rgba(76, 175, 80, 0.3);
}
.result-section.mismatch {
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
border: 1px solid rgba(244, 67, 54, 0.3);
}
.result-section.invalid {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
border: 1px solid rgba(255, 152, 0, 0.4);
}
.result-section.norecord {
background: linear-gradient(135deg, #eceff1 0%, #cfd8dc 100%);
border: 1px solid rgba(96, 125, 139, 0.3);
}
.result-section.unknown {
@apply bg-gray-50 border border-gray-200;
}
.result-icon-wrap {
@apply mb-2;
}
.result-icon {
@apply inline-flex items-center justify-center w-12 h-12 rounded-full text-2xl font-bold text-white;
}
.result-icon.icon-match {
background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.4);
}
.result-icon.icon-mismatch {
background: linear-gradient(135deg, #e53935 0%, #c62828 100%);
box-shadow: 0 2px 8px rgba(244, 67, 54, 0.4);
}
.result-icon.icon-invalid {
background: linear-gradient(135deg, #fb8c00 0%, #ef6c00 100%);
box-shadow: 0 2px 8px rgba(255, 152, 0, 0.4);
}
.result-icon.icon-norecord {
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
}
.result-icon.icon-unknown {
background: linear-gradient(135deg, #78909c 0%, #546e7a 100%);
box-shadow: 0 2px 8px rgba(96, 125, 139, 0.3);
}
.result-label {
@apply text-sm text-gray-500 mb-1;
}
.result-value {
@apply text-xl font-semibold;
}
.result-desc {
@apply text-sm mt-2 m-0 text-gray-600 max-w-xs mx-auto;
}
.result-match {
color: #2e7d32;
}
.result-mismatch {
color: #c62828;
}
.result-invalid {
color: #e65100;
}
.result-norecord {
color: #546e7a;
}
.result-unknown {
@apply text-gray-500;
}
.info-rows {
@apply space-y-3 pt-2 border-t border-gray-100;
}
.info-row {
@apply flex items-center text-sm;
}
.info-label {
@apply w-20 text-gray-500 shrink-0;
}
.info-value {
@apply font-medium text-gray-800;
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="card">
<div class="bg-gray-100 text-gray-800 p-4 rounded-lg mb-4">
<h3 class="text-lg font-semibold">{{ title }}</h3>
<p class="text-sm mt-1">返回数据如下传参后续可按接口单独配置</p>
</div>
<div v-if="hasRawData" class="text-xs">
<pre
class="bg-gray-50 rounded p-3 overflow-x-auto whitespace-pre-wrap break-all border border-gray-200">{{ prettyData }}</pre>
</div>
<div v-else class="text-gray-500 text-sm">暂无数据</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const titleMap = {
QCXG4D2E: '名下车辆(数量)',
QCXG5U0Z: '车辆静态信息查询',
QCXG1U4U: '车辆里程记录(混合查询)',
QCXGY7F2: '二手车VIN估值',
QCXG1H7Y: '车辆过户简版查询',
QCXG4I1Z: '车辆过户详版查询',
QCXG3Y6B: '车辆维保简版查询',
QCXG3Z3L: '车辆维保详细版查询',
QCXGP00W: '车辆出险详版查询',
QCXG6B4E: '车辆出险记录核验',
};
const title = computed(() => titleMap[props.apiId] || props.apiId || '车辆查询');
const hasRawData = computed(() => !!props.data && Object.keys(props.data).length > 0);
const prettyData = computed(() => {
try {
return JSON.stringify(props.data, null, 2);
} catch {
return String(props.data || '');
}
});
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
.card {
@apply bg-white rounded-lg p-4 shadow-sm border border-gray-100;
}
</style>

479
src/ui/CQYGL2S0W.vue Normal file
View File

@@ -0,0 +1,479 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">失信被执行人</h3>
<p class="header-desc">
展示命中最高法院公布的失信被执行人信息用于识别严重违约风险
</p>
</div>
<div v-if="hasList" class="summary-section">
<div class="summary-row">
<div class="summary-card summary-risk">
<div class="summary-label">命中失信被执行人</div>
<div class="summary-value">{{ totalCount }}</div>
<div class="summary-sub">
{{ areaSummary }}
</div>
</div>
<div class="summary-card summary-status">
<div class="summary-label">履行情况</div>
<div class="summary-value">
{{ mainPerformance }}
</div>
<div class="summary-sub">
重点关注全部未履行部分履行等高风险状态
</div>
</div>
</div>
</div>
<div v-if="hasList" class="block">
<div class="block-title">失信记录详情</div>
<div class="record-list">
<div v-for="(item, index) in list" :key="item.id || index" class="record-wrapper">
<div class="record-card">
<!-- 摘要区域仿 FLXG7E8F 的案件卡片风格 -->
<div class="record-summary" @click="toggleRecordExpand(item.id || index)">
<div class="summary-top">
<div class="summary-title">
<span class="record-case-no">
{{ item.case_code || '暂无案号' }}
</span>
<span class="record-type-tag">
{{ item.disrupt_type_name || '失信被执行人' }}
</span>
</div>
<span class="relate-tag">
{{ item.relateType || '失信被执行人' }}
</span>
</div>
<div class="summary-middle">
<span class="summary-label">立案</span>
<span class="summary-value">{{ formatDate(item.reg_date) }}</span>
<span class="summary-label ml-2">法院</span>
<span class="summary-value">{{ item.court_name || item.gist_unit || '-' }}</span>
</div>
<div class="summary-bottom">
<span class="risk-tag">
{{ item.performance || '履行情况未知' }}
</span>
<span class="expand-indicator">
<span class="expand-text">
{{ isRecordExpanded(item.id || index) ? '收起详情' : '展开详情' }}
</span>
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
:class="{ 'rotate-180': isRecordExpanded(item.id || index) }" />
</span>
</div>
</div>
<!-- 详情区域可展开/收起 -->
<div class="record-detail" :class="{
'detail-collapsed': !isRecordExpanded(item.id || index),
'detail-expanded': isRecordExpanded(item.id || index),
}">
<div class="record-body">
<div class="info-row">
<span class="info-label">被执行人</span>
<span class="info-value">
{{ item.entity_name || '-' }}
<span v-if="item.sexy || item.age" class="info-sub">
{{ [item.sexy, item.age && item.age + ''].filter(Boolean).join('') }}
</span>
</span>
</div>
<div class="info-row">
<span class="info-label">主体代码</span>
<span class="info-value">
{{ maskId(item.entity_id) }}
</span>
</div>
<div class="info-row">
<span class="info-label">身份类型</span>
<span class="info-value">
{{ item.party_type_name || '自然人/法人' }}
<span v-if="item.business_entity" class="info-sub">
法定代表人/负责人{{ item.business_entity }}
</span>
</span>
</div>
<div class="info-row">
<span class="info-label">涉案地域</span>
<span class="info-value">{{ item.area_name || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">执行法院</span>
<span class="info-value">{{ item.court_name || item.gist_unit || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">执行依据</span>
<span class="info-value">{{ item.gist_id || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">立案时间</span>
<span class="info-value">{{ formatDate(item.reg_date) }}</span>
</div>
<div class="info-row">
<span class="info-label">发布日期</span>
<span class="info-value">{{ formatDate(item.publish_date) }}</span>
</div>
<div class="info-row">
<span class="info-label">履行情况</span>
<span class="info-value">
{{ item.performance || '-' }}
<span v-if="item.performed_part || item.unPerform_part" class="info-sub">
{{ formatPerformDetail(item) }}
</span>
</span>
</div>
<div class="info-row">
<span class="info-label">履行义务</span>
<span class="info-value">
{{ item.duty || '-' }}
</span>
</div>
<div class="info-row">
<span class="info-label">下架状态</span>
<span class="info-value">
{{ item.case_status || '-' }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-tip">
暂未命中失信被执行人记录
</div>
</div>
</template>
<script setup>
import { computed, ref, watchEffect } from "vue";
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: "" },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
// 统一 dataList 结构
const list = computed(() => {
const d = props.data || {};
if (Array.isArray(d.dataList)) return d.dataList;
if (d.data && Array.isArray(d.data.dataList)) return d.data.dataList;
return [];
});
const hasList = computed(() => list.value.length > 0);
const totalCount = computed(() => list.value.length);
const areaSummary = computed(() => {
if (!hasList.value) return "未命中失信被执行人";
const areas = Array.from(
new Set(
list.value
.map((i) => i.area_name)
.filter((v) => !!v)
)
);
if (!areas.length) return "地区信息未知";
if (areas.length === 1) return `集中在 ${areas[0]}`;
return `涉及 ${areas.length} 个地区,如 ${areas.slice(0, 2).join("、")}`;
});
const mainPerformance = computed(() => {
if (!hasList.value) return "暂无记录";
const perf = list.value
.map((i) => i.performance)
.filter((v) => !!v);
if (!perf.length) return "履行情况未知";
const first = perf[0];
if (perf.every((p) => p === first)) return first;
return `${first} 等多种情况`;
});
function formatPerformDetail(item) {
const parts = [];
if (item.performed_part) parts.push(`已履行:${item.performed_part}`);
if (item.unPerform_part) parts.push(`未履行:${item.unPerform_part}`);
return parts.join("");
}
function formatDate(str) {
if (!str) return "-";
// 支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
if (str.length >= 10) return str.slice(0, 10);
return str;
}
function maskId(id) {
if (!id) return "-";
const s = String(id);
if (s.length <= 8) return s[0] + "***" + s.slice(-1);
return s.slice(0, 4) + "****" + s.slice(-4);
}
// 展开/收起记录详情,参考 FLXG7E8F 的交互
const expandedRecords = ref({});
function toggleRecordExpand(key) {
const id = String(key);
expandedRecords.value[id] = !expandedRecords.value[id];
}
function isRecordExpanded(key) {
const id = String(key);
return !!expandedRecords.value[id];
}
// 上报风险命中情况(命中视为高风险)
watchEffect(() => {
if (!props.notifyRiskStatus) return;
const hit = hasList.value;
props.notifyRiskStatus(props.apiId || "QYGL2S0W", props.index || 0, {
hasRisk: hit,
});
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.1rem;
font-weight: 600;
color: #333333;
}
.header-desc {
margin-top: 0.25rem;
font-size: 0.85rem;
color: #666666;
}
.summary-section {
padding: 0.75rem 0;
border-top: 1px solid #f1f1f1;
border-bottom: 1px solid #f1f1f1;
margin-bottom: 1rem;
}
.summary-row {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
@media (min-width: 768px) {
.summary-row {
flex-direction: row;
}
}
.summary-card {
flex: 1;
padding: 0.75rem;
border-radius: 0.75rem;
}
.summary-risk {
background: rgba(235, 60, 60, 0.04);
border: 1px solid rgba(235, 60, 60, 0.3);
}
.summary-status {
background: rgba(214, 148, 62, 0.04);
border: 1px solid rgba(214, 148, 62, 0.3);
}
.summary-label {
font-size: 0.85rem;
color: #666666;
margin-bottom: 0.25rem;
}
.summary-value {
font-size: 1.4rem;
font-weight: 600;
color: #333333;
}
.summary-sub {
margin-top: 0.25rem;
font-size: 0.75rem;
color: #999999;
}
.block {
margin-top: 1rem;
}
.block-title {
font-size: 0.95rem;
font-weight: 600;
color: #333333;
margin-bottom: 0.5rem;
}
.record-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.record-wrapper {
width: 100%;
}
.record-card {
border-radius: 0.75rem;
border: 1px solid #dddddd;
background-color: #ffffff;
overflow: hidden;
}
.record-summary {
padding: 0.75rem 0.75rem 0.5rem;
cursor: pointer;
}
.summary-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.25rem;
}
.summary-title {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
}
.record-case-no {
font-size: 0.9rem;
font-weight: 600;
color: #333333;
}
.record-type-tag {
padding: 0.1rem 0.4rem;
border-radius: 999px;
font-size: 0.75rem;
background-color: #f9ecec;
color: #eb3c3c;
}
.relate-tag {
flex-shrink: 0;
padding: 0.2rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
background-color: rgba(214, 148, 62, 0.08);
color: #d6943e;
}
.summary-middle {
font-size: 0.8rem;
padding-bottom: 0.25rem;
}
.summary-label {
color: #666666;
}
.summary-value {
color: #333333;
}
.summary-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.risk-tag {
padding: 0.2rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 500;
background-color: rgba(235, 60, 60, 0.08);
color: #eb3c3c;
}
.expand-indicator {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: #999999;
}
.expand-indicator img {
transition: transform 0.2s ease;
}
.expand-indicator img.rotate-180 {
transform: rotate(180deg);
}
.record-detail {
border-top: 1px dashed #e5e5e5;
padding: 0 0.75rem 0.5rem;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.25s ease, opacity 0.25s ease;
}
.record-detail.detail-expanded {
max-height: 500px;
opacity: 1;
}
.info-row {
display: flex;
font-size: 0.8rem;
margin-bottom: 0.25rem;
}
.info-label {
width: 4.5rem;
color: #999999;
flex-shrink: 0;
}
.info-value {
color: #333333;
word-break: break-all;
}
.info-sub {
margin-left: 0.25rem;
font-size: 0.75rem;
color: #777777;
}
.empty-tip {
margin-top: 0.75rem;
padding: 0.75rem;
text-align: center;
font-size: 0.85rem;
color: #999999;
background-color: #fafafa;
border-radius: 0.75rem;
}
</style>

171
src/ui/CQYGL5F6A.vue Normal file
View File

@@ -0,0 +1,171 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">名下企业关联</h3>
<p class="header-desc">展示查询对象作为法人/股东/高管关联的企业</p>
</div>
<div v-if="companyItems && companyItems.length" class="company-list">
<div v-for="(item, idx) in companyItems" :key="idx" class="company-card">
<div class="company-header">
<div class="company-name">{{ item.orgName || item.basicInfo?.name || '未知企业' }}</div>
<div class="company-tags">
<span v-for="tag in item.relationshipTags" :key="tag" class="tag">
{{ tag }}
</span>
</div>
</div>
<div class="company-body">
<div class="row">
<span class="label">企业状态</span>
<span class="value">{{ item.basicInfo?.regStatus || '-' }}</span>
</div>
<div class="row">
<span class="label">成立日期</span>
<span class="value">{{ item.basicInfo?.estiblishTime || '-' }}</span>
</div>
<div class="row">
<span class="label">注册资本</span>
<span class="value">{{ item.basicInfo?.regCapital || '-' }}</span>
</div>
<div class="row">
<span class="label">行业</span>
<span class="value">{{ item.basicInfo?.industry || '-' }}</span>
</div>
<div class="row">
<span class="label">企业类型</span>
<span class="value">{{ item.basicInfo?.companyOrgType || '-' }}</span>
</div>
<div class="row">
<span class="label">法定代表人</span>
<span class="value">{{ item.basicInfo?.legalPersonName || '-' }}</span>
</div>
</div>
</div>
</div>
<div v-if="!companyItems || !companyItems.length" class="empty-tip">
暂未查询到名下企业关联信息
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const rawItems = computed(() => {
const report = props.data?.ent_report_001;
const items = report?.queryResult?.items;
return Array.isArray(items) ? items : [];
});
const relationshipMap = {
lp: '法人',
sh: '股东',
tm: '高管',
};
const companyItems = computed(() =>
rawItems.value.map((item) => {
const relArr = Array.isArray(item.relationship) ? item.relationship : [];
const relationshipTags = relArr.map((r) => relationshipMap[r] || r);
return {
...item,
relationshipTags,
};
})
);
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.company-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.company-card {
border-radius: 0.75rem;
border: 1px solid #e5e7eb;
background: #f9fafb;
padding: 0.75rem 0.875rem;
}
.company-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.company-name {
font-size: 0.95rem;
font-weight: 600;
color: #111827;
}
.company-tags {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.tag {
font-size: 0.75rem;
padding: 0.1rem 0.4rem;
border-radius: 999px;
background: #eef2ff;
color: #4f46e5;
}
.company-body .row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.label {
color: #6b7280;
}
.value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

542
src/ui/CQYGL66SL.vue Normal file
View File

@@ -0,0 +1,542 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">企业司法涉诉</h3>
<p class="header-desc">展示企业在全国法院公开信息中的涉诉情况</p>
<p v-if="entName" class="header-ent">被查询企业{{ entName }}</p>
</div>
<div v-if="hasCivilData" class="summary-section">
<div class="summary-row">
<div class="summary-card summary-risk">
<div class="summary-label">民事案件总数</div>
<div class="summary-value">{{ totalCivilCases }}</div>
<div class="summary-sub">
已结案 {{ civilClosedCases }} · 未结案 {{ civilPendingCases }}
</div>
</div>
<div class="summary-card summary-money">
<div class="summary-label">案件地域/案由概览</div>
<div class="summary-value small">
{{ civilAreaStat || '地域分布未知' }}
</div>
<div class="summary-sub">
{{ civilAyStat || '案由分布暂无统计' }}
</div>
</div>
</div>
<div class="tag-row" v-if="civilAreaStat || civilAyStat || civilJafsStat">
<span v-if="civilAreaStat" class="stat-tag">
涉案地域{{ civilAreaStat }}
</span>
<span v-if="civilAyStat" class="stat-tag">
案由分布{{ civilAyStat }}
</span>
<span v-if="civilJafsStat" class="stat-tag">
结案方式{{ civilJafsStat }}
</span>
</div>
</div>
<div v-if="civilCases.length" class="block">
<div class="block-title">民事案件列表</div>
<div class="case-list">
<div v-for="(item, index) in civilCases" :key="item.c_id || index" class="case-wrapper">
<div class="case-card">
<!-- 可点击的摘要区域参考 FLXG7E8F 样式 -->
<div class="case-summary" @click="toggleCaseExpand(item.c_id || index)">
<div class="summary-top">
<div class="summary-title">
<span class="case-no-text">
{{ item.c_ah || '暂无案号' }}
</span>
<span class="case-type-tag">
{{ item.n_ajlx || item.n_laay_tag || item.n_laay || '民事案件' }}
</span>
</div>
</div>
<div class="summary-middle">
<span class="summary-label">立案</span>
<span class="summary-value">{{ formatDate(item.d_larq) }}</span>
</div>
<div class="summary-bottom">
<span class="risk-tag" :class="statusClass(item.n_ajjzjd)">
{{ item.n_ajjzjd || '未知进展' }}
</span>
<span v-if="item.n_pj_victory" class="victory-tag">
胜诉估计{{ item.n_pj_victory }}
</span>
<span class="expand-indicator">
<span class="expand-text">
{{ isCaseExpanded(item.c_id || index) ? '收起详情' : '展开详情' }}
</span>
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4"
:class="{ 'rotate-180': isCaseExpanded(item.c_id || index) }" />
</span>
</div>
</div>
<!-- 详情区域可展开/收起 -->
<div class="case-detail" :class="{
'detail-collapsed': !isCaseExpanded(item.c_id || index),
'detail-expanded': isCaseExpanded(item.c_id || index),
}">
<div class="case-body">
<div class="info-row">
<span class="info-label">立案时间</span>
<span class="info-value">{{ formatDate(item.d_larq) }}</span>
</div>
<div class="info-row">
<span class="info-label">结案时间</span>
<span class="info-value">{{ formatDate(item.d_jarq) }}</span>
</div>
<div class="info-row">
<span class="info-label">经办法院</span>
<span class="info-value">
{{ item.n_jbfy || '-' }}
</span>
</div>
<div class="info-row">
<span class="info-label">审理程序</span>
<span class="info-value">
{{ item.n_slcx || '-' }}
</span>
</div>
<div class="info-row">
<span class="info-label">结案案由</span>
<span class="info-value">
{{ item.n_jaay || '-' }}
</span>
</div>
<div class="info-row">
<span class="info-label">立案案由</span>
<span class="info-value">
{{ item.n_laay || '-' }}
</span>
</div>
<div class="info-row" v-if="item.c_gkws_pjjg">
<span class="info-label">判决结果</span>
<span class="info-value">
{{ item.c_gkws_pjjg }}
</span>
</div>
<div class="info-row" v-if="casePartiesText(item)">
<span class="info-label">当事人</span>
<span class="info-value">
{{ casePartiesText(item) }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="hasAnyData" class="empty-tip">
暂未检索到民事案件记录可留意其他风险模块
</div>
<div v-else class="empty-tip">
暂无企业涉诉信息
</div>
</div>
</template>
<script setup>
import { computed, ref } from "vue";
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: "" },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const entName = computed(() => {
return props.params?.ent_name || props.params?.entName || "";
});
// 提取 entout.data 结构
const entoutData = computed(() => {
const d = props.data || {};
// 优先使用 entout.data
if (d.entout && d.entout.data) return d.entout.data;
if (d.entout) return d.entout;
// 兼容 data.entout.data 结构
if (d.data && d.data.entout && d.data.entout.data) return d.data.entout.data;
return null;
});
const civil = computed(() => entoutData.value?.civil || {});
const civilCases = computed(() => civil.value.cases || []);
const civilCount = computed(() => civil.value.count || {});
const totalCivilCases = computed(() => civilCount.value.count_total ?? civilCases.value.length ?? 0);
const civilClosedCases = computed(() => civilCount.value.count_jie_total ?? 0);
const civilPendingCases = computed(() => civilCount.value.count_wei_total ?? 0);
const civilAreaStat = computed(() => civilCount.value.area_stat || "");
const civilAyStat = computed(() => civilCount.value.ay_stat || "");
const civilJafsStat = computed(() => civilCount.value.jafs_stat || "");
const hasCivilData = computed(() => totalCivilCases.value > 0);
const hasAnyData = computed(() => !!entoutData.value);
// 展开/收起案件详情,参考 FLXG7E8F 的交互
const expandedCases = ref({});
function toggleCaseExpand(key) {
const id = String(key);
expandedCases.value[id] = !expandedCases.value[id];
}
function isCaseExpanded(key) {
const id = String(key);
return !!expandedCases.value[id];
}
const moneySummary = computed(() => {
const total = civilCount.value.money_jie_total ?? null;
if (total == null) return "暂无金额统计";
if (typeof total === "number" && total === 0) return "金额较小或未公开";
return "已结案金额估计 " + formatMoney(total) + " 元";
});
function formatMoney(val) {
if (val == null || val === "") return "-";
const num = Number(val);
if (Number.isNaN(num)) return String(val);
if (num >= 1e8) {
return (num / 1e8).toFixed(2) + " 亿";
}
if (num >= 1e4) {
return (num / 1e4).toFixed(2) + " 万";
}
return num.toFixed(2);
}
function formatDate(str) {
if (!str) return "-";
// 支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
if (str.length >= 10) {
return str.slice(0, 10);
}
return str;
}
function statusClass(status) {
if (!status) return "status-tag status-unknown";
if (String(status).includes("已结案")) return "status-tag status-closed";
if (String(status).includes("执行中") || String(status).includes("审理中")) {
return "status-tag status-processing";
}
return "status-tag status-unknown";
}
function casePartiesText(item) {
const list = item.c_dsrxx || [];
if (!Array.isArray(list) || list.length === 0) return "";
// 尝试优先提取与当前企业相关的角色
const name = entName.value;
const related = name
? list.filter((p) => typeof p.c_mc === "string" && p.c_mc.includes(name))
: [];
const targetList = related.length ? related : list;
const parts = targetList.map((p) => {
const n = p.c_mc || "";
const role = p.n_ssdw || "";
if (n && role) return `${n}${role}`;
return n || role || "";
}).filter(Boolean);
return parts.join("");
}
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.1rem;
font-weight: 600;
color: #333333;
}
.header-desc {
margin-top: 0.25rem;
font-size: 0.85rem;
color: #666666;
}
.header-ent {
margin-top: 0.25rem;
font-size: 0.85rem;
color: #333333;
}
.summary-section {
padding: 0.75rem 0;
border-top: 1px solid #f1f1f1;
border-bottom: 1px solid #f1f1f1;
margin-bottom: 1rem;
}
.summary-row {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
@media (min-width: 768px) {
.summary-row {
flex-direction: row;
}
}
.summary-card {
flex: 1;
padding: 0.75rem;
border-radius: 0.75rem;
}
.summary-risk {
background: rgba(235, 60, 60, 0.04);
border: 1px solid rgba(235, 60, 60, 0.3);
}
.summary-money {
background: rgba(214, 148, 62, 0.04);
border: 1px solid rgba(214, 148, 62, 0.3);
}
.summary-label {
font-size: 0.85rem;
color: #666666;
margin-bottom: 0.25rem;
}
.summary-value {
font-size: 1.4rem;
font-weight: 600;
color: #333333;
}
.summary-value.small {
font-size: 1rem;
}
.summary-sub {
margin-top: 0.25rem;
font-size: 0.75rem;
color: #999999;
}
.tag-row {
margin-top: 0.75rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.stat-tag {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
border-radius: 999px;
background-color: #f5f5f5;
color: #666666;
}
.block {
margin-top: 1rem;
}
.block-title {
font-size: 0.95rem;
font-weight: 600;
color: #333333;
margin-bottom: 0.5rem;
}
.case-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.case-wrapper {
width: 100%;
}
.case-card {
border-radius: 0.75rem;
border: 1px solid #dddddd;
background-color: #ffffff;
overflow: hidden;
}
.case-summary {
padding: 0.75rem 0.75rem 0.5rem;
cursor: pointer;
position: relative;
}
.summary-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.25rem;
}
.summary-title {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
}
.case-no-text {
font-size: 0.9rem;
font-weight: 600;
color: #333333;
}
.case-type-tag {
padding: 0.1rem 0.4rem;
border-radius: 999px;
font-size: 0.75rem;
background-color: #f9ecec;
color: #eb3c3c;
}
.summary-middle {
font-size: 0.8rem;
padding-bottom: 0.25rem;
}
.summary-label {
color: #666666;
}
.summary-value {
color: #333333;
}
.summary-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.risk-tag {
padding: 0.2rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 500;
}
.status-closed {
background-color: rgba(31, 190, 93, 0.1);
color: #1fbe5d;
}
.status-processing {
background-color: rgba(235, 60, 60, 0.08);
color: #eb3c3c;
}
.status-unknown {
background-color: rgba(153, 153, 153, 0.1);
color: #666666;
}
.victory-tag {
font-size: 0.75rem;
color: #d6943e;
}
.expand-indicator {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: #999999;
}
.expand-indicator img {
transition: transform 0.2s ease;
}
.expand-indicator img.rotate-180 {
transform: rotate(180deg);
}
.case-detail {
border-top: 1px dashed #eeeeee;
padding: 0 0.75rem 0.5rem;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.25s ease, opacity 0.25s ease;
}
.case-detail.detail-expanded {
max-height: 500px;
opacity: 1;
}
.status-closed {
background-color: rgba(31, 190, 93, 0.1);
color: #1fbe5d;
}
.status-processing {
background-color: rgba(235, 60, 60, 0.08);
color: #eb3c3c;
}
.status-unknown {
background-color: rgba(153, 153, 153, 0.1);
color: #666666;
}
.case-body {
border-top: 1px dashed #e5e5e5;
padding-top: 0.5rem;
}
.info-row {
display: flex;
font-size: 0.8rem;
margin-bottom: 0.25rem;
}
.info-label {
width: 4.5rem;
color: #999999;
flex-shrink: 0;
}
.info-value {
color: #333333;
word-break: break-all;
}
.empty-tip {
margin-top: 0.75rem;
padding: 0.75rem;
text-align: center;
font-size: 0.85rem;
color: #999999;
background-color: #fafafa;
border-radius: 0.75rem;
}
</style>

212
src/ui/CYYSY3M8S.vue Normal file
View File

@@ -0,0 +1,212 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">运营商二要素</h3>
<p class="header-desc">核验手机号与姓名是否一致</p>
</div>
<div v-if="hasData" class="result-section" :class="resultSectionClass">
<div class="result-main">
<div class="result-label">核验结果</div>
<div class="result-value" :class="resultClass">
{{ resultText }}
</div>
</div>
<div class="result-sub">
<span>计费状态</span>
<span class="font-medium">{{ feeText }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const resultCode = computed(() => {
const v = props.data?.result;
if (v === 0 || v === '0') return 0;
if (v === 1 || v === '1') return 1;
return null;
});
const resultText = computed(() => {
if (resultCode.value === 0) return '一致';
if (resultCode.value === 1) return '不一致';
return '暂无结果';
});
const fee = computed(() => {
const v = props.data?.fee;
if (v === 0 || v === '0') return 0;
if (v === 1 || v === '1') return 1;
return null;
});
const feeText = computed(() => {
if (fee.value === 0) return '不计费';
if (fee.value === 1) return '计费';
return '未知';
});
const resultClass = computed(() => {
if (resultCode.value === 0) return 'result-ok';
if (resultCode.value === 1) return 'result-bad';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
if (resultCode.value === 0) return 'result-section ok';
if (resultCode.value === 1) return 'result-section bad';
return 'result-section unknown';
});
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no || p.name;
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-unknown {
color: #6b7280;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

162
src/ui/CYYSY6F2B.vue Normal file
View File

@@ -0,0 +1,162 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机消费区间验证</h3>
<p class="header-desc">根据运营商数据评估消费能力档位</p>
</div>
<div v-if="hasData" class="result-section">
<div class="result-main">
<div class="result-label">消费档位</div>
<div class="result-value">
{{ stateText }}
</div>
</div>
<div class="result-sub">
<span>号码所属运营商{{ operatorText }}</span>
<span v-if="isPortedText" class="ml-2">是否携号转网{{ isPortedText }}</span>
<span v-if="operatorRealText" class="ml-2">实际运营商{{ operatorRealText }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const state = computed(() => props.data?.state || '');
const operator = computed(() => props.data?.operator || '');
const operatorReal = computed(() => props.data?.operator_real || '');
const isXhzw = computed(() => props.data?.is_xhzw || '');
const operatorMap = {
'1': '移动',
'2': '联通',
'3': '电信',
};
const operatorText = computed(() => operatorMap[operator.value] || operator.value || '-');
const operatorRealText = computed(() => operatorMap[operatorReal.value] || '');
const isPortedText = computed(() => {
if (isXhzw.value === '0') return '否';
if (isXhzw.value === '1') return '是';
return '';
});
const stateText = computed(() => {
// 这里只提示“消费档位编号”,具体金额区间见注释说明
if (!state.value) return '未知';
return `消费档位:${state.value}(不同运营商对应区间不同)`;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
color: #111827;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

124
src/ui/CYYSY9E4A.vue Normal file
View File

@@ -0,0 +1,124 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机号码归属地核验</h3>
<p class="header-desc">展示手机号码的省市运营商和区号等信息</p>
</div>
<div v-if="hasData" class="result-section">
<div class="info-row">
<span class="info-label">号段</span>
<span class="info-value font-mono">{{ data.mobilePrefix || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">省份</span>
<span class="info-value">{{ data.provinceName || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">城市</span>
<span class="info-value">{{ data.cityName || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">运营商</span>
<span class="info-value">{{ data.channel || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">区号</span>
<span class="info-value">{{ data.areaCode || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">邮编</span>
<span class="info-value">{{ data.postCode || '-' }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
background: #f9fafb;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

192
src/ui/CYYSYE7V5.vue Normal file
View File

@@ -0,0 +1,192 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机在网状态</h3>
<p class="header-desc">判断号码当前是否在网可用</p>
</div>
<div v-if="hasData" class="result-section" :class="statusSectionClass">
<div class="result-main">
<div class="result-label">在网状态</div>
<div class="result-value" :class="statusClass">
{{ statusText }}
</div>
</div>
<div class="result-sub">
<span>运营商{{ channel || '-' }}</span>
<span v-if="status === 1 && desc" class="ml-2">原因{{ desc }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const status = computed(() => {
const v = props.data?.status;
if (v === 0 || v === '0') return 0;
if (v === 1 || v === '1') return 1;
return null;
});
const statusText = computed(() => {
if (status.value === 0) return '在网';
if (status.value === 1) return '不在网';
return '未知';
});
const statusClass = computed(() => {
if (status.value === 0) return 'result-ok';
if (status.value === 1) return 'result-bad';
return 'result-unknown';
});
const statusSectionClass = computed(() => {
if (status.value === 0) return 'result-section ok';
if (status.value === 1) return 'result-section bad';
return 'result-section unknown';
});
const channel = computed(() => props.data?.channel || '');
const desc = computed(() => props.data?.desc || '');
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-unknown {
color: #6b7280;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

210
src/ui/CYYSYF2T7.vue Normal file
View File

@@ -0,0 +1,210 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">号码二次放号</h3>
<p class="header-desc">判断该手机号是否为二次放号</p>
</div>
<div v-if="hasData" class="result-section" :class="resultSectionClass">
<div class="result-main">
<div class="result-label">核验结果</div>
<div class="result-value" :class="resultClass">
{{ resultText }}
</div>
</div>
<div class="result-sub">
<span>运营商</span>
<span class="font-medium">{{ channelText }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const result = computed(() => {
const v = props.data?.result;
if (v === 0 || v === '0') return 0;
if (v === 1 || v === '1') return 1;
if (v === 2 || v === '2') return 2;
return null;
});
const resultText = computed(() => {
switch (result.value) {
case 0:
return '是二次卡';
case 1:
return '不是二次卡';
case 2:
return '数据库中无信息';
default:
return '暂无结果';
}
});
const resultClass = computed(() => {
if (result.value === 0) return 'result-bad';
if (result.value === 1) return 'result-ok';
if (result.value === 2) return 'result-unknown';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
if (result.value === 0) return 'result-section bad';
if (result.value === 1) return 'result-section ok';
if (result.value === 2) return 'result-section unknown';
return 'result-section unknown';
});
const channelRaw = computed(() => props.data?.channel || '');
const channelText = computed(() => {
const c = channelRaw.value;
if (!c) return '-';
if (c === 'cmcc') return '移动 (cmcc)';
if (c === 'cucc') return '联通 (cucc)';
if (c === 'ctcc') return '电信 (ctcc)';
return c;
});
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-unknown {
color: #6b7280;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

218
src/ui/CYYSYK8R3.vue Normal file
View File

@@ -0,0 +1,218 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机空号检测</h3>
<p class="header-desc">判断号码是空号实号还是沉默/风险号</p>
</div>
<div v-if="hasData" class="result-section" :class="statusSectionClass">
<div class="result-main">
<div class="result-label">号码状态</div>
<div class="result-value" :class="statusClass">
{{ statusText }}
</div>
</div>
<div class="result-sub" v-if="area || channel">
<span v-if="area">归属地{{ area }}</span>
<span v-if="channel" class="ml-2">运营商{{ channel }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const statusCode = computed(() => {
const v = props.data?.status;
if (v == null) return null;
const n = Number(v);
return Number.isFinite(n) ? n : null;
});
const statusText = computed(() => {
switch (statusCode.value) {
case 0:
return '空号';
case 1:
return '实号';
case 2:
return '停机';
case 3:
return '库无(预留)';
case 4:
return '沉默号';
case 5:
return '风险号';
default:
return '未知';
}
});
const statusClass = computed(() => {
const s = statusCode.value;
if (s === 1) return 'result-ok';
if (s === 0 || s === 4 || s === 5) return 'result-bad';
if (s === 2 || s === 3) return 'result-warn';
return 'result-unknown';
});
const statusSectionClass = computed(() => {
const s = statusCode.value;
if (s === 1) return 'result-section ok';
if (s === 0 || s === 4 || s === 5) return 'result-section bad';
if (s === 2 || s === 3) return 'result-section warn';
return 'result-section unknown';
});
const area = computed(() => props.data?.area || '');
const channel = computed(() => props.data?.channel || '');
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.warn {
background: #fffbeb;
border-color: #f9731633;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-warn {
color: #d97706;
}
.result-unknown {
color: #6b7280;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

216
src/ui/CYYSYK9R4.vue Normal file
View File

@@ -0,0 +1,216 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">全网手机三要素验证周更</h3>
<p class="header-desc">核验手机号+身份证+姓名是否一致</p>
</div>
<div v-if="hasData" class="result-section" :class="resultSectionClass">
<div class="result-main">
<div class="result-label">核验结果</div>
<div class="result-value" :class="resultClass">
{{ resultText }}
</div>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验信息</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">姓名</span>
<span class="info-value">{{ maskedName }}</span>
</div>
<div class="info-row">
<span class="info-label">身份证号</span>
<span class="info-value font-mono">{{ maskedIdCard }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const state = computed(() => {
const v = props.data?.state;
if (v == null) return null;
return String(v);
});
const resultText = computed(() => {
switch (state.value) {
case '1':
return '验证一致';
case '2':
return '验证不一致';
case '3':
return '异常情况';
default:
return '暂无结果';
}
});
const resultClass = computed(() => {
if (state.value === '1') return 'result-ok';
if (state.value === '2') return 'result-bad';
if (state.value === '3') return 'result-warn';
return 'result-unknown';
});
const resultSectionClass = computed(() => {
if (state.value === '1') return 'result-section ok';
if (state.value === '2') return 'result-section bad';
if (state.value === '3') return 'result-section warn';
return 'result-section unknown';
});
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no || p.name || p.id_card;
});
const maskedName = computed(() => {
const name = props.params?.name || '';
if (!name) return '-';
return name.length > 1 ? name[0] + '*'.repeat(name.length - 1) : '*';
});
const maskedIdCard = computed(() => {
const id = props.params?.id_card || '';
if (!id || id.length < 8) return id || '-';
return id.slice(0, 4) + '********' + id.slice(-4);
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
.result-section.ok {
background: #ecfdf3;
border-color: #22c55e33;
}
.result-section.bad {
background: #fef2f2;
border-color: #ef444433;
}
.result-section.warn {
background: #fffbeb;
border-color: #f9731633;
}
.result-section.unknown {
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
}
.result-ok {
color: #16a34a;
}
.result-bad {
color: #dc2626;
}
.result-warn {
color: #d97706;
}
.result-unknown {
color: #6b7280;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

172
src/ui/CYYSYP0T4.vue Normal file
View File

@@ -0,0 +1,172 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机号码在网时长</h3>
<p class="header-desc">展示号码在当前运营商下的在网时长区间</p>
</div>
<div v-if="hasData" class="result-section">
<div class="result-main">
<div class="result-label">在网时长</div>
<div class="result-value">
{{ timeText }}
</div>
</div>
<div class="result-sub">
<span>运营商{{ channelText }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const time = computed(() => props.data?.time || '');
const timeText = computed(() => {
const t = time.value;
if (!t) return '-';
switch (t) {
case '[0,3)':
return '03 个月';
case '[3,6)':
return '36 个月';
case '[6,12)':
return '612 个月';
case '[12,24)':
return '1224 个月';
case '[24,-1)':
return '24 个月及以上';
default:
return t;
}
});
const channelRaw = computed(() => props.data?.channel || '');
const channelText = computed(() => {
const c = channelRaw.value;
if (!c) return '-';
if (c === 'cmcc') return '移动 (cmcc)';
if (c === 'cucc') return '联通 (cucc)';
if (c === 'ctcc') return '电信 (ctcc)';
if (c === 'gdcc') return '广电 (gdcc)';
return c;
});
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 1rem;
font-weight: 600;
color: #111827;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

151
src/ui/CYYSYS9W1.vue Normal file
View File

@@ -0,0 +1,151 @@
<template>
<div class="card">
<div class="header-box">
<h3 class="header-title">手机携号转网</h3>
<p class="header-desc">查询号码是否发生携号转网及前后运营商</p>
</div>
<div v-if="hasData" class="result-section">
<div class="result-main">
<div class="result-label">携号转网情况</div>
<div class="result-value">
{{ portabilityText }}
</div>
</div>
<div class="result-sub">
<span>原运营商{{ ispType || '-' }}</span>
<span class="ml-2">当前运营商{{ newIspType || '-' }}</span>
</div>
</div>
<div v-if="hasParams" class="info-block">
<div class="block-title">被核验号码</div>
<div class="info-row">
<span class="info-label">提交手机号</span>
<span class="info-value font-mono">{{ params.mobile || params.mobile_no || '-' }}</span>
</div>
</div>
<div v-if="!hasData" class="empty-tip">暂无核验结果</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
data: { type: Object, default: () => ({}) },
params: { type: Object, default: () => ({}) },
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const hasData = computed(() => props.data && Object.keys(props.data).length > 0);
const ispType = computed(() => props.data?.ispType || '');
const newIspType = computed(() => props.data?.newIspType || '');
const portabilityText = computed(() => {
if (!ispType.value && !newIspType.value) return '暂无结果';
if (ispType.value && newIspType.value && ispType.value !== newIspType.value) {
return `已发生携号转网(从 ${ispType.value} 转到 ${newIspType.value}`;
}
return '未发现运营商变更';
});
const hasParams = computed(() => {
const p = props.params || {};
return p.mobile || p.mobile_no;
});
</script>
<style scoped>
.card {
padding: 1rem;
}
.header-box {
margin-bottom: 1rem;
}
.header-title {
font-size: 1.125rem;
font-weight: 600;
}
.header-desc {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.result-section {
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
background: #f9fafb;
}
.result-main {
display: flex;
align-items: center;
justify-content: space-between;
}
.result-label {
font-size: 0.875rem;
color: #6b7280;
}
.result-value {
font-size: 0.95rem;
font-weight: 600;
color: #111827;
}
.result-sub {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #4b5563;
}
.info-block {
margin-top: 1rem;
padding: 0.75rem 0.875rem;
border-radius: 0.75rem;
background: #f9fafb;
}
.block-title {
font-size: 0.875rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
padding: 0.15rem 0;
}
.info-label {
color: #6b7280;
}
.info-value {
color: #111827;
margin-left: 1rem;
text-align: right;
}
.empty-tip {
color: #9ca3af;
font-size: 0.875rem;
padding: 1rem 0;
text-align: center;
}
</style>

126
src/ui/QCXG5F3A.vue Normal file
View File

@@ -0,0 +1,126 @@
<template>
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
<!-- 车辆总数统计 -->
<div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div>
<div class="text-lg font-semibold text-gray-900">名下车辆(车牌)</div>
</div>
</div>
<div class="bg-blue-50 text-blue-700 px-4 py-2 rounded-full text-sm font-medium">
{{ vehicleCount }}
</div>
</div>
<!-- 车辆列表 -->
<div class="space-y-3" v-if="vehicleList && vehicleList.length > 0">
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:bg-blue-50 hover:border-blue-200 transition-colors duration-200"
v-for="(vehicle, index) in vehicleList" :key="index">
<div class="space-y-3">
<div class="text-xl font-bold text-gray-900 font-mono tracking-wider">
{{ vehicle.plateNum }}
</div>
<div class="flex items-center gap-3">
<div class="inline-flex items-center gap-1 px-3 py-1 rounded text-xs font-medium text-white"
:class="getPlateColorClass(vehicle.plateColor)">
<span>🏷</span>
<span>{{ getPlateColorText(vehicle.plateColor) }}</span>
</div>
<div class="text-sm text-gray-600">
<span class="text-gray-500">车辆类型:</span>
<span class="font-medium text-gray-900 ml-1">{{ getVehicleTypeText(vehicle.vehicleType)
}}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 无数据状态 -->
<div class="text-center py-12 text-gray-500" v-else>
<div class="text-4xl mb-3">🚫</div>
<div class="text-lg font-medium mb-1">暂无车辆信息</div>
<div class="text-sm">No vehicle records found</div>
</div>
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue';
import { useRiskNotifier } from '@/composables/useRiskNotifier';
const props = defineProps({
data: Object,
params: Object,
apiId: { type: String, default: '' },
index: { type: Number, default: 0 },
notifyRiskStatus: { type: Function, default: () => { } },
});
const plateColorMap = {
0: '蓝色 - 普通燃油车',
1: '黄色 - 大型车/货车',
2: '黑色 - 外籍车辆/港澳台车',
3: '白色 - 警车/军车/武警车',
4: '渐变绿色 - 新能源汽车',
5: '黄绿双拼色 - 大型新能源汽车',
6: '蓝白渐变色 - 临时牌照',
7: '临时牌照 - 临时行驶车辆',
11: '绿色 - 新能源汽车',
12: '红色 - 教练车/试验车'
};
const vehicleTypeMap = {
1: '一型客车',
2: '二型客车',
3: '三型客车',
4: '四型客车',
11: '一型货车',
12: '二型货车',
13: '三型货车',
14: '四型货车',
15: '五型货车',
16: '六型货车',
21: '一型专项作业车',
22: '二型专项作业车',
23: '三型专项作业车',
24: '四型专项作业车',
25: '五型专项作业车',
26: '六型专项作业车'
};
const vehicleList = computed(() => props.data?.list || []);
const vehicleCount = computed(() => props.data?.vehicleCount || 0);
const getPlateColorText = (plateColor) => {
return plateColorMap[plateColor] || '未知颜色 - 未知类型';
};
const getPlateColorClass = (plateColor) => {
const colorClassMap = {
0: 'bg-blue-500',
1: 'bg-yellow-500',
2: 'bg-gray-800',
3: 'bg-gray-200 text-gray-800',
4: 'bg-green-500',
5: 'bg-gradient-to-r from-yellow-500 to-green-500',
6: 'bg-gradient-to-r from-blue-500 to-white text-blue-800',
7: 'bg-red-500',
11: 'bg-green-500',
12: 'bg-red-500'
};
return colorClassMap[plateColor] || 'bg-gray-500';
};
const getVehicleTypeText = (vehicleType) => {
return vehicleTypeMap[vehicleType] || '未知类型';
};
const riskScore = computed(() => 100);
useRiskNotifier(props, riskScore);
defineExpose({ riskScore });
</script>
<style scoped>
/* 保持与 CQCXG9P1C 一致的布局风格 */
</style>

View File

@@ -48,8 +48,9 @@ const onLoad = () => {
}
}
// 允许进入报告页:成功直接看报告,查询中可看加载并轮询,失败/退款不跳转
function toDetail(item) {
if (item.query_state != "success") return
if (item.query_state === "failed" || item.query_state === "refunded") return
router.push({ path: '/report', query: { orderId: item.order_id } });
}

View File

@@ -0,0 +1,116 @@
<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getInquireCategoryConfig } from '@/config/inquireCategories'
import fxpgBanner from '@/assets/images/fxpg_banner.png'
import hygjBanner from '@/assets/images/hygj_banner.png'
import carBanner from '@/assets/images/car_banner.png'
import grssIcon from '@/assets/images/grss_icon.svg'
import qyssIcon from '@/assets/images/qyss_icon.svg'
import sjsysIcon from '@/assets/images/sjsys_icon.svg'
import zrrscztIcon from '@/assets/images/zrrsczt_icon.svg'
import mxclIcon from '@/assets/images/mxcl_icon.svg'
import clxx from '@/assets/images/category/clxx.svg'
import clcx from '@/assets/images/category/clcx.svg'
import esc from '@/assets/images/category/esc.svg'
import escgz from '@/assets/images/category/escgz.svg'
import cxcl from '@/assets/images/category/cxcl.svg'
import rchyjb from '@/assets/images/category/rchyjb.svg'
import rchyxb from '@/assets/images/category/rchyxb.svg'
import iconVerifyId from '@/assets/images/category/icon_verify_id.svg'
import iconVerifyOperator from '@/assets/images/category/icon_verify_operator.svg'
import iconVerifyBank from '@/assets/images/category/icon_verify_bank.svg'
import iconVerifyCompany from '@/assets/images/category/icon_verify_company.svg'
import iconVerifyScore from '@/assets/images/category/icon_verify_score.svg'
const route = useRoute()
const router = useRouter()
const category = computed(() => route.params.category)
const config = computed(() => getInquireCategoryConfig(category.value))
const bannerImages = {
'fxpg_banner.png': fxpgBanner,
'hygj_banner.png': hygjBanner,
'car_banner.png': carBanner,
// 核验工具复用
}
const bannerSrc = computed(() => {
if (!config.value?.banner) return null
return bannerImages[config.value.banner] || null
})
const categoryIcons = {
'grss_icon.svg': grssIcon,
'qyss_icon.svg': qyssIcon,
'sjsys_icon.svg': sjsysIcon,
'zrrsczt_icon.svg': zrrscztIcon,
'mxcl_icon.svg': mxclIcon,
'clxx.svg': clxx,
'clcx.svg': clcx,
'esc.svg': esc,
'escgz.svg': escgz,
'cxcl.svg': cxcl,
'rchyjb.svg': rchyjb,
'rchyxb.svg': rchyxb,
'icon_verify_id.svg': iconVerifyId,
'icon_verify_operator.svg': iconVerifyOperator,
'icon_verify_bank.svg': iconVerifyBank,
'icon_verify_company.svg': iconVerifyCompany,
'icon_verify_score.svg': iconVerifyScore,
}
function goInquire(feature) {
router.push(`/inquire/${feature}`)
}
function getIconUrl(item) {
if (!item?.icon) return '/inquire_icons/default.svg'
if (item.iconFrom === 'category' && categoryIcons[item.icon]) {
return categoryIcons[item.icon]
}
return `/inquire_icons/${item.icon}`
}
</script>
<template>
<div class="inquire-category min-h-screen p-4">
<template v-if="config">
<!-- 大类顶部海报 -->
<div v-if="bannerSrc" class="category-banner mb-4 rounded-xl overflow-hidden shadow-md">
<img :src="bannerSrc" :alt="config.title" class="w-full h-auto object-cover block" />
</div>
<div class="mb-4 text-sm text-gray-500">
{{ config.title }} · 请选择要进行的查询
</div>
<div class="grid grid-cols-3 gap-3">
<div v-for="(item, index) in config.items" :key="item.feature"
class="flex flex-col items-center justify-center cursor-pointer rounded-2xl bg-white py-5 shadow-lg"
:class="index % 2 === 0 ? 'rounded-br-lg rounded-tr-lg' : 'rounded-bl-lg rounded-tl-lg'"
@click="goInquire(item.feature)">
<div class="flex flex-col items-center justify-center gap-2">
<div class="flex-shrink-0 w-14 h-14 flex items-center justify-center">
<img :src="getIconUrl(item)" :alt="item.name" class="w-10 h-10 object-contain"
@error="(e) => { e.target.src = '/inquire_icons/default.svg' }" />
</div>
<div class="text-sm font-semibold text-gray-800 text-center px-1 truncate w-full">
{{ item.name }}
</div>
</div>
</div>
</div>
</template>
<div v-else class="flex flex-col items-center justify-center py-20 text-gray-500">
<div class="text-base">暂无该分类</div>
<button class="mt-4 px-6 py-2 rounded-full bg-primary text-white text-sm" @click="router.back()">
返回
</button>
</div>
</div>
</template>
<style scoped>
.inquire-category {
background: linear-gradient(to bottom, rgb(224 242 254 / 0.2), white);
}
</style>

View File

@@ -1,13 +1,22 @@
<template>
<BaseReport v-if="queryState === 'success'" :order-id="orderId" :order-no="orderNo" :feature="feature"
:reportData="reportData" :reportParams="reportParams" :reportName="reportName" :reportDateTime="reportDateTime"
:isEmpty="isEmpty" :isDone="isDone" />
<div v-else-if="queryState === 'pending'" class="loading-container">
<div class="loading-spinner"></div>
<p>报告生成中请稍候...</p>
</div>
<div class="p-4" v-else-if="queryState === 'failed'">
<LEmpty />
<div class="report-page">
<BaseReport v-if="queryState === 'success'" :order-id="orderId" :order-no="orderNo" :feature="feature"
:reportData="reportData" :reportParams="reportParams" :reportName="reportName"
:reportDateTime="reportDateTime" :isEmpty="isEmpty" :isDone="isDone" />
<div v-else-if="queryState === 'pending'" class="loading-container">
<div class="loading-spinner"></div>
<p>报告生成中请稍候...</p>
</div>
<div class="p-4" v-else-if="queryState === 'failed'">
<LEmpty />
</div>
<!-- 无论同步/异步都提供返回历史查询入口 -->
<button class="history-btn" type="button" @click="toHistory">
返回查询历史
</button>
</div>
</template>
@@ -15,6 +24,8 @@
import LEmpty from "@/components/LEmpty.vue";
const route = useRoute();
const router = useRouter();
const feature = ref("");
const reportData = ref([]);
const reportParams = ref({});
@@ -27,13 +38,11 @@ const orderNo = ref("");
const queryState = ref("");
const pollingInterval = ref(null);
onBeforeMount(() => {
const query = new URLSearchParams(window.location.search);
orderNo.value = query.get("out_trade_no");
orderId.value = query.get("order_id");
if (!orderNo.value && !orderId.value) {
orderId.value = route.query.orderId;
orderNo.value = route.query.orderNo;
@@ -50,8 +59,6 @@ onBeforeUnmount(() => {
}
});
const getReport = async () => {
let queryUrl = "";
if (orderNo.value) {
@@ -72,7 +79,6 @@ const getReport = async () => {
reportData.value = data.value.data.query_data.sort((a, b) => {
return a.feature.sort - b.feature.sort;
});
// console.log("reportData", reportData.value[1].data.data)
reportParams.value = data.value.data.query_params;
reportName.value = data.value.data.product_name;
@@ -85,11 +91,11 @@ const getReport = async () => {
pollingInterval.value = null;
}
} else if (queryState.value === "pending") {
// 如果是pending状态且没有轮询启动轮询
// 如果是 pending 状态且没有轮询,启动轮询
if (!pollingInterval.value) {
pollingInterval.value = setInterval(() => {
getReport();
}, 2000); // 每2秒轮询一次
}, 2000); // 每 2 秒轮询一次
}
} else if (queryState.value === "failed") {
isEmpty.value = true;
@@ -103,15 +109,21 @@ const getReport = async () => {
} else if (data.value.code === 200003) {
isEmpty.value = true;
isDone.value = true;
} else if (data.value.code === 200002) {
isPending.value = true;
isDone.value = true;
}
}
};
const toHistory = () => {
router.push("/historyQuery");
};
</script>
<style lang="scss" scoped>
.report-page {
position: relative;
min-height: 100vh;
}
.loading-container {
display: flex;
flex-direction: column;
@@ -135,6 +147,25 @@ const getReport = async () => {
}
}
.history-btn {
position: fixed;
right: 12px;
bottom: 20px;
padding: 8px 14px;
font-size: 14px;
border-radius: 999px;
border: none;
color: #ffffff;
background: linear-gradient(135deg, #2563eb, #1d4ed8);
box-shadow: 0 4px 10px rgba(37, 99, 235, 0.4);
cursor: pointer;
z-index: 50;
}
.history-btn:active {
transform: scale(0.97);
}
@keyframes spin {
0% {
transform: rotate(0deg);

View File

@@ -48,20 +48,19 @@ const showServiceNotice = ref(false);
function toInquire(name) {
if (name === "Marriage") {
// 婚恋风险:直接进入婚恋报告查询页
router.push(`/inquire/marriage`);
} else if (name === "Api") {
window.location.href = "https://www.tianyuanapi.com/";
} else if (name === "Lawsuit") {
// 司法涉诉跳转到个人司法涉诉查询
router.push(`/inquire/toc_PersonalLawsuit`);
router.push(`/inquire/category/lawsuit`);
} else if (name === "MarriageStatus") {
// 婚姻状况跳转到婚姻查询
router.push(`/inquire/toc_Marriage`);
router.push(`/inquire/category/marriageStatus`);
} else if (name === "VehiclesUnderName") {
// 车辆查询跳转
router.push(`/inquire/toc_VehiclesUnderName`);
router.push(`/inquire/category/vehicle`);
} else if (name === "Verify") {
router.push(`/inquire/category/verify`);
} else if (name === "Promote") {
// 推广报告跳转
router.push(`/agent/promote`);
} else {
router.push(`/list${name}`);
@@ -146,6 +145,20 @@ function toPromote() {
</div>
</div>
<div class="relative flex flex-col cursor-pointer rounded-bl-[35px] rounded-br-lg rounded-tl-[35px] rounded-tr-lg bg-white px-4 py-6 shadow-lg"
@click="toInquire('Verify')">
<div class="min-h-18 gap-2 bg-white px-1">
<div class="mb-2 flex justify-around">
<img class="h-12 w-12 flex-shrink-0" src="@/assets/images/hygj.svg" alt="核验工具图标" />
<div class="mt-1 max-w-max flex-shrink-0 text-left text-lg text-gray-600 font-bold">
核验工具
</div>
</div>
<div class="max-w-max text-left text-xs text-gray-600">
身份核验运营商核验银行卡核验名下企业关联等
</div>
</div>
</div>
<div class="relative flex flex-col cursor-pointer rounded-bl-lg rounded-br-[35px] rounded-tl-lg rounded-tr-[35px] bg-white px-4 py-6 shadow-lg"
@click="toInquire('Promote')">
<div class="min-h-18 gap-2 bg-white px-1">
<div class="mb-2 flex justify-around">
@@ -159,7 +172,7 @@ function toPromote() {
</div>
</div>
</div>
<div class="relative flex flex-col cursor-pointer rounded-bl-lg rounded-br-[35px] rounded-tl-lg rounded-tr-[35px] bg-white px-4 py-6 shadow-lg"
<!-- <div class="relative flex flex-col cursor-pointer rounded-bl-lg rounded-br-[35px] rounded-tl-lg rounded-tr-[35px] bg-white px-4 py-6 shadow-lg"
@click="toInquire('Api')">
<div class="min-h-18 gap-2 bg-white px-1">
<div class="mb-2 flex justify-around">
@@ -173,7 +186,7 @@ function toPromote() {
提供婚姻司法核验等高效便捷的API服务帮助企业快速获取数据提升业务效率
</div>
</div>
</div>
</div> -->
</div>
<div class="mt-4 font-bold">更多服务</div>
<div class="mt-4 box-border h-14 w-full flex items-center rounded-xl bg-white px-4 text-gray-700 shadow-xl"