需要修复基于vue js的加密图表中损坏的图标
Need to fix broken icons in crypto chart based on vue js
我正在使用显示加密货币列表最新价格的 Vue Js 加密货币图表,问题是有很多图标链接已损坏,但我无法单独修复它们,它们都是从 github 图标库中检索到的。有什么办法可以解决这个问题,或者至少设置一个默认的“无图像”图标以显示在没有图标的地方?
负责检索图标的部分:
// vue instance
new Vue({
// mount point
el: '#app',
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
cache : {}, // coins data cache
coins : [], // live coin list from api
asset : 'USDT', // filter by base asset pair
search : '', // filter by search string
sort : 'assetVolume', // sort by param
order : 'desc', // sort order ( asc, desc )
limit : 50, // limit list
status : 0, // socket status ( 0: closed, 1: open, 2: active, -1: error )
sock : null, // socket inst
cx : 0,
cy : 0,
},
我的完整Js:
// common number filters
Vue.filter( 'toFixed', ( num, asset ) => {
if ( typeof asset === 'number' ) return Number( num ).toFixed( asset );
return Number( num ).toFixed( ( asset === 'USDT' ) ? 3 : 8 );
});
Vue.filter( 'toMoney', num => {
return Number( num ).toFixed( 0 ).replace( /./g, ( c, i, a ) => {
return i && c !== "." && ( ( a.length - i ) % 3 === 0 ) ? ',' + c : c;
});
});
// component for creating line chart
Vue.component( 'linechart', {
props: {
width: { type: Number, default: 400, required: true },
height: { type: Number, default: 40, required: true },
values: { type: Array, default: [], required: true },
},
data() {
return { cx: 0, cy: 0 };
},
computed: {
viewBox() {
return '0 0 '+ this.width +' '+ this.height;
},
chartPoints() {
let data = this.getPoints();
let last = data.length ? data[ data.length - 1 ] : { x: 0, y: 0 };
let list = data.map( d => ( d.x - 10 ) +','+ d.y );
this.cx = last.x - 5;
this.cy = last.y;
return list.join( ' ' );
},
},
methods: {
getPoints() {
this.width = parseFloat( this.width ) || 0;
this.height = parseFloat( this.height ) || 0;
let min = this.values.reduce( ( min, val ) => val < min ? val : min, this.values[ 0 ] );
let max = this.values.reduce( ( max, val ) => val > max ? val : max, this.values[ 0 ] );
let len = this.values.length;
let half = this.height / 2;
let range = ( max > min ) ? ( max - min ) : this.height;
let gap = ( len > 1 ) ? ( this.width / ( len - 1 ) ) : 1;
let points = [];
for ( let i = 0; i < len; ++i ) {
let d = this.values[ i ];
let val = 2 * ( ( d - min ) / range - 0.5 );
let x = i * gap;
let y = -val * half * 0.8 + half;
points.push( { x, y } );
}
return points;
}
},
template: `
<svg :viewBox="viewBox" xmlns="http://www.w3.org/2000/svg">
<polyline class="cryptocolor" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" :points="chartPoints" />
<circle class="cryptocolor" :cx="cx" :cy="cy" r="4" fill="#fff" stroke="none" />
</svg>`,
});
// vue instance
new Vue({
// mount point
el: '#app',
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
cache : {}, // coins data cache
coins : [], // live coin list from api
asset : 'USDT', // filter by base asset pair
search : '', // filter by search string
sort : 'assetVolume', // sort by param
order : 'desc', // sort order ( asc, desc )
limit : 50, // limit list
status : 0, // socket status ( 0: closed, 1: open, 2: active, -1: error )
sock : null, // socket inst
cx : 0,
cy : 0,
},
// computed methods
computed: {
// process coins list
coinsList() {
let list = this.coins.slice();
let search = this.search.replace( /[^\s\w\-\.]+/g, '' ).replace( /[\r\s\t\n]+/g, ' ' ).trim();
if ( this.asset ) {
list = list.filter( i => i.asset === this.asset );
}
if ( search && search.length > 1 ) {
let reg = new RegExp( '^('+ search +')', 'i' );
list = list.filter( i => reg.test( i.token ) );
}
if ( this.sort ) {
list = this.sortList( list, this.sort, this.order );
}
if ( this.limit ) {
list = list.slice( 0, this.limit );
}
return list;
},
// show socket connection loader
loaderVisible() {
return ( this.status === 2 ) ? false : true;
},
// sort-by label for buttons, etc
sortLabel() {
switch ( this.sort ) {
case 'token' : return 'Token';
case 'percent' : return 'Percent';
case 'close' : return 'Price';
case 'change' : return 'Change';
case 'assetVolume' : return 'Volume';
case 'tokenVolume' : return 'Volume';
case 'trades' : return 'Trades';
default : return 'Default';
}
},
},
// custom methods
methods: {
// apply sorting and toggle order
sortBy( key, order ) {
if ( this.sort !== key ) { this.order = order || 'asc'; }
else { this.order = ( this.order === 'asc' ) ? 'desc' : 'asc'; }
this.sort = key;
},
// filter by asset
filterAsset( asset ) {
this.asset = String( asset || 'BTC' );
},
// set list limit
setLimit( limit ) {
this.limit = parseInt( limit ) || 0;
},
// on socket connected
onSockOpen( e ) {
this.status = 1; // open
console.info( 'WebSocketInfo:', 'Connection open ('+ this.endpoint +').' );
},
// on socket closed
onSockClose( e ) {
this.status = 0; // closed
console.info( 'WebSocketInfo:', 'Connection closed ('+ this.endpoint +').' );
setTimeout( this.sockInit, 10000 ); // try again
},
// on socket error
onSockError( err ) {
this.status = -1; // error
console.error( 'WebSocketError:', err.message || err );
setTimeout( this.sockInit, 10000 ); // try again
},
// process data from socket
onSockData( e ) {
let list = JSON.parse( e.data ) || [];
for ( let item of list ) {
// cleanup data for each coin
let c = this.getCoinData( item );
// keep to up 100 previous close prices in hostiry for each coin
c.history = this.cache.hasOwnProperty( c.symbol ) ? this.cache[ c.symbol ].history : this.fakeHistory( c.close );
if ( c.history.length > 100 ) c.history = c.history.slice( c.history.length - 100 );
c.history.push( c.close );
// add coin data to cache
this.cache[ c.symbol ] = c;
}
// convert cache object to final prices list for each symbol
this.coins = Object.keys( this.cache ).map( s => this.cache[ s ] );
this.status = 2; // active
},
// start socket connection
sockInit() {
if ( this.status > 0 ) return;
try {
this.status = 0; // closed
this.sock = new WebSocket( this.endpoint );
this.sock.addEventListener( 'open', this.onSockOpen );
this.sock.addEventListener( 'close', this.onSockClose );
this.sock.addEventListener( 'error', this.onSockError );
this.sock.addEventListener( 'message', this.onSockData );
}
catch( err ) {
console.error( 'WebSocketError:', err.message || err );
this.status = -1; // error
this.sock = null;
}
},
// start socket connection
sockClose() {
if ( this.sock ) {
this.sock.close();
}
},
// come up with some fake history prices to fill in the initial line chart
fakeHistory( close ) {
let num = close * 0.0001; // faction of current price
let min = -Math.abs( num );
let max = Math.abs( num );
let out = [];
for ( let i = 0; i < 50; ++i ) {
let rand = Math.random() * ( max - min ) + min;
out.push( close + rand );
}
return out;
},
// finalize data for each coin from socket
getCoinData( item ) {
let reg = /^([A-Z]+)(BTC|ETH|BNB|USDT|TUSD)$/;
let symbol = String( item.s ).replace( /[^\w\-]+/g, '' ).toUpperCase();
let token = symbol.replace( reg, '' );
let asset = symbol.replace( reg, '' );
let name = token;
let pair = token +'/'+ asset;
let icon = this.iconbase + token.toLowerCase() + '_.png';
let open = parseFloat( item.o );
let high = parseFloat( item.h );
let low = parseFloat( item.l );
let close = parseFloat( item.c );
let change = parseFloat( item.p );
let percent = parseFloat( item.P );
let trades = parseInt( item.n );
let tokenVolume = Math.round( item.v );
let assetVolume = Math.round( item.q );
let sign = ( percent >= 0 ) ? '+' : '';
let arrow = ( percent >= 0 ) ? '▲' : '▼';
let info = [ pair, close.toFixed( 8 ), '(', arrow, sign + percent.toFixed( 2 ) +'%', '|', sign + change.toFixed( 8 ), ')' ].join( ' ' );
let style = '';
if ( percent > 0 ) style = 'gain';
if ( percent < 0 ) style = 'loss';
return { symbol, token, asset, name, pair, icon, open, high, low, close, change, percent, trades, tokenVolume, assetVolume, sign, arrow, style, info };
},
// sort an array by key and order
sortList( list, key, order ) {
return list.sort( ( a, b ) => {
let _a = a[ key ];
let _b = b[ key ];
if ( _a && _b ) {
_a = ( typeof _a === 'string' ) ? _a.toUpperCase() : _a;
_b = ( typeof _b === 'string' ) ? _b.toUpperCase() : _b;
if ( order === 'asc' ) {
if ( _a < _b ) return -1;
if ( _a > _b ) return 1;
}
if ( order === 'desc' ) {
if ( _a > _b ) return -1;
if ( _a < _b ) return 1;
}
}
return 0;
});
},
},
// app mounted
mounted() {
this.sockInit();
},
// app destroyed
destroyed() {
this.sockClose();
}
});
Js Fiddle:
https://jsfiddle.net/g5mhe923/1/
我的版本来自这个修改:
https://codepen.io/rainner/pen/bjJYjp
原始源代码:
https://github.com/rainner/binance-watch
更新版本的 Js 文件:
https://rainner.github.io/binance-watch/public/bundles/app.min.js
https://rainner.github.io/binance-watch/public/js/crypto-js.min.js
原始演示:
https://rainner.github.io/binance-watch/
谁能告诉我在哪里可以找到这部分的参考资料?
我也找不到关于这部分的任何参考:
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
我知道 2 个损坏 link 图片的技巧:
- 首先是隐藏破损的图片link:
<img :src="c.icon" :alt="c.pair" onerror="this.style.display='none'"/>
- 第二种方法是使用您选择的默认图像并显示它以防图像 link 损坏。就像下面我把你的一张图片作为默认图片
<img :src="c.icon" :alt="c.pair" onerror="this.src='https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/xtz_.png'" />
我正在使用显示加密货币列表最新价格的 Vue Js 加密货币图表,问题是有很多图标链接已损坏,但我无法单独修复它们,它们都是从 github 图标库中检索到的。有什么办法可以解决这个问题,或者至少设置一个默认的“无图像”图标以显示在没有图标的地方?
负责检索图标的部分:
// vue instance
new Vue({
// mount point
el: '#app',
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
cache : {}, // coins data cache
coins : [], // live coin list from api
asset : 'USDT', // filter by base asset pair
search : '', // filter by search string
sort : 'assetVolume', // sort by param
order : 'desc', // sort order ( asc, desc )
limit : 50, // limit list
status : 0, // socket status ( 0: closed, 1: open, 2: active, -1: error )
sock : null, // socket inst
cx : 0,
cy : 0,
},
我的完整Js:
// common number filters
Vue.filter( 'toFixed', ( num, asset ) => {
if ( typeof asset === 'number' ) return Number( num ).toFixed( asset );
return Number( num ).toFixed( ( asset === 'USDT' ) ? 3 : 8 );
});
Vue.filter( 'toMoney', num => {
return Number( num ).toFixed( 0 ).replace( /./g, ( c, i, a ) => {
return i && c !== "." && ( ( a.length - i ) % 3 === 0 ) ? ',' + c : c;
});
});
// component for creating line chart
Vue.component( 'linechart', {
props: {
width: { type: Number, default: 400, required: true },
height: { type: Number, default: 40, required: true },
values: { type: Array, default: [], required: true },
},
data() {
return { cx: 0, cy: 0 };
},
computed: {
viewBox() {
return '0 0 '+ this.width +' '+ this.height;
},
chartPoints() {
let data = this.getPoints();
let last = data.length ? data[ data.length - 1 ] : { x: 0, y: 0 };
let list = data.map( d => ( d.x - 10 ) +','+ d.y );
this.cx = last.x - 5;
this.cy = last.y;
return list.join( ' ' );
},
},
methods: {
getPoints() {
this.width = parseFloat( this.width ) || 0;
this.height = parseFloat( this.height ) || 0;
let min = this.values.reduce( ( min, val ) => val < min ? val : min, this.values[ 0 ] );
let max = this.values.reduce( ( max, val ) => val > max ? val : max, this.values[ 0 ] );
let len = this.values.length;
let half = this.height / 2;
let range = ( max > min ) ? ( max - min ) : this.height;
let gap = ( len > 1 ) ? ( this.width / ( len - 1 ) ) : 1;
let points = [];
for ( let i = 0; i < len; ++i ) {
let d = this.values[ i ];
let val = 2 * ( ( d - min ) / range - 0.5 );
let x = i * gap;
let y = -val * half * 0.8 + half;
points.push( { x, y } );
}
return points;
}
},
template: `
<svg :viewBox="viewBox" xmlns="http://www.w3.org/2000/svg">
<polyline class="cryptocolor" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" :points="chartPoints" />
<circle class="cryptocolor" :cx="cx" :cy="cy" r="4" fill="#fff" stroke="none" />
</svg>`,
});
// vue instance
new Vue({
// mount point
el: '#app',
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
cache : {}, // coins data cache
coins : [], // live coin list from api
asset : 'USDT', // filter by base asset pair
search : '', // filter by search string
sort : 'assetVolume', // sort by param
order : 'desc', // sort order ( asc, desc )
limit : 50, // limit list
status : 0, // socket status ( 0: closed, 1: open, 2: active, -1: error )
sock : null, // socket inst
cx : 0,
cy : 0,
},
// computed methods
computed: {
// process coins list
coinsList() {
let list = this.coins.slice();
let search = this.search.replace( /[^\s\w\-\.]+/g, '' ).replace( /[\r\s\t\n]+/g, ' ' ).trim();
if ( this.asset ) {
list = list.filter( i => i.asset === this.asset );
}
if ( search && search.length > 1 ) {
let reg = new RegExp( '^('+ search +')', 'i' );
list = list.filter( i => reg.test( i.token ) );
}
if ( this.sort ) {
list = this.sortList( list, this.sort, this.order );
}
if ( this.limit ) {
list = list.slice( 0, this.limit );
}
return list;
},
// show socket connection loader
loaderVisible() {
return ( this.status === 2 ) ? false : true;
},
// sort-by label for buttons, etc
sortLabel() {
switch ( this.sort ) {
case 'token' : return 'Token';
case 'percent' : return 'Percent';
case 'close' : return 'Price';
case 'change' : return 'Change';
case 'assetVolume' : return 'Volume';
case 'tokenVolume' : return 'Volume';
case 'trades' : return 'Trades';
default : return 'Default';
}
},
},
// custom methods
methods: {
// apply sorting and toggle order
sortBy( key, order ) {
if ( this.sort !== key ) { this.order = order || 'asc'; }
else { this.order = ( this.order === 'asc' ) ? 'desc' : 'asc'; }
this.sort = key;
},
// filter by asset
filterAsset( asset ) {
this.asset = String( asset || 'BTC' );
},
// set list limit
setLimit( limit ) {
this.limit = parseInt( limit ) || 0;
},
// on socket connected
onSockOpen( e ) {
this.status = 1; // open
console.info( 'WebSocketInfo:', 'Connection open ('+ this.endpoint +').' );
},
// on socket closed
onSockClose( e ) {
this.status = 0; // closed
console.info( 'WebSocketInfo:', 'Connection closed ('+ this.endpoint +').' );
setTimeout( this.sockInit, 10000 ); // try again
},
// on socket error
onSockError( err ) {
this.status = -1; // error
console.error( 'WebSocketError:', err.message || err );
setTimeout( this.sockInit, 10000 ); // try again
},
// process data from socket
onSockData( e ) {
let list = JSON.parse( e.data ) || [];
for ( let item of list ) {
// cleanup data for each coin
let c = this.getCoinData( item );
// keep to up 100 previous close prices in hostiry for each coin
c.history = this.cache.hasOwnProperty( c.symbol ) ? this.cache[ c.symbol ].history : this.fakeHistory( c.close );
if ( c.history.length > 100 ) c.history = c.history.slice( c.history.length - 100 );
c.history.push( c.close );
// add coin data to cache
this.cache[ c.symbol ] = c;
}
// convert cache object to final prices list for each symbol
this.coins = Object.keys( this.cache ).map( s => this.cache[ s ] );
this.status = 2; // active
},
// start socket connection
sockInit() {
if ( this.status > 0 ) return;
try {
this.status = 0; // closed
this.sock = new WebSocket( this.endpoint );
this.sock.addEventListener( 'open', this.onSockOpen );
this.sock.addEventListener( 'close', this.onSockClose );
this.sock.addEventListener( 'error', this.onSockError );
this.sock.addEventListener( 'message', this.onSockData );
}
catch( err ) {
console.error( 'WebSocketError:', err.message || err );
this.status = -1; // error
this.sock = null;
}
},
// start socket connection
sockClose() {
if ( this.sock ) {
this.sock.close();
}
},
// come up with some fake history prices to fill in the initial line chart
fakeHistory( close ) {
let num = close * 0.0001; // faction of current price
let min = -Math.abs( num );
let max = Math.abs( num );
let out = [];
for ( let i = 0; i < 50; ++i ) {
let rand = Math.random() * ( max - min ) + min;
out.push( close + rand );
}
return out;
},
// finalize data for each coin from socket
getCoinData( item ) {
let reg = /^([A-Z]+)(BTC|ETH|BNB|USDT|TUSD)$/;
let symbol = String( item.s ).replace( /[^\w\-]+/g, '' ).toUpperCase();
let token = symbol.replace( reg, '' );
let asset = symbol.replace( reg, '' );
let name = token;
let pair = token +'/'+ asset;
let icon = this.iconbase + token.toLowerCase() + '_.png';
let open = parseFloat( item.o );
let high = parseFloat( item.h );
let low = parseFloat( item.l );
let close = parseFloat( item.c );
let change = parseFloat( item.p );
let percent = parseFloat( item.P );
let trades = parseInt( item.n );
let tokenVolume = Math.round( item.v );
let assetVolume = Math.round( item.q );
let sign = ( percent >= 0 ) ? '+' : '';
let arrow = ( percent >= 0 ) ? '▲' : '▼';
let info = [ pair, close.toFixed( 8 ), '(', arrow, sign + percent.toFixed( 2 ) +'%', '|', sign + change.toFixed( 8 ), ')' ].join( ' ' );
let style = '';
if ( percent > 0 ) style = 'gain';
if ( percent < 0 ) style = 'loss';
return { symbol, token, asset, name, pair, icon, open, high, low, close, change, percent, trades, tokenVolume, assetVolume, sign, arrow, style, info };
},
// sort an array by key and order
sortList( list, key, order ) {
return list.sort( ( a, b ) => {
let _a = a[ key ];
let _b = b[ key ];
if ( _a && _b ) {
_a = ( typeof _a === 'string' ) ? _a.toUpperCase() : _a;
_b = ( typeof _b === 'string' ) ? _b.toUpperCase() : _b;
if ( order === 'asc' ) {
if ( _a < _b ) return -1;
if ( _a > _b ) return 1;
}
if ( order === 'desc' ) {
if ( _a > _b ) return -1;
if ( _a < _b ) return 1;
}
}
return 0;
});
},
},
// app mounted
mounted() {
this.sockInit();
},
// app destroyed
destroyed() {
this.sockClose();
}
});
Js Fiddle: https://jsfiddle.net/g5mhe923/1/
我的版本来自这个修改: https://codepen.io/rainner/pen/bjJYjp
原始源代码: https://github.com/rainner/binance-watch
更新版本的 Js 文件: https://rainner.github.io/binance-watch/public/bundles/app.min.js https://rainner.github.io/binance-watch/public/js/crypto-js.min.js
原始演示: https://rainner.github.io/binance-watch/
谁能告诉我在哪里可以找到这部分的参考资料?
我也找不到关于这部分的任何参考:
// app data
data: {
endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
我知道 2 个损坏 link 图片的技巧:
- 首先是隐藏破损的图片link:
<img :src="c.icon" :alt="c.pair" onerror="this.style.display='none'"/>
- 第二种方法是使用您选择的默认图像并显示它以防图像 link 损坏。就像下面我把你的一张图片作为默认图片
<img :src="c.icon" :alt="c.pair" onerror="this.src='https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/xtz_.png'" />