feat(文件管理): 文件管理部分页面&部分组件调整&ms-list 列表组件&fileList 上传文件列表组件

This commit is contained in:
baiqi 2023-09-08 11:11:36 +08:00 committed by fit2-zhao
parent 226bbcf175
commit f09edacc9a
33 changed files with 2278 additions and 667 deletions

View File

@ -0,0 +1,14 @@
import MSR from '@/api/http/index';
import { FileDetailUrl, FileListUrl } from '@/api/requrls/project-management/fileManagement';
import type { TableQueryParams, CommonList } from '@/models/common';
// 获取文件列表
export function getFileList(data: TableQueryParams) {
return MSR.post<CommonList<any>>({ url: FileListUrl, data });
}
// 获取文件详情
export function getFileDetail(data: TableQueryParams) {
return MSR.post<CommonList<any>>({ url: FileDetailUrl, data });
}

View File

@ -0,0 +1,2 @@
export const FileListUrl = '/project/member/get-member/option';
export const FileDetailUrl = '/project/member/get-member/option';

View File

@ -1,7 +1,7 @@
@font-face {
font-family: iconfont; /* Project id 3462279 */
src: url('iconfont.woff2?t=1693381191489') format('woff2'), url('iconfont.woff?t=1693381191489') format('woff'),
url('iconfont.ttf?t=1693381191489') format('truetype'), url('iconfont.svg?t=1693381191489#iconfont') format('svg');
src: url('iconfont.woff2?t=1693882799546') format('woff2'), url('iconfont.woff?t=1693882799546') format('woff'),
url('iconfont.ttf?t=1693882799546') format('truetype'), url('iconfont.svg?t=1693882799546#iconfont') format('svg');
}
.iconfont {
font-size: 16px;
@ -10,6 +10,21 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-icon_folder_expansion1::before {
content: '\e75b';
}
.icon-icon_feishu::before {
content: '\e75a';
}
.icon-a-icon_file-jar_colorful::before {
content: '\e759';
}
.icon-icon_dingding::before {
content: '\e757';
}
.icon-icon_bot1::before {
content: '\e758';
}
.icon-icon_folder_collapse1::before {
content: '\e756';
}
@ -289,9 +304,6 @@
.icon-icon_reduce::before {
content: '\e63e';
}
.icon-icon_folder_expansion::before {
content: '\e633';
}
.icon-icon_functional_testing::before {
content: '\e630';
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,41 @@
"css_prefix_text": "icon-",
"description": "DE、MS项目icon管理",
"glyphs": [
{
"icon_id": "37233527",
"name": "icon_folder_expansion",
"font_class": "icon_folder_expansion1",
"unicode": "e75b",
"unicode_decimal": 59227
},
{
"icon_id": "37227747",
"name": "icon_feishu",
"font_class": "icon_feishu",
"unicode": "e75a",
"unicode_decimal": 59226
},
{
"icon_id": "37227608",
"name": "icon_file- jar_colorful",
"font_class": "a-icon_file-jar_colorful",
"unicode": "e759",
"unicode_decimal": 59225
},
{
"icon_id": "37227685",
"name": "icon_dingding",
"font_class": "icon_dingding",
"unicode": "e757",
"unicode_decimal": 59223
},
{
"icon_id": "37227125",
"name": "icon_bot",
"font_class": "icon_bot1",
"unicode": "e758",
"unicode_decimal": 59224
},
{
"icon_id": "37159110",
"name": "icon_folder_collapse",
@ -656,13 +691,6 @@
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "36195396",
"name": "icon_folder_expansion",
"font_class": "icon_folder_expansion",
"unicode": "e633",
"unicode_decimal": 58931
},
{
"icon_id": "36194683",
"name": "icon_functional_testing",

View File

@ -14,6 +14,16 @@
/>
<missing-glyph />
<glyph glyph-name="icon_folder_expansion1" unicode="&#59227;" d="M399.701 810.667a42.667 42.667 0 0 0 32.811-15.36l99.456-119.339h361.813c46.123 0 83.968-35.755 87.296-81.067l0.256-6.57v-202.07a42.667 42.667 0 1 0-85.333 0v202.07a2.261 2.261 0 0 1-2.261 2.261H512a42.667 42.667 0 0 0-32.768 15.36l-99.499 119.381H130.261c-0.853 0-1.578-0.426-1.962-1.109l-0.299-1.152V4.48c0-1.237 1.024-2.219 2.261-2.219h336.811a42.667 42.667 0 1 0 0-85.333h-336.81a87.595 87.595 0 0 0-87.595 87.552V723.072c0 48.384 39.21 87.595 87.594 87.595h269.44z m456.875-528.982l112.256-112.298a42.667 42.667 0 0 0 0-60.331L856.576-3.243a42.667 42.667 0 1 0-60.373 60.374l82.09 82.133-82.09 82.09a42.667 42.667 0 0 0-3.542 56.32l3.542 4.011a42.667 42.667 0 0 0 60.373 0z m-179.67 0a42.667 42.667 0 0 0 0-60.33l-82.133-82.091 82.134-82.133a42.667 42.667 0 0 0 3.541-56.32l-3.541-4.054a42.667 42.667 0 0 0-60.331 0L504.277 109.056a42.667 42.667 0 0 0 0 60.33l112.299 112.3a42.667 42.667 0 0 0 60.33 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_feishu" unicode="&#59226;" d="M981.333333 835.584l-254.677333-913.749333-224.853333 219.605333-3.584 203.605333 226.858666 230.314667c12.288-8.789333 25.685333-12.8 40.277334-11.946667 21.802667 1.194667 35.925333 14.378667 42.538666 22.144 6.613333 7.765333 14.378667 20.693333 13.738667 39.893334-0.426667 12.8-4.650667 24.618667-12.629333 35.413333L981.333333 835.626667zM956.416 853.333333l-173.824-169.301333c-28.245333 12.928-52.394667 10.197333-72.448-8.277333-11.733333-10.794667-19.285333-26.026667-19.285333-45.226667 0-8.533333 3.114667-19.2 9.301333-31.872l-234.282667-228.437333-203.605333 3.541333L42.666667 598.656 956.416 853.333333z" horiz-adv-x="1024" />
<glyph glyph-name="a-icon_file-jar_colorful" unicode="&#59225;" d="M648.533 853.333a42.667 42.667 0 0 0 33.323-16L852.523 624l2.73-3.925 1.152-1.878a40.021 40.021 0 0 0 4.054-9.941 41.259 41.259 0 0 0 1.109-5.973l0.299-4.95V512h42.666a85.333 85.333 0 0 0 85.334-85.333V128a85.333 85.333 0 0 0-85.334-85.333h-42.666v-85.334a42.667 42.667 0 0 0-42.667-42.666H221.867a42.667 42.667 0 0 0-42.667 42.666v85.334h-42.667A85.333 85.333 0 0 0 51.2 128V426.667A85.333 85.333 0 0 0 136.533 512H179.2V810.667a42.667 42.667 0 0 0 42.667 42.666h426.666z m128-810.666h-512V0h512v42.667z m128 384h-768V128h768V426.667z m-556.8-42.24v-116.182c0-24.405-2.176-43.008-6.528-55.722-4.309-12.758-13.013-23.51-26.112-32.384-13.056-8.832-29.781-13.227-50.218-13.227-21.59 0-38.315 2.859-50.176 8.704a63.104 63.104 0 0 0-27.478 25.6c-6.485 11.221-10.282 25.13-11.434 41.643l63.146 8.618c0.086-9.429 0.896-16.469 2.475-21.034a22.87 22.87 0 0 1 7.85-11.094c2.56-1.834 6.145-2.73 10.795-2.73 7.382 0 12.8 2.73 16.256 8.234 3.456 5.504 5.206 14.763 5.206 27.734v131.84h66.176z m174.848 0l80.299-213.76h-69.12l-10.496 35.285H448l-10.368-35.285h-67.456l80.341 213.76h72.064z m212.48 0c20.395 0 36.011-1.707 46.806-5.206 10.794-3.498 19.498-9.984 26.112-19.498 6.57-9.472 9.898-20.992 9.898-34.603 0-11.861-2.56-22.101-7.594-30.72a59.307 59.307 0 0 0-20.864-20.907c-5.632-3.413-13.355-6.229-23.168-8.49 7.893-2.603 13.61-5.248 17.194-7.851 2.432-1.75 5.974-5.504 10.582-11.221 4.608-5.76 7.68-10.155 9.258-13.27l31.958-61.994h-74.667l-35.285 65.322c-4.48 8.448-8.491 13.952-11.947 16.47a27.733 27.733 0 0 1-16.213 4.992h-5.846v-86.784H624.94v213.76h110.08z m-249.258-55.382l-23.467-76.842h47.104l-23.637 76.8z m234.496 12.246h-29.014v-43.478h27.862c2.986 0 8.832 0.982 17.493 2.944a17.493 17.493 0 0 1 10.71 6.699 19.755 19.755 0 0 1 4.18 12.373c0 6.912-2.218 12.246-6.57 15.915-4.395 3.712-12.587 5.547-24.661 5.547zM563.2 768H264.533v-256h512v42.667H605.867a42.667 42.667 0 0 0-42.667 42.666V768z m85.333-25.6V640h81.92l-81.92 102.4z" horiz-adv-x="1024" />
<glyph glyph-name="icon_dingding" unicode="&#59223;" d="M815.445333 584.533333c-26.794667 10.581333-219.221333 80.981333-295.594666 108.714667C361.173333 751.573333 177.92 839.082667 149.717333 852.096a13.525333 13.525333 0 0 1-14.293333-1.877333 12.757333 12.757333 0 0 1-5.12-8.32c-10.154667-62.08 12.970667-169.386667 69.418667-208.768 34.218667-24.149333 259.498667-97.706667 351.573333-126.890667 2.816-0.938667 1.322667-4.693333-0.938667-4.138667-95.744 21.76-347.392 80.042667-352.512 80.512-5.12 0.469333-8.789333-0.426667-12.458666-4.138666a14.08 14.08 0 0 1-3.669334-12.074667c12.458667-69.418667 78.165333-159.232 135.509334-170.794667 33.792-6.954667 173.44-3.285333 231.296-1.877333 2.773333 0 3.242667-3.669333 0.469333-4.138667-65.237333-9.258667-223.957333-33.322667-228.096-34.261333a12.885333 12.885333 0 0 1-9.728-7.381333 13.952 13.952 0 0 1 0-13.013334c9.728-20.394667 37.973333-47.658667 37.973333-47.658666 61.994667-63.445333 129.536-56.064 146.602667-52.778667a426.453333 426.453333 0 0 1 77.226667 25.472 2.005333 2.005333 0 0 0 2.858666-2.346667l-36.096-129.578666c-0.426667-1.408 0.469333-2.816 1.834667-2.816l107.818667-0.938667c1.322667 0 2.261333-1.365333 1.792-2.688l-62.890667-204.245333c-0.938667-2.304 2.346667-3.712 3.712-1.834667l236.373333 300.373333c0.981333 1.450667 0 3.328-1.834666 3.328h-112.384c-1.877333 0-2.816 1.792-1.877334 3.2 15.274667 25.002667 114.261333 188.885333 146.176 252.330667 10.197333 18.090667 14.336 43.946667 7.381334 63.402667-6.442667 19.456-21.674667 34.730667-50.346667 46.293333v0.042667z" horiz-adv-x="1024" />
<glyph glyph-name="icon_bot1" unicode="&#59224;" d="M513.024 896a128 128 0 0 0 41.685333-249.088L554.666667 512h256a85.333333 85.333333 0 0 0 85.333333-85.333333v-469.333334a85.333333 85.333333 0 0 0-85.333333-85.333333H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333V426.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h256V647.68A128.042667 128.042667 0 0 0 513.024 896zM810.666667 426.666667H213.333333v-469.333334h597.333334V426.666667z m-213.333334-298.666667a42.666667 42.666667 0 0 0 0-85.333333h-170.666666a42.666667 42.666667 0 0 0 0 85.333333h170.666666z m-213.333333 170.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m256 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334zM42.666667 341.333333a42.666667 42.666667 0 0 0 42.666666-42.666666v-170.666667a42.666667 42.666667 0 0 0-85.333333 0v170.666667a42.666667 42.666667 0 0 0 42.666667 42.666666z m938.666666 0a42.666667 42.666667 0 0 0 42.666667-42.666666v-170.666667a42.666667 42.666667 0 0 0-85.333333 0v170.666667a42.666667 42.666667 0 0 0 42.666666 42.666666z" horiz-adv-x="1024" />
<glyph glyph-name="icon_folder_collapse1" unicode="&#59222;" d="M42.666667 725.333333c0 47.146667 40.021333 85.333333 89.386666 85.333334h268.202667c13.226667 0 25.856-5.632 34.346667-15.36L532.906667 682.666667h358.997333C941.312 682.666667 981.333333 644.48 981.333333 597.333333v-554.666666c0-47.146667-40.021333-85.333333-89.386666-85.333334H132.053333C82.688-42.666667 42.666667-4.48 42.666667 42.666667V725.333333z m336.64 0H132.053333v-682.666666h759.893334V597.333333H512c-13.226667 0-25.856 5.632-34.346667 15.36L379.306667 725.333333z m190.464-268.501333c17.493333 16.64 45.781333 16.64 63.232 0l111.786666-106.666667a41.301333 41.301333 0 0 0 0-60.330666l-111.786666-106.666667c-17.493333-16.64-45.738667-16.64-63.232 0a41.301333 41.301333 0 0 0 0 60.330667l80.170666 76.501333-80.170666 76.501333a41.301333 41.301333 0 0 0 0 60.330667z m-115.541334-60.330667a41.301333 41.301333 0 0 1 0 60.330667 46.208 46.208 0 0 1-63.232 0l-111.786666-106.666667a41.301333 41.301333 0 0 1 0-60.330666l111.786666-106.666667c17.493333-16.64 45.738667-16.64 63.232 0a41.301333 41.301333 0 0 1 0 60.330667L374.058667 320l80.170666 76.501333z" horiz-adv-x="1024" />
<glyph glyph-name="icon_folder_filled1" unicode="&#59221;" d="M42.666667 725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667h357.632a42.666667 42.666667 0 0 0 38.144-23.594667L512 682.666667h426.666667a42.666667 42.666667 0 0 0 42.666666-42.666667v-597.333333a42.666667 42.666667 0 0 0-42.666666-42.666667H85.333333a42.666667 42.666667 0 0 0-42.666666 42.666667V725.333333z m106.666666-128a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h725.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-725.333334z" horiz-adv-x="1024" />
@ -200,8 +210,6 @@
<glyph glyph-name="icon_reduce" unicode="&#58942;" d="M883.2 853.333333a106.666667 106.666667 0 0 0 106.666667-106.666666v-725.333334a106.666667 106.666667 0 0 0-106.666667-106.666666h-725.333333a106.666667 106.666667 0 0 0-106.666667 106.666666v725.333334A106.666667 106.666667 0 0 0 157.866667 853.333333h725.333333z m0-85.333333h-725.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-725.333334a21.333333 21.333333 0 0 1 21.333334-21.333333h725.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v725.333334a21.333333 21.333333 0 0 1-21.333333 21.333333z m-173.013333-341.333333a42.666667 42.666667 0 1 0 0-85.333334h-379.306667a42.666667 42.666667 0 0 0 0 85.333334h379.306667z" horiz-adv-x="1066" />
<glyph glyph-name="icon_folder_expansion" unicode="&#58931;" d="M399.701333 810.666667a42.666667 42.666667 0 0 0 32.810667-15.36l99.456-119.338667h361.813333c46.122667 0 83.968-35.754667 87.296-81.066667l0.256-6.570666v-202.069334a42.666667 42.666667 0 1 0-85.333333 0V588.330667a2.261333 2.261333 0 0 1-2.261333 2.261333H512a42.666667 42.666667 0 0 0-32.768 15.36L379.733333 725.333333H130.261333c-0.853333 0-1.578667-0.426667-1.962666-1.109333L128 723.072v-718.592c0-1.237333 1.024-2.218667 2.261333-2.218667h336.810667a42.666667 42.666667 0 1 0 0-85.333333H130.261333A87.594667 87.594667 0 0 0 42.666667 4.48V723.072C42.666667 771.456 81.877333 810.666667 130.261333 810.666667h269.44z m456.874667-528.981334l112.256-112.298666a42.666667 42.666667 0 0 0 0-60.330667l-112.256-112.298667a42.666667 42.666667 0 1 0-60.373333 60.373334l82.090666 82.133333-82.090666 82.090667a42.666667 42.666667 0 0 0-3.541334 56.32l3.541334 4.010666a42.666667 42.666667 0 0 0 60.373333 0z m-179.669333 0a42.666667 42.666667 0 0 0 0-60.330666l-82.133334-82.090667 82.133334-82.133333a42.666667 42.666667 0 0 0 3.541333-56.32l-3.541333-4.053334a42.666667 42.666667 0 0 0-60.330667 0l-112.298667 112.298667a42.666667 42.666667 0 0 0 0 60.330667l112.298667 112.298666a42.666667 42.666667 0 0 0 60.330667 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_functional_testing" unicode="&#58928;" d="M85.333333 768a42.666667 42.666667 0 0 0 42.666667 42.666667h768a42.666667 42.666667 0 0 0 42.666667-42.666667v-85.333333H85.333333V768z m704-533.333333a170.666667 170.666667 0 1 0 0-341.333334 170.666667 170.666667 0 0 0 0 341.333334z m4.608-69.205334a7.765333 7.765333 0 0 1-8.661333-6.784l-22.698667-184.746666c-0.512-4.266667 2.517333-8.149333 6.784-8.661334l15.36-1.877333c4.266667-0.554667 8.106667 2.474667 8.661334 6.741333l22.698666 184.746667a7.765333 7.765333 0 0 1-6.741333 8.704l-15.402667 1.877333zM938.666667 597.333333v-352.298666A234.666667 234.666667 0 0 1 563.498667 0H128a42.666667 42.666667 0 0 0-42.666667 42.666667V597.333333h853.333334z m-76.544-482.090666l-12.714667-8.874667a7.765333 7.765333 0 0 1-1.877333-10.794667l22.016-31.488-22.4-32a7.765333 7.765333 0 0 1 1.877333-10.837333l12.714667-8.874667a7.765333 7.765333 0 0 1 10.794666 1.877334l31.146667 44.501333a7.808 7.808 0 0 1 1.237333 2.688 7.765333 7.765333 0 0 1-0.853333 7.424l-31.146667 44.501333a7.765333 7.765333 0 0 1-10.794666 1.877334z m-155.989334-1.493334l-31.146666-44.501333a7.808 7.808 0 0 1-1.237334-2.688 7.722667 7.722667 0 0 1 0.853334-7.424l31.146666-44.501333c2.432-3.498667 7.253333-4.352 10.794667-1.877334l12.714667 8.874667a7.765333 7.765333 0 0 1 1.877333 10.794667l-22.016 31.488 22.4 32a7.765333 7.765333 0 0 1-1.877333 10.837333l-12.714667 8.874667a7.765333 7.765333 0 0 1-10.794667-1.877334z m-215.466666 65.792H357.333333a32 32 0 0 1 0-64H490.666667a32 32 0 0 1 0 64zM228.053333 472.405333a32 32 0 0 1 0-45.226666l101.802667-101.845334-101.802667-101.802666a32 32 0 0 1 45.226667-45.269334l124.458667 124.458667a32 32 0 0 1 0 45.226667L273.28 472.405333a32 32 0 0 1-45.226667 0z" horiz-adv-x="1024" />
<glyph glyph-name="icon_full_screen_one" unicode="&#58929;" d="M704 810.666667H896a42.666667 42.666667 0 0 0 42.666667-42.666667v-192a42.666667 42.666667 0 1 0-85.333334 0V667.136l-140.501333-140.501333a42.666667 42.666667 0 1 0-60.330667 60.330666L790.826667 725.333333H704a42.666667 42.666667 0 1 0 0 85.333334zM170.666667 682.666667v-106.666667a42.666667 42.666667 0 0 0-85.333334 0V768a42.666667 42.666667 0 0 0 42.666667 42.666667h192a42.666667 42.666667 0 0 0 0-85.333334H246.912l124.416-138.24a42.666667 42.666667 0 1 0-60.032-60.586666L170.666667 682.666667z m200.661333-499.626667a42.666667 42.666667 0 0 1-60.032 60.586667L170.666667 104.448V192a42.666667 42.666667 0 1 1-85.333334 0V0a42.666667 42.666667 0 0 1 42.666667-42.666667h192a42.666667 42.666667 0 1 1 0 85.333334H229.546667l141.781333 140.373333z m283.306667 60.458667a42.666667 42.666667 0 0 0 60.373333 0L853.333333 105.173333V192a42.666667 42.666667 0 1 0 85.333334 0V0a42.666667 42.666667 0 0 0-42.666667-42.666667h-192a42.666667 42.666667 0 1 0 0 85.333334h91.136l-140.501333 140.501333a42.666667 42.666667 0 0 0 0 60.330667z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

@ -161,6 +161,7 @@
.btn-base-sec-active();
}
.arco-btn-text {
color: rgb(var(--primary-5)) !important;
.btn-text-sec-hover();
.btn-text-sec-active();
.btn-text-sec-disabled();
@ -335,7 +336,7 @@
.arco-radio-checked {
@apply bg-white;
color: rgb(var(--primary-4));
color: rgb(var(--primary-5));
}
}
.arco-radio {
@ -650,3 +651,8 @@
.arco-picker-input-active input {
border-radius: var(--border-radius-mini);
}
/** tooltip **/
.arco-trigger-arrow {
border-bottom-right-radius: var(--border-radius-mini) !important;
}

View File

@ -0,0 +1,51 @@
<svg width="64" height="46" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="46" rx="4" fill="url(#paint0_linear_5116_233479)"/>
<g opacity="0.3" filter="url(#filter0_f_5116_233479)">
<rect x="20" y="39" width="24" height="2" rx="1" fill="#811FA3"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.6667 7C19.1939 7 18 8.16406 18 9.6V32.4C18 33.836 19.1939 35 20.6667 35H41.3333C42.8061 35 44 33.836 44 32.4V13.5C44 12.064 42.8061 10.9 41.3333 10.9H32.6245L29.691 7.46776C29.4376 7.17137 29.0624 7 28.6667 7H20.6667Z" fill="#811FA3"/>
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="#811FA3"/>
<g filter="url(#filter1_i_5116_233479)">
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="url(#paint1_linear_5116_233479)"/>
</g>
<g filter="url(#filter2_d_5116_233479)">
<path d="M29.1439 25.6719C29.1439 26.4826 28.8773 27.0879 28.3439 27.4879C27.8106 27.8826 27.0986 28.0799 26.2079 28.0799C25.5679 28.0799 25.0213 27.9786 24.5679 27.7759C24.1199 27.5679 23.7813 27.3066 23.5519 26.9919C23.3279 26.6772 23.2159 26.3599 23.2159 26.0399C23.2159 25.9919 23.2319 25.9519 23.2639 25.9199C23.3013 25.8826 23.3439 25.8639 23.3919 25.8639H25.1999C25.2479 25.8639 25.2879 25.8799 25.3199 25.9119C25.3573 25.9439 25.3839 25.9866 25.3999 26.0399C25.5013 26.3599 25.7573 26.5199 26.1679 26.5199C26.4186 26.5199 26.6159 26.4612 26.7599 26.3439C26.9093 26.2266 26.9839 26.0586 26.9839 25.8399V24.0399H24.0639C24.0053 24.0399 23.9546 24.0186 23.9119 23.9759C23.8693 23.9332 23.8479 23.8826 23.8479 23.8239V22.6159C23.8479 22.5572 23.8693 22.5066 23.9119 22.4639C23.9546 22.4212 24.0053 22.3999 24.0639 22.3999H28.9279C28.9866 22.3999 29.0373 22.4212 29.0799 22.4639C29.1226 22.5066 29.1439 22.5572 29.1439 22.6159V25.6719Z" fill="white"/>
<path d="M32.0448 27.7279C31.9755 27.9092 31.8555 27.9999 31.6848 27.9999H30.1728C30.1248 27.9999 30.0822 27.9839 30.0448 27.9519C30.0128 27.9146 29.9968 27.8719 29.9968 27.8239L30.0048 27.7759L31.7488 22.6639C31.7702 22.5946 31.8102 22.5332 31.8688 22.4799C31.9275 22.4266 32.0075 22.3999 32.1088 22.3999H34.3328C34.4342 22.3999 34.5142 22.4266 34.5728 22.4799C34.6315 22.5332 34.6715 22.5946 34.6928 22.6639L36.4368 27.7759L36.4448 27.8239C36.4448 27.8719 36.4262 27.9146 36.3888 27.9519C36.3568 27.9839 36.3168 27.9999 36.2688 27.9999H34.7568C34.5862 27.9999 34.4662 27.9092 34.3968 27.7279L34.1968 27.1679H32.2448L32.0448 27.7279ZM33.2208 23.9439L32.6528 25.6079H33.7888L33.2208 23.9439Z" fill="white"/>
<path d="M43.1697 27.7519C43.1804 27.7732 43.1857 27.7972 43.1857 27.8239C43.1857 27.8719 43.167 27.9146 43.1297 27.9519C43.0977 27.9839 43.0577 27.9999 43.0097 27.9999H41.1937C41.119 27.9999 41.0497 27.9812 40.9857 27.9439C40.927 27.9066 40.8844 27.8586 40.8577 27.7999L40.1537 26.2159H39.4817V27.7839C39.4817 27.8426 39.4604 27.8932 39.4177 27.9359C39.375 27.9786 39.3244 27.9999 39.2657 27.9999H37.5377C37.479 27.9999 37.4284 27.9786 37.3857 27.9359C37.343 27.8932 37.3217 27.8426 37.3217 27.7839V22.6159C37.3217 22.5572 37.343 22.5066 37.3857 22.4639C37.4284 22.4212 37.479 22.3999 37.5377 22.3999H40.6497C41.1297 22.3999 41.5484 22.4772 41.9057 22.6319C42.2684 22.7866 42.5457 23.0106 42.7377 23.3039C42.9297 23.5972 43.0257 23.9412 43.0257 24.3359C43.0257 25.0612 42.7457 25.5866 42.1857 25.9119L43.1697 27.7519ZM40.4817 24.7359C40.599 24.7359 40.6897 24.6986 40.7537 24.6239C40.8177 24.5439 40.8497 24.4452 40.8497 24.3279C40.8497 24.2106 40.8177 24.1092 40.7537 24.0239C40.695 23.9332 40.6044 23.8879 40.4817 23.8879H39.4817V24.7359H40.4817Z" fill="white"/>
</g>
<defs>
<filter id="filter0_f_5116_233479" x="16" y="35" width="32" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_5116_233479"/>
</filter>
<filter id="filter1_i_5116_233479" x="18.5311" y="14" width="29.9376" height="21" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_5116_233479"/>
</filter>
<filter id="filter2_d_5116_233479" x="22.2159" y="22.3999" width="21.9697" height="7.67993" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5116_233479"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5116_233479" result="shape"/>
</filter>
<linearGradient id="paint0_linear_5116_233479" x1="62.4941" y1="1.50001" x2="-31.3204" y2="32.2514" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2E9F6"/>
<stop offset="1" stop-color="#F9F4FA" stop-opacity="0.12"/>
</linearGradient>
<linearGradient id="paint1_linear_5116_233479" x1="34.2045" y1="35" x2="34.2045" y2="14" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,49 @@
<svg width="64" height="46" viewBox="0 0 64 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="46" rx="4" fill="url(#paint0_linear_5116_233509)"/>
<g opacity="0.3" filter="url(#filter0_f_5116_233509)">
<rect x="20" y="39" width="24" height="2" rx="1" fill="#811FA3"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.6667 7C19.1939 7 18 8.16406 18 9.6V32.4C18 33.836 19.1939 35 20.6667 35H41.3333C42.8061 35 44 33.836 44 32.4V13.5C44 12.064 42.8061 10.9 41.3333 10.9H32.6245L29.691 7.46776C29.4376 7.17137 29.0624 7 28.6667 7H20.6667Z" fill="#811FA3"/>
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="#811FA3"/>
<g filter="url(#filter1_i_5116_233509)">
<path d="M22.6341 15.5368C22.8486 14.6358 23.6536 14 24.5797 14H46.4679C47.7598 14 48.7127 15.2065 48.4135 16.4632L44.3659 33.4632C44.1514 34.3642 43.3464 35 42.4203 35H20.5321C19.2402 35 18.2873 33.7935 18.5865 32.5368L22.6341 15.5368Z" fill="url(#paint1_linear_5116_233509)"/>
</g>
<g filter="url(#filter2_d_5116_233509)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.8206 26.6147C29.8206 27.2358 29.7735 27.975 30.2878 28.3234C30.3402 28.3589 30.3934 28.3935 30.4472 28.4272C31.1044 28.8382 31.9182 28.2969 32.6418 28.0186C32.8156 27.9518 33.0044 27.9151 33.2018 27.9151C33.3992 27.9151 33.588 27.9518 33.7619 28.0186C34.4854 28.2969 35.2992 28.8382 35.9565 28.4271C36.0103 28.3935 36.0634 28.3589 36.1158 28.3234C36.6301 27.975 36.583 27.2358 36.583 26.6147C36.583 25.7528 37.2817 25.0541 38.1435 25.0541C38.2315 25.0541 38.3117 24.9963 38.3268 24.9096C38.3773 24.6186 38.4036 24.3193 38.4036 24.0138C38.4036 23.4777 38.3225 22.9606 38.1719 22.474C38.1681 22.4615 38.1565 22.4531 38.1435 22.4532C37.2817 22.4532 36.583 21.7545 36.583 20.8927C36.583 20.469 36.5354 19.998 36.1888 19.7545C36.0601 19.664 35.927 19.5793 35.7901 19.5006C35.1335 19.1232 34.3431 19.599 33.6123 19.7978C33.4815 19.8333 33.3439 19.8523 33.2018 19.8523C33.0597 19.8523 32.9221 19.8333 32.7913 19.7978C32.0605 19.599 31.2701 19.1232 30.6135 19.5006C30.4766 19.5793 30.3436 19.664 30.2148 19.7545C29.8682 19.998 29.8206 20.469 29.8206 20.8927C29.8206 21.7545 29.122 22.4532 28.2601 22.4532C28.2471 22.4531 28.2355 22.4615 28.2317 22.474C28.0811 22.9606 28 23.4777 28 24.0138C28 24.3193 28.0263 24.6186 28.0769 24.9096C28.0919 24.9963 28.1721 25.0541 28.2601 25.0541C29.122 25.0541 29.8206 25.7528 29.8206 26.6147ZM35.0226 24.0137C35.0226 25.0192 34.2074 25.8344 33.2019 25.8344C32.1964 25.8344 31.3813 25.0192 31.3813 24.0137C31.3813 23.0082 32.1964 22.1931 33.2019 22.1931C34.2074 22.1931 35.0226 23.0082 35.0226 24.0137Z" fill="white"/>
</g>
<defs>
<filter id="filter0_f_5116_233509" x="16" y="35" width="32" height="10" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_5116_233509"/>
</filter>
<filter id="filter1_i_5116_233509" x="18.5311" y="14" width="29.9376" height="21" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_5116_233509"/>
</filter>
<filter id="filter2_d_5116_233509" x="27" y="19.3594" width="12.4036" height="11.2166" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.505882 0 0 0 0 0.121569 0 0 0 0 0.639216 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5116_233509"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5116_233509" result="shape"/>
</filter>
<linearGradient id="paint0_linear_5116_233509" x1="62.4941" y1="1.50001" x2="-31.3204" y2="32.2514" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2E9F6"/>
<stop offset="1" stop-color="#F9F4FA" stop-opacity="0.12"/>
</linearGradient>
<linearGradient id="paint1_linear_5116_233509" x1="34.2045" y1="35" x2="34.2045" y2="14" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -3,6 +3,7 @@
v-bind="props"
ref="treeRef"
v-model:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"
:data="treeData"
class="ms-tree"
@drop="onDrop"
@ -75,6 +76,7 @@
selectable?: boolean; //
fieldNames?: MsTreeFieldNames; //
focusNodeKey?: string | number; // key
selectedKeys?: Array<string | number>; // key
nodeMoreActions?: ActionsItem[]; //
expandAll?: boolean; // /true false
emptyText?: string; //
@ -103,6 +105,7 @@
): void;
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
(e: 'update:focusNodeKey', val: string | number): void;
(e: 'update:selectedKeys', val: Array<string | number>): void;
(e: 'moreActionsClose'): void;
}>();
@ -296,6 +299,22 @@
}
}
);
const selectedKeys = ref(props.selectedKeys || []);
watch(
() => props.selectedKeys,
(val) => {
selectedKeys.value = val || [];
}
);
watch(
() => selectedKeys.value,
(val) => {
emit('update:selectedKeys', val);
}
);
</script>
<style lang="less">

View File

@ -1,5 +1,5 @@
<template>
<div :class="`ms-button ms-button--${props.type}`" @click="clickHandler">
<div :class="`ms-button ms-button-${props.type} ms-button--${props.status}`" @click="clickHandler">
<slot></slot>
</div>
</template>
@ -7,10 +7,12 @@
<script setup lang="ts">
const props = withDefaults(
defineProps<{
type?: 'text';
type?: 'text' | 'icon' | 'button';
status?: 'primary' | 'danger' | 'secondary';
}>(),
{
type: 'text',
status: 'primary',
}
);
@ -28,8 +30,43 @@
@apply mr-4;
}
padding: 0 4px;
font-size: 1rem;
border-radius: var(--border-radius-mini);
line-height: 22px;
}
.ms-button-text {
@apply p-0;
color: rgb(var(--primary-5));
}
.ms-button-icon {
color: var(--color-text-4);
&:hover {
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-9));
.arco-icon {
color: rgb(var(--primary-5));
}
}
}
.ms-button--secondary {
color: var(--color-text-2);
&:hover {
background-color: var(--color-text-n8);
}
}
.ms-button--primary {
color: rgb(var(--primary-5));
&:hover {
background-color: rgb(var(--primary-9));
}
}
.ms-button--danger {
color: rgb(var(--danger-6));
&:hover {
color: rgb(var(--danger-6));
background-color: rgb(var(--danger-1));
}
}
</style>

View File

@ -1,5 +1,6 @@
<template>
<a-drawer
v-bind="props"
v-model:visible="visible"
:width="props.width"
:footer="props.footer"
@ -17,26 +18,25 @@
</div>
</slot>
</template>
<a-scrollbar
:style="{
overflowY: 'auto',
height: 'calc(100vh - 146px)',
}"
>
<slot>
<MsDescription
v-if="props.descriptions && props.descriptions.length > 0"
:descriptions="props.descriptions"
:show-skeleton="props.showSkeleton"
:skeleton-line="10"
>
<template #value="{ item }">
<slot name="descValue" :item="item">
{{ item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value }}
</slot>
</template>
</MsDescription>
</slot>
<a-scrollbar class="overflow-y-auto" style="height: calc(100vh - 146px)">
<div class="ms-drawer-body">
<slot>
<MsDescription
v-if="props.descriptions && props.descriptions.length > 0"
:descriptions="props.descriptions"
:show-skeleton="props.showSkeleton"
:skeleton-line="10"
>
<template #value="{ item }">
<slot name="descValue" :item="item">
{{
item.value === undefined || item.value === null || item.value?.toString() === '' ? '-' : item.value
}}
</slot>
</template>
</MsDescription>
</slot>
</div>
</a-scrollbar>
<template #footer>
<slot name="footer">

View File

@ -0,0 +1,275 @@
<template>
<a-list
v-bind="props"
ref="listRef"
:data="data"
:class="['ms-list', listStatusClass]"
@reach-bottom="handleReachBottom"
>
<template #item="{ item, index }">
<slot name="item" :item="item" :index="index">
<div
:key="index"
:class="[
'ms-list-item',
props.noHover ? 'ms-list-item--no-hover' : '',
props.itemBorder ? 'ms-list-item--bordered' : '',
innerFocusItemKey === item[itemKeyField] ? 'ms-list-item--focus' : '',
]"
@click="emit('itemClick', item, index)"
>
<slot name="title" :item="item" :index="index"></slot>
<div
v-if="$slots['itemAction'] || (props.itemMoreActions && props.itemMoreActions.length > 0)"
class="ms-list-item-actions"
>
<slot name="itemAction" :item="item" :index="index"></slot>
<MsTableMoreAction
v-if="props.itemMoreActions && props.itemMoreActions.length > 0"
:list="props.itemMoreActions"
trigger="click"
@select="handleMoreActionSelect($event, item)"
@close="handleMoreActionClose"
>
<MsButton type="icon" size="mini" class="ms-list-item-actions-btn" @click="handleClickMore(item)">
<MsIcon type="icon-icon_more_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</MsTableMoreAction>
</div>
<div
v-if="props.mode === 'remote' && index === props.data.length - 1"
class="flex h-[32px] items-center justify-center"
>
<div v-if="noMoreData" class="text-[var(--color-text-4)]">{{ t('ms.timeline.noMoreData') }}</div>
<a-spin v-else />
</div>
</div>
</slot>
</template>
<template v-if="$slots['empty'] || props.emptyText" #empty>
<slot name="empty">
<div
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] text-[var(--color-text-4)]"
>
{{ props.emptyText }}
</div>
</slot>
</template>
</a-list>
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, Ref, ref, watch } from 'vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
const props = withDefaults(
defineProps<{
mode?: 'static' | 'remote'; //
data: Record<string, any>[];
focusItemKey?: string | number; // key
itemMoreActions?: ActionsItem[]; //
itemKeyField?: string; // key key
itemHeight?: number; //
emptyText?: string; //
noMoreData?: boolean; //
noHover?: boolean; // hover
itemBorder?: boolean; //
}>(),
{
mode: 'static',
itemKeyField: 'key',
itemHeight: 20,
}
);
const emit = defineEmits([
'update:focusItemKey',
'itemClick',
'close',
'moreActionSelect',
'moreActionsClose',
'clickMore',
'reachBottom',
]);
const { t } = useI18n();
const innerFocusItemKey = ref(props.focusItemKey || ''); //
watch(
() => props.focusItemKey,
(val) => {
innerFocusItemKey.value = val || '';
}
);
watch(
() => innerFocusItemKey.value,
(val) => {
emit('update:focusItemKey', val);
}
);
const popVisible = ref(false);
function handleClickMore(item: Record<string, any>) {
innerFocusItemKey.value = item[props.itemKeyField];
emit('clickMore', item);
}
function handleMoreActionSelect(event: ActionsItem, item: Record<string, any>) {
popVisible.value = true;
innerFocusItemKey.value = item[props.itemKeyField];
emit('moreActionSelect', event, item);
}
function handleMoreActionClose() {
if (!popVisible.value) {
innerFocusItemKey.value = '';
}
emit('moreActionsClose');
}
const listRef: Ref = ref(null);
const isArrivedTop = ref(true);
const isArrivedBottom = ref(true);
const isInitListener = ref(false);
/**
* 监听列表内容区域滚动以切换顶部底部阴影
* @param event 滚动事件
*/
function listenScroll(event: Event) {
if (event.target) {
const listContent = event.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = listContent;
const scrollBottom = scrollHeight - clientHeight - scrollTop;
isArrivedTop.value = scrollTop < props.itemHeight;
isArrivedBottom.value = scrollBottom < props.itemHeight;
}
}
watch(
props.data,
() => {
if (props.data.length > 0 && !isInitListener.value) {
nextTick(() => {
const listContent = listRef.value?.$el.querySelector('.arco-list-content');
isInitListener.value = true;
listContent.addEventListener('scroll', listenScroll);
});
}
nextTick(() => {
//
const listContent = listRef.value?.$el.querySelector('.arco-list-content');
const { scrollTop, scrollHeight, clientHeight } = listContent;
isArrivedBottom.value = scrollHeight - clientHeight - scrollTop < props.itemHeight;
});
},
{
immediate: true,
}
);
function handleReachBottom() {
if (props.mode === 'remote' && !props.noMoreData && props.data.length > 0) {
emit('reachBottom');
}
}
const listStatusClass = computed(() => {
if (isArrivedTop.value && isArrivedBottom.value) {
//
return 'ms-list-hidden-shadow';
}
if (isArrivedTop.value) {
//
return 'ms-list--hidden-top-shadow';
}
if (isArrivedBottom.value) {
//
return 'ms-list--hidden-bottom-shadow';
}
//
return '';
});
onBeforeUnmount(() => {
const listContent = listRef.value?.$el.querySelector('.arco-list-content');
if (listContent) {
listContent.removeEventListener('scroll', listenScroll);
}
});
</script>
<style lang="less" scoped>
.ms-list {
box-shadow: inset 0 10px 6px -10px rgb(0 0 0 / 15%), inset 0 -10px 6px -10px rgb(0 0 0 / 15%);
transition: box-shadow 0.1s cubic-bezier(0.165, 0.84, 0.44, 1);
:deep(.arco-list) {
@apply rounded-none;
.ms-list-item {
@apply flex w-full cursor-pointer items-center justify-between;
padding: 8px 4px;
border-radius: var(--border-radius-small);
&:hover {
background-color: rgb(var(--primary-1));
.ms-list-item-actions {
@apply visible;
}
}
.ms-list-item-actions {
@apply invisible flex items-center justify-end;
.ms-list-item-actions-btn {
@apply !mr-0;
padding: 4px;
border-radius: var(--border-radius-mini);
}
}
}
.ms-list-item--no-hover {
@apply cursor-auto;
&:hover {
background-color: transparent;
}
}
.ms-list-item--bordered {
border: 1px solid var(--color-text-n8);
}
.ms-list-item--focus {
background-color: rgb(var(--primary-1));
.ms-list-item-actions {
@apply visible;
}
}
.arco-list-item-meta {
@apply p-0;
.arco-list-item-meta-title:not(:last-child) {
@apply mb-0;
}
.arco-list-item-meta-avatar {
margin-right: 12px;
}
}
.arco-list-item-action > li:not(:last-child) {
margin-right: 4px;
}
}
}
.ms-list-hidden-shadow {
box-shadow: none;
}
.ms-list--hidden-top-shadow {
box-shadow: inset 0 -10px 6px -10px rgb(0 0 0 / 15%);
}
.ms-list--hidden-bottom-shadow {
box-shadow: inset 0 10px 6px -10px rgb(0 0 0 / 15%);
}
</style>

View File

@ -6,11 +6,13 @@
isArrivedBottom ? 'ms-timeline--hidden-bottom-shadow' : '',
]"
>
<a-list
<MsList
ref="listRef"
:data="props.list"
:virtual-list-props="{ height: props.maxHeight }"
:bordered="false"
:mode="props.mode"
no-hover
@reach-bottom="handleReachBottom"
>
<template #item="{ item, index }">
@ -25,21 +27,15 @@
</slot>
</a-timeline-item>
</a-list-item>
<div
v-if="props.mode === 'remote' && index === props.list.length - 1"
class="flex h-[32px] items-center justify-center"
>
<div v-if="noMoreData" class="text-[var(--color-text-4)]">{{ t('ms.timeline.noMoreData') }}</div>
<a-spin v-else />
</div>
</div>
</template>
</a-list>
</MsList>
</a-timeline>
</template>
<script setup lang="ts">
import { nextTick, ref, Ref, watch, onBeforeUnmount } from 'vue';
import MsList from '@/components/pure/ms-list/index.vue';
import { useI18n } from '@/hooks/useI18n';
import type { MsTimeLineListItem } from './types';

View File

@ -0,0 +1,297 @@
<template>
<div class="sticky top-[0] z-[9999] mb-[8px] flex justify-between bg-white">
<a-radio-group v-model:model-value="fileListTab" type="button" size="small">
<a-radio value="all">{{ `${t('ms.upload.all')} (${innerFileList.length})` }}</a-radio>
<a-radio value="waiting">{{ `${t('ms.upload.uploading')} (${waitingList.length})` }}</a-radio>
<a-radio value="success">{{ `${t('ms.upload.success')} (${successList.length})` }}</a-radio>
<a-radio value="error">{{ `${t('ms.upload.fail')} (${failList.length})` }}</a-radio>
</a-radio-group>
<slot name="tabExtra"></slot>
</div>
<MsList :data="filterFileList" :bordered="false" :split="false" item-border no-hover>
<template #item="{ item }">
<a-list-item
class="mb-[8px] rounded-[var(--border-radius-small)] border border-solid border-[var(--color-text-n8)] !p-[8px_12px]"
>
<a-list-item-meta>
<template #avatar>
<a-avatar shape="square" class="rounded-[var(--border-radius-mini)] bg-[var(--color-text-n9)]">
<a-image
v-if="item.file.type.includes('image/')"
:src="item.url"
:alt="item.file.name"
width="40"
height="40"
/>
<MsIcon
v-else
:type="getFileIcon(item)"
size="24"
:class="getFileEnum(item.file?.type) === 'unknown' ? 'text-[var(--color-text-4)]' : ''"
></MsIcon>
</a-avatar>
</template>
<template #title>
<div class="font-normal">{{ item.file.name }}</div>
</template>
<template #description>
<div v-if="item.status === UploadStatus.init" class="text-[12px] text-[var(--color-text-4)]">
{{ t('ms.upload.waiting') }}
</div>
<div v-else-if="item.status === UploadStatus.done" class="text-[12px] text-[var(--color-text-4)]">
{{
`${formatFileSize(item.file.size)} ${t('ms.upload.uploadAt')} ${dayjs(item.uploadedTime).format(
'YYYY-MM-DD HH:mm:ss'
)}`
}}
</div>
<a-progress
v-else-if="item.status === UploadStatus.uploading"
:percent="progress / 100"
:show-text="false"
size="large"
class="w-[200px]"
/>
<div v-else-if="item.status === UploadStatus.error" class="text-[rgb(var(--danger-6))]">
{{ t('ms.upload.uploadFail') }}
</div>
</template>
</a-list-item-meta>
<template #actions>
<div class="flex items-center">
<MsButton
v-if="item.file.type.includes('image/')"
type="button"
status="primary"
class="!mr-0"
@click="handlePreview(item)"
>
{{ t('ms.upload.preview') }}
</MsButton>
<MsButton
v-if="item.status === UploadStatus.error"
type="button"
status="secondary"
class="!mr-0"
@click="reupload(item)"
>
{{ t('ms.upload.reUpload') }}
</MsButton>
<MsButton type="button" status="danger" class="!mr-[4px]" @click="deleteFile(item)">
{{ t('ms.upload.delete') }}
</MsButton>
<slot name="actions" :item="item"></slot>
</div>
</template>
</a-list-item>
</template>
</MsList>
<a-image-preview-group
v-model:visible="previewVisible"
v-model:current="previewCurrent"
infinite
:src-list="previewList"
/>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import dayjs from 'dayjs';
import { useI18n } from '@/hooks/useI18n';
import { formatFileSize } from '@/utils';
import MsList from '@/components/pure/ms-list/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { UploadStatus } from '@/enums/uploadEnum';
import { getFileEnum, getFileIcon } from './iconMap';
import type { MsFileItem } from './types';
const props = defineProps<{
fileList: MsFileItem[];
handleDelete?: (item: MsFileItem) => void;
handleReupload?: (item: MsFileItem) => void;
}>();
const emit = defineEmits<{
(e: 'update:fileList', fileList: MsFileItem[]): void;
(e: 'delete', item: MsFileItem): void;
(e: 'finish'): void;
(e: 'start'): void;
}>();
const { t } = useI18n();
const fileListTab = ref('all');
const innerFileList = ref<MsFileItem[]>(props.fileList);
watch(
() => props.fileList,
(val) => {
innerFileList.value = val.sort((a, b) => {
if (a.status === UploadStatus.init && b.status !== UploadStatus.init) {
return -1; // "init"
}
if (a.status !== UploadStatus.init && b.status === UploadStatus.init) {
return 1; // "init"
}
return 0; //
});
}
);
watch(
() => innerFileList.value,
(val) => {
emit('update:fileList', val);
}
);
const waitingList = computed(() => {
return innerFileList.value.filter(
(e) => e.status && (e.status === UploadStatus.init || e.status === UploadStatus.uploading)
);
});
const successList = computed(() => {
return innerFileList.value.filter((e) => e.status && e.status === UploadStatus.done);
});
const failList = computed(() => {
return innerFileList.value.filter((e) => e.status && e.status === UploadStatus.error);
});
const filterFileList = computed(() => {
switch (fileListTab.value) {
case 'waiting':
return waitingList.value;
case 'success':
return successList.value;
case 'error':
return failList.value;
default:
return innerFileList.value;
}
});
const uploadQueue = ref<MsFileItem[]>([]);
const progress = ref(0);
let timer: any = null;
/**
* 开始上传队列中的文件
* @param fileItem 文件项
*/
async function uploadFileFromQueue(fileItem?: MsFileItem) {
if (fileItem) {
fileItem.status = UploadStatus.uploading; //
}
if (timer === null) {
//
timer = setInterval(() => {
if (progress.value < 50) {
// 0-50%
const randomIncrement = Math.floor(Math.random() * 10) + 1; // 5-10
progress.value += randomIncrement;
} else if (progress.value < 100) {
// 50%-100%
const randomIncrement = Math.floor(Math.random() * 10) + 1; // 1-5
progress.value = Math.min(progress.value + randomIncrement, 99);
} else {
clearInterval(timer);
timer = null;
}
}, 100); // 100
}
try {
await new Promise((resolve) => {
setTimeout(() => {
resolve(null);
}, 3000);
});
if (fileItem?.file?.type.includes('jpeg')) {
throw new Error('上传失败');
}
if (fileItem) {
fileItem.status = UploadStatus.done;
fileItem.uploadedTime = Date.now();
}
} catch (error) {
console.log(error);
if (fileItem) {
fileItem.status = UploadStatus.error;
}
} finally {
// /
progress.value = 0;
clearInterval(timer);
timer = null;
if (uploadQueue.value.length > 0) {
//
uploadFileFromQueue(uploadQueue.value.shift());
} else {
emit('finish');
}
}
}
/**
* 开始上传
*/
function startUpload() {
emit('start');
// init
uploadQueue.value = innerFileList.value.filter((item) => item.status === UploadStatus.init);
uploadFileFromQueue(uploadQueue.value.shift());
}
const previewVisible = ref(false);
const previewCurrent = ref(0);
const previewList = computed(() => {
return innerFileList.value.filter((item) => item.file?.type.includes('image/')).map((item) => item.url);
});
function handlePreview(item: MsFileItem) {
previewVisible.value = true;
previewCurrent.value = previewList.value.indexOf(item.url);
}
function deleteFile(item: MsFileItem) {
if (typeof props.handleDelete === 'function') {
props.handleDelete(item);
} else {
const index = innerFileList.value.findIndex((e) => e.uid === item.uid);
if (index !== -1) {
innerFileList.value.splice(index, 1);
}
emit('delete', item);
}
}
function reupload(item: MsFileItem) {
if (typeof props.handleReupload === 'function') {
props.handleReupload(item);
} else {
item.status = UploadStatus.init;
if (uploadQueue.value.length > 0) {
// push
uploadQueue.value.push(item);
} else {
//
startUpload();
}
}
}
//
onBeforeUnmount(() => {
if (timer !== null) {
clearInterval(timer);
timer = null;
}
});
defineExpose({
startUpload,
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,99 @@
import { UploadStatus, UploadAcceptEnum } from '@/enums/uploadEnum';
import type { MsFileItem } from './types';
// 使用映射类型和索引签名
export type FileIconMapping = {
[key in keyof typeof UploadAcceptEnum]: any;
};
export const FileIconMap: FileIconMapping = {
csv: {
[UploadStatus.init]: 'icon-icon_file-CSV_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-CSV_colorful1',
},
excel: {
[UploadStatus.init]: 'icon-icon_file-excel_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-excel_colorful1',
},
image: {
[UploadStatus.init]: 'icon-icon_file-image_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-image_colorful1',
},
jar: {
[UploadStatus.init]: 'icon-a-icon_file-jar_colorful_ash',
[UploadStatus.done]: 'icon-a-icon_file-jar_colorful',
},
pdf: {
[UploadStatus.init]: 'icon-icon_file-pdf_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-pdf_colorful1',
},
sql: {
[UploadStatus.init]: 'icon-icon_file-sql_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-sql_colorful1',
},
txt: {
[UploadStatus.init]: 'icon-icon_file-text_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-text_colorful1',
},
word: {
[UploadStatus.init]: 'icon-icon_file-word_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-word_colorful1',
},
video: {
[UploadStatus.init]: 'icon-icon_file-vedio_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-vedio_colorful1',
},
xmind: {
[UploadStatus.init]: 'icon-icon_file-xmind_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-xmind_colorful1',
},
zip: {
[UploadStatus.init]: 'icon-a-icon_file-compressed_colorful_ash2',
[UploadStatus.done]: 'icon-a-icon_file-compressed_colorful2',
},
sketch: {
[UploadStatus.init]: 'icon-icon_file-sketch_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-sketch_colorful1',
},
ppt: {
[UploadStatus.init]: 'icon-icon_file-ppt_colorful_ash',
[UploadStatus.done]: 'icon-icon_file-ppt_colorful1',
},
unknown: {
[UploadStatus.init]: 'icon-icon_file-unknow_colorful1',
[UploadStatus.done]: 'icon-icon_file-unknow_colorful1',
},
none: {
[UploadStatus.init]: 'icon-icon_file-unknow_colorful1',
[UploadStatus.done]: 'icon-icon_file-unknow_colorful1',
},
};
/**
*
* @param fileType
*/
export function getFileEnum(fileType?: string): keyof typeof UploadAcceptEnum {
if (fileType) {
const keys = Object.keys(UploadAcceptEnum);
for (let i = 0; i < keys.length; i++) {
const key = keys[i] as unknown as keyof typeof UploadAcceptEnum;
if (UploadAcceptEnum[key].split(',').includes(`.${fileType.split('/')[1]}`)) {
return key;
}
}
}
return 'unknown' as keyof typeof UploadAcceptEnum;
}
/**
*
* @param item
*/
export function getFileIcon(item: MsFileItem) {
if (item.status === UploadStatus.done) {
return FileIconMap[getFileEnum(item.file?.type)]?.[item.status] ?? FileIconMap.unknown[UploadStatus.done];
}
return FileIconMap[getFileEnum(item.file?.type)]?.[UploadStatus.init] ?? FileIconMap.unknown[UploadStatus.done];
}

View File

@ -5,16 +5,22 @@
:accept="UploadAcceptEnum[props.accept]"
:multiple="props.multiple"
:disabled="props.disabled"
@change="handleChange"
@before-upload="beforeUpload"
>
<template #upload-button>
<slot>
<div class="ms-upload-area">
<div class="ms-upload-icon-box">
<MsIcon v-if="fileList.length > 0" :type="IconMap[props.accept]" class="ms-upload-icon" />
<MsIcon
v-if="props.accept !== UploadAcceptEnum.none"
:type="FileIconMap[props.accept][UploadStatus.done]"
class="ms-upload-icon"
/>
<div v-else class="ms-upload-icon ms-upload-icon--default"></div>
</div>
<template v-if="fileList.length === 0">
<!-- 支持多文件上传时不需要展示选择文件后的信息已选的文件使用文件列表搭配展示 -->
<template v-if="fileList.length === 0 || props.multiple">
<div class="ms-upload-main-text">
{{ t(props.mainText || 'ms.upload.importModalDragText') }}
</div>
@ -41,13 +47,14 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import { UploadAcceptEnum } from '@/enums/uploadEnum';
import { UploadAcceptEnum, UploadStatus } from '@/enums/uploadEnum';
import { formatFileSize } from '@/utils';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { FileIconMap } from './iconMap';
import { FileItem, Message } from '@arco-design/web-vue';
import type { UploadType } from './types';
import type { UploadType, MsFileItem } from './types';
const { t } = useI18n();
@ -67,18 +74,18 @@
isLimit: boolean; //
}> & {
accept: UploadType;
fileList: FileItem[];
fileList: MsFileItem[];
};
const props = withDefaults(defineProps<UploadProps>(), {
showSubText: true,
isLimit: true,
});
const emit = defineEmits(['update:fileList']);
const emit = defineEmits(['update:fileList', 'change']);
const defaultMaxSize = 50;
const fileList = ref<FileItem[]>(props.fileList);
const fileList = ref<MsFileItem[]>(props.fileList);
watch(
() => props.fileList,
@ -94,21 +101,6 @@
}
);
const IconMap = {
excel: 'icon-icon_file-excel_colorful1',
word: 'icon-icon_file-word_colorful1',
pdf: 'icon-icon_file-pdf_colorful1',
txt: 'icon-icon_file-text_colorful1',
video: 'icon-icon_file-vedio_colorful1',
sql: 'icon-icon_file-sql_colorful1',
csv: 'icon-icon_file-CSV_colorful1',
zip: 'icon-a-icon_file-compressed_colorful1',
xmind: 'icon-icon_file-xmind_colorful1',
image: 'icon-icon_file-image_colorful1',
jar: 'icon-icon_file-jar_colorful',
none: 'icon-icon_file-text_colorful1',
};
async function beforeUpload(file: File) {
if (!props.multiple && fileList.value.length > 0) {
//
@ -122,6 +114,10 @@
}
return Promise.resolve(true);
}
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
emit('change', _fileList, fileItem);
}
</script>
<style lang="less" scoped>

View File

@ -3,4 +3,10 @@ export default {
'ms.upload.overSize': 'The file size exceeds the limit, please reselect the file',
'ms.upload.importModalDragText': 'Drag or click this area to select a file',
'ms.upload.importModalFileTip': 'Only {type} format files are supported, and the file size does not exceed {size} MB',
'ms.upload.waiting': 'Waiting',
'ms.upload.reUpload': 'Reupload',
'ms.upload.preview': 'Preview',
'ms.upload.uploadAt': 'Uploaded at',
'ms.upload.fail': 'Upload failed',
'ms.upload.delete': 'Delete',
};

View File

@ -3,4 +3,14 @@ export default {
'ms.upload.overSize': '文件大小超出限制,请重新选择文件',
'ms.upload.importModalDragText': '拖拽或点击此区域选择文件',
'ms.upload.importModalFileTip': '只支持 {type} 格式文件,文件大小不超过 {size} MB',
'ms.upload.waiting': '等待上传',
'ms.upload.reUpload': '重新上传',
'ms.upload.preview': '预览',
'ms.upload.uploadAt': '上传于',
'ms.upload.uploadFail': '上传失败',
'ms.upload.delete': '删除',
'ms.upload.all': '全部',
'ms.upload.uploading': '等待/上传中',
'ms.upload.success': '成功',
'ms.upload.fail': '失败',
};

View File

@ -1,4 +1,13 @@
import { UploadAcceptEnum } from '@/enums/uploadEnum';
import { UploadAcceptEnum, UploadStatus } from '@/enums/uploadEnum';
import type { FileItem } from '@arco-design/web-vue';
// 上传类型
export type UploadType = keyof typeof UploadAcceptEnum;
// MS文件类型
export type MsFileItem = FileItem & {
status?: keyof typeof UploadStatus;
enable?: boolean; // jar类型文件是否可用
uploadedTime?: string | number; // 上传完成时间
};

View File

@ -0,0 +1,7 @@
export enum GitPlatformEnum {
GITHUB = 'Github',
GITLAB = 'Gitlab',
GITEE = 'Gitee',
}
export default {};

View File

@ -18,6 +18,7 @@ export enum TableKeyEnum {
PROJECT_MEMBER = 'projectMember',
ORGANIZATION_MEMBER = 'organizationMember',
ORGANIZATION_PROJECT = 'organizationProject',
FILE_MANAGEMENT_FILE = 'fileManagementFile',
}
// 具有特殊功能的列

View File

@ -2,15 +2,23 @@ export enum UploadAcceptEnum {
excel = '.xlsx,.xls',
word = '.docx,.doc',
pdf = '.pdf',
ppt = '.pptx,.ppt',
txt = '.txt',
video = '.mp4',
sql = '.sql',
csv = '.csv',
zip = '.zip',
xmind = '.xmind',
image = '.jpg,.jpeg,.png,.svg',
image = '.jpg,.jpeg,.png,.svg,.webp,.gif,.bmp',
jar = '.jar',
sketch = '.sketch',
none = 'none',
unknown = 'unknown',
}
export default UploadAcceptEnum;
export enum UploadStatus {
init = 'init',
done = 'done',
error = 'error',
uploading = 'uploading',
}

View File

@ -6,6 +6,9 @@ export const phoneRegex = /^\d{11}$/;
export const passwordLengthRegex = /^.{8,32}$/;
// 密码校验,必须包含数字和字母
export const passwordWordRegex = /^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$/;
// Git地址校验
export const gitRepositoryUrlRegex =
/^(?:(?:git:\/\/|https?:\/\/)(?:www\.)?)?(github\.com|gitee\.com)\/([^/]+)\/([^/]+)\.git$/;
/**
*
@ -51,3 +54,12 @@ export function validateWordPassword(password: string): boolean {
export function validatePassword(password: string): boolean {
return validatePasswordLength(password) && validateWordPassword(password);
}
/**
* Git地址
* @param url Git地址
* @returns boolean
*/
export function validateGitUrl(url: string): boolean {
return gitRepositoryUrlRegex.test(url);
}

View File

@ -0,0 +1,202 @@
<template>
<a-input
v-model:model-value="moduleKeyword"
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
allow-clear
class="mb-[8px]"
></a-input>
<MsTree
v-model:focus-node-key="focusNodeKey"
:selected-keys="props.selectedKeys"
:data="folderTree"
:keyword="moduleKeyword"
:node-more-actions="folderMoreActions"
:expand-all="props.isExpandAll"
:empty-text="t('project.fileManagement.noFolder')"
draggable
block-node
@select="folderNodeSelect"
@more-action-select="handleFolderMoreSelect"
@more-actions-close="moreActionsClose"
>
<template #title="nodeData">
<span class="text-[var(--color-text-1)]">{{ nodeData.title }}</span>
<span class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count }})</span>
</template>
<template #extra="nodeData">
<popConfirm mode="add" :all-names="[]" @close="resetFocusNodeKey">
<MsButton type="icon" size="mini" class="ms-tree-node-extra__btn !mr-0" @click="setFocusNodeKe(nodeData)">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</popConfirm>
<popConfirm mode="rename" :title="renameFolderTitle" :all-names="[]" @close="resetFocusNodeKey">
<span :id="`renameSpan${nodeData.key}`" class="relative"></span>
</popConfirm>
</template>
</MsTree>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import useModal from '@/hooks/useModal';
import { useI18n } from '@/hooks/useI18n';
import MsTree from '@/components/business/ms-tree/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import popConfirm from './popConfirm.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
const props = defineProps<{
isExpandAll: boolean;
selectedKeys?: Array<string | number>; // key
}>();
const emit = defineEmits(['update:selectedKeys', 'folderNodeSelect']);
const { t } = useI18n();
const { openModal } = useModal();
const moduleKeyword = ref('');
const folderTree = ref([
{
title: 'Trunk',
key: 'node1',
count: 18,
children: [
{
title: 'Leaf',
key: 'node2',
count: 28,
},
],
},
{
title: 'Trunk',
key: 'node3',
count: 180,
children: [
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
],
},
{
title: 'Trunk',
key: 'node6',
children: [],
count: 0,
},
]);
const focusNodeKey = ref<string | number>('');
function setFocusNodeKe(node: MsTreeNodeData) {
focusNodeKey.value = node.key || '';
}
const folderMoreActions: ActionsItem[] = [
{
label: 'project.fileManagement.rename',
eventTag: 'rename',
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
},
];
const renamePopVisible = ref(false);
/**
* 删除文件夹
* @param node 节点信息
*/
function deleteFolder(node: MsTreeNodeData) {
openModal({
type: 'error',
title: t('project.fileManagement.deleteFolderTipTitle', { name: node.title }),
content: t('project.fileManagement.deleteFolderTipContent'),
okText: t('project.fileManagement.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
Message.success(t('project.fileManagement.deleteSuccess'));
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
const renameFolderTitle = ref(''); //
function resetFocusNodeKey() {
focusNodeKey.value = '';
renamePopVisible.value = false;
renameFolderTitle.value = '';
}
/**
* 处理文件夹树节点选中事件
*/
function folderNodeSelect(selectedKeys: (string | number)[]) {
emit('folderNodeSelect', selectedKeys);
}
/**
* 处理树节点更多按钮事件
* @param item
*/
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) {
case 'delete':
deleteFolder(node);
resetFocusNodeKey();
break;
case 'rename':
renameFolderTitle.value = node.title || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${node.key}`)?.dispatchEvent(new Event('click'));
break;
default:
break;
}
}
function moreActionsClose() {
if (!renamePopVisible.value) {
// key
resetFocusNodeKey();
}
}
const selectedKeys = ref(props.selectedKeys || []);
watch(
() => props.selectedKeys,
(val) => {
selectedKeys.value = val || [];
}
);
watch(
() => selectedKeys.value,
(val) => {
emit('update:selectedKeys', val);
}
);
</script>
<style lang="less" scoped></style>

View File

@ -1,64 +0,0 @@
<template>
<div class="flex items-center gap-[4px]">
<popConfirm mode="add" :all-names="[]" @close="emit('close')">
<MsButton type="text" size="mini" class="action-btn" @click="emit('add', props.item)">
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</popConfirm>
<MsTableMoreAction
:list="folderMoreActions"
trigger="click"
@select="emit('select', $event, props.item)"
@close="emit('actionsClose')"
>
<MsButton type="text" size="mini" class="action-btn" @click="emit('clickMore', props.item)">
<MsIcon type="icon-icon_more_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</MsTableMoreAction>
<popConfirm mode="rename" :title="item.name" :all-names="[]" @close="emit('close')">
<span ref="renameSpanRef" class="relative"></span>
</popConfirm>
</div>
</template>
<script setup lang="ts">
import popConfirm from './popConfirm.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
const props = defineProps<{
item: Record<string, any>;
}>();
const emit = defineEmits(['add', 'close', 'select', 'actionsClose', 'clickMore']);
const folderMoreActions: ActionsItem[] = [
{
label: 'project.fileManagement.rename',
eventTag: 'rename',
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
},
];
</script>
<style lang="less" scoped>
.action-btn {
@apply !mr-0;
padding: 4px;
border-radius: var(--border-radius-mini);
&:hover {
background-color: rgb(var(--primary-9));
.arco-icon {
color: rgb(var(--primary-5));
}
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="p-[24px]">
<div class="header">
<a-button type="primary">{{ t('project.fileManagement.addFile') }}</a-button>
<a-button type="primary" @click="uploadDrawerVisible = true">{{ t('project.fileManagement.addFile') }}</a-button>
<div class="header-right">
<a-input-search
v-model:model-value="keyword"
@ -9,9 +9,15 @@
allow-clear
class="w-[240px]"
></a-input-search>
<a-radio-group v-model:model-value="fileType" type="button" class="file-show-type" @change="changeFileType">
<a-radio value="Module" class="show-type-icon">{{ t('project.fileManagement.module') }}</a-radio>
<a-radio value="Storage" class="show-type-icon">{{ t('project.fileManagement.storage') }}</a-radio>
<a-radio-group
v-if="props.activeFolderType === 'folder'"
v-model:model-value="fileType"
type="button"
class="file-show-type min-w-[92px]"
@change="changeFileType"
>
<a-radio value="module" class="show-type-icon">{{ t('project.fileManagement.module') }}</a-radio>
<a-radio value="storage" class="show-type-icon">{{ t('project.fileManagement.storage') }}</a-radio>
</a-radio-group>
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
<a-radio value="list" class="show-type-icon p-[2px]"><MsIcon type="icon-icon_view-list_outlined" /></a-radio>
@ -19,18 +25,135 @@
</a-radio-group>
</div>
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #name="{ record }">
<a-button type="text" class="px-0" @click="openFileDetail(record.id)">{{ record.name }}</a-button>
</template>
<!-- <template #action="{ record }">
<MsButton @click="editAuth(record)">{{ t('system.config.auth.edit') }}</MsButton>
<MsButton v-if="record.enable" @click="disabledAuth(record)">
{{ t('system.config.auth.disable') }}
</MsButton>
<MsButton v-else @click="enableAuth(record)">{{ t('system.config.auth.enable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template> -->
<template v-if="keyword.trim() === ''" #empty>
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('project.fileManagement.tableNoFile') }}
<MsButton class="ml-[8px]" @click="uploadDrawerVisible = true">
{{ t('project.fileManagement.addFile') }}
</MsButton>
</div>
</template>
</ms-base-table>
</div>
<MsDrawer v-model:visible="uploadDrawerVisible" :title="t('project.fileManagement.addFile')" :width="680">
<div class="mb-[8px] flex items-center justify-between text-[var(--color-text-1)]">
{{ t('project.fileManagement.fileType') }}
<div class="flex items-center text-[12px] text-[rgb(var(--warning-6))]">
<icon-exclamation-circle class="mr-[2px]" />
{{ t('project.fileManagement.fileTypeTip') }}
</div>
</div>
<div class="mb-[24px] grid grid-cols-2 gap-[16px]">
<a-tooltip :content="t('project.fileManagement.uploadingTip')" :disabled="!isUploading || acceptType === 'none'">
<div :class="getCardClass('none')" @click="setAcceptType('none')">
<svg-icon width="64px" height="46px" name="project_setting" class="file-type-card-icon" />
<div>
<div class="file-type-card-text">{{ t('project.fileManagement.normalFile') }}</div>
<div class="file-type-card-desc">{{ t('project.fileManagement.normalFileDesc') }}</div>
</div>
</div>
</a-tooltip>
<a-tooltip :content="t('project.fileManagement.uploadingTip')" :disabled="!isUploading || acceptType === 'jar'">
<div :class="getCardClass('jar')" @click="setAcceptType('jar')">
<svg-icon width="64px" height="46px" name="JAR" class="file-type-card-icon" />
<div>
<div class="file-type-card-text">{{ t('project.fileManagement.jarFile') }}</div>
<div class="file-type-card-desc">{{ t('project.fileManagement.jarFileDesc') }}</div>
</div>
</div>
</a-tooltip>
</div>
<MsUpload
v-show="!isUploading"
v-model:file-list="fileList"
:accept="acceptType"
:auto-upload="false"
:sub-text="acceptType === 'jar' ? '' : t('project.fileManagement.normalFileSubText', { size: 50 })"
multiple
size-unit="MB"
class="mb-[16px] w-full"
@change="handleFileChange"
/>
<MsFileList ref="fileListRef" v-model:file-list="fileList" @start="handleUploadStart" @finish="uploadFinish">
<template #tabExtra>
<div v-if="acceptType === 'jar'" class="flex items-center gap-[4px]">
<a-switch size="small" @change="enableAllJar"></a-switch>
{{ t('project.fileManagement.enableAll') }}
<a-tooltip :content="t('project.fileManagement.uploadTip')">
<MsIcon type="icon-icon-maybe_outlined" class="cursor-pointer hover:text-[rgb(var(--primary-5))]" />
</a-tooltip>
</div>
</template>
<template #actions="{ item }">
<a-switch v-if="acceptType === 'jar'" v-model:model-value="item.enable" size="small"></a-switch>
</template>
</MsFileList>
<template #footer>
<a-button type="secondary" @click="uploadDrawerVisible = false">
{{ t('project.fileManagement.cancel') }}
</a-button>
<a-button type="secondary" :disabled="noWaitingUpload" @click="uploadDrawerVisible = false">
{{ t('project.fileManagement.backendUpload') }}
</a-button>
<a-button type="primary" :disabled="noWaitingUpload || isUploading" @click="startUpload">
{{ t('project.fileManagement.startUpload') }}
</a-button>
</template>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { debounce } from 'lodash-es';
import { useI18n } from '@/hooks/useI18n';
import useTableStore from '@/store/modules/ms-table';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import useTable from '@/components/pure/ms-table/useTable';
import { TableKeyEnum } from '@/enums/tableEnum';
import { getFileList } from '@/api/modules/project-management/fileManagement';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
import { UploadStatus } from '@/enums/uploadEnum';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type { MsFileItem, UploadType } from '@/components/pure/ms-upload/types';
const props = defineProps<{
activeFolder: string | number;
activeFolderType: 'folder' | 'module' | 'storage';
}>();
const { t } = useI18n();
const keyword = ref('');
const fileType = ref('Module');
const fileType = ref('module');
const acceptType = ref<UploadType>('none');
const isUploading = ref(false);
watch(
() => props.activeFolderType,
(val) => {
if (val === 'folder') {
fileType.value = 'module';
} else {
fileType.value = val;
}
}
);
function changeFileType() {
console.log(fileType.value);
@ -41,6 +164,127 @@
function changeShowType() {
console.log(showType.value);
}
function getCardClass(type: 'none' | 'jar') {
if (acceptType.value !== type && isUploading.value) {
return 'file-type-card file-type-card--disabled';
}
if (acceptType.value === type) {
return 'file-type-card file-type-card--active';
}
return 'file-type-card';
}
const columns: MsTableColumn = [
{
title: 'system.config.auth.name',
slotName: 'name',
dataIndex: 'name',
width: 200,
showInTable: true,
},
{
title: 'system.config.auth.status',
slotName: 'enable',
dataIndex: 'enable',
showInTable: true,
},
{
title: 'system.config.auth.desc',
dataIndex: 'description',
showInTable: true,
},
{
title: 'system.config.auth.createTime',
dataIndex: 'createTime',
showInTable: true,
},
{
title: 'system.config.auth.updateTime',
dataIndex: 'updateTime',
showInTable: true,
},
{
title: 'system.config.auth.action',
slotName: 'action',
fixed: 'right',
width: 120,
showInTable: true,
},
];
const tableStore = useTableStore();
tableStore.initColumn(TableKeyEnum.FILE_MANAGEMENT_FILE, columns, 'drawer');
const { propsRes, propsEvent, loadList } = useTable(getFileList, {
tableKey: TableKeyEnum.SYSTEM_AUTH,
columns,
scroll: { y: 'auto' },
});
watch(
() => props.activeFolder,
() => {
keyword.value = '';
debounce(loadList, 200)();
},
{ immediate: true }
);
async function openFileDetail(id: string) {}
const uploadDrawerVisible = ref(false);
const fileList = ref<MsFileItem[]>([]);
const noWaitingUpload = computed(
() =>
fileList.value.filter((e) => e.status && (e.status === UploadStatus.init || e.status === UploadStatus.uploading))
.length === 0
);
function setAcceptType(type: UploadType) {
if (isUploading.value) return;
acceptType.value = type;
fileList.value = [];
}
/**
* 选择文件时初始化文件参数
* @param files 文件列表
*/
function handleFileChange(files: MsFileItem[]) {
fileList.value = files.map((e) => {
if (e.enable !== undefined) {
return e;
}
return {
...e,
enable: false, //
};
});
}
/**
* 开启/关闭所有jar包
* @param val 是否启用
*/
function enableAllJar(val: string | number | boolean) {
for (let i = 0; i < fileList.value.length; i++) {
fileList.value[i].enable = !!val;
}
}
const fileListRef = ref<InstanceType<typeof MsFileList>>();
function handleUploadStart() {
isUploading.value = true;
}
function startUpload() {
fileListRef.value?.startUpload();
}
function uploadFinish() {
isUploading.value = false;
}
</script>
<style lang="less" scoped>
@ -61,4 +305,40 @@
}
}
}
.file-type-card {
@apply flex cursor-pointer;
padding: 16px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
&:hover {
box-shadow: 0 4px 10px rgb(100 100 102 / 15%);
}
.file-type-card-icon {
margin-right: 12px;
}
.file-type-card-text {
margin-bottom: 8px;
color: var(--color-text-1);
}
.file-type-card-desc {
font-size: 12px;
color: var(--color-text-4);
}
}
.file-type-card--active {
border-color: rgb(var(--primary-5));
}
.file-type-card--disabled {
@apply cursor-not-allowed hover:shadow-none;
background-color: var(--color-text-n9);
.file-type-card-icon {
opacity: 0.6;
}
.file-type-card-text,
.file-type-card-desc {
color: var(--color-text-4);
}
}
</style>

View File

@ -0,0 +1,678 @@
<template>
<a-input
v-model:model-value="storageKeyword"
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
allow-clear
class="mb-[8px]"
></a-input>
<MsList
v-model:focus-item-key="focusItemKey"
:virtual-list-props="{
height: 'calc(100vh - 310px)',
}"
:data="storageList"
:bordered="false"
:split="false"
:item-more-actions="folderMoreActions"
:empty-text="t('project.fileManagement.noStorage')"
class="mr-[-6px]"
@more-action-select="handleMoreSelect"
@more-actions-close="moreActionsClose"
>
<template #title="{ item, index }">
<div :key="index" class="storage" @click="setActiveFolder(item.key)">
<div :class="props.activeFolder === item.key ? 'storage-text storage-text--active' : 'storage-text'">
<MsIcon type="icon-icon_git" class="storage-icon" />
<div class="storage-name">{{ item.title }}</div>
<div class="storage-count">({{ item.count }})</div>
</div>
</div>
</template>
<template #itemAction="{ item }">
<popConfirm mode="rename" :title="renameStorageTitle" :all-names="[]" @close="resetFocusItemKey">
<span :id="`renameSpan${item.key}`" class="relative"></span>
</popConfirm>
</template>
</MsList>
<MsDrawer
v-model:visible="showDrawer"
:title="t(isEdit ? 'project.fileManagement.updateStorageTitle' : 'project.fileManagement.addStorage')"
:ok-text="t(isEdit ? 'project.fileManagement.save' : 'project.fileManagement.add')"
:ok-loading="drawerLoading"
:width="680"
:show-continue="!isEdit"
@confirm="handleDrawerConfirm"
@continue="handleDrawerConfirm(true)"
@cancel="handleDrawerCancel"
>
<a-form ref="storageFormRef" :model="activeStorageForm" layout="vertical">
<a-form-item
:label="t('project.fileManagement.storageName')"
field="name"
asterisk-position="end"
:rules="[{ required: true, message: t('project.fileManagement.storageNameNotNull') }]"
required
>
<a-input
v-model:model-value="activeStorageForm.name"
:max-length="255"
:placeholder="t('project.fileManagement.storageNamePlaceholder')"
allow-clear
show-word-limit
></a-input>
</a-form-item>
<a-form-item :label="t('project.fileManagement.storagePlatform')" field="platform" asterisk-position="end">
<a-radio-group v-model:model-value="activeStorageForm.platform" type="button" @change="platformChange">
<a-radio v-for="item of gitPlatformTypes" :key="item" :value="item">{{ item }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
:label="t('project.fileManagement.storageUrl')"
field="url"
asterisk-position="end"
:rules="[
{ required: true, message: t('project.fileManagement.storageUrlNotNull') },
{ validator: validatePlatformUrl },
]"
:disabled="isEdit"
required
>
<a-input
v-model:model-value="activeStorageForm.url"
:max-length="1500"
:placeholder="t('project.fileManagement.storageUrlPlaceholder')"
allow-clear
></a-input>
<MsFormItemSub
v-if="!isEdit"
:text="t('project.fileManagement.storageExUrl', { url: exampleUrl })"
@fill="fillUrl"
/>
</a-form-item>
<a-form-item
:label="t('project.fileManagement.storageToken')"
field="token"
asterisk-position="end"
:rules="[{ required: true, message: t('project.fileManagement.storageTokenNotNull') }]"
required
>
<a-input-password
v-model:model-value="activeStorageForm.token"
:max-length="1500"
:placeholder="t('project.fileManagement.storageTokenPlaceholder')"
allow-clear
autocomplete="new-password"
/>
</a-form-item>
<a-form-item
:label="t('project.fileManagement.storageUsername')"
field="username"
asterisk-position="end"
:rules="usernameRules"
>
<a-input
v-model:model-value="activeStorageForm.username"
:max-length="250"
:placeholder="t('project.fileManagement.storageUsernamePlaceholder')"
allow-clear
></a-input>
</a-form-item>
<div>
<a-button type="outline" class="mr-[16px]" :loading="testLoading" @click="testLink">
{{ t('project.fileManagement.testLink') }}
</a-button>
</div>
</a-form>
</MsDrawer>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { debounce } from 'lodash-es';
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { validateGitUrl } from '@/utils/validate';
import MsList from '@/components/pure/ms-list/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import popConfirm from './popConfirm.vue';
import { GitPlatformEnum } from '@/enums/commonEnum';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
const props = defineProps<{
activeFolder: string | number;
drawerVisible: boolean;
}>();
const emit = defineEmits(['update:drawerVisible', 'itemClick']);
const { t } = useI18n();
const { openModal } = useModal();
const folderMoreActions: ActionsItem[] = [
{
label: 'project.fileManagement.rename',
eventTag: 'rename',
},
{
label: 'project.fileManagement.edit',
eventTag: 'edit',
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
},
];
const storageKeyword = ref('');
const originStorageList = ref([
{
title: 'storage1',
key: '1',
count: 129,
},
{
title: 'storage2',
key: '2',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3sss',
count: 129,
},
{
title: 'storage3',
key: '3asa',
count: 129,
},
{
title: 'storage3',
key: '3sda',
count: 129,
},
{
title: 'storage3',
key: '3ads',
count: 129,
},
{
title: 'storage3',
key: '3asdd',
count: 129,
},
{
title: 'storage3',
key: '3sdfsdf',
count: 129,
},
{
title: 'storage3',
key: '3dgsg',
count: 129,
},
{
title: 'storage3',
key: '3asd',
count: 129,
},
{
title: 'storage3',
key: '3fwcdw',
count: 129,
},
{
title: 'storage3',
key: '3wef',
count: 129,
},
{
title: 'storage3',
key: '3f2ed',
count: 129,
},
{
title: 'storage3',
key: '3fe2fe',
count: 129,
},
{
title: 'storage3',
key: '3fe2feg2',
count: 129,
},
{
title: 'storage3',
key: '32s22',
count: 129,
},
{
title: 'storage3',
key: '323ff22f',
count: 129,
},
{
title: 'storage3',
key: '33f3f',
count: 129,
},
{
title: 'storage1',
key: '1f2f',
count: 129,
},
{
title: 'storage2',
key: '2ef2ef',
count: 129,
},
{
title: 'storage3',
key: '3sd2fd',
count: 129,
},
{
title: 'storage3',
key: '32ef2ef',
count: 129,
},
{
title: 'storage3',
key: '3f32v',
count: 129,
},
{
title: 'storage3',
key: '323fgt2',
count: 129,
},
{
title: 'storage3',
key: '324g23r',
count: 129,
},
{
title: 'storage3',
key: '3233d2',
count: 129,
},
{
title: 'storage3',
key: '32gftr3',
count: 129,
},
{
title: 'storage3',
key: '323f2dd',
count: 129,
},
{
title: 'storage3',
key: '32fgede',
count: 129,
},
{
title: 'storage3',
key: '32efsad',
count: 129,
},
{
title: 'storage3',
key: '3fsdgsd',
count: 129,
},
{
title: 'storage3',
key: '3sdgvsdxcs',
count: 129,
},
{
title: 'storage3',
key: '3asxc',
count: 129,
},
{
title: 'storage3',
key: '3csdcdg',
count: 129,
},
{
title: 'storage3',
key: '3gsg3f3',
count: 129,
},
{
title: 'storage3',
key: '3d3dxcsd',
count: 129,
},
{
title: 'storage3',
key: '3c3wervvb',
count: 129,
},
{
title: 'storage1',
key: '13vf33',
count: 129,
},
{
title: 'storage2',
key: '234444',
count: 129,
},
{
title: 'storage3',
key: '32323d',
count: 129,
},
{
title: 'storage3',
key: '323ffef',
count: 129,
},
{
title: 'storage3',
key: 'f23f23f3',
count: 129,
},
{
title: 'storage3',
key: '3f2f2f',
count: 129,
},
{
title: 'storage3',
key: '3rf4f2',
count: 129,
},
{
title: 'storage3',
key: '3dsewd',
count: 129,
},
{
title: 'storage3',
key: '3x2ef23f',
count: 129,
},
{
title: 'storage3',
key: '3f3f43',
count: 129,
},
{
title: 'storage3',
key: '3h4hgp',
count: 129,
},
{
title: 'storage3',
key: '3yu6n',
count: 129,
},
{
title: 'storage3',
key: '3nyuk',
count: 129,
},
{
title: 'storage3',
key: '3hn6',
count: 129,
},
{
title: 'storage3',
key: '3nyguhnmm6',
count: 129,
},
{
title: 'storage3',
key: '36n6n',
count: 129,
},
{
title: 'storage3',
key: '3yukyuk',
count: 129,
},
{
title: 'storage3',
key: '3yhnn',
count: 129,
},
{
title: 'storage3',
key: '3gntnty',
count: 129,
},
]);
const storageList = ref(originStorageList.value);
const searchStorage = debounce(() => {
storageList.value = originStorageList.value.filter((item) => item.title.includes(storageKeyword.value));
}, 300);
watch(
() => storageKeyword.value,
() => {
if (storageKeyword.value === '') {
storageList.value = originStorageList.value;
}
searchStorage();
}
);
const focusItemKey = ref('');
function setActiveFolder(id: string) {
emit('itemClick', id);
}
const renamePopVisible = ref(false);
/**
* 删除存储库
* @param item 列表项信息
*/
function deleteStorage(item: any) {
openModal({
type: 'error',
title: t('project.fileManagement.deleteStorageTipTitle', { name: item.title }),
content: t('project.fileManagement.deleteStorageTipContent'),
okText: t('project.fileManagement.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
Message.success(t('project.fileManagement.deleteSuccess'));
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
const renameStorageTitle = ref(''); //
function resetFocusItemKey() {
focusItemKey.value = '';
renamePopVisible.value = false;
renameStorageTitle.value = '';
}
function moreActionsClose() {
if (!renamePopVisible.value) {
// key
resetFocusItemKey();
}
}
const showDrawer = ref(false);
watch(
() => props.drawerVisible,
(val) => {
showDrawer.value = val;
}
);
watch(
() => showDrawer.value,
(val) => {
emit('update:drawerVisible', val);
}
);
const drawerLoading = ref(false);
const isEdit = ref(false);
const activeStorageForm = ref({
name: '',
platform: GitPlatformEnum.GITHUB,
url: '',
token: '',
username: '',
});
const storageFormRef = ref<FormInstance>();
const gitPlatformTypes = Object.values(GitPlatformEnum);
async function getStorageDetail(id: string) {
try {
drawerLoading.value = true;
activeStorageForm.value = {
name: 'xxx',
platform: GitPlatformEnum.GITHUB,
url: 'xxxxxxx',
token: 'sxsxsx',
username: 'ddwdwdwwd',
};
} catch (error) {
console.log(error);
} finally {
drawerLoading.value = false;
}
}
/**
* 处理列表项更多按钮事件
* @param item
* @param listItem
*/
function handleMoreSelect(item: ActionsItem, listItem: any) {
switch (item.eventTag) {
case 'delete':
deleteStorage(listItem);
resetFocusItemKey();
break;
case 'rename':
renameStorageTitle.value = listItem.title || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${listItem.key}`)?.dispatchEvent(new Event('click'));
break;
case 'edit':
isEdit.value = true;
showDrawer.value = true;
getStorageDetail(listItem.key);
break;
default:
break;
}
}
const usernameRules = computed(() => {
if (activeStorageForm.value.platform === GitPlatformEnum.GITEE) {
return [{ required: true, message: t('project.fileManagement.storageUsernameNotNull') }];
}
return [];
});
function platformChange() {
storageFormRef.value?.resetFields('username');
}
const exampleUrl = 'http://github.com/xxxxx/xxxxxx.git';
function fillUrl() {
activeStorageForm.value.url = exampleUrl;
storageFormRef.value?.validateField('url');
}
function validatePlatformUrl(value: any, callback: (error?: string | undefined) => void) {
if (!validateGitUrl(value)) {
callback(t('project.fileManagement.storageUrlError'));
}
}
async function saveStorage(isContinue: boolean) {}
/**
* 处理抽屉确认
* @param isContinue 是否继续添加
*/
function handleDrawerConfirm(isContinue: boolean) {
storageFormRef.value?.validate(async (errors: Record<string, ValidatedError> | undefined) => {
if (!errors) {
saveStorage(isContinue);
}
});
}
function handleDrawerCancel() {
showDrawer.value = false;
storageFormRef.value?.resetFields();
}
const testLoading = ref(false);
function testLink() {
storageFormRef.value?.validate(async (errors: Record<string, ValidatedError> | undefined) => {
if (!errors) {
testLoading.value = true;
setTimeout(() => {
testLoading.value = false;
}, 2000);
}
});
}
</script>
<style lang="less" scoped>
.storage {
@apply flex cursor-pointer items-center justify-between;
border-radius: var(--border-radius-small);
&:hover {
background-color: rgb(var(--primary-1));
}
.storage-text {
@apply flex cursor-pointer items-center;
.storage-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.storage-name {
color: var(--color-text-1);
}
.storage-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
.storage-text--active {
.storage-icon,
.storage-name,
.storage-count {
color: rgb(var(--primary-5));
}
}
}
</style>

View File

@ -20,16 +20,25 @@
<a-tooltip
:content="isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')"
>
<a-button type="text" size="mini" class="p-[4px]" @click="changeExpand">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion'" />
</a-button>
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>
<a-dropdown trigger="click" @select="handleAddSelect">
<MsButton type="icon" class="!mr-0 p-[2px]">
<MsIcon
type="icon-icon_create_planarity"
size="18"
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
/>
</MsButton>
<template #content>
<a-doption value="module">{{ t('project.fileManagement.addSubModule') }}</a-doption>
<a-doption value="storage">{{ t('project.fileManagement.addStorage') }}</a-doption>
</template>
</a-dropdown>
<popConfirm mode="add" :all-names="[]">
<a-tooltip :content="t('project.fileManagement.addSubModule')">
<a-button type="text" size="mini" class="p-[2px]">
<MsIcon type="icon-icon_create_planarity" size="18" />
</a-button>
</a-tooltip>
<span id="allPlus" class="invisible"></span>
</popConfirm>
</div>
</div>
@ -39,130 +48,70 @@
<a-radio value="Storage">{{ t('project.fileManagement.storage') }}</a-radio>
</a-radio-group>
<div v-show="showType === 'Module'">
<a-input
v-model:model-value="moduleKeyword"
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
allow-clear
class="mb-[8px]"
></a-input>
<MsTree
v-model:focus-node-key="focusNodeKey"
:data="folderTree"
:keyword="moduleKeyword"
:node-more-actions="folderMoreActions"
:expand-all="isExpandAll"
:empty-text="t('project.fileManagement.noFolder')"
draggable
block-node
@select="folderNodeSelect"
@more-action-select="handleFolderMoreSelect"
@more-actions-close="moreActionsClose"
>
<template #title="nodeData">
<span class="text-[var(--color-text-1)]">{{ nodeData.title }}</span>
<span class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count }})</span>
</template>
<template #extra="nodeData">
<popConfirm mode="add" :all-names="[]" @close="resetFocusNodeKey">
<MsButton
type="text"
size="mini"
class="ms-tree-node-extra__btn !mr-0"
@click="setFocusNodeKe(nodeData)"
>
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
</MsButton>
</popConfirm>
<popConfirm mode="rename" :title="renameFolderTitle" :all-names="[]" @close="resetFocusNodeKey">
<span :id="`renameSpan${nodeData.key}`" class="relative"></span>
</popConfirm>
</template>
</MsTree>
<FolderTree
v-model:selected-keys="selectedKeys"
:is-expand-all="isExpandAll"
@folder-node-select="folderNodeSelect"
/>
</div>
<div v-show="showType === 'Storage'">
<a-input
v-model:model-value="storageKeyword"
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
allow-clear
class="mb-[8px]"
></a-input>
<a-list
:virtual-list-props="{
height: 'calc(100vh - 310px)',
}"
:data="storageList"
:bordered="false"
:split="false"
>
<template #item="{ item, index }">
<div
:key="index"
:class="['folder', focusNodeKey === item.key ? 'ms-tree-node-extra--focus' : '']"
@click="setActiveFolder(item.key)"
>
<div :class="getFolderClass(item.key)">
<MsIcon type="icon-icon_git" class="folder-icon" />
<div class="folder-name">{{ item.title }}</div>
<div class="folder-count">({{ item.count }})</div>
</div>
<itemActions
:item="item"
@click.stop
@add="setFocusNodeKe"
@close="resetFocusNodeKey"
@actions-close="moreActionsClose"
@click-more="setFocusNodeKe"
/>
</div>
</template>
<template #empty>
<div
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] text-[var(--color-text-4)]"
>
{{ t('project.fileManagement.noStorage') }}
</div>
</template>
</a-list>
<StorageList
v-model:drawer-visible="storageDrawerVisible"
:active-folder="activeFolder"
@item-click="storageItemSelect"
/>
</div>
</div>
</template>
<template #right>
<rightBox />
<rightBox :active-folder="activeFolder" :active-folder-type="activeFolderType" />
</template>
</MsSplitBox>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { debounce } from 'lodash-es';
import { computed, ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsTree from '@/components/business/ms-tree/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import useModal from '@/hooks/useModal';
import FolderTree from './components/folderTree.vue';
import StorageList from './components/storageList.vue';
import popConfirm from './components/popConfirm.vue';
import rightBox from './components/rightBox.vue';
import itemActions from './components/itemActions.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import type { SelectedValue } from '@/components/pure/ms-table-more-action/types';
const { t } = useI18n();
const { openModal } = useModal();
const myFileCount = ref(0);
const allFileCount = ref(0);
const isExpandAll = ref(false);
const activeFolderType = ref<'folder' | 'module' | 'storage'>('folder');
const storageDrawerVisible = ref(false);
function changeExpand() {
isExpandAll.value = !isExpandAll.value;
}
/**
* 处理全部文件夹更多操作选中事件
* @param val 选中的值
*/
function handleAddSelect(val: SelectedValue) {
if (val === 'module') {
document.querySelector('#allPlus')?.dispatchEvent(new Event('click'));
} else {
storageDrawerVisible.value = true;
}
}
const activeFolder = ref<string | number>('all');
const selectedKeys = computed({
get: () => [activeFolder.value],
set: (val) => val,
});
function setActiveFolder(id: string) {
activeFolder.value = id;
@ -184,442 +133,28 @@
showType.value = val as FileShowType;
}
const moduleKeyword = ref('');
const folderTree = ref([
{
title: 'Trunk',
key: 'node1',
count: 18,
children: [
{
title: 'Leaf',
key: 'node2',
count: 28,
},
],
},
{
title: 'Trunk',
key: 'node3',
count: 180,
children: [
{
title: 'Leaf',
key: 'node4',
count: 138,
},
{
title: 'Leaf',
key: 'node5',
count: 108,
},
],
},
{
title: 'Trunk',
key: 'node6',
children: [],
count: 0,
},
]);
const focusNodeKey = ref<string | number>('');
function setFocusNodeKe(node: MsTreeNodeData) {
focusNodeKey.value = node.key || '';
}
const folderMoreActions: ActionsItem[] = [
{
label: 'project.fileManagement.rename',
eventTag: 'rename',
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
danger: true,
},
];
const renamePopVisible = ref(false);
/**
* 删除文件夹
* @param node 节点信息
*/
function deleteFolder(node: MsTreeNodeData) {
openModal({
type: 'error',
title: t('project.fileManagement.deleteTipTitle', { name: node.title }),
content: t('project.fileManagement.deleteTipContent'),
okText: t('project.fileManagement.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
Message.success(t('project.fileManagement.deleteSuccess'));
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
const renameFolderTitle = ref(''); //
function resetFocusNodeKey() {
focusNodeKey.value = '';
renamePopVisible.value = false;
renameFolderTitle.value = '';
}
/**
* 处理文件夹树节点选中事件
*/
function folderNodeSelect(selectedKeys: (string | number)[]) {
[activeFolder.value] = selectedKeys;
function folderNodeSelect(keys: (string | number)[]) {
[activeFolder.value] = keys;
activeFolderType.value = 'module';
}
/**
* 处理树节点更多按钮事件
* @param item
* 处理存储库列表项选中事件
*/
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) {
case 'delete':
deleteFolder(node);
resetFocusNodeKey();
break;
case 'rename':
renameFolderTitle.value = node.title || '';
renamePopVisible.value = true;
document.querySelector(`#renameSpan${node.key}`)?.dispatchEvent(new Event('click'));
break;
default:
break;
}
function storageItemSelect(key: string | number) {
activeFolder.value = key;
activeFolderType.value = 'storage';
}
function moreActionsClose() {
if (!renamePopVisible.value) {
// key
resetFocusNodeKey();
}
}
const storageKeyword = ref('');
const originStorageList = ref([
{
title: 'storage1',
key: '1',
count: 129,
},
{
title: 'storage2',
key: '2',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage1',
key: '1',
count: 129,
},
{
title: 'storage2',
key: '2',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage1',
key: '1',
count: 129,
},
{
title: 'storage2',
key: '2',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
{
title: 'storage3',
key: '3',
count: 129,
},
]);
const storageList = ref(originStorageList.value);
const searchStorage = debounce(() => {
storageList.value = originStorageList.value.filter((item) => item.title.includes(storageKeyword.value));
}, 300);
watch(
() => storageKeyword.value,
() => {
if (storageKeyword.value === '') {
storageList.value = originStorageList.value;
}
searchStorage();
}
);
</script>
<style lang="less" scoped>
.page {
@apply bg-white;
min-width: 1000px;
height: calc(100vh - 88px);
border-radius: var(--border-radius-large);
.folder {

View File

@ -4,7 +4,8 @@ export default {
'project.fileManagement.defaultFile': '默认文件',
'project.fileManagement.expandAll': '展开全部子模块',
'project.fileManagement.collapseAll': '收起全部子模块',
'project.fileManagement.addSubModule': '添加子模块',
'project.fileManagement.addSubModule': '添加模块',
'project.fileManagement.addStorage': '添加存储库',
'project.fileManagement.rename': '重命名',
'project.fileManagement.nameNotNull': '名字不能为空',
'project.fileManagement.namePlaceholder': '请输入分组名称,按回车键保存',
@ -16,10 +17,47 @@ export default {
'project.fileManagement.folderSearchPlaceholder': '输入名称搜索',
'project.fileManagement.delete': '删除',
'project.fileManagement.deleteSuccess': '删除成功',
'project.fileManagement.deleteTipTitle': '是否删除 `{name}` 模块?',
'project.fileManagement.deleteTipContent': '该操作会删除模块及其下所有资源,请谨慎操作!',
'project.fileManagement.deleteFolderTipTitle': '是否删除 `{name}` 模块?',
'project.fileManagement.deleteFolderTipContent': '该操作会删除模块及其下所有资源,请谨慎操作!',
'project.fileManagement.deleteConfirm': '确认删除',
'project.fileManagement.noFolder': '暂无匹配的相关模块',
'project.fileManagement.noStorage': '暂无匹配的相关存储库',
'project.fileManagement.addFile': '添加文件',
'project.fileManagement.deleteStorageTipTitle': '是否删除 `{name}` 存储库?',
'project.fileManagement.deleteStorageTipContent': '该操作会删除此存储库及其下所有资源,请谨慎操作!',
'project.fileManagement.updateStorageTitle': '编辑存储库',
'project.fileManagement.save': '保存',
'project.fileManagement.add': '添加',
'project.fileManagement.edit': '编辑',
'project.fileManagement.cancel': '取消',
'project.fileManagement.testLink': '测试连接',
'project.fileManagement.storageName': '存储库名称',
'project.fileManagement.storageNamePlaceholder': '请输入存储库名称',
'project.fileManagement.storageNameNotNull': '存储库名称不能为空',
'project.fileManagement.storagePlatform': '对接平台',
'project.fileManagement.storageUrl': '存储库地址',
'project.fileManagement.storageExUrl': '例如:{url}',
'project.fileManagement.storageUrlPlaceholder': '请输入存储库地址',
'project.fileManagement.storageUrlNotNull': '存储库地址不能为空',
'project.fileManagement.storageUrlError': '存储库地址格式有误',
'project.fileManagement.storageToken': 'Token',
'project.fileManagement.storageTokenPlaceholder': '请输入 Token',
'project.fileManagement.storageTokenNotNull': 'Token 不能为空',
'project.fileManagement.storageUsername': '用户名',
'project.fileManagement.storageUsernamePlaceholder': '请输入用户名',
'project.fileManagement.storageUsernameNotNull': '用户名不能为空',
'project.fileManagement.tableNoFile': '暂无数据,请',
'project.fileManagement.fileType': '文件类型',
'project.fileManagement.normalFile': '常规文件',
'project.fileManagement.normalFileDesc': '所有文件类型',
'project.fileManagement.jarFile': 'JAR 文件',
'project.fileManagement.jarFileDesc': '用于接口测试的文件',
'project.fileManagement.uploadTip': '接口测试脚本执行需开启,可一键开启;可对文件单独开启',
'project.fileManagement.fileTypeTip': '切换文件类型,已选/已上传文件列表会清空',
'project.fileManagement.normalFileSubText': '支持任意文件类型,文件大小不超过 {size} MB',
'project.fileManagement.enableAll': '一键开启',
'project.fileManagement.uploadingTip': '上传中不可更改文件类型',
'project.fileManagement.emptyFileList': '暂无文件',
'project.fileManagement.backendUpload': '后台上传',
'project.fileManagement.startUpload': '开始上传',
};