2025-04-04 13:15:39 +08:00
ComponentSystem . register ( 'mirrors-table' , {
template : `
2025-10-16 02:17:52 +08:00
< div class = "mirrors-table" >
2025-10-14 15:32:16 +08:00
< t - config - provider : global - config = "globalConfig" >
2025-10-16 02:17:52 +08:00
< t - space v - if = "!isMobile" align = "center" style = "margin-bottom: 8px; gap: 20px" >
< blockquote >
< p > { { startTitle [ 0 ] } } < code > Debian < / c o d e > 、 < c o d e > U b u n t u < / c o d e > 、 < c o d e > C e n t O S < / c o d e > 、 < c o d e > o p e n E u l e r < / c o d e > { { s t a r t T i t l e [ 1 ] } } < / p >
< / b l o c k q u o t e >
< t - space style = "width: 100%" >
< t - popup placement = "bottom" : show - arrow = "false" >
< template # content >
< t - checkbox - group v - model = "selectedCellStatuses" style = "padding: 6px" @ change = "onCellStatusChange" >
< t - space align = "start" direction = "vertical" style = "gap: 4px" >
< t - checkbox value = "supported" >
< t - space align = "center" style = "gap: 2px" >
< t - tag theme = "success" variant = "light" style = "background-color: transparent; vertical-align: -0.35em" >
< template # icon >
< svg xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24" > < path fill = "currentColor" d = "M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z" > < / s v g >
< / t e m p l a t e >
< / t - t a g >
< span > { { statusLabels . supported } } < / s p a n >
< / t - s p a c e >
< / t - c h e c k b o x >
< t - checkbox value = "unsupported" >
< t - space align = "center" style = "gap: 0" >
< t - tag theme = "danger" variant = "light" style = "background-color: transparent; vertical-align: -0.35em" >
< template # icon >
< svg xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24" style = "vertical-align: -0.2em" > < path fill = "currentColor" d = "M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" > < / s v g >
< / t e m p l a t e >
< / t - t a g >
< span > { { statusLabels . unsupported } } < / s p a n >
< / t - s p a c e >
< / t - c h e c k b o x >
< t - checkbox value = "incompatible" >
< t - space align = "center" style = "gap: 2px" >
< t - tag theme = "warning" variant = "light" style = "background-color: transparent; vertical-align: -0.35em" >
< svg xmlns = "http://www.w3.org/2000/svg" style = "margin-left: 2px" width = "16" height = "16" viewBox = "0 0 24 24" style = "vertical-align: -0.15em" > < path fill = "#F6B604" d = "M22.11 21.46L2.39 1.73L1.11 3l2.95 2.95A9.95 9.95 0 0 0 2 12c0 5.5 4.5 10 10 10c2.28 0 4.37-.77 6.05-2.06l2.79 2.79l1.27-1.27M12 20c-4.42 0-8-3.58-8-8c0-1.73.56-3.32 1.5-4.62L16.62 18.5A7.78 7.78 0 0 1 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2c5.5 0 10 4.5 10 10c0 1.94-.56 3.75-1.5 5.28l-1.47-1.45c.62-1.14.97-2.44.97-3.83c0-4.42-3.58-8-8-8c-1.39 0-2.69.35-3.83.97Z" > < / s v g >
< / t - t a g >
< span > { { statusLabels . incompatible } } < / s p a n >
< / t - s p a c e >
< / t - c h e c k b o x >
< / t - s p a c e >
< / t - c h e c k b o x - g r o u p >
< / t e m p l a t e >
< t - button variant = "text" shape = "circle" >
< svg fill = "none" viewBox = "0 0 24 24" width = "1em" height = "1em" class = "t-icon t-icon-filter" style = "fill: none;" > < g id = "filter" > < path id = "fill1" fill = "transparent" d = "M19.5 4H4.5L10.5 12.5V20H13.5V12.5L19.5 4Z" fill - rule = "evenodd" clip - rule = "evenodd" > < / p a t h > < p a t h i d = " s t r o k e 1 " s t r o k e = " c u r r e n t C o l o r " d = " M 1 9 . 5 4 H 4 . 5 L 1 0 . 5 1 2 . 5 V 2 0 H 1 3 . 5 V 1 2 . 5 L 1 9 . 5 4 Z " f i l l - r u l e = " e v e n o d d " s t r o k e - l i n e c a p = " s q u a r e " s t r o k e - w i d t h = " 2 " c l i p - r u l e = " e v e n o d d " > < / p a t h > < / g > < / s v g >
< / t - b u t t o n >
< / t - p o p u p >
< t - select
v - model = "selectedRowFilters"
: options = "rowFilterOptionsRendered"
: min - collapsed - num = "1"
multiple
clearable
size = "large"
: placeholder = "rowSelectPlaceholder"
style = "min-width: 160px; width: 250px"
@ change = "onRowFilterChange"
/ >
< t - select
v - model = "selectedColumnFilters"
: options = "filterOptions"
: min - collapsed - num = "1"
multiple
clearable
size = "large"
: placeholder = "selectPlaceholder"
style = "min-width: 160px; width: 230px"
@ change = "onFilterChange"
/ >
< / d i v >
2025-10-14 15:32:16 +08:00
< / t - s p a c e >
2025-10-16 02:17:52 +08:00
< blockquote v - if = "isMobile" >
< p > { { startTitle [ 0 ] } } < code > Debian < / c o d e > 、 < c o d e > U b u n t u < / c o d e > 、 < c o d e > C e n t O S < / c o d e > 、 < c o d e > o p e n E u l e r < / c o d e > { { s t a r t T i t l e [ 1 ] } } < / p >
< / b l o c k q u o t e >
2025-10-14 15:32:16 +08:00
< t - table
: columns = "columns"
: data = "data"
row - key = "name"
size = "small"
verticalAlign = "bottom"
2025-10-16 02:17:52 +08:00
hover
2025-10-14 15:32:16 +08:00
@ data - change = "dataChange"
@ filter - change = "onTableFilterChange"
>
< template v - for = "col in columns" : key = "col.colKey" # [ col . colKey ] = "{ row }" >
< div v - if = "col.colKey === 'name'" >
2025-10-16 02:17:52 +08:00
< t - popup placement = "bottom" : show - arrow = "false" >
2025-10-14 15:32:16 +08:00
< template # content >
< t - space direction = "vertical" algin = "center" style = "gap: 2px" >
< span > { { row . officialName } } < / s p a n >
< a : href = "row.url" target = "_blank" style = "color: var(--md-typeset-a-color)" > { { row . domain } } < / a >
2025-10-12 05:13:57 +08:00
< / t - s p a c e >
2025-10-14 15:32:16 +08:00
< / t e m p l a t e >
< a : href = "row.url" target = "_blank" >
< t - space align = "center" style = "gap: 6px" >
< span style = "display: flex; height: 16px; width: 16px; align-items: center; justify-content: center" >
< img v - if = "row.icon" : src = "'/assets/images/icon/mirrors/' + row.icon" width = "16" height = "16" >
< / s p a n >
< span style = "display: flex; align-items: center; justify-content: center" > { { row . name } } < / s p a n >
< / t - s p a c e >
< / a >
< / t - p o p u p >
< / d i v >
< div v - else >
2025-10-16 02:17:52 +08:00
< t - tag v - if = "typeof row[col.colKey] === 'boolean'" : theme = "row[col.colKey] ? 'success' : 'danger'" variant = "light" size = "small" style = "background-color: transparent; height: 26px" style = "z-index: 2" >
2025-10-14 15:32:16 +08:00
< template # icon >
< div v - if = "row[col.colKey] === true && !['ipv6'].includes(col.colKey) && showSupported" >
2025-10-16 02:17:52 +08:00
< t - popup placement = "bottom" : show - arrow = "false" >
< template # content >
< a : href = "getMirrorSiteBranchUrl(row.domain, col.colKey)" target = "_blank" style = "color: var(--md-typeset-a-color)" > { { getMirrorSiteBranchUrl ( row . domain , col . colKey ) } } < / a >
< / t e m p l a t e >
< a : href = "getMirrorSiteBranchUrl(row.domain, col.colKey)" target = "_blank" style = "color: var(--td-success-color)" >
< svg xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24" > < path fill = "currentColor" d = "M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z" > < / s v g >
< / a >
< / t - p o p u p >
< / d i v >
2025-10-14 15:32:16 +08:00
< svg v - else - if = "row[col.colKey] === true && showSupported" xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24" > < path fill = "currentColor" d = "M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59L21 7Z" > < / s v g >
< svg v - else - if = "row[col.colKey] === false && showUnsupported" xmlns = "http://www.w3.org/2000/svg" width = "20" height = "20" viewBox = "0 0 24 24" > < path fill = "currentColor" d = "M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" > < / s v g >
2025-10-12 05:13:57 +08:00
< / t e m p l a t e >
2025-10-14 15:32:16 +08:00
< / t - t a g >
< t - tag v - else theme = "warning" variant = "light" size = "small" style = "background-color: transparent; height: 26px" >
< template # icon >
< svg v - if = "showIncompatible" xmlns = "http://www.w3.org/2000/svg" width = "16" height = "16" viewBox = "0 0 24 24" > < path fill = "#F6B604" d = "M22.11 21.46L2.39 1.73L1.11 3l2.95 2.95A9.95 9.95 0 0 0 2 12c0 5.5 4.5 10 10 10c2.28 0 4.37-.77 6.05-2.06l2.79 2.79l1.27-1.27M12 20c-4.42 0-8-3.58-8-8c0-1.73.56-3.32 1.5-4.62L16.62 18.5A7.78 7.78 0 0 1 12 20M8.17 4.97L6.72 3.5C8.25 2.56 10.06 2 12 2c5.5 0 10 4.5 10 10c0 1.94-.56 3.75-1.5 5.28l-1.47-1.45c.62-1.14.97-2.44.97-3.83c0-4.42-3.58-8-8-8c-1.39 0-2.69.35-3.83.97Z" > < / s v g >
2025-10-12 05:13:57 +08:00
< / t e m p l a t e >
2025-10-14 15:32:16 +08:00
< / t - t a g >
< / t e m p l a t e >
< / t e m p l a t e >
< / t - t a b l e >
< / t - c o n f i g - p r o v i d e r >
< / d i v >
` ,
2025-04-04 13:15:39 +08:00
data ( ) {
return {
2025-10-14 15:32:16 +08:00
allColumns : mirrorsTableColumns ,
2025-04-04 13:15:39 +08:00
columns : mirrorsTableColumns ,
2025-10-14 15:32:16 +08:00
originalData : mirrorsTableData ,
data : mirrorsTableData . slice ( ) ,
rawFilterOptions : mirrorsTableFilterSelectOptions ,
selectedColumnFilters : [ ] ,
selectedRowFilters : [ ] ,
selectedCellStatuses : [ 'supported' , 'unsupported' , 'incompatible' ] ,
activeTableFilters : { } ,
2025-04-04 13:15:39 +08:00
}
} ,
2025-10-14 15:32:16 +08:00
created ( ) {
const allKeys = this . _flattenFilterKeys ( this . filterOptions )
this . selectedColumnFilters = allKeys . slice ( )
this . selectedRowFilters = Array . isArray ( this . originalData ) ? this . originalData . map ( ( r ) => r . name ) : [ ]
this . _debouncedUpdateColumns = debounce ( this . _updateColumns . bind ( this ) , 120 )
this . _debouncedUpdateRows = debounce ( this . _updateRows . bind ( this ) , 120 )
this . _updateColumns ( )
this . _updateRows ( )
} ,
computed : {
isMobile ( ) {
return window . matchMedia ( '(max-width: 768px)' ) . matches
} ,
localeFlags ( ) {
const p = ( window && window . location && window . location . pathname ) || ''
return {
isZhHant : p . includes ( '/zh-Hant' ) ,
isEn : p . includes ( '/en' ) ,
}
} ,
2025-10-16 02:17:52 +08:00
startTitle ( ) {
const f = this . localeFlags
return f . isZhHant ? [ '下方列表中的鏡像站均同步了' , '軟體倉庫,列表根據單位性質、地理位置、名稱長度排序,與實際下載速度無關。' ] : f . isEn ? [ 'All mirror sites in the list below synchronize the' , 'software repositories. The list is sorted by institution type, geographic location, and name length, and is not related to actual download speed.' ] : [ '下方列表中的镜像站均同步了' , '软件仓库,列表根据单位性质、地理位置、名称长度进行排序,与实际速度无关。' ]
} ,
2025-10-14 15:32:16 +08:00
globalConfig ( ) {
const f = this . localeFlags
return {
animation : { include : [ 'expand' , 'fade' ] , exclude : [ ] } ,
table : f . isZhHant
? {
empty : '\u66AB\u7121\u6578\u64DA' ,
loadingText : '\u6B63\u5728\u8F09\u5165\u4E2D\uFF0C\u8ACB\u7A0D\u5F8C' ,
loadingMoreText : '\u9EDE\u64CA\u8F09\u5165\u66F4\u591A' ,
filterInputPlaceholder : '\u8ACB\u8F38\u5165\u5185\u5BB9\uFF08\u7121\u9ED8\u8A8D\u503C\uFF09' ,
sortAscendingOperationText : '\u9EDE\u64CA\u5347\u5E8F' ,
sortCancelOperationText : '\u9EDE\u64CA\u53D6\u6D88\u6392\u5E8F' ,
sortDescendingOperationText : '\u9EDE\u64CA\u964D\u5E8F' ,
clearFilterResultButtonText : '\u6E05\u7A7A\u7BE9\u9078' ,
columnConfigButtonText : '\u884C\u914D\u7F6E' ,
columnConfigTitleText : '\u8868\u683C\u884C\u914D\u7F6E' ,
columnConfigDescriptionText : '\u8ACB\u9078\u64C7\u9700\u8981\u5728\u8868\u683C\u4E2D\u986F\u793A\u7684\u6578\u64DA\u884C' ,
confirmText : '\u78BA\u8A8D' ,
cancelText : '\u53D6\u6D88' ,
resetText : '\u91CD\u7F6E' ,
selectAllText : '\u5168\u9078' ,
searchResultText : '\u641C\u5C0B"{result}"\uFF0C\u627E\u5230{count}\u9805\u7D50\u679C' ,
}
: f . isEn
? {
empty : 'Empty Data' ,
loadingText : 'loading...' ,
loadingMoreText : 'loading more' ,
filterInputPlaceholder : '' ,
sortAscendingOperationText : 'click to sort ascending' ,
sortCancelOperationText : 'click to cancel sorting' ,
sortDescendingOperationText : 'click to sort descending' ,
clearFilterResultButtonText : 'Clear' ,
columnConfigButtonText : 'Column Config' ,
columnConfigTitleText : 'Table Column Config' ,
columnConfigDescriptionText : 'Please select columns to show them in the table' ,
confirmText : 'Confirm' ,
cancelText : 'Cancel' ,
resetText : 'Reset' ,
selectAllText : 'Select All' ,
searchResultText : 'Search "{result}". Found no items. | Search "{result}". Found 1 item. | Search "{result}". Found {count} items.' ,
}
: undefined ,
select : f . isZhHant
? {
empty : '\u66AB\u7121\u6578\u64DA' ,
loadingText : '\u8F09\u5165\u4E2D' ,
placeholder : '\u8ACB\u9078\u64C7' ,
}
: f . isEn
? {
empty : 'Empty Data' ,
loadingText : 'loading...' ,
placeholder : 'please select' ,
}
: undefined ,
}
} ,
selectPlaceholder ( ) {
const f = this . localeFlags
return f . isZhHant ? '选择要显示的列' : f . isEn ? 'Select columns to show' : '选择要显示的列'
} ,
rowSelectPlaceholder ( ) {
const f = this . localeFlags
return f . isZhHant ? '筛选显示的镜像' : f . isEn ? 'Filter mirrors to show' : '筛选显示的镜像'
} ,
rowFilterOptions ( ) {
const f = this . localeFlags
const arr = Array . isArray ( this . originalData ) ? this . originalData . map ( ( r ) => ( { value : r . name , label : r . name , iconName : r . icon } ) ) : [ ]
const head = { value : '__all__' , label : f . isZhHant ? '全選' : f . isEn ? 'Select All' : '全选' , checkAll : true }
return [ head ] . concat ( arr )
} ,
rowFilterOptionsRendered ( ) {
return Array . isArray ( this . rowFilterOptions ) ? this . rowFilterOptions . map ( ( o ) => this . _mapOptionForRow ( o ) ) : [ ]
} ,
statusLabels ( ) {
const f = this . localeFlags
return {
supported : f . isZhHant ? '支援' : f . isEn ? 'Supported' : '支持' ,
unsupported : f . isZhHant ? '不支援' : f . isEn ? 'Unsupported' : '不支持' ,
incompatible : f . isZhHant ? '不相容' : f . isEn ? 'Incompatible' : '不兼容' ,
}
} ,
showSupported ( ) {
return Array . isArray ( this . selectedCellStatuses ) && this . selectedCellStatuses . includes ( 'supported' )
} ,
showUnsupported ( ) {
return Array . isArray ( this . selectedCellStatuses ) && this . selectedCellStatuses . includes ( 'unsupported' )
} ,
showIncompatible ( ) {
return Array . isArray ( this . selectedCellStatuses ) && this . selectedCellStatuses . includes ( 'incompatible' )
} ,
filterOptions ( ) {
return Array . isArray ( this . rawFilterOptions ) ? this . rawFilterOptions . map ( ( o ) => this . _mapOptionForFilter ( o ) ) : [ ]
} ,
} ,
2025-06-15 20:18:44 +08:00
methods : {
2025-10-16 02:17:52 +08:00
dataChange ( data ) {
try {
const hasColumnFilters = this . activeTableFilters && Object . keys ( this . activeTableFilters ) . length > 0
const hasRowFilters = Array . isArray ( this . selectedRowFilters ) && this . selectedRowFilters . length > 0
if ( hasColumnFilters || hasRowFilters ) {
return
}
this . data = data
} catch { }
} ,
onFilterChange ( ) {
if ( this . _debouncedUpdateColumns ) this . _debouncedUpdateColumns ( )
else this . _updateColumns ( )
} ,
onRowFilterChange ( ) {
try {
if ( Array . isArray ( this . selectedRowFilters ) && this . selectedRowFilters . includes ( '__all__' ) ) {
this . selectedRowFilters = Array . isArray ( this . originalData ) ? this . originalData . map ( ( r ) => r . name ) : [ ]
}
} catch { }
if ( this . _debouncedUpdateRows ) this . _debouncedUpdateRows ( )
else this . _updateRows ( )
} ,
getMirrorSiteBranchUrl ( domain , branchName ) {
return ` https:// ${ domain } / ${ branchName . replace ( /_/ , '-' ) } `
} ,
2025-10-14 15:32:16 +08:00
_mapOptionForRow ( opt ) {
const prefix = '/assets/images/icon/mirrors/'
const copy = Object . assign ( { } , opt )
if ( copy . iconName ) copy . prefixIcon = prefix + copy . iconName
copy . content = function ( h , ctx ) {
const option = ( ctx && ctx . option ) || copy
const children = [ ]
if ( option . iconName ) {
children . push ( h ( 'img' , { src : prefix + option . iconName , width : 16 , height : 16 , style : 'vertical-align: middle' } ) )
}
children . push ( h ( 'span' , { style : option . iconName ? 'margin-left: 8px' : '' } , option . label || option . value || '' ) )
return h ( 'div' , { style : 'display: flex; align-items: center' } , children )
}
return copy
} ,
_mapOptionForFilter ( opt ) {
const prefix = '/assets/images/icon/'
const copy = Object . assign ( { } , opt )
if ( copy . iconName ) copy . prefixIcon = prefix + copy . iconName
copy . content = function ( h , ctx ) {
const option = ( ctx && ctx . option ) || copy
const children = [ ]
if ( option . iconName ) {
children . push ( h ( 'img' , { src : prefix + option . iconName , width : 16 , height : 16 , style : 'vertical-align: middle' } ) )
}
children . push ( h ( 'span' , { style : option . iconName ? 'margin-left: 8px' : '' } , option . label || option . value || '' ) )
return h ( 'div' , { style : 'display: flex; align-items: center' } , children )
}
if ( Array . isArray ( copy . children ) ) {
copy . children = copy . children . map ( ( c ) => this . _mapOptionForFilter ( c ) )
}
return copy
} ,
onCellStatusChange ( ) {
if ( this . _debouncedUpdateRows ) this . _debouncedUpdateRows ( )
else this . _updateRows ( )
} ,
onTableFilterChange ( filters ) {
try {
this . activeTableFilters = filters || { }
if ( this . _debouncedUpdateRows ) this . _debouncedUpdateRows ( )
else this . _updateRows ( )
2025-10-16 02:17:52 +08:00
} catch { }
2025-10-14 15:32:16 +08:00
} ,
_updateColumns ( ) {
try {
const keys = new Set ( this . selectedColumnFilters || [ ] )
this . columns = this . allColumns . filter ( ( col ) => col . colKey === 'name' || keys . has ( col . colKey ) )
} catch ( e ) {
this . columns = this . allColumns
}
} ,
_updateRows ( ) {
try {
this . _computeFilteredData ( )
2025-10-16 02:17:52 +08:00
} catch { }
2025-10-14 15:32:16 +08:00
} ,
_computeFilteredData ( ) {
try {
let rows = Array . isArray ( this . originalData ) ? this . originalData . slice ( ) : [ ]
if ( Array . isArray ( this . selectedRowFilters ) && this . selectedRowFilters . length ) {
const names = new Set ( this . selectedRowFilters || [ ] )
rows = rows . filter ( ( r ) => names . has ( r . name ) )
}
const filters = this . activeTableFilters || { }
const filterKeys = Object . keys ( filters )
if ( filterKeys . length ) {
rows = rows . filter ( ( row ) => {
for ( let i = 0 ; i < filterKeys . length ; i ++ ) {
const key = filterKeys [ i ]
let value = filters [ key ]
if ( Array . isArray ( value ) ) value = value . length ? value [ 0 ] : undefined
if ( ! value ) continue
const cell = Object . prototype . hasOwnProperty . call ( row , key ) ? row [ key ] : undefined
if ( value === 'supported' ) {
if ( cell !== true ) return false
} else if ( value === 'unsupported' ) {
if ( cell !== false ) return false
} else if ( value === 'incompatible' ) {
if ( cell !== 'incompatible' ) return false
} else {
continue
}
}
return true
} )
}
this . data = rows
2025-10-16 02:17:52 +08:00
} catch { }
2025-10-14 15:32:16 +08:00
} ,
_flattenFilterKeys ( options ) {
const set = new Set ( )
options . forEach ( ( opt ) => {
if ( opt . group && Array . isArray ( opt . children ) ) {
opt . children . forEach ( ( child ) => {
if ( child && child . value ) set . add ( child . value )
} )
} else if ( Array . isArray ( opt . children ) ) {
opt . children . forEach ( ( child ) => {
if ( child && child . value ) set . add ( child . value )
} )
} else if ( opt . value ) {
set . add ( opt . value )
}
} )
return Array . from ( set )
2025-06-15 20:18:44 +08:00
} ,
} ,
2025-04-04 13:15:39 +08:00
} )