前端源码 v3.9.1

This commit is contained in:
JEECG
2026-01-21 18:14:55 +08:00
parent 41877a6e8b
commit 901f05ed21
150 changed files with 38968 additions and 2698 deletions

View File

@ -24,7 +24,7 @@
},
"dependencies": {
"@jeecg/online": "3.9.0-beta2",
"@jeecg/aiflow":"3.9.0-beta2",
"@jeecg/aiflow":"3.9.1-beta",
"@logicflow/core": "^2.0.10",
"@logicflow/extension": "^2.0.14",
"@logicflow/vue-node-registry": "^1.0.12",
@ -63,6 +63,7 @@
"path-to-regexp": "^6.3.0",
"pinia": "2.1.7",
"print-js": "^1.6.0",
"pinyin-pro": "^3.27.0",
"qs": "^6.14.0",
"qrcode": "^1.5.4",
"resize-observer-polyfill": "^1.5.1",
@ -103,6 +104,7 @@
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.14.0",
"@types/pinyin": "^2.10.2",
"@types/showdown": "^2.0.6",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 13h12v1H2v-1zm0-3h12v1H2v-1zm0-3h12v1H2V7zm0-6h12v1H2V1zm0 3h12v1H2V4z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@ -0,0 +1 @@
<svg width="4" height="7" xmlns="http://www.w3.org/2000/svg"><path fill="#6F6F6F" d="M0 3.5L4 0v7z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 127 B

View File

@ -0,0 +1 @@
<svg width="4" height="7" xmlns="http://www.w3.org/2000/svg"><path fill="#6F6F6F" d="M4 3.5L0 0v7z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 127 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="1729581274416" class="icon" viewBox="0 0 1229 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3284" xmlns:xlink="http://www.w3.org/1999/xlink" width="240.0390625" height="200"><path d="M814.461729 219.012428" fill="#272636" p-id="3285"></path><path d="M18.646281 1023.999972l154.377255 0L173.023535-1.3e-05 18.646281-1.3e-05 18.646281 1023.999972zM223.337993 1023.999972l72.590174 0L295.928168-1.3e-05l-72.590174 0L223.337993 1023.999972zM390.338738 1023.999972l91.366339 0L481.705076-1.3e-05l-91.366339 0L390.338738 1023.999972zM651.850748 1023.999972l157.612662 0L809.46341-1.3e-05 651.850748-1.3e-05 651.850748 1023.999972zM850.31244 1023.999972l59.979819 0L910.292259-1.3e-05l-59.979819 0L850.31244 1023.999972zM1004.780175-1.3e-05 1004.780175 1023.999972 1212.730645 1023.999972 1212.730645-1.3e-05 1004.780175-1.3e-05z" fill="#272636" p-id="3286"></path></svg>

After

Width:  |  Height:  |  Size: 1022 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.131 6.9c2.035 0 2.569-.9 2.569-1.869 0-.968-.64-1.831-2.623-1.831H5.2v3.7h2.931zm.524 5.9c2.045 0 2.545-1.305 2.545-2.3 0-.985-.506-2.4-2.81-2.4H5.2v4.7h3.455zM4 2h4.71c2.367 0 3.19 1.583 3.19 3s-.325 1.852-1.1 2.5c1.2.5 1.569 1.379 1.6 3 .03 1.606-.586 3.5-3.769 3.5H4V2z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757"><path d="M13 0c.552 0 1 .48 1 1.071V13.93c0 .59-.448 1.07-1 1.07H3c-.552 0-1-.48-1-1.071V1.07C2 .48 2.448 0 3 0h10zm0 1H3v13h10V1z"/><path d="M5 10v1H4v-1h1zm6 0v1H6v-1h5zM5 7v1H4V7h1zm6 0v1H6V7h5zM5 4v1H4V4h1zm6 0v1H6V4h5z"/></g></svg>

After

Width:  |  Height:  |  Size: 337 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="1731892090058" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14313" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M799.3 630.1l19.5-18.1c0.8-0.7 1.6-1.5 2.3-2.2 13.6-14 13.6-36.9 0-50.8L346.7 72.7c-13.6-14-35.9-14-49.6 0-13.6 14-13.6 36.9 0 50.8l118.1 121.1-290.6 297.9c-24.1 24.7-24.1 65 0 89.7L417.1 932c24.1 24.7 63.4 24.7 87.5 0l292.5-299.8c0.8-0.7 1.5-1.4 2.2-2.1z m-621.5-44.8l284.9-292 284.9 292H177.8z m774 244.2l-88.6-179.4-90.2 182c-9 16-14.1 34.6-14.1 54.5 0 59.9 46.7 108.5 104.2 108.5 57.6 0 104.2-48.6 104.2-108.5 0.1-21-5.7-40.4-15.5-57.1z m0 0" p-id="14314" fill="#2c2c2c"></path></svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 13h12v1H2v-1zm2-3h8v1H4v-1zM2 7h12v1H2V7zm0-6h12v1H2V1zm2 3h8v1H4V4z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 207 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="1723003465965" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7196" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M128 64H64v896h896v-64H128V64z" fill="#333333" p-id="7197"></path><path d="M480 259.2V576h278.4c-28.8 108.8-128 192-246.4 192-140.8 0-256-115.2-256-256 0-131.2 99.2-236.8 224-252.8m32-67.2c-176 0-320 144-320 320s144 320 320 320 320-144 320-320H544V192h-32z" fill="#333333" p-id="7198"></path><path d="M672 134.4c124.8 22.4 201.6 112 220.8 249.6H672V134.4M608 64v384h352c0-224-128-384-352-384z" fill="#333333" p-id="7199"></path></svg>

After

Width:  |  Height:  |  Size: 767 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.9 8.192l4.242 4.243-.707.707L8.192 8.9 3.95 13.142l-.707-.707 4.242-4.243L3.243 3.95l.707-.707 4.242 4.242 4.243-4.242.707.707L8.9 8.192z" fill="#6A6A6A" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M7.997 3.429L6.398 8h3.2L7.997 3.429zM8.497 2L12 12h-1L9.949 9h-3.9L5 12H4L7.496 2h1z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 221 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="1715245280824" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1321" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.544887 753.01171l-124.702576 0 0 116.708821 123.903201 0 0 127.900078 186.254489-186.254489-185.455113-185.455113L838.544887 753.01171 838.544887 753.01171zM838.544887 753.01171" p-id="1322"></path><path d="M783.387978 949.65808 163.0726 949.65808c-7.194379 0-12.790008-5.595628-12.790008-12.790008L150.282592 86.332553c0-7.194379 5.595628-12.790008 12.790008-12.790008L575.550351 73.542545c7.194379 0 12.790008 5.595628 12.790008 12.790008L588.340359 311.75644c0 29.576893 23.981265 54.357533 54.357533 54.357533l225.423888 0c7.194379 0 12.790008 5.595628 12.790008 12.790008L880.911788 575.550351l72.743169 73.542545L953.654957 281.380172c0-15.98751-6.395004-31.97502-17.586261-43.166276L715.441062 17.586261C703.450429 6.395004 688.262295 0 671.47541 0L138.291959 0c-33.57377 0-61.551913 27.178767-61.551913 61.551913l0 900.896175c0 33.57377 27.178767 61.551913 61.551913 61.551913l645.096019 0L783.387978 949.65808zM661.083528 101.520687c0-12.790008 15.188134-19.185012 24.78064-10.391881L863.325527 267.790788c8.79313 8.79313 2.398126 24.78064-10.391881 24.78064L675.472287 292.571429c-7.993755 0-14.388759-6.395004-14.388759-14.388759L661.083528 101.520687z" p-id="1323"></path><path d="M806.569867 438.857143 663.481655 438.857143c-7.993755 0-14.388759 6.395004-14.388759 14.388759l0 32.774395c0 7.993755 6.395004 14.388759 14.388759 14.388759l19.984387 0c8.79313 0 15.98751 7.993755 13.589383 17.586261l-52.758782 232.618267c-1.598751 7.993755-3.197502 15.98751-3.996877 26.379391l-0.799375 11.990632-2.398126 0-1.598751-11.990632c0 0 0-0.799375 0-0.799375 0-2.398126-0.799375-5.595628-2.398126-11.990632-0.799375-6.395004-2.398126-11.191257-3.197502-14.388759l-79.138173-300.565183c-1.598751-6.395004-7.194379-10.391881-13.589383-10.391881l-43.166276 0c-6.395004 0-11.990632 3.996877-13.589383 10.391881l-79.138173 300.565183c-0.799375 3.197502-1.598751 7.993755-2.398126 13.589383-0.799375 5.595628-1.598751 9.592506-2.398126 11.990632l-2.398126 11.990632L391.693989 787.384856l-0.799375-11.990632c-0.799375-9.592506-2.398126-18.385636-3.996877-26.379391l-52.758782-232.618267c-1.598751-8.79313 4.796253-17.586261 13.589383-17.586261l19.984387 0c7.993755 0 14.388759-6.395004 14.388759-14.388759l0-32.774395c0-7.993755-6.395004-14.388759-14.388759-14.388759L226.223263 437.258392c-8.79313 0-15.98751 7.194379-15.98751 15.98751l0 31.175644c0 7.993755 6.395004 14.388759 14.388759 14.388759l15.188134 0c6.395004 0 11.990632 4.796253 13.589383 10.391881l87.931304 355.722092c1.598751 6.395004 7.194379 10.391881 13.589383 10.391881L423.669009 875.316159c6.395004 0 11.990632-3.996877 13.589383-10.391881l70.345043-266.192037c2.398126-7.993755 4.796253-15.98751 5.595628-26.379391 0.799375-6.395004 0.799375-10.391881 0.799375-13.589383l2.398126 0 1.598751 12.790008c0 0.799375 0 0.799375 0 1.598751 0 1.598751 0.799375 5.595628 1.598751 10.391881 0.799375 6.395004 1.598751 11.191257 3.197502 15.188134l70.345043 266.192037c1.598751 6.395004 7.194379 10.391881 13.589383 10.391881l68.746292 0c6.395004 0 11.990632-4.796253 13.589383-10.391881l87.931304-355.722092c1.598751-6.395004 7.194379-10.391881 13.589383-10.391881l15.188134 0c7.993755 0 14.388759-6.395004 14.388759-14.388759l0-32.774395C820.958626 445.252147 814.563622 438.857143 806.569867 438.857143z" p-id="1324"></path></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M9 9h1v4H9z"/><path d="M9 9h4v1H9zM7 7H6V3h1z"/><path d="M7 7H3V6h4z"/></g></svg>

After

Width:  |  Height:  |  Size: 191 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M8.213 13H6.8l6.636-6.636-4.243-4.243-7.07 7.071L5.928 13H4.515L1.06 9.546a.5.5 0 010-.707L8.839 1.06a.5.5 0 01.707 0l4.95 4.95a.5.5 0 010 .707L8.213 13z" fill-rule="nonzero"/><path d="M4.536 6.364l4.95 4.95-.707.707-4.95-4.95zM4.521 13h10.03v1H5.496z"/></g></svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M13.31 5h-1.92a2.203 2.203 0 00-.39-.034c-.135 0-.27.012-.402.034H10v.18c-.578.256-1 .714-1 1.214v1.928c0 .196.117.43.3.658v1.23a3.543 3.543 0 01-.3-.4V11H8V1h1v4.265C9 4.763 10 4 10.942 4c1.19 0 1.92.422 2.367 1zM2 6c-.03-.498-.175-2 2.5-2C7.11 4 7 5 7 6.902V11H5.984v-.993c.38.662-.115.993-1.484.993C2.708 11 2 9.931 2 9c0-1.428.447-2 2.5-2h1.484c0-1 .031-2-1.484-2-1.533 0-1.577.485-1.577 1H2zm2.5 2C3.601 8 3 7.768 3 9c0 1.31.438 1 1.5 1 .617 0 1.484-.665 1.484-1.847V8H4.5z"/><path d="M13.085 6.316l-2.814 3a1 1 0 101.458 1.368l2.815-3a1 1 0 00-1.459-1.368z" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 725 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(45 5.5 13.328)" fill="#3D4757" fill-rule="evenodd"><path d="M7 6H6V3.515a2.5 2.5 0 10-5 0V6H0V3.515a3.5 3.5 0 117 0V6zm0 4v2.5a3.5 3.5 0 01-7 0V10h1v2.5a2.5 2.5 0 105 0V10h1z" fill-rule="nonzero"/><rect x="3.062" y="5.209" width="1" height="5.5" rx=".5"/></g></svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_0-公共_x2F_02工具栏_x2F_插入图片-16px-"><g id="Group-19" transform="translate(1 1)"><path id="Combined-Shape" class="st0" d="M1 0h12c.6 0 1 .4 1 1v11c0 .6-.4 1-1 1H1c-.6 0-1-.4-1-1V1c0-.6.4-1 1-1zm0 1v11h12V1H1z"/><circle id="椭圆形" class="st0" cx="10" cy="4" r="1"/><path id="Path" class="st0" d="M8.5 11.2l-4-4.1L1 10.7V9.2c1.7-1.6 2.7-2.5 3-2.8.4-.5.7-.4 1 0L8.5 10 11 7.3c.4-.5.6-.5 1-.1l2 2.8v1.5l-2.5-3.4-3 3.1z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M10.017 3L8.08 13H9v1H6v-1h1.182L9 3H8V2h3v1h-.983z" fill="#3D4757"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 13h12v1H2zm0-3h8v1H2zm0-3h12v1H2zm0-6h12v1H2zm0 3h8v1H2z" fill="#3d4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 195 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h5v1H4zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2zm4 0h5v1h-5zm7 0h2v1h-2zm4 0h2v1h-2z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h6v1H4zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3zm6 0h6v1h-6zm9 0h3v1h-3z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M5 10h4v1H5zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4zm8 0h4v1h-4z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h4v1H4zm5 0h4v1H9zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h4v1h-4zm5 0h3v1h-3z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 389 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h1v1H4zm12 0h1v1h-1zM6 10h1v1H6zm12 0h1v1h-1zM8 10h1v1H8zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm2 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm2 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm2 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm2 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1zm12 0h1v1h-1zm-10 0h1v1h-1z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 885 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h118v1H4zm0 3h118v1H4z"/></svg>

After

Width:  |  Height:  |  Size: 109 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path d="M4 10h118v1H4z" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 116 B

View File

@ -0,0 +1 @@
<svg width="126" height="20" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="#000000" d="M4 10 Q6 8,8 10 T12 10 T16 10 T20 10 T24 10 T28 10 T32 10 T36 10 T40 10 T44 10 T48 10 T52 10 T56 10 T60 10 T64 10 T68 10 T72 10 T76 10 T80 10 T84 10 T88 10 T92 10 T96 10 T100 10 T104 10 T108 10 T112 10 T116 10 T120 10 T124 10"/></svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#3D4757" d="M7 12h7v1H7zm0-5h7v1H7zm0-5h7v1H7zM3 4V2H2V1h2v3h1v1H2V4h1z"/><path d="M2 6h3v1H2V6zm0 3h3v1H2V9z" fill="#4F4F4F"/><path fill="#3D4757" fill-rule="nonzero" d="M4.5 6L5 7l-2.5 3L2 9z"/><path d="M4 14l-1-2H2v-1h3v1H4l1 2v1H2v-1h2z" fill="#3D4757"/></g></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M1072.147851 406.226367c-6.331285-33.456782-26.762037-55.073399-52.047135-55.073399-0.323417 0-0.651455 0.003081-0.830105 0.009241l-4.655674 0c-73.124722 0-132.618162-59.491899-132.618162-132.618162 0-23.731152 11.447443-50.336101 11.546009-50.565574 13.104573-29.498767 3.023185-65.672257-23.427755-84.127081l-1.601687-1.127342-134.400039-74.661726-1.700252-0.745401c-8.753836-3.805547-18.334698-5.735272-28.479231-5.735272-20.789593 0-41.235746 8.344174-54.683758 22.306575-14.741683 15.216028-65.622973 58.649474-104.721083 58.649474-39.450789 0-90.633935-44.286652-105.438762-59.784516-13.518857-14.247316-34.128258-22.753199-55.127302-22.753199-9.945862 0-19.354234 1.861961-27.958682 5.531982l-1.746455 0.74078-139.141957 76.431283-1.643269 1.139662c-26.537186 18.437884-36.675557 54.579032-23.584845 84.062398 0.115506 0.264895 11.579891 26.725075 11.579891 50.634877 0 73.126262-59.491899 132.618162-132.618162 132.618162l-4.581749 0c-0.318797-0.00616-0.636055-0.01078-0.951772-0.01078-25.260456 0-45.672728 21.618157-52.002472 55.0811-0.462025 2.453354-11.313456 60.622322-11.313456 106.117939 0 45.494078 10.85143 103.659965 11.314996 106.119479 6.334365 33.458322 26.758957 55.076479 52.036353 55.076479 0.320337 0 0.651455-0.00616 0.842426-0.012321l4.655674 0c73.126262 0 132.618162 59.491899 132.618162 132.616622 0 23.760413-11.444363 50.333021-11.546009 50.565574-13.093793 29.474125-3.041666 65.646075 23.395414 84.151722l1.569346 1.093459 131.838879 73.726895 1.675611 0.7377c8.750757 3.84251 18.305437 5.790715 28.397607 5.790715 21.082208 0 41.676209-8.706094 55.0888-23.290689 18.724339-20.347588 69.527086-62.362616 107.04815-62.362616 40.625872 0 92.72537 47.100385 107.759669 63.583903 13.441852 14.831008 34.176001 23.689571 55.470741 23.695731l0.00616 0c9.895039 0 19.27877-1.883523 27.893999-5.598205l1.711034-0.73924 136.659342-75.531873 1.617088-1.128882c26.492523-18.456365 36.601633-54.600594 23.538642-84.016195-0.115506-0.267974-11.595291-27.082374-11.595291-50.67646 0-73.124722 59.49344-132.616622 132.618162-132.616622l4.517066-0.00154c0.300316 0.00616 0.599092 0.009241 0.899409 0.009241 25.331299-0.00154 45.785153-21.619697 52.107197-55.054918 0.112426-0.589852 11.325776-59.507301 11.325776-106.14104C1083.464388 466.640776 1072.609877 408.67356 1072.147851 406.226367zM377.486862 945.656142l-115.32764-64.487932c5.082277-13.052211 15.437801-43.51815 15.437801-75.017486 0-109.382917-84.176364-199.816642-192.587488-208.134635-2.647404-15.427021-8.873963-54.967133-8.873963-85.667166 0-30.65691 6.223479-70.232445 8.869343-85.671786 108.415744-8.311832 192.592108-98.745557 192.592108-208.134635 0-31.416171-10.300081-61.797405-15.371577-74.854236l122.721583-67.40331c0.003081 0 0.00462 0.00154 0.007701 0.00154 4.423121 4.518606 22.121764 22.080182 46.558275 39.493911 39.929754 28.46229 77.952885 42.894416 113.014434 42.894416 34.716571 0 72.437845-14.151831 112.115025-42.06431 24.282503-17.07953 41.896442-34.302288 46.308782-38.74543 0.009241-0.00154 0.018481-0.00462 0.026182-0.00616l118.301542 65.726159c-5.077657 13.055291-15.416239 43.499669-15.416239 74.958962 0 109.389077 84.174824 199.822802 192.590568 208.134635 2.645865 15.462442 8.872423 55.107281 8.872423 85.671786 0 30.687711-6.223479 70.241685-8.869343 85.673326C890.042174 606.334084 805.86427 696.767809 805.86427 806.158426c0 31.450053 10.317022 61.851309 15.393138 74.903519l-119.783103 66.198965c-5.168521-5.490399-22.603811-23.363073-46.740005-41.288109-40.701336-30.224145-79.662378-45.549521-115.800446-45.549521-35.79155 0-74.458435 15.038919-114.927219 44.694774C400.22004 922.554885 382.666163 940.255068 377.486862 945.656142zM731.271848 511.646647c0-105.803762-86.081448-191.88059-191.888289-191.88059-105.803762 0-191.88059 86.076827-191.88059 191.88059 0 105.803762 86.076827 191.882129 191.88059 191.882129C645.19194 703.528777 731.271848 617.450409 731.271848 511.646647zM539.383558 395.903184c63.825696 0 115.751164 51.922387 115.751164 115.743463 0 63.825696-51.925468 115.751164-115.751164 115.751164-63.821076 0-115.743463-51.925468-115.743463-115.751164C423.640095 447.824031 475.562482 395.903184 539.383558 395.903184z" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:none;stroke:#3d4757}.st1{fill:#3d4757}</style><g id="_x30_1-文字_x2F_02插入_x2F_分页-16px"><path id="矩形" class="st0" d="M5.5 2.5h7c.6 0 1 .4 1 1v3c0 .6-.4 1-1 1h-7c-.6 0-1-.4-1-1v-3c0-.6.4-1 1-1z"/><path id="三角形" class="st1" d="M4 8.5L1 6v5z"/><path id="合并形状" class="st0" d="M10.8 9.5H5c-.3 0-.5.2-.5.5v4c0 .3.2.5.5.5h8c.3 0 .5-.2.5-.5v-1.8l-2.7-2.7z"/><path id="矩形_1_" class="st1" d="M10 10h1v3h-1z"/><path id="矩形-copy-2" class="st1" d="M10 12h3v1h-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 635 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g transform="translate(2 1)"><rect stroke="#3D4757" x=".5" y=".5" width="11" height="8" rx="1"/><path fill="#3D4757" fill-rule="nonzero" d="M2 3h4v1H2zm0 2h8v1H2z"/></g><g fill="#3D4757" fill-rule="nonzero"><path d="M14 15h-1v-5H3v5H2V9h12v6z"/><path d="M4 12h4v1H4zm0 2h8v1H4z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 413 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M7 7V3h2v4h4v2H9v4H7V9H3V7h4z" fill="#636363" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 165 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M3 7h10v2H3z" fill="#636363" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 148 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M7.5 2v2.5H4a.5.5 0 00-.5.5v2a.5.5 0 00.5.5h9a.5.5 0 00.5-.5V5a.5.5 0 00-.5-.5H9.5V2a.5.5 0 00-.5-.5H8a.5.5 0 00-.5.5z" stroke="#3D4757"/><path fill="#3D4757" d="M13 7h1v4h-1z"/><path d="M11 13a2 2 0 002-2V8.764A3 3 0 118.764 13H11z" fill="#3D4757"/><path fill="#3D4757" d="M1 13h10v1H1z"/><path d="M1 13a2 2 0 002-2V8.764A3 3 0 011 14v-1z" fill="#3D4757"/><path fill="#3D4757" d="M3 7h1v4H3z"/></g></svg>

After

Width:  |  Height:  |  Size: 532 B

View File

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_1-文字_x2F_04页面_x2F_纸张方向-16px"><g id="编组" transform="translate(1 1)"><path id="合并形状" class="st0" d="M14 6v7c0 .6-.4 1-1 1H4c-.6 0-1-.4-1-1V6c0-.6.4-1 1-1h9c.6 0 1 .4 1 1zm-1 0H4v7h9V6z"/><path id="路径" class="st0" d="M9 4H8V1H1v9h1v1H1c-.6 0-1-.4-1-1V1c0-.6.4-1 1-1h7c.6 0 1 .4 1 1v3z"/><path id="矩形" class="st0" d="M11 7h1v5h-1z"/><path id="矩形-copy-2" class="st0" d="M2 2h5v1H2z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 599 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect stroke="#3D4757" x="2.5" y="1.5" width="11" height="13" rx="1"/><path fill="#3D4757" fill-rule="nonzero" d="M3 4h10v1H3zm0 7h10v1H3z"/><path fill="#3D4757" fill-rule="nonzero" d="M5 14V2h1v12zm5 0V2h1v12z"/></g></svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path d="M10.793 1.5L13.5 4.207V14L3 14.5 2.5 2l8.293-.5z" stroke="#3D4757"/><path fill="#3D4757" d="M10 2h1v3h-1z"/><path fill="#3D4757" d="M10 4h3v1h-3z"/></g><path d="M7 3v1H5v2H4V3h3z" fill="#3D4757"/><path fill="#3D4757" d="M5.169 3.43l6.62 8.784-.799.602-6.62-8.785z"/><path d="M9 13v-1h2v-2h1v3H9z" fill="#3D4757"/></g></svg>

After

Width:  |  Height:  |  Size: 453 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="1715223357853" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1284" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M668.7 150.3c-14.3 0-25.9-11.6-25.9-25.9s11.6-25.9 25.9-25.9h151.4c59.6 0 107.9 48.2 107.9 107.8v151.4c0 14.3-11.6 25.9-25.9 25.9s-25.9-11.6-25.9-25.9V206.3c0-30.9-25-56-56-56H668.7z m207.4 506.3c-0.6-14.3 10.4-26.4 24.7-27.1 14.3-0.6 26.4 10.4 27.1 24.7V820.3c0 59.6-48.2 107.8-107.8 107.8H717.8c-14.3 0-25.9-11.6-25.9-25.9s11.6-25.9 25.9-25.9h102.4c30.9 0 56-25 56-56V656.6zM369.8 876.4c14.3 0 25.9 11.6 25.9 25.9s-11.6 25.9-25.9 25.9H206c-59.5 0-107.8-48.3-107.8-107.8V668.9c0-14.3 11.6-25.9 25.9-25.9s25.9 11.6 25.9 25.9v151.4c0 30.9 25 56 56 56h163.8v0.1zM150.1 357.8c0 14.3-11.6 25.9-25.9 25.9s-25.9-11.6-25.9-25.9V206.3c0-59.7 48.2-107.9 107.8-107.9h151.4c14.3 0 25.9 11.6 25.9 25.9s-11.6 25.9-25.9 25.9H206.1c-30.9 0-56 25-56 56v151.6z m363 285.2c88.8 0 171.5-46.1 249.8-142.6-78.3-96.5-161-142.6-249.8-142.6-88.8 0-171.5 46.2-249.9 142.6C341.6 596.8 424.3 643 513.1 643z m0 51.8c-105.8 0-202.6-53.9-290.1-161.8-15.4-19-15.4-46.3 0-65.3 87.5-107.9 184.3-161.8 290.1-161.8 105.8 0 202.5 53.9 290.1 161.8 15.4 19 15.4 46.3 0 65.3-87.6 107.9-184.3 161.8-290.1 161.8z" p-id="1285"></path><path d="M513.1 565.2c35.8 0 64.8-29 64.8-64.8s-29-64.8-64.8-64.8-64.8 29-64.8 64.8 29 64.8 64.8 64.8z m0 51.9c-64.4 0-116.7-52.2-116.7-116.7s52.2-116.7 116.7-116.7 116.7 52.2 116.7 116.7-52.2 116.7-116.7 116.7z" p-id="1286"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M12 4h-1V2H5v2H4V2a1 1 0 011-1h6a1 1 0 011 1v2zm0 5v4a1 1 0 01-1 1H5a1 1 0 01-1-1V9h1v4h6V9h1z"/><path d="M12 12v-1h2V5H2v6h2v1H2a1 1 0 01-1-1V5a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1h-2z"/><path d="M3 8h10v1H3zm8-2h2v1h-2z"/></g></svg>

After

Width:  |  Height:  |  Size: 369 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="1729582027290" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4641" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M48.384 45.376l412.224 0 0 84.992-412.224 0 0-84.992ZM565.44 45.376l238.656 0 0 84.992-238.656 0 0-84.992ZM891.584 45.376l86.72 0 0 86.72-86.72 0 0-86.72ZM48.384 374.4l412.224 0 0 85.056-412.224 0 0-85.056ZM45.696 45.376l84.992 0 0 412.224-84.992 0 0-412.224ZM375.616 45.376l84.992 0 0 412.224-84.992 0 0-412.224ZM189.376 200.832l115.712 0 0 115.712-115.712 0 0-115.712ZM565.44 130.368l82.752 0 0 87.232-82.752 0 0-87.232ZM804.032 130.368l90.752 0 0 160.512-90.752 0 0-160.512ZM891.584 217.6l86.72 0 0 151.872-86.72 0 0-151.872ZM651.328 282.688l152.768 0 0 109.312-152.768 0 0-109.312ZM736.832 369.408l155.392 0 0 88.128-155.392 0 0-88.128ZM565.952 372.16l85.376 0 0 85.376-85.376 0 0-85.376ZM45.696 542.784l84.992 0 0 192.576-84.992 0 0-192.576ZM130.88 717.248l91.968 0 0 92.032-91.968 0 0-92.032ZM45.696 805.632l85.184 0 0 173.056-85.184 0 0-173.056ZM217.664 542.784l261.696 0 0 105.728-261.696 0 0-105.728ZM281.344 639.104l109.184 0 0 100.48-109.184 0 0-100.48ZM370.176 717.248l109.184 0 0 174.848-109.184 0 0-174.848ZM285.44 805.632l105.088 0 0 173.056-105.088 0 0-173.056ZM197.952 869.12l102.016 0 0 109.568-102.016 0 0-109.568ZM629.184 542.784l195.264 0 0 174.464-195.264 0 0-174.464ZM871.168 542.784l107.136 0 0 107.136-107.136 0 0-107.136ZM545.088 630.016l107.136 0 0 194.304-107.136 0 0-194.304ZM716.672 692.8l107.776 0 0 111.872-107.776 0 0-111.872ZM545.088 869.12l107.136 0 0 109.568-107.136 0 0-109.568ZM802.816 892.096l175.488 0 0 86.592-175.488 0 0-86.592ZM890.56 804.672l87.744 0 0 105.728-87.744 0 0-105.728Z" fill="#272636" p-id="4642"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M3 14v-3a4 4 0 014-4h3V6H7a5 5 0 00-5 5v3h1zm7.016-11.282v7.543l4.29-3.73z" fill="#3D4757"/></svg>

After

Width:  |  Height:  |  Size: 190 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M3 3h1v4H3z"/><path d="M3 3h4v1H3zm10 10h-1V9h1z"/><path d="M13 13H9v-1h4z"/></g></svg>

After

Width:  |  Height:  |  Size: 217 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 13h12v1H2v-1zm4-3h8v1H6v-1zM2 7h12v1H2V7zm0-6h12v1H2V1zm4 3h8v1H6V4z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 207 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M2 14h12v1H2v-1zm7-5h5v1H9V9zm0-3h5v1H9V6zM2 1h12v1H2V1zm2.5 3L7 7H2zm0 8L7 9H2z" fill="#3D4757" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 216 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="1714708781943" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6105" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M701.3 316.1c15.9 0 28.8-13.3 28.8-29.8v-57.6c0-16.4-12.9-29.8-28.8-29.8s-28.8 13.3-28.8 29.8v57.6c0 16.5 12.9 29.8 28.8 29.8z" fill="#00D8A0" p-id="6106"></path><path d="M820.6 960H203.8c-77.2 0-140-62.8-140-140V203.2c0-77.2 62.8-140 140-140h616.7c77.2 0 140 62.8 140 140v616.7c0.1 77.3-62.7 140.1-139.9 140.1zM203.8 103.2c-55.2 0-100 44.9-100 100v616.7c0 55.2 44.9 100 100 100h616.7c55.2 0 100-44.9 100-100V203.2c0-55.2-44.9-100-100-100H203.8z" fill="#1A1A1A" p-id="6107"></path><path d="M764.7 442.1H261.9c-39 0-70.7-31.7-70.7-70.7V79.9h40v291.5c0 16.9 13.8 30.7 30.7 30.7h502.8c16.9 0 30.7-13.8 30.7-30.7V79.9h40v291.5c0 39-31.7 70.7-70.7 70.7z" fill="#1A1A1A" p-id="6108"></path></svg>

After

Width:  |  Height:  |  Size: 1023 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle stroke="#3D4757" cx="6" cy="6" r="4.5"/><path d="M10.061 10.968L8.707 9.414l.707-.707 1.514 1.457.435-.404 2.632 2.462a1.154 1.154 0 01.05 1.635 1.184 1.184 0 01-1.655.064l-2.788-2.527.46-.426z" fill="#3D4757" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M3.9 2.8l2.01 2.506c.2.25.58.25.78 0L8.7 2.799l2.01 2.507c.2.25.58.25.78 0l2.4-2.993-.78-.626-2.01 2.507-2.01-2.507a.5.5 0 00-.78 0L6.3 4.194 4.29 1.687a.5.5 0 00-.78 0l-2.4 3 .78.625L3.9 2.8zM1 8h13v1H1zm0 4h3v1H1zm5 0h3v1H6zm5 0h3v1h-3z" fill="#3D4757"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M6.215 3.29H7.64L11.855 14H10.52l-1.14-3H4.46l-1.14 3H2L6.215 3.29zM4.85 9.965h4.14L6.965 4.61h-.06L4.85 9.965z"/><path d="M12 4V2h1v2h2v1h-2v2h-1V5h-2V4h2z" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 319 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path fill-rule="nonzero" d="M11 4h4v1h-4z"/><path d="M6.215 3.29H7.64L11.855 14H10.52l-1.14-3H4.46l-1.14 3H2L6.215 3.29zM4.85 9.965h4.14L6.965 4.61h-.06L4.85 9.965z"/></g></svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M10.42 7.903H6.692a9.182 9.182 0 01-.41-.172 5.54 5.54 0 01-.814-.447 2.955 2.955 0 01-.655-.595 2.728 2.728 0 01-.44-.777 2.877 2.877 0 01-.162-1.006c0-.472.094-.888.282-1.25.188-.36.453-.663.793-.907s.747-.43 1.22-.558A5.97 5.97 0 018.063 2c.504 0 .95.049 1.337.147.387.097.725.23 1.013.398.287.169.53.365.73.59a3.337 3.337 0 01.772 1.486c.03.13.054.255.073.379h-1.276a2.393 2.393 0 00-.22-.615 2.315 2.315 0 00-.59-.724 2.467 2.467 0 00-.834-.44 3.376 3.376 0 00-1.005-.146 4.69 4.69 0 00-.958.097 2.77 2.77 0 00-.839.314 1.765 1.765 0 00-.597.566c-.152.233-.229.518-.229.854 0 .348.086.642.258.884.171.241.401.449.689.622.287.174.615.323.983.448s.749.247 1.142.367c.31.097.62.196.934.297a8.439 8.439 0 01.973.38zm1.376 1c.175.217.315.466.418.746.105.285.158.612.158.98 0 .554-.104 1.041-.312 1.462-.207.42-.496.772-.867 1.054-.37.282-.81.495-1.32.64A6.12 6.12 0 018.205 14c-.543 0-1.071-.09-1.586-.273a4.44 4.44 0 01-1.374-.773 3.873 3.873 0 01-.97-1.217 3.695 3.695 0 01-.395-1.612h1.27c.028.407.122.78.282 1.12a2.835 2.835 0 001.581 1.465c.363.138.76.207 1.192.207.387 0 .758-.042 1.112-.126a2.85 2.85 0 00.938-.399 2.01 2.01 0 00.647-.708c.16-.29.241-.642.241-1.054 0-.337-.087-.623-.261-.86a2.333 2.333 0 00-.69-.61 4.651 4.651 0 00-.495-.257h2.099z"/><path d="M3 7h10v1H3z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_0-公共_x2F_02工具栏_x2F_subscript"><path id="_x32_" class="st0" d="M15 13.3v.7h-3c0-.3.1-.5.3-.8.2-.2.5-.6 1-1 .4-.3.6-.5.7-.7.1-.2.2-.3.2-.5s-.1-.3-.2-.4c-.1-.1-.2-.1-.4-.1s-.3 0-.4.1-.2.4-.2.7l-.9-.1c.1-.4.2-.7.5-.9.3-.2.6-.3 1-.3s.8.1 1 .3c.3.2.4.5.4.8 0 .2 0 .4-.1.5-.1.2-.2.3-.3.5-.1.1-.3.3-.6.5s-.4.4-.5.4c-.1.1-.1.2-.2.3H15z"/><path id="合并形状" class="st0" d="M7.2 8.5l4 5.5H9.8L6.5 9.4 3.2 14H1.8l4-5.5-4-5.5h1.4l3.3 4.6L9.8 3h1.3L7.2 8.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@ -0,0 +1 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_0-公共_x2F_02工具栏_x2F_superscript"><path id="_x32_" class="st0" d="M15 6.3V7h-3c0-.3.1-.5.3-.8.2-.2.5-.6 1-1 .3-.2.6-.5.7-.6.1-.2.2-.3.2-.5s-.1-.3-.2-.4c-.1-.1-.2-.1-.4-.1s-.3 0-.4.1-.2.3-.2.6l-.9-.1c.1-.4.2-.7.5-.9s.6-.3 1-.3.8.1 1 .3c.3.2.4.5.4.8 0 .2 0 .4-.1.5s-.2.4-.4.5c-.1.1-.3.3-.6.5s-.4.4-.4.5c-.1.1-.1.2-.2.3l1.7-.1z"/><path id="合并形状" class="st0" d="M7.4 8.5l4 5.5H10L6.7 9.4 3.4 14H2l4-5.5L2 3h1.4l3.3 4.6L10 3h1.3L7.4 8.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 627 B

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="13px" height="14px" viewBox="0 0 13 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 10</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="iPhone-SE备份" transform="translate(-230.000000, -215.000000)" fill="currentColor">
<g id="编组-10" transform="translate(230.000000, 215.000000)">
<rect id="矩形" x="1" y="4" width="11" height="1"></rect>
<rect id="矩形备份-168" x="1" y="9" width="11" height="1"></rect>
<rect id="矩形" x="3" y="1" width="1" height="12"></rect>
<path d="M13,0 L13,14 L0,14 L0,0 L13,0 Z M12,1 L1,1 L1,13 L12,13 L12,1 Z" id="形状结合"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="#3D4757" fill-rule="evenodd"><path d="M14 1a1 1 0 011 1v12a1 1 0 01-1 1H2a1 1 0 01-1-1V2a1 1 0 011-1h12zm0 1H2v12h12V2z" fill-rule="nonzero"/><path fill-rule="nonzero" d="M4 11h8v1H4zm0-3h4v1H4zm0-3h2v1H4z"/><path d="M9 5h1v4H9z"/><path d="M7 5h5v1H7z"/></g></svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M5 2v6a3 3 0 106 0V2h1v6a4 4 0 11-8 0V2h1zM4 13h8v1H4z" fill="#3D4757"/></svg>

After

Width:  |  Height:  |  Size: 170 B

View File

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M6 2.763v7.544l-4.29-3.73zM13 14v-3a4 4 0 00-4-4H6V6h3a5 5 0 015 5v3h-1z" fill="#3D4757"/></svg>

After

Width:  |  Height:  |  Size: 188 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="1727524019529" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1348" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M393.2 701.7c-8.2 0-16.4-3.1-22.6-9.4-12.5-12.5-12.5-32.8 0-45.3l115.3-115.3c14-14 36.9-14 50.9 0L652.1 647c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-95.5-95.5-95.5 95.5c-6.2 6.2-14.4 9.4-22.6 9.4z" fill="" p-id="1349"></path><path d="M511.3 921.1c-17.7 0-32-14.3-32-32v-276c0-17.7 14.3-32 32-32s32 14.3 32 32v276c0 17.6-14.3 32-32 32z" fill="" p-id="1350"></path><path d="M732.7 784.9c-17.7 0-32-14.3-32-32s14.3-32 32-32c90.6 0 164.3-73.7 164.3-164.3 0-82.9-61.9-153-144-163.1l-22.7-2.8-4.7-22.4c-20.8-99.9-110.1-172.4-212.4-172.4-102.2 0-191.5 72.5-212.4 172.4l-4.7 22.4-22.7 2.8c-82.1 10.1-144 80.2-144 163.1 0 90.6 73.7 164.3 164.3 164.3 17.7 0 32 14.3 32 32s-14.3 32-32 32c-61 0-118.3-23.8-161.5-66.9-43.1-43.1-66.9-100.5-66.9-161.5 0-107.6 75.2-199.7 178.2-222.8 15.8-53.8 47.7-102.2 91.3-138.1 50.1-41.2 113.4-63.9 178.3-63.9s128.3 22.7 178.3 63.9c43.6 35.9 75.5 84.3 91.3 138.1C885.9 356.8 961 448.9 961 556.5c0 61-23.8 118.3-66.9 161.5-43.1 43.1-100.4 66.9-161.4 66.9z" fill="" p-id="1351"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect stroke="#3D4757" x="2.5" y="1.5" width="11" height="13" rx="1"/><path d="M4 7h8v1H4V7zm0-2h8v1H4V5zm0 6h8v1H4v-1zm0-2h8v1H4V9z" fill="#909AA9" fill-rule="nonzero"/><path d="M6 5.623c.36-.354.936-.638 2-.604 1.785.057 2-.008 2 1.894v4.098H8.984v-.993c1.046.662.885.993-.484.993-1.792 0-2.5-1.068-2.5-2 0-1.427.447-2 2.5-2h.484c0-1-.006-1.007-.984-1-.978.008-1.419.437-2 .437v-.825zm2.5 2.389c-.899 0-1.5-.232-1.5 1 0 1.31.438 1 1.5 1 .617 0 .484-.665.484-1.848v-.152H8.5z" fill="#3D4757"/></g></svg>

After

Width:  |  Height:  |  Size: 602 B

View File

@ -1,5 +1,5 @@
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
import { ref, getCurrentInstance, unref } from 'vue';
import { ref, getCurrentInstance, unref, onUnmounted } from 'vue';
import { isProdMode } from '/@/utils/env';
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
@ -10,9 +10,14 @@ export function useDescription(props?: Partial<DescriptionProps>): UseDescReturn
const loaded = ref(false);
function register(instance: DescInstance) {
if (unref(loaded) && isProdMode()) {
return;
}
// update-begin--author:liaozhiyang---date:20251223---for:【pull/9125】在抽屉中配置destroy-on-close再次打开未正确渲染
isProdMode() &&
onUnmounted(() => {
desc.value = null;
loaded.value = false;
});
if (unref(loaded) && isProdMode() && instance === unref(desc)) return;
// update-end--author:liaozhiyang---date:20251223---for:【pull/9125】在抽屉中配置destroy-on-close再次打开未正确渲染
desc.value = instance;
props && instance.setDescProps(props);
loaded.value = true;

View File

@ -17,6 +17,8 @@
type: propTypes.string.def(JInputTypeEnum.JINPUT_QUERY_LIKE),
placeholder: propTypes.string.def(''),
trim: propTypes.bool.def(false),
class: propTypes.string,
style: propTypes.object,
},
emits: ['change', 'update:value'],
setup(props, { emit }) {

View File

@ -43,7 +43,9 @@
<template #notFoundContent>
<a-spin v-if="loading" size="small" />
</template>
<a-select-option v-for="d in options" :key="d?.value" :value="d?.value">{{ d?.text }}</a-select-option>
<a-select-option v-for="d in options" :key="d?.value" :value="d?.value">
<span :class="[useDicColor && d.color ? 'colorText' : '']" :style="{ backgroundColor: `${useDicColor && d.color}` }">{{ d?.text || d?.label }}</span>
</a-select-option>
</a-select>
</template>
@ -91,6 +93,10 @@
type: Boolean,
default: false
},
useDicColor: {
type: Boolean,
default: false,
},
},
emits: ['change', 'update:value'],
setup(props, { emit, refs }) {
@ -534,4 +540,15 @@
});
</script>
<style scoped></style>
<style scoped>
.colorText {
display: inline-block;
height: 20px;
line-height: 20px;
padding: 0 6px;
border-radius: 8px;
background-color: red;
color: #fff;
font-size: 12px;
}
</style>

View File

@ -10,9 +10,11 @@
allowClear
:getPopupContainer="getParentContainer"
>
<a-select-option v-for="(item, index) in dictOptions" :key="index" :getPopupContainer="getParentContainer" :value="item.value">
<span :class="[useDicColor && item.color ? 'colorText' : '']" :style="{ backgroundColor: `${useDicColor && item.color}` }">{{ item.text || item.label }}</span>
</a-select-option>
<template v-for="item of getOptions" :key="item.key">
<a-select-option :value="item.value" :getPopupContainer="getParentContainer">
<span :class="item.class" :style="item.style">{{ item.text }}</span>
</a-select-option>
</template>
</a-select>
</template>
<script lang="ts">
@ -21,10 +23,8 @@
import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { getDictItems } from '/@/api/common/api';
import { useMessage } from '/@/hooks/web/useMessage';
import { setPopContainer } from '/@/utils';
const { createMessage, createErrorModal } = useMessage();
export default defineComponent({
name: 'JSelectMultiple',
components: {},
@ -83,6 +83,26 @@
const attrs = useAttrs();
const [state, , , formItemContext] = useRuleFormItem(props, 'value', 'change', emitData);
// 处理下拉选项
const getOptions = computed(() => {
if (!Array.isArray(dictOptions.value)) {
return [];
}
return dictOptions.value.map((item, index) => {
const {useDicColor} = props;
const text = item.text || item.label || item.label || '';
const key = item.value + '_' + text + '_' + index;
return {
key: key,
text: text,
value: item.value,
color: item.color,
class: [useDicColor && item.color ? 'colorText' : ''],
style: {backgroundColor: `${useDicColor && item.color}`},
};
});
});
onMounted(() => {
if (props.dictCode) {
loadDictOptions();
@ -172,6 +192,7 @@
return {
state,
attrs,
getOptions,
dictOptions,
onChange,
arrayValue,

View File

@ -243,6 +243,10 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
if (props.izOnlySelectDepartPost) {
setCompanyDepartCheckable(record);
}
//是否缩写departNameAbbr
if (props.izShowDepartNameAbbr) {
record = getDepartAbbrData(record);
}
if (!props.serverTreeData) {
//前端处理数据为tree结构
record = listToTree(record, props, startPid);
@ -289,6 +293,17 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
const companyData = record.filter(item=>item.orgCategory && ['1','4'].includes(item.orgCategory));
return companyData
}
/**
* 获取到公司/子公司数据
* @param record
*/
function getDepartAbbrData(record){
const departAbbrData = record;
departAbbrData.forEach(item=>{
item.title = item.departNameAbbr || item.title;
})
return departAbbrData
}
/**
* 异步加载时检测是否含有下级节点
* @param pid 父节点

View File

@ -87,4 +87,6 @@ export const treeProps = {
multiple: propTypes.bool.def(true),
// 是否只选择岗位
izOnlySelectDepartPost: propTypes.bool.def(false),
// 是否显示部门简称
izShowDepartNameAbbr: propTypes.bool.def(false),
};

View File

@ -30,7 +30,8 @@
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
const option = reactive({
title: {
text: '基础雷达图',
text: '',
// text: '基础雷达图',
},
legend: {
data: ['文综'],
@ -79,7 +80,9 @@
//data数据
data.push(obj);
});
option.radar.axisName = indicator;
option.legend.data = typeArr;
// option.radar.axisName = indicator;
option.radar.indicator = indicator;
option.series[0]['data'] = data;
setOptions(option);
}

View File

@ -2,7 +2,7 @@
<ConfigProvider :locale="getAntdLocale">
<Modal v-bind="getProps">
<Spin :spinning="loading">
<div style="padding: 20px;">
<div class="j-prompt-content-body">
<div v-html="options.content" style="margin-bottom: 8px"></div>
<BasicForm @register="registerForm">
<template #customInput="{ model, field }">
@ -116,7 +116,7 @@
function onChange() {
validate()
}
/** 提交表单 */
async function onSubmit() {
try {
@ -158,3 +158,19 @@
},
});
</script>
<style lang="less">
.j-prompt-content-body {
padding: 20px;
.ant-form-item-control-input-content {
& > div {
width: 100%;
& > .j-form-item-middleware {
width: 100%;
}
}
}
}
</style>

View File

@ -3,7 +3,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import { isString } from '/@/utils/is';
import { JVxeComponent, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
@ -15,12 +15,41 @@
setup(props: JVxeComponent.Props) {
const { innerValue, cellProps, handleChangeCommon, handleBlurCommon } = useJVxeComponent(props);
// 是否是数字类型输入框
const isNumberType = props.type === JVxeTypes.inputNumber;
/**
* 计算数字类型的限制属性
* 包含最大值、最小值和精度配置,供输入组件和格式化逻辑使用
*/
const numberProps = computed<{
max?: number,
min?: number,
precision?: number,
}>(() => {
const {max, min, precision} = cellProps.value as Recordable;
const nProps: Recordable = {};
// 最大值
if (typeof max === 'number') {
nProps.max = max;
}
// 最小值
if (typeof min === 'number') {
nProps.min = min;
}
// 数值精度,保留小数位数
if (typeof precision === 'number') {
nProps.precision = precision;
}
return nProps;
});
/** 处理change事件 */
function handleChange(event) {
let { target } = event;
let { value, selectionStart } = target;
let change = true;
if (props.type === JVxeTypes.inputNumber) {
if (isNumberType) {
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
if (!NumberRegExp.test(value) && value !== '' && value !== '-') {
change = false;
@ -51,20 +80,53 @@
function handleBlur(event) {
let { target } = event;
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
if (props.type === JVxeTypes.inputNumber) {
if (isNumberType) {
if (!NumberRegExp.test(target.value)) {
target.value = '';
} else {
target.value = Number.parseFloat(target.value);
const parsedValue = Number.parseFloat(target.value);
const clampedValue = clampNumber(parsedValue);
target.value = applyPrecision(clampedValue);
}
}
handleChangeCommon(target.value, true);
handleBlurCommon(target.value);
}
/**
* 依据最小值和最大值限制数值
* @param value 需要裁剪的数值
*/
function clampNumber(value: number): number {
let result = value;
const { max, min } = numberProps.value;
// 应用最小值限制
if (typeof min === 'number') {
result = Math.max(min, result);
}
// 应用最大值限制
if (typeof max === 'number') {
result = Math.min(max, result);
}
return result;
}
/**
* 按配置精度格式化数值
* @param value 待格式化的数值
*/
function applyPrecision(value: number): number {
const { precision } = numberProps.value;
if (typeof precision === 'number') {
return Number(value.toFixed(precision));
}
return value;
}
return {
innerValue,
cellProps,
isNumberType,
handleChange,
handleBlur,
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,75 @@
<!-- 高亮颜色 -->
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="设置高亮颜色" @ok="handleSubmit" :width="500">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
const formSchema: FormSchema[] = [
{
label: '高亮颜色',
field: 'highlightColor',
component: 'Select',
required: true,
componentProps: {
options: [
{ value: '#ffffff00', label: '无色' },
{ value: '#FFFF00', label: '黄色' },
{ value: '#00FF00', label: '绿色' },
{ value: '#00FFFF', label: '青色' },
{ value: '#FF00FF', label: '粉红色' },
{ value: '#0000FF', label: '蓝色' },
{ value: '#FF0000', label: '红色' },
{ value: '#000080', label: '深蓝色' },
{ value: '#008080', label: '深青色' },
{ value: '#008000', label: '深绿色' },
{ value: '#800080', label: '紫色' },
{ value: '#800000', label: '深红色' },
{ value: '#808000', label: '深黄色' },
{ value: '#808080', label: '深灰色' },
{ value: '#C0C0C0', label: '浅灰色' },
{ value: '#000000', label: '黑色' },
],
getPopupContainer: ()=> document.body
},
},
];
// Emits声明
const emit = defineEmits(['register', 'ok']);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
schemas: formSchema,
showActionButtonGroup: false,
wrapperCol: { span: 24 },
labelCol: { span: 24 },
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false });
//表单赋值
await setFieldsValue({
...data,
});
});
//表单提交事件
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
//关闭弹窗
closeModal();
//刷新列表
emit('ok', values);
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style scoped lang="less"></style>

View File

@ -0,0 +1,62 @@
<!--超链接-->
<template>
<BasicDrawer @register="registerBaseDrawer" title="超链接" width="600" showFooter @ok="handleOk">
<BasicForm @register="registerForm" />
</BasicDrawer>
</template>
<script setup lang="ts">
import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
let emit = defineEmits(['register', 'ok']);
const schemas: FormSchema[] = [
{
label: '链接名称',
field: 'name',
component: 'Input',
rules: [{ required: true, message: '请填写链接名称' }],
},
{
label: '链接地址',
field: 'url',
component: 'Input',
componentProps: {
min: 0,
},
rules: [{ required: true, message: '请填写链接地址' }],
},
];
const [registerForm, { setFieldsValue, resetFields, validate, clearValidate }] = useForm({
schemas,
showActionButtonGroup: false,
wrapperCol: { span: 24 },
labelCol: { span: 24 },
});
//注册Drawer
const [registerBaseDrawer, { closeDrawer }] = useDrawerInner((data) => {
resetFields();
setFieldsValue({
...data,
});
clearValidate();
});
/**
* 确定事件
*/
async function handleOk() {
let values = await validate();
emit('ok', values);
closeDrawer();
}
</script>
<style scoped lang="less">
:deep(.ant-input-number) {
width: 100%;
}
</style>

View File

@ -0,0 +1,82 @@
<!--边距页面-->
<template>
<BasicDrawer @register="registerBaseDrawer" title="设置边距" width="600" showFooter @ok="handleOk">
<BasicForm @register="registerForm" />
</BasicDrawer>
</template>
<script setup lang="ts">
import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
let emit = defineEmits(['register','ok']);
const schemas: FormSchema[] = [
{
label:'上边距',
field: 'marginTop',
component: 'InputNumber',
componentProps: {
min: 0,
},
rules: [{ required: true, message: '请填写上边距' }],
},
{
label:'下边距',
field: 'marginBottom',
component: 'InputNumber',
componentProps: {
min: 0,
},
rules: [{ required: true, message: '请填写下边距' }],
},
{
label:'左边距',
field: 'marginLeft',
component: 'InputNumber',
componentProps: {
min: 0,
},
rules: [{ required: true, message: '请填写左边距' }],
},
{
label:'右边距',
field: 'marginRight',
component: 'InputNumber',
componentProps: {
min: 0,
},
rules: [{ required: true, message: '请填写右边距' }],
},
];
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
schemas,
showActionButtonGroup: false,
wrapperCol: { span: 24 },
labelCol: { span: 24 },
});
//注册Drawer
const [registerBaseDrawer, { closeDrawer }] = useDrawerInner((data) => {
resetFields();
setFieldsValue({
...data,
});
});
/**
* 确定事件
*/
async function handleOk() {
let values = await validate();
emit('ok', values);
closeDrawer();
}
</script>
<style scoped lang="less">
:deep(.ant-input-number){
width: 100%;
}
</style>

View File

@ -0,0 +1,108 @@
<!--水印页面-->
<template>
<BasicDrawer @register="registerBaseDrawer" title="设置水印" width="600" showFooter @ok="handleOk">
<BasicForm @register="registerForm" />
</BasicDrawer>
</template>
<script setup lang="ts">
import { BasicDrawer, useDrawerInner } from '@/components/Drawer';
import { BasicForm, FormSchema, useForm } from '@/components/Form';
let emit = defineEmits(['register', 'ok']);
const schemas: FormSchema[] = [
{
label: '内容',
field: 'data',
component: 'Input',
rules: [{ required: true, message: '请填写内容' }],
},
{
label: '颜色',
field: 'color',
component: 'Input',
componentProps: {
type: 'color',
},
rules: [{ required: true, message: '请选择颜色' }],
defaultValue: '#AEB5C0',
},
{
label: '字体大小',
field: 'size',
component: 'InputNumber',
componentProps: {
min: 10,
},
defaultValue: 50,
rules: [{ required: true, message: '请填写字体大小' }],
},
{
label: '透明度',
field: 'opacity',
component: 'Slider',
componentProps: {
min: 0.1,
max: 1,
step: 0.1,
},
defaultValue: 0.3,
rules: [{ required: true, message: '请填写透明度' }],
},
{
label: '重复',
field: 'repeat',
component: 'Select',
componentProps: {
options: [
{ label: '不重复', value: '0' },
{ label: '重复', value: '1' },
],
},
defaultValue: '0',
},
{
label: '水平间隔',
field: 'horizontalGap',
component: 'InputNumber',
defaultValue: 10,
},
{
label: '垂直间隔',
field: 'verticalGap',
component: 'InputNumber',
defaultValue: 10,
},
];
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
schemas,
showActionButtonGroup: false,
wrapperCol: { span: 24 },
labelCol: { span: 24 },
});
//注册Drawer
const [registerBaseDrawer, { closeDrawer }] = useDrawerInner((data) => {
resetFields();
setFieldsValue({
...data,
});
});
/**
* 确定事件
*/
async function handleOk() {
let values = await validate();
emit('ok', values);
closeDrawer();
}
</script>
<style scoped lang="less">
:deep(.ant-input-number) {
width: 100%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,9 @@ import Big from 'big.js';
import { Modal } from "ant-design-vue";
import { defHttp } from "@/utils/http/axios";
import { useI18n } from "@/hooks/web/useI18n";
import {$electron} from "@/electron";
import {router} from "@/router";
import {encryptByBase64} from "@/utils/cipher";
//存放部门路径的数组
const departNamePath = ref<Record<string, string>>({});
@ -38,6 +41,17 @@ export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
} catch (err) {}
return result;
};
/**
* 获取桌面端wps的文件服务访问路径
* @param fileUrl 文件路径
*/
export const getElectronFileUrl = (url) => {
let fileUrl: any = url;
if (url && $electron.isElectron()) {
fileUrl = router.resolve({path: '/onlinePreview', query: {url: encryptByBase64(getFileAccessHttpUrl(url))}}).href;
}
return fileUrl;
};
/**
* 触发 window.resize
@ -703,4 +717,65 @@ export function getDepartName(departNamePath) {
},textElements)
}
return departNamePath;
}
}
/**
* 获取文件表
* @param fileUrl
*/
export function getFileIcon(fileUrl) {
if(!fileUrl) {
return 'ant-design:file-outlined';
}
const suffix = fileUrl.substring(fileUrl.lastIndexOf('.') + 1).toLowerCase();
if(['xls','xlsx','csv'].includes(suffix)) {
return 'ant-design:file-excel-filled';
}
if(['doc','docx'].includes(suffix)) {
return 'ant-design:file-word-filled';
}
if(['pdf'].includes(suffix)) {
return 'ant-design:file-pdf-filled';
}
if(['ppt','pptx'].includes(suffix)) {
return 'ant-design:file-ppt-filled';
}
if(['txt'].includes(suffix)) {
return 'ant-design:file-text-filled';
}
if(['md'].includes(suffix)) {
return 'ant-design:file-markdown-filled';
}
return 'ant-design:file-unknown-filled';
}
/**
* 获取文件图标颜色
*
* @param fileUrl
*/
export function getFileIconColor(fileUrl) {
if(!fileUrl) {
return '#999';
}
const suffix = fileUrl.substring(fileUrl.lastIndexOf('.') + 1).toLowerCase();
if(['xls','xlsx','csv'].includes(suffix)) {
return '#52c41a';
}
if(['doc','docx'].includes(suffix)) {
return '#1890ff';
}
if(['pdf'].includes(suffix)) {
return '#ff4d4f';
}
if(['ppt','pptx'].includes(suffix)) {
return '#fa8c16';
}
if(['txt'].includes(suffix)) {
return '#666';
}
if(['md'].includes(suffix)) {
return '#000';
}
return '#999';
}

View File

@ -108,6 +108,33 @@ export function filterMultiDictText(dictOptions, text) {
return re.substring(0, re.length - 1);
}
/**
* 字典值替换文本通用方法(多选)
* @param dictOptions 字典数组
* @param val 字典值
* @return {*[]} 返回字典项原对象
*/
export function filterMultiDictObjs(dictOptions, val) {
val = val?.toString?.() ?? '';
if (!val || !dictOptions || dictOptions.length === 0) {
return [];
}
const objs = [];
const vals = val.split(',');
for (const item of vals) {
const option = dictOptions.find((option) => option && option.value === item);
if (option) {
objs.push({
value: item,
text: option.text || option.title || option.label,
color: option.color,
hasColor: !!option.color,
});
}
}
return objs;
}
/**
* 翻译字段值对应的文本
* @param children

View File

@ -75,11 +75,12 @@
import xss from 'xss';
import { options } from './XssWhiteList';
import { ref, unref } from 'vue';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import { getElectronFileUrl, getFileAccessHttpUrl } from '@/utils/common/compUtils';
import { useGlobSetting } from '@/hooks/setting';
import { encryptByBase64 } from '@/utils/cipher';
import { getToken } from '@/utils/auth';
import {defHttp} from "@/utils/http/axios";
import {$electron} from "@/electron";
const router = useRouter();
const glob = useGlobSetting();
const isUpdate = ref(true);
@ -110,7 +111,8 @@
content.value = data.record;
if(content.value.sender){
const userInfo = await defHttp.get({ url: '/sys/user/queryUserComponentData?isMultiTranslate=true', params: { username: content.value.sender } });
content.value.sender = userInfo && userInfo?.records && userInfo?.records.length>0?userInfo.records[0].realname : content.value.sender;
content.value.sender = userInfo && userInfo?.records && userInfo?.records.length>0
?userInfo.records.find((item) => item.username === content.value.sender)?.realname : content.value.sender;
}
console.log('data---------->>>', data);
if (data.record?.files && data.record?.files.length > 0) {
@ -279,6 +281,11 @@
console.log('glob.onlineUrl', glob.viewUrl);
let url = encodeURIComponent(encryptByBase64(filePath));
let previewUrl = `${glob.viewUrl}?url=` + url;
//update-begin-author:liusq---date:2025-12-16--for: JHHB-1139桌面端 文件预览统一修改
if($electron.isElectron()){
previewUrl = getElectronFileUrl(filePath);
}
//update-end-author:liusq---date:2025-12-16--for: JHHB-1139桌面端 文件预览统一修改
window.open(previewUrl, '_blank');
}
}

View File

@ -9,8 +9,11 @@ export enum Api {
delete = '/airag/app/delete',
queryById = '/airag/app/queryById',
queryBathById = '/airag/knowledge/query/batch/byId',
queryKnowledgeById = '/airag/knowledge/queryById',
queryFlowById = '/airag/flow/queryById',
queryFlowByIds = '/airag/flow/list',
promptGenerate = '/airag/app/prompt/generate',
generateMemoryByAppId = '/airag/app/prompt/generateMemoryByAppId',
}
/**
@ -29,6 +32,14 @@ export const queryKnowledgeBathById = (params) => {
return defHttp.get({ url: Api.queryBathById, params }, { isTransformResponse: false });
};
/**
* 查询知识库(单条)
* @param params
*/
export const queryKnowledgeById = (params) => {
return defHttp.get({ url: Api.queryKnowledgeById, params }, { isTransformResponse: false });
};
/**
* 根据应用id查询应用
* @param params
@ -84,6 +95,14 @@ export const queryFlowById = (params) => {
return defHttp.get({ url: Api.queryFlowById, params }, { isTransformResponse: false });
};
/**
* 根据应用id查询流程
* @param params
*/
export const queryFlowByIds = (params) => {
return defHttp.get({ url: Api.queryFlowByIds, params }, { isTransformResponse: false });
};
/**
* 应用编排
* @param params
@ -101,3 +120,21 @@ export const promptGenerate = (params) => {
}
);
};
/**
* 应用编排
* @param params
*/
export const generateMemoryByAppId = (params) => {
return defHttp.post(
{
url: Api.generateMemoryByAppId+'?variables='+ params.variables + '&memoryId='+ params.memoryId,
adapter: 'fetch',
responseType: 'stream',
timeout: 5 * 60 * 1000,
},
{
isTransformResponse: false,
}
);
};

View File

@ -57,7 +57,7 @@
</div>
<div class="header-tag">
<a-tag color="#EBF1FF" style="margin-right: 0" v-if="item.type === 'chatSimple'">
<span style="color: #3370ff">简单配置</span>
<span style="color: #3370ff">智能体</span>
</a-tag>
<a-tag color="#FDF6EC" style="margin-right: 0" v-if="item.type === 'chatFLow'">
<span style="color: #e6a343">高级编排</span>
@ -69,14 +69,16 @@
<div class="card-footer">
<a-tooltip title="演示">
<div class="card-footer-icon" @click.prevent.stop="handleViewClick(item.id)">
<Icon class="operation" icon="ant-design:youtube-outlined" size="20" color="#1F2329"></Icon>
<Icon class="operation" icon="ant-design:youtube-outlined" size="18"
color="#1F2329"></Icon>
</div>
</a-tooltip>
<template v-if="item.status !== 'release'">
<a-divider type="vertical" style="float: left" />
<a-tooltip title="删除">
<div class="card-footer-icon" @click.prevent.stop="handleDeleteClick(item)">
<Icon icon="ant-design:delete-outlined" class="operation" size="18" color="#1F2329"></Icon>
<Icon icon="ant-design:delete-outlined" class="operation" size="16"
color="#1F2329"></Icon>
</div>
</a-tooltip>
</template>
@ -84,20 +86,21 @@
<a-tooltip title="发布">
<a-dropdown class="card-footer-icon" placement="bottomRight" :trigger="['click']">
<div @click.prevent.stop>
<Icon style="position: relative;top: 1px" icon="ant-design:send-outlined" size="16" color="#1F2329"></Icon>
<Icon style="position: relative;top: 1px" icon="ant-design:send-outlined"
size="14" color="#1F2329"></Icon>
</div>
<template #overlay>
<a-menu>
<template v-if="item.status === 'enable'">
<a-menu-item key="release" @click.prevent.stop="handleSendClick(item,'release')">
<Icon icon="lineicons:rocket-5" size="16"></Icon>
<Icon icon="lineicons:rocket-5" size="14"></Icon>
发布
</a-menu-item>
<a-menu-divider/>
</template>
<template v-else-if="item.status === 'release'">
<a-menu-item key="un-release" @click.prevent.stop="handleSendClick(item,'un-release')">
<Icon icon="tabler:rocket-off" size="16"></Icon>
<Icon icon="tabler:rocket-off" size="14"></Icon>
取消发布
</a-menu-item>
<a-menu-divider/>
@ -464,8 +467,8 @@
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
.header-img {
width: 40px;
height: 40px;
width: 25px;
height: 25px;
border-radius: 0.5rem;
}
.header-text {

View File

@ -17,8 +17,8 @@
<div class="header-actions">
<div v-if="showAdvertising" class="header-advertisint">
AI客服由
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
敲敲云
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://jeecg.com/aigcIndex" target="_blank">
JEECG AI
</a>
提供
</div>
@ -35,14 +35,18 @@
:text="item.content"
:inversion="item.inversion || item.role"
:error="item.error"
:errorMsg="item.errorMsg"
:currentToolTag="currentToolTag"
:loading="item.loading"
:appData="appData"
:presetQuestion="item.presetQuestion"
:images = "item.images"
:files = "item.files"
:retrievalText="item.retrievalText"
:referenceKnowledge="item.referenceKnowledge"
:eventType="item.eventType"
:showAvatar="item.showAvatar"
:isLast="index === chatData.length -1"
@send="handleOutQuestion"
></chatMessage>
</div>
@ -90,13 +94,51 @@
</svg>
</a-button>
<div class="chat-textarea" :class="textareaActive?'textarea-active':''">
<div class="textarea-top" v-if="uploadUrlList && uploadUrlList.length>0">
<div v-for="(item,index) in uploadUrlList" class="top-image" :key="index">
<img :src="getImage(item)" @click="handlePreview(item)"/>
<div class="upload-icon" @click="deleteImage(index)">
<Icon icon="ant-design:close-outlined" size="12px"></Icon>
<!-- begin 兼容文件显示 -->
<div class="textarea-top" v-if="(uploadUrlList && uploadUrlList.length>0) || (fileList && fileList.length>0)">
<!-- 只拥有图片 -->
<template v-if="(!fileList || fileList.length===0)">
<div v-for="(item,index) in uploadUrlList" class="top-image" :key="index">
<img :src="getImage(item)" @click="handlePreview(item)"/>
<div class="upload-icon" @click="deleteImage(index)">
<Icon icon="ant-design:close-outlined" size="12px"></Icon>
</div>
</div>
</div>
</template>
<!-- 拥有文件 -->
<template v-else>
<div class="file-card-container" style="display: flex; gap: 8px; flex-wrap: wrap;">
<!-- 图片渲染 -->
<div v-for="(url, index) in uploadUrlList" :key="'img-'+index" class="file-card">
<div class="file-card-icon">
<img :src="getImage(url)" class="file-thumb" @click="handlePreview(url)"/>
</div>
<div class="file-card-info">
<div class="file-name" :title="fileInfoList[index]?.name || '图片'">{{ fileInfoList[index]?.name || '图片' }}</div>
<div class="file-size">{{ calculateFileSize(fileInfoList[index]?.size) }}</div>
</div>
<div class="file-card-close" @click="deleteImage(index)">
<Icon icon="ant-design:close-outlined" size="12px"/>
</div>
</div>
<!-- 文件渲染 -->
<template v-for="(item, index) in fileList" :key="'file-'+index">
<div class="file-card" v-if="item.status !== 'error'">
<div class="file-card-icon">
<Icon :icon="getFileIcon(item.name)" :color="getFileIconColor(item.name)" size="32" />
</div>
<div class="file-card-info">
<div class="file-name" :title="item.name">{{ item.name }}</div>
<div class="file-size">{{ calculateFileSize(item.size) }}</div>
</div>
<div class="file-card-close" @click="deleteFile(index)">
<Icon icon="ant-design:close-outlined" size="12px"/>
</div>
</div>
</template>
</div>
</template>
<!-- end 兼容文件显示 -->
</div>
<div class="textarea-bottom">
<a-textarea
@ -113,83 +155,139 @@
@paste="paste"
>
</a-textarea>
<a-button v-if="loading" type="primary" danger @click="handleStopChat" class="stopBtn">
<svg
t="1706148514627"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5214"
width="18"
height="18"
>
<path
d="M512 967.111111c-250.311111 0-455.111111-204.8-455.111111-455.111111s204.8-455.111111 455.111111-455.111111 455.111111 204.8 455.111111 455.111111-204.8 455.111111-455.111111 455.111111z m0-56.888889c221.866667 0 398.222222-176.355556 398.222222-398.222222s-176.355556-398.222222-398.222222-398.222222-398.222222 176.355556-398.222222 398.222222 176.355556 398.222222 398.222222 398.222222z"
fill="currentColor"
p-id="5215"
/>
<path d="M341.333333 341.333333h341.333334v341.333334H341.333333z" fill="currentColor" p-id="5216"/>
</svg>
</a-button>
<a-tooltip v-if="!loading && showWebSearch" :title="enableSearch ? '关闭联网搜索' : '开启联网搜索'">
<a-button
class="sendBtn webSearchBtn"
type="text"
:class="{'enabled': enableSearch}"
@click="toggleWebSearch"
>
<Icon icon="ant-design:global-outlined" :style="enableSearch ? {color: '#52c41a'} : {color: '#3d4353'}"></Icon>
</a-button>
</a-tooltip>
<a-upload
accept=".jpg,.jpeg,.png"
v-if="!loading"
name="file"
v-model:file-list="fileInfoList"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUpload"
@change="handleChange"
:multiple="true"
:action="uploadUrl"
:max-count="3"
>
<a-tooltip title="图片上传支持jpg/jpeg/png">
<div class="textarea-action-bar">
<div class="left-actions">
<a-dropdown placement="topLeft" trigger="['click']" overlayClassName="chat-upload-dropdown">
<template #overlay>
<a-menu mode="vertical">
<a-menu-item key="img">
<a-upload
accept=".jpg,.jpeg,.png"
v-if="!loading"
name="file"
v-model:file-list="fileInfoList"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUpload"
@change="handleChange"
:multiple="true"
:action="uploadUrl"
:max-count="3"
>
<div style="display: flex; align-items: center">
<Icon icon="ant-design:picture-outlined" style="margin-right:8px;color:#3d4353" />
上传图片
</div>
</a-upload>
</a-menu-item>
<a-menu-item key="file">
<a-upload
accept=".txt, .pdf, .docx, .doc, .pptx, .ppt, .xlsx, .xls, .md"
:maxCount="3"
v-if="!loading"
name="file"
v-model:file-list="fileList"
:showUploadList="false"
:headers="headers"
:beforeUpload="beforeUploadFile"
@change="handleChangeFile"
:multiple="true"
:action="uploadUrl"
>
<div style="display: flex; align-items: center">
<Icon icon="ant-design:file-add-outlined" style="margin-right:8px;color:#3d4353" />
上传文件
</div>
</a-upload>
</a-menu-item>
</a-menu>
</template>
<a-button class="sendBtn" type="text">
<Icon icon="ant-design:picture-outlined" style="color: #3d4353"></Icon>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M17.3977 3.9588C15.8361 2.39727 13.3037 2.39727 11.7422 3.9588L5.03365 10.6673C2.60612 13.0952 2.60612 17.0314 5.03365 19.4592C7.46144 21.887 11.3983 21.8875 13.8262 19.4599L20.5348 12.7514C20.8472 12.439 21.3534 12.439 21.6658 12.7514C21.9781 13.0638 21.9782 13.5701 21.6658 13.8825L14.9573 20.591C11.9046 23.6435 6.95518 23.6429 3.90255 20.5903C0.850191 17.5377 0.850191 12.5889 3.90255 9.53624L10.6111 2.82771C12.7975 0.641334 16.3424 0.641334 18.5288 2.82771C20.7149 5.01409 20.7151 8.55906 18.5288 10.7454L11.8699 17.4042C10.5369 18.7372 8.37542 18.7365 7.04241 17.4035C5.70963 16.0705 5.7095 13.9096 7.04241 12.5767L13.7012 5.91785C14.0136 5.60547 14.5199 5.60557 14.8323 5.91785C15.1447 6.23027 15.1447 6.73652 14.8323 7.04894L8.1735 13.7078C7.46543 14.4159 7.46556 15.5642 8.1735 16.2724C8.88167 16.9806 10.03 16.9806 10.7381 16.2724L17.397 9.61358C18.9584 8.05211 18.959 5.52035 17.3977 3.9588Z" fill="currentColor"></path></svg>
</a-button>
</a-tooltip>
</a-upload>
<a-divider v-if="!loading" type="vertical" style="border-color:#38374314"></a-divider>
<a-button
@click="
</a-dropdown>
<a-divider type="vertical" v-if="showThink || showWebSearch || showDraw "/>
<a-tooltip v-if="showThink" :title="enableThink ? '关闭深度思考' : '开启深度思考'">
<a-button
class="sendBtn webSearchBtn"
type="text"
:class="{ 'enabled': enableThink }"
@click="toggleThink">
<svg style="margin-right: 6px" :style="enableThink ? { color: '#06f' } : { color: '#3d4353' }" width="16" height="16" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.06428 5.93342C7.6876 5.93342 8.19304 6.43904 8.19319 7.06233C8.19319 7.68573 7.68769 8.19123 7.06428 8.19123C6.44096 8.19113 5.93537 7.68567 5.93537 7.06233C5.93552 6.43911 6.44105 5.93353 7.06428 5.93342Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M8.68147 0.963693C10.1168 0.447019 11.6266 0.374829 12.5633 1.31135C13.5 2.24805 13.4276 3.75776 12.911 5.19319C12.7126 5.74431 12.4385 6.31796 12.0965 6.89729C12.4969 7.54638 12.8141 8.19018 13.036 8.80647C13.5527 10.2419 13.625 11.7516 12.6883 12.6883C11.7516 13.625 10.2419 13.5527 8.80647 13.036C8.19019 12.8141 7.54638 12.4969 6.89729 12.0965C6.31794 12.4386 5.74432 12.7125 5.19319 12.911C3.75774 13.4276 2.24807 13.5 1.31135 12.5633C0.374829 11.6266 0.447019 10.1168 0.963693 8.68147C1.17182 8.10338 1.46318 7.50063 1.82893 6.8924C1.52179 6.35711 1.27232 5.82825 1.08869 5.31819C0.572038 3.88278 0.499683 2.37306 1.43635 1.43635C2.37304 0.499655 3.88277 0.572044 5.31819 1.08869C5.82825 1.27232 6.35712 1.5218 6.8924 1.82893C7.50063 1.46318 8.10338 1.17181 8.68147 0.963693ZM11.3572 8.01154C10.9083 8.62253 10.3901 9.22873 9.8094 9.8094C9.22874 10.3901 8.62252 10.9083 8.01154 11.3572C8.42567 11.5841 8.82867 11.7688 9.21272 11.9071C10.5455 12.3868 11.4246 12.2547 11.8397 11.8397C12.2547 11.4246 12.3869 10.5456 11.9071 9.21272C11.7688 8.82866 11.5841 8.42568 11.3572 8.01154ZM2.56526 8.02912C2.3734 8.39322 2.21492 8.74796 2.0926 9.08772C1.61288 10.4204 1.74509 11.2995 2.15998 11.7147C2.57502 12.1297 3.45412 12.2618 4.78694 11.7821C5.11053 11.6656 5.44783 11.5164 5.79377 11.3367C5.24897 10.9223 4.70919 10.4533 4.19026 9.9344C3.57575 9.31987 3.03166 8.67633 2.56526 8.02912ZM6.90705 3.2469C6.24062 3.70479 5.56457 4.26321 4.91389 4.91389C4.26322 5.56456 3.70479 6.24063 3.2469 6.90705C3.72671 7.63325 4.32774 8.37685 4.91389 8.96299C5.50003 9.54914 6.24362 10.1502 6.96983 10.6299C7.69601 10.1502 8.43961 9.54914 9.02575 8.96299C9.6119 8.37685 10.2129 7.63325 10.6927 6.90705C10.2129 6.18086 9.6119 5.43725 9.02575 4.8511C8.43961 4.26496 7.69601 3.66391 6.96983 3.18419C6.94896 3.205 6.92803 3.22593 6.90705 3.2469Z" fill="currentColor"></path></svg>
深度思考
</a-button>
</a-tooltip>
<a-tooltip v-if="showWebSearch" :title="enableSearch ? '关闭联网搜索' : '开启联网搜索'">
<a-button
class="sendBtn webSearchBtn"
type="text"
:class="{ 'enabled': enableSearch }"
@click="toggleWebSearch">
<Icon size="16" icon="ant-design:global-outlined" :style="enableSearch ? { color: '#06f' } : { color: '#3d4353' }"></Icon>
联网搜索
</a-button>
</a-tooltip>
<a-tooltip v-if="showDraw" :title="enableDraw ? '关闭图像生成' : '开启图像生成'">
<a-button
class="sendBtn webSearchBtn"
type="text"
:class="{ 'enabled': enableDraw }"
@click="handleGenerateImage">
<svg style="margin-right: 6px" :style="enableDraw ? { color: '#06f' } : { color: '#3d4353' }" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class=""><path d="M12 2.3584C14.1681 2.35841 16.1541 2.52965 17.7266 2.72754C19.9228 3.00409 21.6336 4.66074 21.9365 6.85352C22.1348 8.28975 22.2998 10.0677 22.2998 12L22.293 12.7168C22.2586 14.3712 22.1101 15.8897 21.9365 17.1465L21.9043 17.3496C21.5268 19.4411 19.8545 21.0045 17.7266 21.2725L17.1182 21.3457C15.655 21.511 13.8972 21.6416 12 21.6416L11.1963 21.6338C9.60724 21.6034 8.13686 21.4874 6.88281 21.3457L6.27441 21.2725C4.14635 21.0046 2.47428 19.4411 2.09668 17.3496L2.06445 17.1465C1.89093 15.8897 1.74239 14.3712 1.70801 12.7168L1.7002 12C1.7002 10.3092 1.82669 8.737 1.99121 7.4082L2.06445 6.85352C2.35801 4.72923 3.9719 3.10743 6.06934 2.75684L6.27441 2.72754C7.84674 2.52969 9.83219 2.35841 12 2.3584ZM11.9775 13.3496C11.4613 13.3496 10.9378 13.4818 10.2207 13.8066C9.48747 14.1388 8.61112 14.6435 7.37793 15.3555L3.76367 17.4424C4.13152 18.6436 5.16153 19.5204 6.47363 19.6855C7.99607 19.8771 9.91342 20.042 12 20.042C14.0865 20.042 16.0039 19.8771 17.5264 19.6855C18.8303 19.5214 19.8566 18.6546 20.2305 17.4648L16.5771 15.3555C15.344 14.6435 14.4676 14.1388 13.7344 13.8066C13.0173 13.4818 12.4938 13.3496 11.9775 13.3496ZM12 3.95801C9.91342 3.95802 7.99607 4.12286 6.47363 4.31445C4.98011 4.50243 3.85117 5.61215 3.64941 7.07324C3.45876 8.45412 3.2998 10.1566 3.2998 12C3.2998 13.3468 3.38385 14.6183 3.50391 15.7441L6.57715 13.9707C7.78367 13.2741 8.73894 12.7218 9.56055 12.3496C10.3981 11.9702 11.1542 11.75 11.9775 11.75C12.8008 11.75 13.557 11.9702 14.3945 12.3496C15.2161 12.7218 16.1714 13.2741 17.3779 13.9707L20.4922 15.7686C20.6134 14.6367 20.7002 13.3565 20.7002 12C20.7002 10.1566 20.5422 8.4541 20.3516 7.07324C20.1498 5.61218 19.0198 4.50249 17.5264 4.31445C16.0039 4.12287 14.0865 3.95802 12 3.95801ZM7.73438 7.0625C8.76128 7.0625 9.59375 7.89497 9.59375 8.92188C9.59375 9.94878 8.76128 10.7812 7.73438 10.7812C6.70747 10.7812 5.875 9.94878 5.875 8.92188C5.875 7.89497 6.70747 7.0625 7.73438 7.0625Z" fill="currentColor"></path></svg>
<span style="font-size: 14px">图像生成</span>
</a-button>
</a-tooltip>
</div>
<div class="right-actions">
<a-button v-if="loading" type="primary" danger @click="handleStopChat" class="stopBtn">
<svg
t="1706148514627"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5214"
width="18"
height="18"
>
<path
d="M512 967.111111c-250.311111 0-455.111111-204.8-455.111111-455.111111s204.8-455.111111 455.111111-455.111111 455.111111 204.8 455.111111 455.111111-204.8 455.111111-455.111111 455.111111z m0-56.888889c221.866667 0 398.222222-176.355556 398.222222-398.222222s-176.355556-398.222222-398.222222-398.222222-398.222222 176.355556-398.222222 398.222222 176.355556 398.222222 398.222222 398.222222z"
fill="currentColor"
p-id="5215"
/>
<path d="M341.333333 341.333333h341.333334v341.333334H341.333333z" fill="currentColor" p-id="5216"/>
</svg>
</a-button>
<a-button
@click="
() => {
handleSubmit();
}
"
:disabled="!prompt"
class="sendBtn"
type="text"
v-if="!loading"
>
<svg
t="1706147858151"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4237"
width="1em"
height="1em"
:disabled="!prompt"
class="sendBtn"
type="text"
v-if="!loading"
>
<path
d="M865.28 202.5472c-17.1008-15.2576-41.0624-19.6608-62.5664-11.5712L177.7664 427.1104c-23.2448 8.8064-38.5024 29.696-39.6288 54.5792-1.1264 24.8832 11.9808 47.104 34.4064 58.0608l97.5872 47.7184c4.5056 2.2528 8.0896 6.0416 9.9328 10.6496l65.4336 161.1776c7.7824 19.1488 24.4736 32.9728 44.7488 37.0688 20.2752 4.096 41.0624-2.1504 55.6032-16.7936l36.352-36.352c6.4512-6.4512 16.5888-7.8848 24.576-3.3792l156.5696 88.8832c9.4208 5.3248 19.8656 8.0896 30.3104 8.0896 8.192 0 16.4864-1.6384 24.2688-5.0176 17.8176-7.68 30.72-22.8352 35.4304-41.6768l130.7648-527.1552c5.5296-22.016-1.7408-45.2608-18.8416-60.416z m-20.8896 50.7904L713.5232 780.4928c-1.536 6.2464-5.8368 11.3664-11.776 13.9264s-12.5952 2.1504-18.2272-1.024L526.9504 704.512c-9.4208-5.3248-19.8656-7.9872-30.208-7.9872-15.9744 0-31.744 6.144-43.52 17.92l-36.352 36.352c-3.8912 3.8912-8.9088 5.9392-14.2336 6.0416l55.6032-152.1664c0.512-1.3312 1.2288-2.56 2.2528-3.6864l240.3328-246.1696c8.2944-8.4992-2.048-21.9136-12.3904-16.0768L301.6704 559.8208c-4.096-3.584-8.704-6.656-13.6192-9.1136L190.464 502.9888c-11.264-5.5296-11.5712-16.1792-11.4688-19.3536 0.1024-3.1744 1.536-13.824 13.2096-18.2272L817.152 229.2736c10.4448-3.9936 18.0224 1.3312 20.8896 3.8912 2.8672 2.4576 9.0112 9.3184 6.3488 20.1728z"
p-id="4238"
fill="currentColor"
/>
</svg>
</a-button>
<svg
t="1706147858151"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4237"
width="18"
height="18"
>
<path
d="M865.28 202.5472c-17.1008-15.2576-41.0624-19.6608-62.5664-11.5712L177.7664 427.1104c-23.2448 8.8064-38.5024 29.696-39.6288 54.5792-1.1264 24.8832 11.9808 47.104 34.4064 58.0608l97.5872 47.7184c4.5056 2.2528 8.0896 6.0416 9.9328 10.6496l65.4336 161.1776c7.7824 19.1488 24.4736 32.9728 44.7488 37.0688 20.2752 4.096 41.0624-2.1504 55.6032-16.7936l36.352-36.352c6.4512-6.4512 16.5888-7.8848 24.576-3.3792l156.5696 88.8832c9.4208 5.3248 19.8656 8.0896 30.3104 8.0896 8.192 0 16.4864-1.6384 24.2688-5.0176 17.8176-7.68 30.72-22.8352 35.4304-41.6768l130.7648-527.1552c5.5296-22.016-1.7408-45.2608-18.8416-60.416z m-20.8896 50.7904L713.5232 780.4928c-1.536 6.2464-5.8368 11.3664-11.776 13.9264s-12.5952 2.1504-18.2272-1.024L526.9504 704.512c-9.4208-5.3248-19.8656-7.9872-30.208-7.9872-15.9744 0-31.744 6.144-43.52 17.92l-36.352 36.352c-3.8912 3.8912-8.9088 5.9392-14.2336 6.0416l55.6032-152.1664c0.512-1.3312 1.2288-2.56 2.2528-3.6864l240.3328-246.1696c8.2944-8.4992-2.048-21.9136-12.3904-16.0768L301.6704 559.8208c-4.096-3.584-8.704-6.656-13.6192-9.1136L190.464 502.9888c-11.264-5.5296-11.5712-16.1792-11.4688-19.3536 0.1024-3.1744 1.536-13.824 13.2096-18.2272L817.152 229.2736c10.4448-3.9936 18.0224 1.3312 20.8896 3.8912 2.8672 2.4576 9.0112 9.3184 6.3488 20.1728z"
p-id="4238"
fill="currentColor"
/>
</svg>
</a-button>
</div>
</div>
</div>
</div>
</div>
@ -212,7 +310,7 @@
import dayjs from 'dayjs';
import { defHttp } from '@/utils/http/axios';
import { cloneDeep } from "lodash-es";
import {getFileAccessHttpUrl, getHeaders} from "@/utils/common/compUtils";
import { calculateFileSize, getFileAccessHttpUrl, getFileIcon, getHeaders, getFileIconColor } from "@/utils/common/compUtils";
import { createImgPreview } from "@/components/Preview";
import { useAppInject } from "@/hooks/web/useAppInject";
import { useGlobSetting } from "@/hooks/setting";
@ -222,7 +320,7 @@
prefixCls: 'ai-chat-message',
});
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData','showAdvertising','hasExtraFlowInputs','conversationSettings']);
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData','showAdvertising','hasExtraFlowInputs','conversationSettings','sessionType']);
const emit = defineEmits(['save','reload-message-title','edit-settings']);
const { scrollRef, scrollToBottom } = useScroll();
const prompt = ref<string>('');
@ -266,7 +364,26 @@
const showWebSearch = ref<boolean>(false);
//模型provider信息
const modelProvider = ref<string>('');
//是否显示深度思考( 只有deepsee-reason支持 )
const showThink = ref<boolean>(false);
//是否开启深度思考
const enableThink = ref<boolean>(false);
//模型名称
const modelName = ref<string>('');
//是否开启绘画
const enableDraw = ref<boolean>(false);
//是否显示工具栏
const showDraw = ref<boolean>(false);
//绘画模型的id
const drawModelId = ref<string>('');
//其他文件列表
const fileUrlList = ref<any>([]);
//文件列表(用于显示和管理)
const fileList = ref<any>([]);
// 当前正在调用的工具
const currentToolTag = ref<string>('');
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
@ -316,6 +433,7 @@
dateTime: new Date().toLocaleString(),
content: userMessage,
images:uploadUrlList.value?uploadUrlList.value:[],
files: fileUrlList.value ? fileUrlList.value : [],
inversion: 'user',
error: false,
conversationOptions: null,
@ -388,12 +506,16 @@
const updateChatSome = (uuid, index, data) => {
chatData.value[index] = { ...chatData.value[index], ...data };
};
const updateChatFail = (uuid, index, data) => {
const updateChatFail = (uuid, data) => {
const index = chatData.value.length - 1
const oldChat = chatData.value[index];
updateChat(uuid.value, chatData.value.length - 1, {
dateTime: new Date().toLocaleString(),
content: data,
...oldChat,
// dateTime: new Date().toLocaleString(),
// content: data,
inversion: 'ai',
error: true,
errorMsg: data,
loading: true,
conversationOptions: null,
requestOptions: null,
@ -417,8 +539,14 @@
wrapClassName:'ai-chat-modal',
async onOk() {
try {
//update-begin---author:wangshuai---date:2025-12-12---for:【QQYUN-14127】【AI】AI应用门户---
let url = '/airag/chat/messages/clear/' + uuid.value;
if(props.sessionType){
url += "/" + props.sessionType;
}
defHttp.get({
url: '/airag/chat/messages/clear/' + uuid.value,
url: url,
//update-end---author:wangshuai---date:2025-12-12---for:【QQYUN-14127】【AI】AI应用门户---
},{ isTransformResponse: false }).then((res) => {
if(res.success){
chatData.value = [];
@ -481,26 +609,42 @@
param = {
content: message,
images: uploadUrlList.value?uploadUrlList.value:[],
files: fileUrlList.value ? fileUrlList.value : [],
topicId: topicId.value,
app: appData.value,
responseMode: 'streaming',
// 添加对话设置参数(调试模式也需要)
flowInputs: props.conversationSettings || {},
// 添加网络搜索参数
enableSearch: enableSearch.value
enableSearch: enableSearch.value,
// 添加深度思考参数
enableThink: enableThink.value,
// 添加绘画参数
enableDraw: enableDraw.value,
drawModelId: enableDraw.value ? drawModelId.value : '',
// 添加消息类型 portal 门户
sessionType: props.sessionType || ''
};
}else{
param = {
content: message,
topicId: usingContext.value?topicId.value:'',
images: uploadUrlList.value?uploadUrlList.value:[],
files: fileUrlList.value ? fileUrlList.value : [],
appId: appData.value.id,
responseMode: 'streaming',
conversationId: uuid.value === "1002"?'':uuid.value,
// 添加对话设置参数
flowInputs: props.conversationSettings || {},
// 添加网络搜索参数
enableSearch: enableSearch.value
enableSearch: enableSearch.value,
// 添加深度思考参数
enableThink: enableThink.value,
// 添加绘画参数
enableDraw: enableDraw.value,
drawModelId: enableDraw.value ? drawModelId.value : '',
// 添加消息类型 portal 门户
sessionType: props.sessionType || ''
};
if(headerTitle.value == '新建聊天'){
@ -512,6 +656,8 @@
uploadUrlList.value = [];
fileInfoList.value = [];
fileUrlList.value = [];
fileList.value = [];
knowList.value = [];
options.message = message;
const readableStream = await defHttp.post(
@ -528,11 +674,11 @@
).catch((e)=>{
//update-begin---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天超时以后提示---
if(e.code === 'ETIMEDOUT'){
updateChatFail(uuid, chatData.value.length - 1, "当前用户较多,排队中,请稍候再次重试!");
updateChatFail(uuid, "当前用户较多,排队中,请稍候再次重试!");
handleStop();
return;
}else{
updateChatFail(uuid, chatData.value.length - 1, "服务器错误,请稍后重试!");
updateChatFail(uuid, "服务器错误,请稍后重试!");
handleStop();
return;
}
@ -569,13 +715,15 @@
async function renderText(item,conversationId,text,options) {
let returnText = "";
if (item.event == 'MESSAGE' || item.event == 'THINKING' || item.event == 'THINKING_END') {
let message = item.data.message;
let message = item.data?.message ?? "";
let messageText = "";
//update-begin---author:wangshuai---date:2025-04-24---for:应该先判断是否包含card---
if(message && message.indexOf("::card::") !== -1){
messageText = message;
} else if(message && message.indexOf("::cardConfig::") !== -1) {
messageText = message;
} else {
text = text + item.data.message;
text = text + message;
messageText = text;
returnText = text;
}
@ -595,6 +743,7 @@
dateTime: new Date().toLocaleString(),
content: item.data.message,
images:uploadUrlList.value?uploadUrlList.value:[],
files: fileUrlList.value ? fileUrlList.value : [],
inversion: 'ai',
error: false,
conversationOptions: null,
@ -634,7 +783,7 @@
if (item.event == 'FLOW_FINISHED') {
//update-begin---author:wangshuai---date:2025-03-07---for:【QQYUN-11457】聊天调用流程执行失败了但是没提示---
if(item.data && !item.data.success){
updateChatFail(uuid, chatData.value.length - 1, item.data.message?item.data.message:'请求出错,请稍后重试!');
updateChatFail(uuid, item.data.message?item.data.message:'请求出错,请稍后重试!');
localStorage.removeItem('chat_requestId_' + uuid.value);
handleStop();
return "";
@ -648,17 +797,27 @@
handleStop();
}
if (item.event == 'ERROR') {
updateChatFail(uuid, chatData.value.length - 1, item.data.message?item.data.message:'请求出错,请稍后重试!');
updateChatFail(uuid, item.data.message?item.data.message:'请求出错,请稍后重试!');
localStorage.removeItem('chat_requestId_' + uuid.value);
handleStop();
return "";
}
// 工具调用开始
if (item.event == 'TOOL_EXEC_BEFORE') {
currentToolTag.value = item.data?.message ?? '';
}
// 工具调用结束
if (item.event == 'TOOL_EXEC_DONE') {
currentToolTag.value = '';
}
//update-begin---author:wangshuai---date:2025-03-21---for:【QQYUN-11495】【AI】实时展示当前思考进度---
if(item.event === "NODE_STARTED"){
if(!item.data || item.data.type !== 'end'){
let aiText = "";
if(item.data.type === 'llm'){
if(item.data.type === 'llm' || item.data.type === 'reply'){
aiText = "正在构建响应内容";
}
if(item.data.type === 'knowledge'){
@ -708,7 +867,7 @@
}
}
}
if(!returnText){
if(!returnText && item.event !== 'NODE_FINISHED'){
returnText = text;
}
return { returnText, conversationId };
@ -887,7 +1046,7 @@
continue;
}
if(!content.endsWith('}')){
buffer = buffer + line;
buffer = buffer + content;
continue;
}
buffer = "";
@ -1059,6 +1218,18 @@
}
}
/**
* 切换网络思考
*/
function toggleThink() {
enableThink.value = !enableThink.value;
if (enableThink.value) {
message.success("已开启深度思考");
} else {
message.info("已关闭深度思考");
}
}
// 检查模型是否支持网络搜索从appData.metadata.modelInfo中获取
function checkModelProvider() {
if (appData.value && appData.value.metadata) {
@ -1066,22 +1237,90 @@
const metadata = typeof appData.value.metadata === 'string'
? JSON.parse(appData.value.metadata)
: appData.value.metadata;
//是否显示绘图工具
showDraw.value = metadata.izDraw === '1';
drawModelId.value = metadata.drawModelId;
if (metadata && metadata.modelInfo) {
modelProvider.value = metadata.modelInfo.provider || '';
modelName.value = metadata.modelInfo.modelName || '';
// 只有千问模型支持网络搜索
showWebSearch.value = modelProvider.value === 'QWEN';
showThink.value = modelName.value === 'deepseek-reasoner';
} else {
showWebSearch.value = false;
showThink.value = false;
}
} catch (e) {
console.error('解析模型信息失败', e);
showWebSearch.value = false;
showThink.value = false;
}
} else {
showWebSearch.value = false;
showThink.value = false;
showDraw.value = false;
}
}
/**
* 生成图片
*/
function handleGenerateImage() {
enableDraw.value = !enableDraw.value;
if (enableDraw.value) {
message.success("已开启生成图片");
} else {
message.info("已关闭生成图片");
}
}
//================================================== begin 【QQYUN-14261】AI助手支持多模态能力- 文档 ====================================
/**
* 通用文件上传前校验
*
* @param file
*/
function beforeUploadFile(file) {
const fileName = file.name;
const fileType = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
const allowFileTypes = ['txt', 'pdf', 'docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls', 'md'];
if (allowFileTypes.indexOf(fileType) === -1) {
message.warning('不支持该文件类型上传,请上传 txt, pdf, docx, doc, pptx, ppt, xlsx, xls, md 格式文件');
return false;
}
return true;
}
/**
* 文件上传(非图片)
* @param info
*/
function handleChangeFile(info) {
let { file, fileList: newFileList } = info;
fileList.value = newFileList;
if (file.status === 'error' || (file.response && file.response.code == 500)) {
message.error(file.response?.message || `${file.name} 上传失败,请查看服务端日志`);
return;
}
fileUrlList.value = fileList.value
.filter(item => item.status === 'done' && item.response)
.map(item => item.response.message);
}
/**
* 删除文件
*/
function deleteFile(index) {
fileList.value.splice(index, 1);
fileUrlList.value = fileList.value
.filter(item => item.status === 'done' && item.response)
.map(item => item.response.message);
}
//================================================== end 【QQYUN-14261】AI助手支持多模态能力- 文档 ====================================
//监听历史信息
watch(
() => props.historyData,
@ -1116,6 +1355,8 @@
scrollToBottom();
uploadUrlList.value = [];
fileInfoList.value = [];
fileUrlList.value = [];
fileList.value = [];
// 检查模型是否支持网络搜索
checkModelProvider();
});
@ -1199,19 +1440,39 @@
font-size: 18px;
}
.sendBtn {
font-size: 18px;
width: 36px;
font-size: 14px;
width: 100%;
display: flex;
padding: 8px;
padding: 4px 6px;
align-items: center;
&.enabled {
color: @primary-color;
color: #0a66ff !important;
}
}
.webSearchBtn {
border-radius: 8px;
padding: 4px 8px;
height: 30px;
background-color: transparent;
border: 1px solid transparent;
color: #3d4353;
transition: all .2s ease;
:deep(.anticon){
margin-right: 6px;
color: #3d4353;
}
&:hover{
border-color: #d2d7e5;
background-color: #f7f9fc;
}
&.enabled {
background-color: rgba(10,102,255,0.08);
border-color: #0a66ff;
color: #0a66ff;
box-shadow: none;
font-weight: 500;
:deep(.anticon) {
color: #52c41a !important;
color: #0a66ff !important;
}
}
}
@ -1309,16 +1570,108 @@
width: 60px;
}
}
/*begin 文件的样式*/
.file-card {
display: flex;
align-items: center;
background: #f4f6f8;
border-radius: 8px;
padding: 8px 12px;
width: 200px;
position: relative;
.file-card-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 8px;
.file-thumb {
width: 32px;
height: 32px;
border-radius: 4px;
object-fit: cover;
}
}
.file-card-info {
flex: 1;
overflow: hidden;
.file-name {
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-size {
font-size: 12px;
color: #999;
}
}
.file-card-close {
position: absolute;
top: -6px;
right: -6px;
width: 16px;
height: 16px;
background: #ccc;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #fff;
font-size: 10px;
opacity: 0;
transition: opacity 0.2s;
&:hover {
background: #ff4d4f;
}
}
&:hover .file-card-close {
opacity: 1;
}
/*end 文件的样式*/
}
}
.textarea-bottom{
display: flex;
flex-direction: row;
align-items: center;
flex-direction: column;
flex: 1 1;
min-height: 48px;
position: relative;
padding: 8px 8px 8px 10px;
padding: 2px 10px;
width: 100%;
/*begin 底部样式*/
.textarea-action-bar {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-top: 8px;
.left-actions {
display: flex;
align-items: center;
gap: 4px;
}
.right-actions {
display: flex;
align-items: center;
gap: 4px;
}
.sendBtn {
width: auto;
padding: 4px 6px;
height: 30px;
}
}
/*end 底部样式*/
}
}
.chat-textarea:hover{
@ -1375,4 +1728,12 @@
.ai-chat-message{
z-index: 9999 !important;
}
.chat-upload-dropdown .ant-dropdown-menu{
border-radius: 10px;
padding: 6px 4px;
}
.chat-upload-dropdown .ant-dropdown-menu-item:hover{
background-color: #f0f6ff;
}
</style>

View File

@ -13,6 +13,14 @@
<div v-for="(item,index) in images" :key="index" class="image" @click="handlePreview(item)">
<img :src="getImageUrl(item)"/>
</div>
</div>
<div v-if="inversion === 'user' && files && files.length>0" class="file-list">
<div v-for="(item,index) in files" :key="index" class="file-item" @click="handleFilePreview(item?.filePath || item)">
<div class="file-icon">
<Icon :icon="getFileIcon(item?.filePath || item)" :color="getFileIconColor(item?.filePath || item)" size="24" />
</div>
<div class="file-name" :title="item.name">{{ getFileName(item?.filePath || item)}}</div>
</div>
</div>
<div v-if="inversion === 'ai' && retrievalText && loading" class="retrieval">
{{retrievalText}}
@ -30,15 +38,22 @@
</a-col>
</a-row>
</div>
<div class="thinkArea" style="margin-bottom: 10px" v-if="!isCard && (eventType === 'thinking' || eventType === 'thinking_end')">
<div v-if="inversion === 'ai' && isCardConfig" class="card">
<a-row>
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex:1;margin-right: 10px;" v-for="item in getCardConfigList()">
<CardTemplate :template-id="cardConfig?.templateId" :card-data="item" :card-config="cardConfig" @click="handleJumpClick(item)"></CardTemplate>
</a-col>
</a-row>
</div>
<div class="thinkArea" style="margin-bottom: 10px" v-if="!isCard && !isCardConfig && (eventType === 'thinking' || eventType === 'thinking_end')">
<a-collapse v-model:activeKey="activeKey" ghost>
<a-collapse-panel :key="uuid" :header="loading?'正在思考中':'思考结束'">
<ThinkText :text="text" :inversion="inversion" :error="error" :loading="loading"></ThinkText>
</a-collapse-panel>
</a-collapse>
</div>
<div class="msgArea" v-else-if="!isCard" :class="showAvatar == 'no' ? 'hidden-avatar' : ''">
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading" :referenceKnowledge="referenceKnowledge"></chatText>
<div class="msgArea" v-else-if="!isCard && !isCardConfig" :class="showAvatar == 'no' ? 'hidden-avatar' : ''">
<chatText :text="text" :inversion="inversion" :error="error" :errorMsg="errorMsg" :currentToolTag="currentToolTag" :loading="loading" :referenceKnowledge="referenceKnowledge" :isLast="isLast"></chatText>
</div>
<div v-if="presetQuestion" v-for="item in presetQuestion" class="question" @click="presetQuestionClick(item.descr)">
<span>{{item.descr}}</span>
@ -55,12 +70,16 @@
import defaultImg from '../img/ailogo.png';
import { ref } from 'vue';
import { buildUUID } from '/@/utils/uuid';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getFileAccessHttpUrl, getFileIcon, getFileIconColor } from '/@/utils/common/compUtils';
import { createImgPreview } from "@/components/Preview";
import { computed } from "vue";
import CardTemplate from '/@/views/super/airag/aiapp/chat/components/CardTemplate.vue';
import { useGlobSetting } from "@/hooks/setting";
import {encryptByBase64} from "@/utils/cipher";
const { domainUrl, viewUrl } = useGlobSetting();
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','errorMsg', 'currentToolTag', 'appData','presetQuestion','images','retrievalText', 'referenceKnowledge', 'eventType', 'showAvatar',"files", 'isLast']);
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images','retrievalText', 'referenceKnowledge', 'eventType', 'showAvatar']);
const uuid = ref<any>(buildUUID());
const activeKey = ref<any>(uuid.value);
const getText = computed(()=>{
@ -77,7 +96,17 @@
return true;
}
return false;
});
const isCardConfig = computed(() => {
let text = props.text;
if (text && text.indexOf('::cardConfig::') != -1) {
return true;
}
return false;
});
//卡片配置
const cardConfig = ref<any>();
const { userInfo } = useUserStore();
const avatar = () => {
@ -107,7 +136,8 @@
url = item.url;
}
if(item.hasOwnProperty('base64Data') && item.base64Data){
return item.base64Data;
let mimeType = item.mimeType ? item.mimeType:'image/png';
return "data:"+ mimeType +";base64,"+ item.base64Data;
}
return getFileAccessHttpUrl(url);
}
@ -145,6 +175,60 @@
function aiCardHandleClick(url){
window.open(url,'_blank');
}
/**
* 从config获取取卡片列表
*/
function getCardConfigList() {
let text = props.text;
let card = text.replace('::cardConfig::', 'cardConfig').replace(/\s+/g, '');
try {
let parse = JSON.parse(card);
cardConfig.value = JSON.parse(parse?.cardConfig);
return JSON.parse(parse?.content);
} catch (e){
console.log(e)
return '';
}
}
/**
* 卡片点击跳转
*/
function handleJumpClick(item) {
if(cardConfig.value?.enableJump){
let src = item[cardConfig.value?.jumpUrl];
let reg = /#\s*{\s*domainURL\s*}/g;
src = src.replace(reg,domainUrl);
window.open(src,"_blank")
}
}
/**
* 获取文件名字
*
* @param fileUrl
*/
function getFileName(fileUrl){
if(!fileUrl) {
return '未命名的文件';
}
let fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1).toLowerCase();
fileName = fileName.substring(0,fileName.lastIndexOf("."));
return fileName;
}
/**
* 文件预览
*
* @param fileUrl
*/
function handleFilePreview(fileUrl) {
let filePath = encodeURIComponent(encryptByBase64(getFileAccessHttpUrl(fileUrl)));
let url = `${viewUrl}?url=` + filePath;
window.open(url, "_blank")
}
</script>
<style lang="less" scoped>
@ -192,6 +276,9 @@
font-size: 28px;
}
}
.chat.chatgpt .avatar img{
border-radius: 4px;
}
.content {
width: 90%;
.date {
@ -237,6 +324,43 @@
}
}
}
/*begin文件列表的样式*/
.file-list {
margin-bottom: 10px;
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-end;
}
.file-item {
display: flex;
align-items: center;
background: #f4f6f8;
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
width: fit-content;
max-width: 100%;
.file-icon {
margin-right: 8px;
display: flex;
align-items: center;
}
.file-name {
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
}
/*end文件列表的样式*/
.retrieval,
.card {
background-color: #f4f6f8;

View File

@ -1,7 +1,7 @@
<template>
<div v-if="text != ''" class="textWrap" :class="[inversion === 'user' ? 'self' : 'chatgpt']" ref="textRef">
<div v-if="parsedText != ''" class="textWrap" :class="[inversion === 'user' ? 'self' : (isOnlyImage ? 'chatgpt-image' : 'chatgpt')]" ref="textRef">
<div v-if="inversion != 'user'" :style="{ width: getIsMobile? screenWidth : 'auto' }">
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" :style="{color:error?'#FF4444 !important':''}" v-html="text" />
<div ref="markdownBodyRef" class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="parsedText" />
<template v-if="showRefKnow">
<a-divider orientation="left">引用</a-divider>
<template v-for="(item, idx) in referenceKnowledge" :key="idx">
@ -20,14 +20,28 @@
</a-tooltip>
</template>
</template>
<div v-if="error" class="error-message">
<p>{{ errorMsg }}</p>
</div>
</div>
<div v-else class="msg" v-html="text" />
<div v-else class="msg" v-html="parsedText" />
</div>
<ImageViewer v-if="amplifyImage" :imageUrl="imageUrl" @hide="pictureHide"></ImageViewer>
<!-- 聊天中渲染JeecgTag -->
<template v-if="jeecgTagList.length">
<template v-for="item of jeecgTagList" :key="item.key">
<teleport :to="item.to">
<Component :is="item.tag.component" :data="item.data" :loading="loading" />
</teleport>
</template>
</template>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue';
import type { JeecgTag } from './jeecg-tags/types';
import { computed, nextTick, onMounted, onUnmounted, onUpdated, ref, watch } from 'vue';
import * as lodash from 'lodash';
import md5 from 'crypto-js/md5';
import MarkdownIt from 'markdown-it';
import mdKatex from '@traptitech/markdown-it-katex';
import mila from 'markdown-it-link-attributes';
@ -38,16 +52,35 @@
import ImageViewer from '@/views/super/airag/aiapp/chat/components/ImageViewer.vue';
import { useAppInject } from "@/hooks/web/useAppInject";
import { useGlobSetting } from "@/hooks/setting";
import { mdPluginJeecgTag, JEECG_TAG_CLASS, jeecgTagMap } from './jeecg-tags'
import knowledgePng from '../../aiknowledge/icon/knowledge.png'
/**
* 屏幕宽度
*/
const screenWidth = ref<string>();
const { domainUrl } = useGlobSetting();
const { getIsMobile } = useAppInject();
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading', 'referenceKnowledge']);
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'errorMsg', 'currentToolTag', 'loading', 'referenceKnowledge', 'isLast']);
const textRef = ref();
const markdownBodyRef = ref<HTMLDivElement>();
//图片地址
const imageUrl = ref<string>('');
//是否放大图片
const amplifyImage = ref<boolean>(false);
const parsedText = ref<string>('');
// 解析出来的 jeecgTag 列表
const jeecgTagList = ref<{
key: string;
to: HTMLDivElement;
tag: JeecgTag;
data: string;
}[]>([]);
const mdi = new MarkdownIt({
html: true,
linkify: true,
@ -61,18 +94,29 @@
},
});
// 自定义 mdi 插件
mdi.use(mdPluginJeecgTag);
// 官方 mdi 插件
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } });
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
const text = computed(() => {
/**
* 处理聊天文本并在一定时间内节流更新
*/
const updateTextContent = lodash.throttle(() => {
let value = props.text ?? '';
if (props.inversion != 'user'){
if (props.inversion !== 'user') {
// 先替换图片宽度与域名占位符后再渲染 markdown
value = replaceImageWith(value);
value = replaceDomainUrl(value);
return mdi.render(value);
parsedText.value = mdi.render(value);
// 解析 jeecgTag 标签
parseJeecgTag();
return;
}
return value.replace("\n","<br>");
});
// 用户消息保留换行展示
parsedText.value = value.replace("\n", "<br>");
}, 100);
// 是否显示引用知识库
const showRefKnow = computed(() => {
@ -83,19 +127,77 @@
return Array.isArray(referenceKnowledge) && referenceKnowledge.length > 0;
})
// 判断是否只有图片
const isOnlyImage = computed(() => {
if (showRefKnow.value){
return false;
}
const content = props.text || '';
if (!content){
return false;
}
//匹配![图片1](url1)
const imageRegex = /!\[.*?\]\(.*?\)/g;
if (!imageRegex.test(content)) {
return false;
}
//替换之后是否存在文本
const remaining = content.replace(imageRegex, '').trim();
return remaining.length === 0;
});
// 监听文本变化,触发界面更新
watch(() => props.text, () => updateTextContent(), {immediate: true});
// 监听 当前调用的工具 变化,追加渲染内容
watch(() => props.currentToolTag, () => {
const {isLast, inversion, currentToolTag, loading} = props;
if (isLast && inversion != 'user' && currentToolTag && loading) {
parsedText.value += mdi.render(currentToolTag);
// 解析 jeecgTag 标签
parseJeecgTag();
}
}, {immediate: true});
//替换图片宽度
const replaceImageWith = markdownContent => {
// 支持图片设置width的写法 ![](/static/jimuImages/screenshot_1617252560523.png =100)
const regex = /!\[([^\]]*)\]\(([^)]+)=([0-9]+)\)/g;
return markdownContent.replace(regex, (match, alt, src, width) => {
function replaceImageWith(markdownContent) {
//update-begin---author:wangshuai---date:2026-01-08---for: 兼容返回多张图片集图片默认宽度调整---
// 1. 支持图片设置width的写法 ![](/static/jimuImages/screenshot_1617252560523.png =100)
// 必须有空格避免匹配到url参数中的=
const regex = /!\[([^\]]*)\]\(([^)]+)\s=([0-9]+)\)/g;
markdownContent = markdownContent.replace(regex, (match, alt, src, width) => {
let reg = /#\s*{\s*domainURL\s*}/g;
src = src.replace(reg,domainUrl);
return `<div><img src='${src}' alt='${alt}' width='${width}' /></div>`;
return `<div class="chat-image-custom"><img src='${src}' alt='${alt}' width='${width}' /></div>`;
});
// 2. 处理普通图片,实现多图并列显示(如生成图片场景)
// 统计剩余的markdown图片数量
const regexStandard = /!\[([^\]]*)\]\(([^)]+)\)/g;
const matches = markdownContent.match(regexStandard);
const count = matches ? matches.length : 0;
if (count > 0) {
markdownContent = markdownContent.replace(regexStandard, (match, alt, src) => {
let reg = /#\s*{\s*domainURL\s*}/g;
src = src.replace(reg, domainUrl);
// 如果有多张图片使用Grid布局一行4个
if (count > 1) {
return `<div class="chat-image-grid-item"><img src='${src}' alt='${alt}' /></div>`;
}
// 单张图片保持默认(或包裹以便控制)
return `<div class="chat-image-single"><img src='${src}' alt='${alt}' /></div>`;
});
}
return markdownContent;
//update-end---author:wangshuai---date:2026-01-08---for: 兼容返回多张图片集图片默认宽度调整---
};
const { domainUrl } = useGlobSetting();
//替换domainURL
const replaceDomainUrl = markdownContent => {
function replaceDomainUrl(markdownContent) {
const regex = /!\[([^\]]*)\]\(.*?#\s*{\s*domainURL\s*}.*?\)/g;
return markdownContent.replace(regex, (match) => {
let reg = /#\s*{\s*domainURL\s*}/g;
@ -103,11 +205,6 @@
})
}
//是否放大图片
const amplifyImage = ref<boolean>(false);
//图片地址
const imageUrl = ref<string>('');
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
}
@ -191,6 +288,51 @@
}
}
// 解析JeecgTag标签
async function parseJeecgTag() {
await nextTick();
if (!markdownBodyRef.value) {
return;
}
jeecgTagList.value = [];
const elements = markdownBodyRef.value.querySelectorAll('.' + JEECG_TAG_CLASS);
elements.forEach((el) => {
const tagName = el.nodeName.toLowerCase();
const tag = jeecgTagMap.get(tagName);
if (!tag) {
console.warn(`未识别的 jeecg 标签:`, tagName, el);
return;
}
const renderEl = el.querySelector('render') as HTMLDivElement;
if (!renderEl) {
if (props.loading) {
// 渲染中
el.innerHTML = `<div style="color: #888; margin-top: 12px;">图表渲染中,请稍候...</div>`;
return;
}
el.innerHTML = `<div style="color: red;">模型返回的图表渲染格式不正确,请优化提示词或重新尝试。</div>`;
return;
}
const dataEl = el.querySelector('data');
const dataStr = dataEl?.textContent || '';
renderJeecgTag(tag, dataStr, renderEl);
});
}
/**
* 提交渲染 jeecg 标签
*/
function renderJeecgTag(tag: JeecgTag, data: string, renderEl: HTMLDivElement) {
jeecgTagList.value.push({
key: md5(tag.name + '_' + data).toString(),
to: renderEl,
tag: tag,
data: data,
});
}
onMounted(() => {
addCopyEvents();
addImageClickEvent();
@ -248,6 +390,10 @@
line-height: 1.25rem;
}
.error-message {
color: #FF4444 !important
}
.self {
// background-color: #d2f9d1;
background-color: @primary-color;
@ -262,6 +408,11 @@
font-size: 0.875rem;
line-height: 1.25rem;
}
.chatgpt-image {
.markdown-body{
background-color: transparent !important;
}
}
@media (max-width: 1024px) {
//手机和平板下的样式
.textWrap{
@ -269,4 +420,47 @@
margin-top: 10px;
}
}
// 生成图片的样式
:deep(.chat-image-grid-item) {
display: inline-block;
width: 24%;
padding: 4px;
box-sizing: border-box;
vertical-align: top;
img {
width: 100%;
height: auto;
border-radius: 4px;
cursor: pointer;
object-fit: cover;
aspect-ratio: 1/1;
}
}
:deep(.chat-image-single) {
img {
max-width: 50%;
border-radius: 4px;
cursor: pointer;
}
}
.markdown-body {
:deep(.jeecg-tag) {
display: block;
margin: 8px 0;
data {
display: none;
}
render {
display: block;
width: 100%;
height: auto;
min-width: 300px;
}
}
}
</style>

View File

@ -0,0 +1,308 @@
<!-- card 官方模板 -->
<template>
<div class="card-select-panel" @click="handleClick">
<!-- 卡片内容 -->
<div class="card-item">
<div class="card-title">{{ getTitle }}</div>
<!-- 缩略图左侧变体 -->
<div v-if="getTemplateId === 'template-1'" class="card-top">
<div class="thumb" v-if="!getImage">
<div class="thumb-dot"></div>
<div class="thumb-mountain"></div>
</div>
<div v-else class="thumb-image">
<img :src="getImage" />
</div>
<div class="desc clamp">
{{ getContent }}
</div>
</div>
<!-- 缩略图右侧变体 -->
<div v-else-if="getTemplateId === 'template-2'" class="card-top">
<div class="desc clamp">
{{ getContent }}
</div>
<div class="thumb" v-if="!getImage">
<div class="thumb-dot"></div>
<div class="thumb-mountain"></div>
</div>
<div v-else class="thumb-image">
<img :src="getImage" />
</div>
</div>
<!-- 横幅图片变体 -->
<div v-else-if="getTemplateId === 'template-3'">
<div class="banner" v-if="!getImage">
<div class="banner-dot"></div>
<div class="banner-mountain"></div>
</div>
<div v-else class="banner-image">
<img :src="getImage" />
</div>
<div class="desc">
{{ getContent }}
</div>
</div>
<!-- 纯文本变体 -->
<div v-else-if="getTemplateId === 'template-4'" class="desc">
{{ getContent }}
</div>
<div v-if="showDeleteIcon" class="delete">
<Icon icon="ant-design:close-outlined" @click.parent.stop="handleDelete"></Icon>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, computed } from 'vue';
import {useGlobSetting} from "@/hooks/setting";
const props = defineProps({
templateId: { type: String, default: '' },
showDeleteIcon: { type: Boolean, default: false },
cardData: { type: Object, default: null },
cardConfig: { type: Object, default: null },
});
const emit = defineEmits(['register', 'click', 'delete']);
const { domainUrl } = useGlobSetting();
const content = ref<string>('内容描述是一种重要的沟通和表达,它在描述事物时发挥着至关重要的作用');
//获取模板id
const getTemplateId = computed(() => props.templateId);
//获取内容
const getContent = computed(() => {
if (props.cardData && props.cardConfig) {
return props.cardData[props.cardConfig?.content];
}
return content;
});
//获取文本
const getTitle = computed(() => {
if (props.cardData && props.cardConfig) {
return props.cardData[props.cardConfig?.title];
}
return '标题';
});
//获取图片
const getImage = computed(() => {
if (props.cardData && props.cardConfig) {
let src = props.cardData[props.cardConfig?.image];
let reg = /#\s*{\s*domainURL\s*}/g;
src = src.replace(reg,domainUrl);
return src;
}
return '';
});
/**
* 卡片点击事件
*/
function handleClick() {
emit('click');
}
/**
* 卡片删除事件
*/
function handleDelete() {
emit('delete');
}
</script>
<style scoped lang="less">
.card-select-panel {
padding: 8px 0;
cursor: pointer;
}
/* 标题 */
.card-title {
width: 100%;
font-weight: 600;
color: #1f2937;
margin-bottom: 10px;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
font-size: 18px;
text-align: left;
-webkit-line-clamp: 1;
}
/* 描述文本 */
.desc {
color: #667085;
font-size: 13px;
line-height: 1.6;
}
/* 多行省略,用于第一张卡片(示例) */
.clamp {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 顶部缩略图行(缩略图 + 描述) */
.card-top {
display: flex;
gap: 12px;
align-items: center;
}
/* 卡片容器 */
.card-item {
display: flex;
flex-direction: column;
position: relative;
border-radius: 14px;
background: #f8fafc;
border: 1px solid #e9edf3;
padding: 14px 10px;
transition: all 0.2s ease;
}
/* 轻微内阴影与悬浮效果 */
.card-item::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
pointer-events: none;
}
.card-item:hover {
border-color: #dbe3ea;
background: #f9fbfd;
.delete {
display: block;
}
}
/* 描述文本 */
.desc {
color: #667085;
margin-top: 10px;
width: 100%;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0;
white-space: pre-line;
-webkit-box-orient: vertical;
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
text-align: left;
-webkit-line-clamp: 3;
}
/* 缩略图占位(与示例一致:左上小图) */
.thumb {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(180deg, #edf2fa 0%, #e9eef7 100%);
position: relative;
flex: 0 0 44px;
}
.thumb-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #cbd5e1;
position: absolute;
top: 9px;
left: 9px;
}
.thumb-mountain {
width: 20px;
height: 12px;
background: #dbe2ee;
border-radius: 3px;
position: absolute;
bottom: 9px;
left: 12px;
}
/* 横幅图片占位(第三张卡片的大图) */
.banner {
width: 100%;
height: 96px;
border-radius: 12px;
background: linear-gradient(180deg, #eef3fb 0%, #e7edf6 100%);
position: relative;
margin-bottom: 10px;
}
.banner-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #cbd5e1;
position: absolute;
top: 22px;
left: 26px;
}
.banner-mountain {
width: 120px;
height: 20px;
background: #dbe2ee;
border-radius: 4px;
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
}
/* 变体微调:确保整体间距与示例一致 */
.variant-text .desc {
margin-top: 2px;
}
.variant-thumb .desc {
margin-top: 2px;
}
.variant-banner .desc {
margin-top: 8px;
}
.delete {
width: 20px;
position: absolute;
top: 6px;
right: 6px;
display: none;
cursor: pointer;
}
.thumb-image {
background-color: transparent;
display: flex;
width: 44px;
height: 44px;
border-radius: 10px;
position: relative;
flex: 0 0 44px;
img {
width: 44px;
height: 44px;
}
}
.banner-image {
background-color: transparent;
width: 100%;
height: 96px;
border-radius: 12px;
position: relative;
margin-bottom: 10px;
img {
width: 100%;
height: 96px;
}
}
</style>

View File

@ -0,0 +1,71 @@
import type { JeecgTag } from './types';
import { shallowRef } from 'vue';
import ToolExecTag from './tool-exec';
import JeecgChart from "./jeecg-chart";
export const jeecgTagMap: Map<string, JeecgTag> = new Map();
// 所有 jeecg 标签名称列表
export const tagNames: string[] = [];
// 注册 工具调用 标签
useJeecgTag(ToolExecTag);
// 注册 图表渲染 标签
useJeecgTag(JeecgChart);
// jeecg 标签统一的 class 名称
export const JEECG_TAG_CLASS = 'jeecg-tag';
/**
* 忽略 jeecg 自定义标签的解析
* @param md
*/
export function mdPluginJeecgTag(md: any) {
// 保存原始的 html_block 渲染规则
const htmlBlockOrigin =
md.renderer.rules.html_block ||
function (tokens, idx) {
return tokens[idx].content;
};
// 覆盖 html_block 渲染规则
md.renderer.rules.html_block = function (tokens, idx) {
const token = tokens[idx];
const content = token.content;
let isJeecgTag = false;
let tagName = '';
for (const name of tagNames) {
// 检查内容是否包含自定义标签的起始或结束标签
const startTag = new RegExp(`<${name}(\\s|>)`, 'i');
const endTag = new RegExp(`</${name}>`, 'i');
if (startTag.test(content) || endTag.test(content)) {
isJeecgTag = true;
tagName = name;
break;
}
}
// jeecg 自定义标签
if (isJeecgTag) {
const box = document.createElement('div');
box.innerHTML = content;
const tag = box.firstElementChild!;
tag.classList.add(JEECG_TAG_CLASS);
return tag.outerHTML;
}
// 其他 HTML 标签按默认方式渲染
return htmlBlockOrigin(tokens, idx);
};
}
export function useJeecgTag(tag: JeecgTag) {
if (jeecgTagMap.has(tag.name)) {
return;
}
tag.component = shallowRef(tag.component);
jeecgTagMap.set(tag.name, tag);
tagNames.push(tag.name);
}

View File

@ -0,0 +1,427 @@
<template>
<div class="ai-chat-chart">
<div v-if="isError" class="ai-chat-chart__error">{{ errorMessage }}</div>
<div v-else-if="!resolvedType || !hasData" class="ai-chat-chart__error">
<div v-if="loading"
style="color: #999; padding: 12px 8px; border: 1px dashed #eee; margin: 8px 0; border-radius: 4px;">
<span>图表渲染中...</span>
</div>
<span v-else>模型返回的图表渲染格式不正确请优化提示词或重新尝试</span>
</div>
<div v-else class="ai-chat-chart__body">
<!-- 折线图 -->
<LineMulti v-if="resolvedType === 'line'" v-bind="lineProps"/>
<!-- 柱状图 -->
<BarMulti v-else-if="resolvedType === 'bar'" v-bind="barProps"/>
<!-- 饼图 -->
<Pie v-else-if="resolvedType === 'pie'" v-bind="pieProps"/>
<!-- 多列柱状图 -->
<BarMulti v-else-if="resolvedType === 'multibar'" v-bind="multiBarProps"/>
<!-- 多行折线图 -->
<LineMulti v-else-if="resolvedType === 'multiline'" v-bind="multiLineProps"/>
<!-- 折柱图 -->
<BarAndLine v-else-if="resolvedType === 'barline'" v-bind="barLineProps"/>
<!-- 面积图 -->
<SingleLine v-else-if="resolvedType === 'area'" v-bind="areaLineProps"/>
<!-- 雷达图 -->
<Radar v-else-if="resolvedType === 'radar'" v-bind="radarProps"/>
<!-- 仪表盘 -->
<Gauge v-else-if="resolvedType === 'gauge'" v-bind="gaugeProps"/>
</div>
</div>
</template>
<script setup lang="ts">
import type { ChartType } from './types';
import { computed, ref, watchEffect } from 'vue';
import LineMulti from '/@/components/chart/LineMulti.vue';
import BarMulti from '/@/components/chart/BarMulti.vue';
import Pie from '/@/components/chart/Pie.vue';
import Radar from '/@/components/chart/Radar.vue';
import Gauge from '/@/components/chart/Gauge.vue';
import BarAndLine from '/@/components/chart/BarAndLine.vue';
import SingleLine from '/@/components/chart/SingleLine.vue';
const props = defineProps({
/**
* 图表配置字符串,示例:
* {"type":"bar","data":[{"x":"数据项1","y":100},{"x":"数据项2","y":80}]}
*/
data: {
type: String,
required: true,
},
loading: {
type: Boolean,
default: false,
},
});
/**
* 解析失败或类型错误的提示文本。
*/
const errorMessage = ref<string>('');
/**
* 是否存在阻止渲染的错误。
*/
const isError = ref<boolean>(false);
/**
* 将字符串解析为配置对象,失败时记录错误信息。
*/
const parsedConfig = computed<Recordable>(() => {
try {
errorMessage.value = '';
isError.value = false;
return JSON.parse(props.data || '{}');
} catch (error) {
errorMessage.value = '图表数据解析错误,无法渲染图表。';
isError.value = true;
return {};
}
});
/**
* 支持的图表类型集合。
*/
// 支持的类型覆盖常见图表,名称与提示词保持宽松映射
const supportedTypes: ChartType[] = ['bar', 'line', 'pie', 'radar', 'gauge', 'barline', 'multibar', 'multiline', 'area'];
/**
* 解析得到的图表类型,统一小写后做合法性校验。
*/
const resolvedType = computed<ChartType>(() => {
const rawType = String((parsedConfig.value as any).type || '').toLowerCase();
const normalizedType = (rawType || '').replace(/[-_\s]/g, '');
const typeAliasMap: Record<string, ChartType> = {
bar: 'bar',
line: 'line',
pie: 'pie',
radar: 'radar',
gauge: 'gauge',
barline: 'barline',
barandline: 'barline',
linebar: 'barline',
multiline: 'multiline',
multibar: 'multibar',
area: 'area',
arealine: 'area',
};
const mapped = typeAliasMap[normalizedType] || '';
if (mapped && supportedTypes.includes(mapped)) {
return mapped as ChartType;
}
return '';
});
/**
* 当类型不被支持时,给出错误提示。
*/
watchEffect(() => {
if (isError.value) {
return;
}
if (resolvedType.value === '') {
const typeValue = (parsedConfig.value as any).type;
if (typeValue) {
errorMessage.value = '当前仅支持 bar、line、pie、radar、gauge、barline、multibar、multiline、area 类型图表。';
isError.value = true;
}
} else {
errorMessage.value = '';
isError.value = false;
}
});
/**
* 原始数据列表。
*/
const rawData = computed<unknown>(() => (parsedConfig.value as any).data);
/**
* 判断是否存在可用的数据数组。
*/
const hasData = computed<boolean>(() => {
if (resolvedType.value === 'gauge') {
return Boolean(rawData.value);
}
return Array.isArray(rawData.value) && rawData.value.length > 0;
});
/**
* 将原始数据标准化为多序列图表所需的结构。
*/
const multiSeriesData = computed<Recordable[]>(() => {
if (!Array.isArray(rawData.value)) {
return [];
}
return rawData.value.map((item: Recordable) => buildMultiSeriesItem(item));
});
/**
* 面积折线与多行折线共享的数据结构,透传 seriesType 控制折线类型。
*/
const areaSeriesData = computed<Recordable[]>(() => {
if (!Array.isArray(rawData.value)) {
return [];
}
return rawData.value.map((item: Recordable) => {
return {
...buildMultiSeriesItem(item),
seriesType: item && item.seriesType ? String(item.seriesType) : 'line',
areaStyle: {},
};
});
});
/**
* 将原始数据标准化为饼图所需的结构。
*/
const pieSeriesData = computed<Recordable[]>(() => {
if (!Array.isArray(rawData.value)) {
return [];
}
return rawData.value.map((item: Recordable) => {
return {
name: resolveName(item),
value: resolveNumber(item),
};
});
});
/**
* 构建多序列图表需要的标准化数据项。
*/
function buildMultiSeriesItem(item: Recordable): Recordable {
const seriesName = resolveSeriesName(item);
const name = resolveName(item);
const value = resolveNumber(item);
return {type: seriesName, name, value};
}
/**
* 解析系列名称,优先使用 series其次使用 type最后回退为“数据”。
*/
function resolveSeriesName(item: Recordable): string {
if (item && item.series !== undefined) {
return String(item.series);
}
if (item && item.type !== undefined) {
return String(item.type);
}
return '数据';
}
/**
* 解析横轴名称,支持 x 与 name 字段。
*/
function resolveName(item: Recordable): string {
if (item && item.x !== undefined) {
return String(item.x);
}
if (item && item.name !== undefined) {
return String(item.name);
}
return '';
}
/**
* 解析数值字段,支持 y 与 value非数字时回退为 0。
*/
function resolveNumber(item: Recordable): number {
const rawValue = item ? item.y ?? item.value : null;
const num = Number(rawValue);
if (Number.isFinite(num)) {
return num;
}
return 0;
}
/**
* 解析序列类型,折柱图需要区分 bar / line。
*/
function resolveSeriesType(item: Recordable): string {
if (item && item.seriesType !== undefined) {
return String(item.seriesType);
}
return 'bar';
}
/**
* 仪表盘数据,允许数组或对象输入,确保返回 name、value。
*/
const gaugeData = computed(() => {
const dataSource = rawData.value as any;
if (Array.isArray(dataSource) && dataSource.length > 0) {
const item = dataSource[0];
return { name: resolveName(item) || '仪表盘', value: resolveNumber(item) };
}
if (dataSource && typeof dataSource === 'object') {
return { name: resolveName(dataSource) || '仪表盘', value: resolveNumber(dataSource as Recordable) };
}
return { name: '仪表盘', value: 0 };
});
/**
* 雷达图数据,支持 max 字段定义指标上限。
*/
const radarSeriesData = computed<Recordable[]>(() => {
if (!Array.isArray(rawData.value)) {
return [];
}
return rawData.value.map((item: Recordable) => {
return {
type: resolveSeriesName(item),
name: resolveName(item),
value: resolveNumber(item),
max: item && item.max !== undefined ? Number(item.max) : undefined,
};
});
});
/**
* 折柱图数据,附带 seriesType 用于区分柱/线。
*/
const barLineSeriesData = computed<Recordable[]>(() => {
if (!Array.isArray(rawData.value)) {
return [];
}
return rawData.value.map((item: Recordable) => {
return {
...buildMultiSeriesItem(item),
seriesType: resolveSeriesType(item),
};
});
});
/**
* 折线图的渲染属性。
*/
const lineProps = computed(() => {
return {
type: 'line',
height: '360px',
width: '100%',
chartData: multiSeriesData.value,
};
});
/**
* 柱状图的渲染属性。
*/
const barProps = computed(() => {
return {
height: '360px',
width: '100%',
chartData: multiSeriesData.value,
};
});
/**
* 饼图的渲染属性。
*/
const pieProps = computed(() => {
return {
height: '360px',
width: '100%',
chartData: pieSeriesData.value,
};
});
/**
* 多列柱状图配置,与 bar 相同但允许区分类型前缀。
*/
const multiBarProps = computed(() => {
return {
height: '360px',
width: '100%',
chartData: multiSeriesData.value,
option: parsedConfig.value.option || {},
};
});
/**
* 多行折线图配置。
*/
const multiLineProps = computed(() => {
return {
type: 'line',
height: '360px',
width: '100%',
chartData: multiSeriesData.value,
option: parsedConfig.value.option || {},
};
});
/**
* 面积折线图配置,开启面积样式。
*/
const areaLineProps = computed(() => {
return {
type: 'line',
height: '360px',
width: '100%',
chartData: areaSeriesData.value,
option: { ...(parsedConfig.value.option || {}), areaStyle: {} },
};
});
/**
* 雷达图配置。
*/
const radarProps = computed(() => {
return {
height: '420px',
width: '100%',
chartData: radarSeriesData.value,
option: parsedConfig.value.option || {},
};
});
/**
* 仪表盘配置。
*/
const gaugeProps = computed(() => {
return {
height: '360px',
width: '100%',
chartData: gaugeData.value,
option: parsedConfig.value.option || {},
seriesColor: (parsedConfig.value as any).seriesColor || undefined,
};
});
/**
* 折柱图配置,支持自定义颜色。
*/
const barLineProps = computed(() => {
return {
height: '360px',
width: '100%',
chartData: barLineSeriesData.value,
customColor: (parsedConfig.value as any).colors || [],
option: parsedConfig.value.option || {},
};
});
</script>
<style scoped lang="less">
.ai-chat-chart {
width: 100%;
min-width: 360px;
padding: 12px 0;
}
.ai-chat-chart__body {
width: 100%;
}
.ai-chat-chart__error {
color: #ff4d4f;
font-size: 14px;
line-height: 20px;
}
</style>

View File

@ -0,0 +1,10 @@
import { JeecgTag } from '../types';
import ChartRender from './ChartRender.vue';
const Tag: JeecgTag = {
name: 'jeecg-chart',
component: ChartRender,
};
export default Tag;

View File

@ -0,0 +1,4 @@
/**
* 支持的图表类型
*/
export type ChartType = 'bar' | 'line' | 'pie' | 'radar' | 'gauge' | 'barline' | 'multibar' | 'multiline' | 'area' | '';

View File

@ -0,0 +1,393 @@
<template>
<div class="tool-exec-wrapper">
<div v-if="!toolRecord" class="tool-exec-empty">暂无工具调用结果</div>
<div v-else-if="toolRecord.loading" class="tool-exec-loading">
<LoadingOutlined spin/>
<span style="margin-left: 8px;">正在运行 {{ titleText }}</span>
</div>
<div v-else class="tool-exec-list">
<div class="tool-exec-card">
<div class="card-header" @click="toggleCard()">
<div class="header-left">
<span class="status-icon" :class="`status-${resolvedStatus}`"></span>
<div class="title-block">
<div class="title-text">
<span class="status-text">{{ statusLabel }}</span>
<span>{{ titleText }}</span>
</div>
<div class="subtitle-text" v-if="toolRecord.subtitle">
<span>{{ toolRecord.subtitle }}</span>
</div>
</div>
</div>
<div class="header-right">
<!-- <span class="collapse-text">{{ expanded ? '收起' : '展开' }}</span>-->
<span class="collapse-icon" :class="{ opened: expanded }">
<DownOutlined/>
</span>
</div>
</div>
<div v-if="expanded" class="card-body">
<div class="section">
<div class="section-title">输入</div>
<pre class="section-content">{{ formattedInput }}</pre>
</div>
<div class="section">
<div class="section-title">输出</div>
<pre class="section-content">{{ formattedOutput }}</pre>
</div>
<div v-if="toolRecord.errorMessage" class="section">
<div class="section-title">错误信息</div>
<pre class="section-content">{{ formattedError }}</pre>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { DownOutlined, LoadingOutlined } from '@ant-design/icons-vue';
/**
* 工具调用结果记录类型
*/
type ToolExecStatus = 'success' | 'running' | 'error';
/**
* 工具调用展示记录
*/
interface ToolExecRecord {
id: string;
name: string;
mcpName?: string;
input: string;
output: string;
loading: boolean;
hasError: boolean;
subtitle?: string;
errorMessage?: string;
}
const props = defineProps({
data: {
type: String,
required: true,
},
loading: {
type: Boolean,
default: false,
},
});
/**
* 解析 props.data 为单条记录
*/
const parsedRecord = computed<ToolExecRecord | null>(() => {
try {
const value = JSON.parse(props.data);
if (value && typeof value === 'object' && !Array.isArray(value)) {
return value as ToolExecRecord;
}
return null;
} catch (error) {
return null;
}
});
/**
* 归一化后的单条记录,补齐常用字段
*/
const toolRecord = computed<ToolExecRecord | null>(() => {
const item = parsedRecord.value;
if (!item) {
return null;
}
return {
...item,
status: status,
toolName: item.name,
input: item.input ?? (item as any).request ?? (item as any).payload ?? (item as any).args,
output: item.output ?? (item as any).response ?? (item as any).result,
errorMessage: '',
} as ToolExecRecord;
});
/**
* 单卡片展开状态
*/
const expanded = ref<boolean>(false);
/**
* 状态文本映射
*/
const statusLabelMap: Record<ToolExecStatus, string> = {
success: '已运行',
running: '执行中',
error: '执行失败',
};
/**
* 已解析状态
*/
const resolvedStatus = computed<ToolExecStatus>(() => {
const record = toolRecord.value;
if (record) {
if (record.loading === true) {
return 'running';
}
if (record.hasError === true) {
return 'error';
}
}
return 'success';
});
/**
* 状态标签文案
*/
const statusLabel = computed<string>(() => {
const current = resolvedStatus.value;
return statusLabelMap[current];
});
/**
* 卡片标题
*/
const titleText = computed<string>(() => {
if (toolRecord.value) {
const parts: string[] = [];
if (toolRecord.value.name) {
parts.push(toolRecord.value.name as string);
}
if (toolRecord.value.mcpName) {
parts.push(toolRecord.value.mcpName as string);
}
if (parts.length > 0) {
return parts.join(' - ');
}
}
return '工具调用';
});
/**
* 输入、输出、错误信息格式化
*/
const formattedInput = computed<string>(() => {
return formatData(toolRecord.value ? toolRecord.value.input : '');
});
const formattedOutput = computed<string>(() => {
return formatData(toolRecord.value ? toolRecord.value.output : '');
});
const formattedError = computed<string>(() => {
return formatData(toolRecord.value ? toolRecord.value.errorMessage : '');
});
/**
* 切换卡片展开状态
*/
function toggleCard(): void {
const next = !expanded.value;
expanded.value = next;
}
/**
* 序列化输入或输出数据,保证预格式化可读
* @param value 输入或输出值
*/
function formatData(value: unknown): string {
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed.length === 0) {
return '';
}
try {
const parsed = JSON.parse(trimmed);
return JSON.stringify(parsed, null, 2);
} catch (error) {
return value;
}
}
try {
return JSON.stringify(value, null, 2);
} catch (error) {
return String(value);
}
}
</script>
<style scoped lang="less">
.tool-exec-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
font-size: 14px;
line-height: 1.5;
color: #333;
}
.tool-exec-loading,
.tool-exec-empty {
padding: 12px 16px;
border: 1px dashed #d9d9d9;
border-radius: 8px;
background-color: #fafafa;
}
.tool-exec-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.tool-exec-card {
border: 1px solid #e5e6eb;
border-radius: 10px;
background-color: #fff;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.card-header:hover {
background-color: #f7f8fa;
}
.header-left {
display: flex;
align-items: center;
gap: 10px;
}
.status-icon {
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid #c8c9cc;
background-color: #fff;
}
.status-success {
border-color: #52c41a;
background-color: #f6ffed;
}
.status-running {
border-color: #1677ff;
background-color: #e6f4ff;
}
.status-error {
border-color: #ff4d4f;
background-color: #fff1f0;
}
.title-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.title-text {
font-weight: 600;
.status-text {
margin-right: 6px;
font-weight: normal;
}
}
.subtitle-text {
font-size: 12px;
color: #666;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
color: #555;
font-size: 13px;
}
.status-text {
font-weight: 500;
}
.collapse-text {
font-size: 12px;
}
.collapse-icon {
display: inline-flex;
align-items: center;
transition: transform 0.2s ease;
&.opened {
transform: rotate(180deg);
}
}
.card-body {
padding: 0 14px 14px 14px;
display: flex;
flex-direction: column;
gap: 12px;
}
.section {
display: flex;
flex-direction: column;
gap: 6px;
}
.section-title {
font-weight: 600;
font-size: 13px;
}
.section-content {
margin: 0;
padding: 10px 12px;
border-radius: 8px;
background-color: #f8f8f8;
border: 1px solid #e5e6eb;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
white-space: pre-wrap;
word-break: break-word;
overflow: auto;
}
.section-content:empty {
display: none;
}
@media (max-width: 600px) {
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.header-right {
width: 100%;
justify-content: flex-end;
}
}
</style>

View File

@ -0,0 +1,10 @@
import { JeecgTag } from '../types';
import JeecgToolExec from './JeecgToolExec.vue';
const Tag: JeecgTag = {
name: 'jeecg-tool-exec',
component: JeecgToolExec,
};
export default Tag;

View File

@ -0,0 +1,9 @@
import { Component } from 'vue';
/**
* JeecgTag类型
*/
export type JeecgTag = {
name: string;
component: Component;
};

View File

@ -0,0 +1,297 @@
<!-- 应用门户 -->
<template>
<div ref="portalRef" class="portal-container" :style="portalContainerStyle">
<div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
<div class="content">
<LeftPortalSession ref="leftPortalRef" @app-click="handleAppClick" @task-click="handleTaskClick"></LeftPortalSession>
</div>
<div class="toggle-btn" @click="handleToggle">
<span class="icon">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
fill="currentColor"
></path>
</svg>
</span>
</div>
</div>
<div class="rightArea" :class="[expand ? 'expand' : 'shrink']">
<chat
:key="uuid"
url="/airag/chat/send"
v-if="uuid"
:uuid="uuid"
:historyData="historyData"
type="view"
:formState="appData"
:prologue="appData.prologue"
:presetQuestion="appData.presetQuestion"
@reload-message-title="reloadMessageTitle"
:chatTitle="chatTitle"
:conversationSettings="getCurrentSettings"
@edit-settings="handleEditSettings"
sessionType="portal"
ref="chatRef"
></chat>
<div v-if="showWelcome" class="emptyArea">
<div class="welcome-text">{{ welcomeText }}</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import LeftPortalSession from './LeftPortalSession.vue';
import chat from '/@/views/super/airag/aiapp/chat/chat.vue';
import { defHttp } from '@/utils/http/axios';
import { useUserStore } from '@/store/modules/user';
const portalContainerStyle = ref<any>(null);
//应用门户的ref
const portalRef = ref();
//左侧列表是否展开
const expand = ref<any>(true);
//左侧列表展开关闭事件
const handleToggle = () => {
expand.value = !expand.value;
};
//随机id
const uuid = ref<string>('');
//历史记录
const historyData = ref<any>();
//应用数据
const appData = ref<any>();
//聊天标题
const chatTitle = ref<any>();
//当前会话的设置
const conversationSettings = ref<Record<string, Record<string, any>>>({});
// 获取当前会话的设置
const getCurrentSettings = computed(() => {
return conversationSettings.value[uuid.value] || {};
});
//对话设置弹窗ref
const settingsModalRef = ref();
//左侧会话的ref
const leftPortalRef = ref();
// 欢迎语(取当前登录用户姓名或用户名)
const userStore = useUserStore();
const welcomeName = computed(() => userStore.getUserInfo?.realname || userStore.getUserInfo?.username || '');
const welcomeText = computed(() => (welcomeName.value ? `你好,${welcomeName.value}。准备好开始了吗?` : '你好,准备好开始了吗?'));
const showWelcome = ref<boolean>(false);
// 指定长度和基数
const getUuid = (len = 10, radix = 16) => {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid: any = [],
i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
} else {
var r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
};
/**
* 重新加载标题
* @param title
*/
function reloadMessageTitle(title) {
showWelcome.value = false;
leftPortalRef.value.addSession(title, uuid.value);
}
// 编辑对话设置
function handleEditSettings() {
if (settingsModalRef.value) {
settingsModalRef.value.open();
}
}
/**
* 应用数据点击返回事件
*
* @param value
*/
function handleAppClick(value) {
//每次点击都是一个新的会话
uuid.value = getUuid(32);
appData.value = value;
chatTitle.value = appData.value.name;
showWelcome.value = true;
}
/**
* 回话点击事件
*
* @param id
*/
function handleTaskClick(id, title) {
showWelcome.value = false;
uuid.value = id;
chatTitle.value = title;
//根据选中的id查询聊天内容
let params = { conversationId: id, sessionType: 'portal' };
//根据id获取历史记录
defHttp.get({ url: '/airag/chat/messages', params }, { isTransformResponse: false }).then((res) => {
if (res.success) {
// 处理新的返回格式包含messages和flowInputs
if (res.result && res.result.messages) {
historyData.value = res.result.messages;
if (res.result?.appData) {
appData.value = res.result.appData;
} else {
appData.value = null;
}
// 加载已保存的设置
if (res.result.flowInputs) {
conversationSettings.value[id] = res.result.flowInputs;
}
} else if (Array.isArray(res.result)) {
// 兼容旧格式
historyData.value = res.result;
} else {
historyData.value = [];
}
} else {
historyData.value = [];
}
});
}
// 当 uuid 变化时,清空右侧聊天的会话数据并初始化当前设置,确保开启全新会话
watch(
() => uuid.value,
(newVal) => {
if (!newVal) return;
// 清空标题与历史记录、防止沿用上一会话数据
historyData.value = [];
// 初始化当前会话设置容器
conversationSettings.value[newVal] = conversationSettings.value[newVal] || {};
}
);
watch(
() => portalRef.value,
() => {
if (portalRef.value.offsetHeight) {
portalRef.value = { height: `${portalRef.value.offsetHeight} px` };
}
}
);
</script>
<style lang="less" scoped>
@width: 260px;
.portal-container {
height: 100%;
width: 100%;
position: absolute;
background: white;
display: flex;
overflow: hidden;
z-index: 800;
border: 1px solid #eeeeee;
}
.leftArea {
width: @width;
transition: 0.3s left;
position: absolute;
left: 0;
height: 100%;
.content {
width: 100%;
height: 100%;
overflow: hidden;
}
&.shrink {
left: -@width;
.toggle-btn {
.icon {
transform: rotate(0deg);
}
}
}
.toggle-btn {
transition:
color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
right 0.3s cubic-bezier(0.4, 0, 0.2, 1),
left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
width: 24px;
height: 24px;
position: absolute;
top: 50%;
right: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: rgb(51, 54, 57);
border: 1px solid rgb(239, 239, 245);
background-color: #fff;
box-shadow: 0 2px 4px 0px #e7e9ef;
transform: translateX(50%) translateY(-50%);
z-index: 1;
}
.icon {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: rotate(180deg);
font-size: 18px;
height: 18px;
svg {
height: 1em;
width: 1em;
vertical-align: top;
}
}
}
.rightArea {
margin-left: @width;
transition: 0.3s margin-left;
&.shrink {
margin-left: 0;
}
flex: 1;
min-width: 0;
}
.emptyArea {
position: absolute;
top: 45%;
left: 45%;
display: flex;
justify-content: center;
align-items: center;
color: #d4d4d4;
}
.emptyArea .welcome-text {
font-size: 32px;
color: #3d4353;
font-weight: 600;
}
</style>

View File

@ -0,0 +1,461 @@
<!-- 左侧应用门户会话设置 -->
<template>
<div class="space-page">
<div class="header">
<img class="header-image" :src="loginLogo" />
<div class="header-name"> JEECG </div>
</div>
<div class="new-session" @click="handleNewSession">
<div class="left-box">
<div class="app-icon">
<Icon icon="ant-design:edit-outlined" size="14"></Icon>
</div>
<div class="app-name">新对话</div>
</div>
</div>
<div class="session-scroll">
<div class="section">
<template v-for="app in apps">
<div class="app-item" :class="activeKey === app.id ? 'active' : ''" @click="handleAppClick(app)">
<div class="app-icon">
<img :src="getAiImg(app)" />
</div>
<div class="app-name" :title="app.name">{{ app.name }}</div>
</div>
</template>
</div>
<div class="section">
<div class="section-title">历史对话</div>
<div v-if="tasks.length" class="task-list">
<div v-for="task in tasks" :key="task.id" class="task-item" :class="activeKey === task.id ? 'active' : ''" @click="handleTaskClick(task)">
<div class="task-title" :title="task.title" v-if="!task.isEdit">{{ task.title }}</div>
<a-input
class="title"
ref="inputRef"
v-if="task.isEdit"
:defaultValue="task.title"
placeholder="请输入标题"
@change="handleInputChange"
@keyup.enter="inputBlur(task)"
/>
<div class="icon-edit">
<a-space>
<span class="icon edit" @click.prevent.stop="handleEdit(task)" v-if="!task.isEdit">
<svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M6.414 15.89L16.556 5.748l-1.414-1.414L5 14.476v1.414zm.829 2H3v-4.243L14.435 2.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414zM3 19.89h18v2H3z"
></path>
</svg>
</span>
<span class="icon del">
<a-popconfirm
:overlayStyle="{ 'z-index': 9999 }"
title="确定删除此记录?"
placement="bottom"
ok-text="确定"
cancel-text="取消"
@confirm.prevent.stop="handleDel(task)"
>
<svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm1 2H6v12h12zm-9 3h2v6H9zm4 0h2v6h-2zM9 4v2h6V4z"
></path>
</svg>
</a-popconfirm>
</span>
</a-space>
</div>
</div>
</div>
<a-empty v-else description="暂无历史对话" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import defaultImg from '@/views/super/airag/aiapp/img/ailogo.png';
import { defHttp } from '@/utils/http/axios';
import loginLogo from '/@/assets/images/logo.png';
const emit = defineEmits(['register', 'app-click', 'task-click', 'new-session-click']);
const defaultApp = ref({
id: '1999373661846880258',
name: '聊天助手',
});
//应用列表
const apps = ref([
{
id: '1998717610730352641',
name: '帮我写作',
icon: 'https://jeecgdev.oss-cn-beijing.aliyuncs.com/upload/test/helpWriting_1765520898059.png',
prologue: '请输入\n出发地\n目的地\n人数',
},
{
id: '1996471445272088578',
name: '图像识别',
icon: 'https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/1dataOCR_1743065089791.png',
prologue: '上传一张图片,我来为你识别图片的内容',
},
{
id: '1902262577996546050',
name: '看图说话',
icon: 'https://jeecgdev.oss-cn-beijing.aliyuncs.com/temp/工具-图片解析_1743065064801.png',
prologue: '上传一张图片,我来为你讲述图片中的故事',
},
]);
//应用数据
const appData = ref<any>({});
//会话
const tasks = ref<any>([]);
/**
* 点击的key
*/
const activeKey = ref<string>('');
/**
* 获取图片
*/
const getAiImg = (app) => {
return getFileAccessHttpUrl(app?.icon) || defaultImg;
};
let inputValue: string = '';
const handleInputChange = (e) => {
inputValue = e.target.value.trim();
};
// 编辑
const handleEdit = (item) => {
item.isEdit = true;
inputValue = item.title;
};
/**
* 应用点击事件
*
* @param app
*/
function handleAppClick(app) {
activeKey.value = app.id;
appData.value = app;
emit('app-click', app);
}
/**
* 添加会话
*/
function addSession(title, id) {
activeKey.value = id;
if (tasks.value?.length > 0) {
let findIndex = tasks.value.findIndex((item) => item.id === id);
if (findIndex >= 0) {
return;
}
}
tasks.value.unshift({ id: id, title: title });
}
/**
* 获取会话
*/
async function getSessionList() {
const res = await defHttp.get(
{
url: '/airag/chat/getConversationsByType',
params: { sessionType: 'portal' },
},
{ isTransformResponse: false }
);
if (res && res.success) {
tasks.value = res.result;
} else {
tasks.value = [];
}
return tasks.value;
}
/**
* 会话点击事件
*
* @param task
*/
function handleTaskClick(task) {
if (task.id === activeKey.value) {
return;
}
activeKey.value = task.id;
emit('task-click', task.id, task.title);
}
// 失去焦点
function inputBlur(item) {
item.isEdit = false;
item.title = inputValue;
defHttp.put(
{
url: '/airag/chat/conversation/update/title',
params: { id: item.id, title: inputValue, sessionType: 'portal' },
},
{ joinParamsToUrl: true }
);
}
/**
* 删除
* @param data
*/
function handleDel(data) {
defHttp
.delete(
{
url: '/airag/chat/conversation/' + data.id + '/portal',
},
{ isTransformResponse: false }
)
.then(() => {
getSessionList();
});
}
/**
* 新对话
*/
function handleNewSession() {
activeKey.value = '';
emit('app-click', defaultApp.value);
}
onMounted(async () => {
activeKey.value = '';
const list = await getSessionList();
if (list && list.length > 0) {
const first = list[0];
activeKey.value = first.id;
emit('task-click', first.id, first.title);
} else {
emit('app-click', defaultApp.value);
}
});
defineExpose({
addSession,
});
</script>
<style scoped lang="less">
.space-page {
padding: 12px 16px;
height: 100%;
background: #fbfcff; // 左侧浅色背景,区分右侧
border-right: 1px solid #eef2f6; // 与右侧的分隔线
display: flex; // 纵向布局,顶部固定,下面滚动
flex-direction: column;
box-sizing: border-box;
}
.space-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.space-title {
font-size: 18px;
font-weight: 600;
}
.section {
margin-top: 12px;
}
.section-title {
font-size: 12px;
font-weight: 600;
margin-bottom: 8px;
}
.app-item {
padding: 8px;
display: flex;
align-items: center;
cursor: pointer;
margin-bottom: 2px;
}
.app-item:hover {
background: rgba(0, 0, 0, 0.04);
border-radius: 10px;
}
.app-icon {
width: 14px;
height: 14px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.app-name {
margin-left: 10px;
font-size: 14px;
color: #1f2329;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.new-session {
margin-top: 10px;
align-items: center;
border-radius: 12px;
display: flex;
justify-content: space-between;
flex-shrink: 0;
height: 36px;
margin-right: 1px;
overflow: visible;
padding: 6px 10px !important;
position: relative;
transition: all 0.15s ease-in-out;
cursor: pointer;
margin-bottom: 2px;
border: 1px solid #e0ecff;
background: #f5f8ff;
}
.new-session:hover {
background: #eaf2ff;
border-color: #d4e5ff;
}
.new-session.active {
background: #eaf2ff;
border-color: #cfe0ff;
}
.new-session .left-box {
display: flex;
align-items: center;
}
.new-session .app-icon {
width: 20px;
height: 20px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background: #eaf2ff;
color: #3761eb;
}
.new-session .app-name {
margin-left: 10px;
font-size: 14px;
color: #3761eb;
font-weight: 600;
}
.task-list {
position: relative;
}
.task-item {
align-items: center;
border-radius: 12px;
display: flex;
flex-shrink: 0;
height: 36px;
margin-right: 1px;
overflow: visible;
padding: 6px 10px !important;
position: relative;
transition: all 0.15s ease-in-out;
cursor: pointer;
margin-bottom: 2px;
justify-content: space-between;
}
.task-item:hover {
background: rgba(0, 0, 0, 0.04);
border-radius: 12px;
.edit,
.del {
display: block;
}
}
.task-title {
font-size: 14px;
color: #1f2329;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.active {
background: rgba(0, 0, 0, 0.04);
border-radius: 12px;
.edit {
display: block;
}
}
.edit,
.del {
display: none;
}
.icon-edit {
display: flex;
}
.session-scroll {
flex: 1; // 占满剩余空间
min-height: 0; // 允许子元素在容器内正确滚动
overflow-y: auto; // 仅列表区域滚动
margin-bottom: 20px;
// 自定义滚动条样式
&::-webkit-scrollbar {
width: 7px;
height: 8px;
}
&::-webkit-scrollbar-thumb {
background-color: #d9dfe7;
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
}
.header {
margin-left: 0;
display: flex;
height: 40px;
padding: 0;
background-color: #fff;
.header-image {
width: 36px;
height: 36px;
}
.header-name {
color: rgba(0, 0, 0, 0.85);
font-size: 16px;
align-self: center;
margin-left: 8px;
}
}
</style>

View File

@ -50,6 +50,15 @@ const ChatRoutes: RouteRecordRaw[] = [
}
],
},
{
path: '/ai/chat/portal',
name: 'ai-chat-portal',
component: () => import('/@/views/super/airag/aiapp/chat/portal/AppPortal.vue'),
meta: {
title: 'AI聊天',
ignoreAuth: false,
},
},
]
/** 注册路由 */

View File

@ -74,7 +74,7 @@
<div class="left-footer" v-if="source!='chatJs'">
AI客服由
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
敲敲云
JEECG AI
</a>
提供
</div>

View File

@ -2,30 +2,35 @@
<div class="p-2">
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
<div class="flex header">
<a-input
<JInput
@pressEnter="loadFlowData"
class="header-search"
size="small"
v-model:value="searchText"
placeholder="请输入流程名称,回车搜索"
></a-input>
/>
</div>
<a-row :span="24">
<a-col :span="12" v-for="item in flowList" @click="handleSelect(item)">
<a-card :style="item.id === flowId ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%;align-items:center">
<img :src="getImage(item.icon)" class="flow-icon"/>
<div style="display: grid;margin-left: 5px;align-items: center">
<span class="checkbox-name ellipsis">{{ item.name }}</span>
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
<span class="tag-input">输入</span>
<div v-for="(metaItem, index) in item.metadata">
<a-tag color="#f2f3f8" class="tags-meadata">
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
</a-tag>
<!-- begin 流程选择支持单选和多选 -->
<a-card :style="getCardStyle(item)" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%;align-items:center; justify-content: space-between">
<div style="display: flex; align-items:center; flex: 1; overflow: hidden; margin-right: 10px;">
<img :src="getImage(item.icon)" class="flow-icon"/>
<div style="display: grid;margin-left: 5px;align-items: center">
<span class="checkbox-name ellipsis">{{ item.name }}</span>
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
<span class="tag-input">输入</span>
<div v-for="(metaItem, index) in item.metadata">
<a-tag color="#f2f3f8" class="tags-meadata">
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
</a-tag>
</div>
</div>
</div>
</div>
<a-checkbox v-if="multiple" v-model:checked="item.checked" @click.stop @change="(e)=>handleChange(e,item)"></a-checkbox>
<!-- end 流程选择支持单选和多选 -->
</div>
<div class="text-desc mt-10">
{{ item.descr || '暂无描述' }}
@ -33,8 +38,13 @@
</a-card>
</a-col>
</a-row>
<div v-if="flowId" class="use-select">
已选择 <span class="ellipsis" style="max-width: 150px">{{flowData.name}}</span>
<div v-if="showFooterSelection" class="use-select">
<template v-if="!multiple">
已选择 <span class="ellipsis" style="max-width: 100px">{{flowData.name}}</span>
</template>
<template v-else>
已选择 {{ flowId.length }} 个流程
</template>
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
</div>
<Pagination
@ -54,10 +64,11 @@
</template>
<script lang="ts">
import { ref, unref } from 'vue';
import { ref, unref, computed } from 'vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModal, useModalInner } from '@/components/Modal';
import { Pagination } from 'ant-design-vue';
import {JInput} from "@/components/Form";
import { list } from '@/views/super/airag/aiknowledge/AiKnowledgeBase.api';
import knowledge from '/@/views/super/airag/aiknowledge/icon/knowledge.png';
import { cloneDeep } from 'lodash-es';
@ -71,14 +82,20 @@
components: {
Pagination,
BasicModal,
JInput,
},
emits: ['success', 'register'],
props: {
multiple:{ type: Boolean, default: false },
// 排除的流程ID多个逗号分隔
excludedIds: { type: String, default: '' },
},
setup(props, { emit }) {
const title = ref<string>('选择流程');
//应用类型
const flowId = ref<any>([]);
//流程数据
const flowList = ref<any>({});
const flowList = ref<any>([]);
//选中的数据
const flowData = ref<any>({})
//当前页数
@ -93,9 +110,16 @@
const pageSizeOptions = ref<any>(['10', '20', '30']);
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
flowId.value = data.flowId ? cloneDeep(data.flowId) : '';
flowData.value = data.flowData ? cloneDeep(data.flowData) : {};
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (props.multiple) {
flowId.value = data.flowId ? (Array.isArray(data.flowId) ? cloneDeep(data.flowId) : data.flowId.split(',')) : [];
flowData.value = data.flowData ? cloneDeep(data.flowData) : [];
} else {
flowId.value = data.flowId ? cloneDeep(data.flowId) : '';
flowData.value = data.flowData ? cloneDeep(data.flowData) : {};
}
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px', height: 'calc(100% - 20px)', overflowY: 'auto' } });
//update-end---author:wangshuai---date:2025-12-24---for:流程选择支持单选和多选---
loadFlowData();
});
@ -116,20 +140,27 @@
//复选框选中事件
const handleSelect = (item) => {
if(flowId.value === item.id){
flowId.value = "";
flowData.value = null;
return;
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if(!props.multiple) {
if (flowId.value === item.id) {
flowId.value = "";
flowData.value = null;
return;
}
flowId.value = item.id;
flowData.value = item;
} else {
item.checked = !item.checked;
updateMultipleSelection(item);
}
flowId.value = item.id;
flowData.value = item;
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
};
/**
* 加载AI流程
*/
function loadFlowData() {
let params = {
let params: Recordable = {
pageNo: pageNo.value,
pageSize: pageSize.value,
column: 'createTime',
@ -137,10 +168,21 @@
name: searchText.value,
status: 'enable,release'
};
// 排除的流程ID多个逗号分隔
if (props.excludedIds) {
params.excludedIds = props.excludedIds;
}
getAiFlowList(params).then((res) =>{
if(res){
for (const data of res.records) {
data.metadata = getMetadata(data.metadata);
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (props.multiple && Array.isArray(flowId.value) && flowId.value.includes(data.id)) {
data.checked = true;
}
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
}
flowList.value = res.records;
total.value = res.total;
@ -170,8 +212,18 @@
* 清空选中状态
*/
function handleClearClick() {
flowId.value = "";
flowData.value = null;
//update-begin---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
if (!props.multiple) {
flowId.value = "";
flowData.value = null;
} else {
flowId.value = [];
flowData.value = [];
if (flowList.value && Array.isArray(flowList.value)) {
flowList.value.forEach(item => item.checked = false);
}
}
//update-end---author:wangshuai---date:2025-12-24---for: 流程选择支持单选和多选 ---
}
/**
@ -194,6 +246,41 @@
let inputsArr = parse['inputs'];
return [...inputsArr];
}
/*===========begin 流程选择支持多选 ===========*/
function handleChange(e, item) {
updateMultipleSelection(item);
}
function updateMultipleSelection(item) {
if (item.checked) {
if (!flowId.value.includes(item.id)) {
flowId.value.push(item.id);
flowData.value.push(item);
}
} else {
const index = flowId.value.indexOf(item.id);
if (index > -1) {
flowId.value.splice(index, 1);
flowData.value.splice(index, 1);
}
}
}
const showFooterSelection = computed(() => {
if (props.multiple) {
return flowId.value && flowId.value.length > 0;
}
return !!flowId.value;
});
function getCardStyle(item) {
if (props.multiple) {
return item.checked ? { border: '1px solid #3370ff' } : {};
}
return item.id === flowId.value ? { border: '1px solid #3370ff' } : {};
}
/*===========end 流程选择支持多选 ===========*/
return {
registerModal,
@ -214,6 +301,9 @@
handleClearClick,
flowData,
getImage,
handleChange,
getCardStyle,
showFooterSelection,
};
},
};
@ -243,7 +333,7 @@
.list-footer {
position: absolute;
bottom: 0;
left: 260px;
left: 210px;
}
.checkbox-card {
margin-bottom: 10px;
@ -333,4 +423,14 @@
font-weight: 500;
max-width: 100%;
}
:deep(.jeecg-modal-wrapper){
height: calc(100% - 20px);
}
.scroll-container {
height: 480px;
overflow-y: auto;
padding-bottom: 20px;
}
</style>

View File

@ -12,19 +12,24 @@
</div>
<a-row :span="24">
<a-col :span="12" v-for="item in appKnowledgeOption" @click="handleSelect(item)">
<a-card :style="item.checked ? { border: '1px solid #3370ff' } : {}" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<a-card :style="getCardStyle(item)" hoverable class="checkbox-card" :body-style="{ width: '100%' }">
<div style="display: flex; width: 100%; justify-content: space-between">
<div>
<img class="checkbox-img" :src="knowledge" />
<span class="checkbox-name">{{ item.name }}</span>
</div>
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e)=>handleChange(e,item)"> </a-checkbox>
<a-checkbox v-if="multiple" v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e)=>handleChange(e,item)"> </a-checkbox>
</div>
</a-card>
</a-col>
</a-row>
<div v-if="knowledgeIds.length > 0" class="use-select">
已选择 {{ knowledgeIds.length }} 知识库
<div v-if="knowledgeIds && knowledgeIds.length > 0" class="use-select">
<template v-if="!multiple">
已选择 <span class="ellipsis" style="max-width: 150px">{{knowledgeData.name}}</span>
</template>
<template v-else>
已选择 {{ knowledgeIds.length }} 知识库
</template>
<span style="margin-left: 8px; color: #3d79fb; cursor: pointer" @click="handleClearClick">清空</span>
</div>
<Pagination
@ -59,6 +64,10 @@
BasicModal,
},
emits: ['success', 'register'],
props: {
multiple:{ type: Boolean, default: true },
type: { type: String, default: 'knowledge' }
},
setup(props, { emit }) {
const title = ref<string>('添加关联知识库');
@ -80,8 +89,15 @@
const pageSizeOptions = ref<any>(['10', '20', '30']);
//注册modal
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds.split(',')) : [];
knowledgeData.value = data.knowledgeDataList ? cloneDeep(data.knowledgeDataList) : [];
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (props.multiple) {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds.split(',')) : [];
knowledgeData.value = data.knowledgeDataList ? cloneDeep(data.knowledgeDataList) : [];
} else {
knowledgeIds.value = data.knowledgeIds ? cloneDeep(data.knowledgeIds) : '';
knowledgeData.value = data.knowledgeData ? cloneDeep(data.knowledgeData) : {};
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
loadKnowledgeData();
});
@ -91,7 +107,13 @@
*/
async function handleOk() {
console.log("知识库确定选中的值",knowledgeData.value);
emit('success', knowledgeIds.value, knowledgeData.value);
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (props.multiple) {
emit('success', knowledgeIds.value, knowledgeData.value);
} else {
emit('success', knowledgeIds.value, knowledgeData.value);
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
handleCancel();
}
@ -104,27 +126,39 @@
//复选框选中事件
function handleSelect(item){
let id = item.id;
const target = appKnowledgeOption.value.find((item) => item.id === id);
if (target) {
target.checked = !target.checked;
}
//存放选中的知识库的id
if (!knowledgeIds.value || knowledgeIds.value.length == 0) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
return;
}
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
if (findIndex === -1) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if(!props.multiple) {
if (knowledgeIds.value === item.id) {
knowledgeIds.value = "";
knowledgeData.value = null;
return;
}
knowledgeIds.value = item.id;
knowledgeData.value = item;
} else {
knowledgeIds.value.splice(findIndex, 1);
knowledgeData.value.splice(findIndex, 1);
let id = item.id;
const target = appKnowledgeOption.value.find((item) => item.id === id);
if (target) {
target.checked = !target.checked;
}
//存放选中的知识库的id
if (!knowledgeIds.value || knowledgeIds.value.length == 0) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
return;
}
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
if (findIndex === -1) {
knowledgeIds.value.push(id);
knowledgeData.value.push(item);
} else {
knowledgeIds.value.splice(findIndex, 1);
knowledgeData.value.splice(findIndex, 1);
}
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
}
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
}
/**
@ -135,10 +169,11 @@
pageNo: pageNo.value,
pageSize: pageSize.value,
name: searchText.value,
type: props.type,
};
list(params).then((res) => {
if (res.success) {
if (knowledgeIds.value.length > 0) {
if (props.multiple && knowledgeIds.value.length > 0) {
for (const item of res.result.records) {
if (knowledgeIds.value.includes(item.id)) {
item.checked = true;
@ -171,11 +206,18 @@
* 清空选中状态
*/
function handleClearClick() {
knowledgeIds.value = [];
knowledgeData.value = [];
appKnowledgeOption.value.forEach((item) => {
item.checked = false;
});
//update-begin---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
if (!props.multiple) {
knowledgeIds.value = "";
knowledgeData.value = null;
} else {
knowledgeIds.value = [];
knowledgeData.value = [];
appKnowledgeOption.value.forEach((item) => {
item.checked = false;
});
}
//update-end---author:wangshuai---date:2025-12-25---for:知识库选择支持单选和多选---
}
/**
@ -197,6 +239,18 @@
}
}
/**
* 获取卡片样式
*
* @param item
*/
function getCardStyle(item) {
if (props.multiple) {
return item.checked ? { border: '1px solid #3370ff' } : {};
}
return item.id === knowledgeIds.value ? { border: '1px solid #3370ff' } : {};
}
return {
registerModal,
title,
@ -215,12 +269,19 @@
loadKnowledgeData,
handleClearClick,
handleChange,
getCardStyle,
knowledgeData,
};
},
};
</script>
<style scoped lang="less">
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header {
color: #646a73;
width: 100%;

View File

@ -34,6 +34,9 @@ export default {
if(!data.metadata.hasOwnProperty("temperature") ){
data.metadata['temperature'] = 0.7;
}
if(!data.metadata.hasOwnProperty("timeout") ){
data.metadata['timeout'] = 60;
}
}else{
if(!data.metadata.hasOwnProperty("topNumber") ){
data.metadata['topNumber'] = 4;
@ -41,6 +44,9 @@ export default {
if(!data.metadata.hasOwnProperty("similarity") ){
data.metadata['similarity'] = 0.76;
}
if(!data.metadata.hasOwnProperty("timeout") ){
data.metadata['timeout'] = 60;
}
}
setTimeout(()=>{
aiModelSeniorFormRef.value.setModalParams(data.metadata);

Some files were not shown because too many files have changed in this diff Show More