feat: redesign crypto market with synced favorites
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `la_crypto_favorite` (
|
||||||
|
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`user_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户ID',
|
||||||
|
`symbol` varchar(20) NOT NULL DEFAULT '' COMMENT '币种代码',
|
||||||
|
`status` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '自选状态 0-取消 1-自选',
|
||||||
|
`create_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
|
||||||
|
`update_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新时间',
|
||||||
|
`delete_time` int NULL DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_symbol` (`user_id`, `symbol`),
|
||||||
|
KEY `idx_user_status` (`user_id`, `status`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户加密行情自选表';
|
||||||
|
|
||||||
|
INSERT INTO `la_config` (`type`, `name`, `value`, `create_time`, `update_time`)
|
||||||
|
SELECT 'crypto_market', 'api_base', 'https://api.coingecko.com/api/v3', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `la_config` WHERE `type` = 'crypto_market' AND `name` = 'api_base'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `la_config` (`type`, `name`, `value`, `create_time`, `update_time`)
|
||||||
|
SELECT 'crypto_market', 'cache_seconds', '60', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `la_config` WHERE `type` = 'crypto_market' AND `name` = 'cache_seconds'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @crypto_market_coins = JSON_ARRAY(
|
||||||
|
JSON_OBJECT('id', 'bitcoin', 'symbol', 'BTC', 'name_zh', '比特币'),
|
||||||
|
JSON_OBJECT('id', 'ethereum', 'symbol', 'ETH', 'name_zh', '以太坊'),
|
||||||
|
JSON_OBJECT('id', 'binancecoin', 'symbol', 'BNB', 'name_zh', '币安币'),
|
||||||
|
JSON_OBJECT('id', 'solana', 'symbol', 'SOL', 'name_zh', 'Solana'),
|
||||||
|
JSON_OBJECT('id', 'ripple', 'symbol', 'XRP', 'name_zh', '瑞波币'),
|
||||||
|
JSON_OBJECT('id', 'cardano', 'symbol', 'ADA', 'name_zh', '艾达币'),
|
||||||
|
JSON_OBJECT('id', 'dogecoin', 'symbol', 'DOGE', 'name_zh', '狗狗币'),
|
||||||
|
JSON_OBJECT('id', 'tron', 'symbol', 'TRX', 'name_zh', '波场'),
|
||||||
|
JSON_OBJECT('id', 'avalanche-2', 'symbol', 'AVAX', 'name_zh', 'Avalanche'),
|
||||||
|
JSON_OBJECT('id', 'chainlink', 'symbol', 'LINK', 'name_zh', 'Chainlink'),
|
||||||
|
JSON_OBJECT('id', 'the-open-network', 'symbol', 'TON', 'name_zh', 'Toncoin'),
|
||||||
|
JSON_OBJECT('id', 'shiba-inu', 'symbol', 'SHIB', 'name_zh', '柴犬币'),
|
||||||
|
JSON_OBJECT('id', 'polkadot', 'symbol', 'DOT', 'name_zh', '波卡币'),
|
||||||
|
JSON_OBJECT('id', 'sui', 'symbol', 'SUI', 'name_zh', 'Sui'),
|
||||||
|
JSON_OBJECT('id', 'near', 'symbol', 'NEAR', 'name_zh', 'NEAR'),
|
||||||
|
JSON_OBJECT('id', 'litecoin', 'symbol', 'LTC', 'name_zh', '莱特币'),
|
||||||
|
JSON_OBJECT('id', 'bitcoin-cash', 'symbol', 'BCH', 'name_zh', '比特币现金'),
|
||||||
|
JSON_OBJECT('id', 'uniswap', 'symbol', 'UNI', 'name_zh', 'Uniswap'),
|
||||||
|
JSON_OBJECT('id', 'aave', 'symbol', 'AAVE', 'name_zh', 'Aave'),
|
||||||
|
JSON_OBJECT('id', 'ethereum-classic', 'symbol', 'ETC', 'name_zh', '以太坊经典')
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `la_config` (`type`, `name`, `value`, `create_time`, `update_time`)
|
||||||
|
SELECT 'crypto_market', 'coins', CAST(@crypto_market_coins AS CHAR), UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `la_config` WHERE `type` = 'crypto_market' AND `name` = 'coins'
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE `la_decorate_tabbar`
|
||||||
|
SET `name` = '行情', `update_time` = UNIX_TIMESTAMP()
|
||||||
|
WHERE `link` LIKE '%pages%crypto%crypto%'
|
||||||
|
AND `name` <> '行情';
|
||||||
@@ -2,12 +2,59 @@
|
|||||||
|
|
||||||
namespace app\api\controller;
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\api\logic\CryptoLogic;
|
||||||
|
use app\api\validate\CryptoValidate;
|
||||||
use think\facade\Cache;
|
use think\facade\Cache;
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
|
|
||||||
class CryptoController extends BaseApiController
|
class CryptoController extends BaseApiController
|
||||||
{
|
{
|
||||||
public array $notNeedLogin = ['proxy'];
|
public array $notNeedLogin = ['proxy', 'market'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人民币加密行情与市场概览
|
||||||
|
*/
|
||||||
|
public function market()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->data(CryptoLogic::market());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('[CryptoMarket] controller failed: ' . $e->getMessage());
|
||||||
|
return $this->fail('行情加载失败,请稍后重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前账号的加密自选列表
|
||||||
|
*/
|
||||||
|
public function favorites()
|
||||||
|
{
|
||||||
|
return $this->data(['symbols' => CryptoLogic::favorites($this->userId)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入加密自选
|
||||||
|
*/
|
||||||
|
public function addFavorite()
|
||||||
|
{
|
||||||
|
$params = (new CryptoValidate())->post()->goCheck('favorite');
|
||||||
|
if (!CryptoLogic::addFavorite($this->userId, (string) $params['symbol'])) {
|
||||||
|
return $this->fail(CryptoLogic::getError());
|
||||||
|
}
|
||||||
|
return $this->success('已加入自选');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消加密自选
|
||||||
|
*/
|
||||||
|
public function cancelFavorite()
|
||||||
|
{
|
||||||
|
$params = (new CryptoValidate())->post()->goCheck('favorite');
|
||||||
|
if (!CryptoLogic::cancelFavorite($this->userId, (string) $params['symbol'])) {
|
||||||
|
return $this->fail(CryptoLogic::getError());
|
||||||
|
}
|
||||||
|
return $this->success('已取消自选');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binance API 代理(解决客户端直连被墙问题)
|
* Binance API 代理(解决客户端直连被墙问题)
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\logic;
|
||||||
|
|
||||||
|
use app\common\enum\YesNoEnum;
|
||||||
|
use app\common\logic\BaseLogic;
|
||||||
|
use app\common\model\crypto\CryptoFavorite;
|
||||||
|
use app\common\service\crypto\CryptoMarketService;
|
||||||
|
|
||||||
|
class CryptoLogic extends BaseLogic
|
||||||
|
{
|
||||||
|
public static function market(): array
|
||||||
|
{
|
||||||
|
return CryptoMarketService::snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function favorites(int $userId): array
|
||||||
|
{
|
||||||
|
$symbols = CryptoFavorite::where([
|
||||||
|
'user_id' => $userId,
|
||||||
|
'status' => YesNoEnum::YES,
|
||||||
|
])->order('create_time asc')->column('symbol');
|
||||||
|
|
||||||
|
return array_values(array_unique(array_map('strtoupper', $symbols)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function addFavorite(int $userId, string $symbol): bool
|
||||||
|
{
|
||||||
|
$symbol = strtoupper(trim($symbol));
|
||||||
|
if (!CryptoMarketService::isSupportedSymbol($symbol)) {
|
||||||
|
self::setError('暂不支持该币种');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$where = ['user_id' => $userId, 'symbol' => $symbol];
|
||||||
|
$favorite = CryptoFavorite::where($where)->findOrEmpty();
|
||||||
|
if ($favorite->isEmpty()) {
|
||||||
|
try {
|
||||||
|
CryptoFavorite::create($where + ['status' => YesNoEnum::YES]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
CryptoFavorite::update(['status' => YesNoEnum::YES], $where);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$favorite->status = YesNoEnum::YES;
|
||||||
|
$favorite->save();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
self::setError('收藏失败,请稍后重试');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cancelFavorite(int $userId, string $symbol): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CryptoFavorite::update(
|
||||||
|
['status' => YesNoEnum::NO],
|
||||||
|
['user_id' => $userId, 'symbol' => strtoupper(trim($symbol))]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
self::setError('取消收藏失败,请稍后重试');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\validate;
|
||||||
|
|
||||||
|
use app\common\validate\BaseValidate;
|
||||||
|
|
||||||
|
class CryptoValidate extends BaseValidate
|
||||||
|
{
|
||||||
|
protected $rule = [
|
||||||
|
'symbol' => 'require|alphaNum|max:20',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $message = [
|
||||||
|
'symbol.require' => '币种不能为空',
|
||||||
|
'symbol.alphaNum' => '币种格式错误',
|
||||||
|
'symbol.max' => '币种格式错误',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function sceneFavorite()
|
||||||
|
{
|
||||||
|
return $this->only(['symbol']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\model\crypto;
|
||||||
|
|
||||||
|
use app\common\model\BaseModel;
|
||||||
|
|
||||||
|
class CryptoFavorite extends BaseModel
|
||||||
|
{
|
||||||
|
protected $name = 'crypto_favorite';
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\service\crypto;
|
||||||
|
|
||||||
|
use app\common\service\ConfigService;
|
||||||
|
use RuntimeException;
|
||||||
|
use think\facade\Cache;
|
||||||
|
use think\facade\Log;
|
||||||
|
|
||||||
|
class CryptoMarketService
|
||||||
|
{
|
||||||
|
private const CACHE_KEY = 'crypto_market_snapshot';
|
||||||
|
private const STALE_CACHE_KEY = 'crypto_market_snapshot_stale';
|
||||||
|
|
||||||
|
public static function snapshot(): array
|
||||||
|
{
|
||||||
|
$cached = Cache::get(self::CACHE_KEY);
|
||||||
|
if (self::isValidSnapshot($cached)) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$snapshot = self::fetchSnapshot();
|
||||||
|
$cacheSeconds = max(30, min(300, (int) ConfigService::get('crypto_market', 'cache_seconds', 60)));
|
||||||
|
Cache::set(self::CACHE_KEY, $snapshot, $cacheSeconds);
|
||||||
|
Cache::set(self::STALE_CACHE_KEY, $snapshot, 86400);
|
||||||
|
return $snapshot;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('[CryptoMarket] fetch failed: ' . $e->getMessage());
|
||||||
|
$stale = Cache::get(self::STALE_CACHE_KEY);
|
||||||
|
if (self::isValidSnapshot($stale)) {
|
||||||
|
$stale['stale'] = true;
|
||||||
|
return $stale;
|
||||||
|
}
|
||||||
|
throw new RuntimeException('行情数据暂时不可用');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function supportedSymbols(): array
|
||||||
|
{
|
||||||
|
return array_values(array_unique(array_column(self::coinConfig(), 'symbol')));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isSupportedSymbol(string $symbol): bool
|
||||||
|
{
|
||||||
|
return in_array(strtoupper(trim($symbol)), self::supportedSymbols(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function fetchSnapshot(): array
|
||||||
|
{
|
||||||
|
$coinConfig = self::coinConfig();
|
||||||
|
if (empty($coinConfig)) {
|
||||||
|
throw new RuntimeException('加密行情币种配置不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiBase = rtrim((string) ConfigService::get(
|
||||||
|
'crypto_market',
|
||||||
|
'api_base',
|
||||||
|
'https://api.coingecko.com/api/v3'
|
||||||
|
), '/');
|
||||||
|
$ids = array_column($coinConfig, 'id');
|
||||||
|
$marketUrl = $apiBase . '/coins/markets?' . http_build_query([
|
||||||
|
'vs_currency' => 'cny',
|
||||||
|
'ids' => implode(',', $ids),
|
||||||
|
'order' => 'market_cap_desc',
|
||||||
|
'per_page' => max(1, count($ids)),
|
||||||
|
'page' => 1,
|
||||||
|
'sparkline' => 'false',
|
||||||
|
'price_change_percentage' => '24h',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$marketRows = self::requestJson($marketUrl);
|
||||||
|
$global = self::requestJson($apiBase . '/global');
|
||||||
|
$globalData = is_array($global['data'] ?? null) ? $global['data'] : [];
|
||||||
|
$configById = [];
|
||||||
|
foreach ($coinConfig as $item) {
|
||||||
|
$configById[$item['id']] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coins = [];
|
||||||
|
foreach ($marketRows as $row) {
|
||||||
|
$id = (string) ($row['id'] ?? '');
|
||||||
|
if (!isset($configById[$id])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$meta = $configById[$id];
|
||||||
|
$coins[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'symbol' => $meta['symbol'],
|
||||||
|
'name' => (string) ($row['name'] ?? $meta['symbol']),
|
||||||
|
'name_zh' => $meta['name_zh'] ?: (string) ($row['name'] ?? $meta['symbol']),
|
||||||
|
'image' => (string) ($row['image'] ?? ''),
|
||||||
|
'current_price' => (float) ($row['current_price'] ?? 0),
|
||||||
|
'market_cap' => (float) ($row['market_cap'] ?? 0),
|
||||||
|
'market_cap_rank' => (int) ($row['market_cap_rank'] ?? 0),
|
||||||
|
'total_volume' => (float) ($row['total_volume'] ?? 0),
|
||||||
|
'price_change_percentage_24h' => round((float) ($row['price_change_percentage_24h'] ?? 0), 2),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($coins)) {
|
||||||
|
throw new RuntimeException('CoinGecko 未返回有效行情');
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatedAt = (int) ($globalData['updated_at'] ?? time());
|
||||||
|
return [
|
||||||
|
'updated_at' => $updatedAt,
|
||||||
|
'updated_time' => date('H:i', $updatedAt),
|
||||||
|
'stale' => false,
|
||||||
|
'overview' => [
|
||||||
|
'btc_market_cap_percentage' => round((float) ($globalData['market_cap_percentage']['btc'] ?? 0), 1),
|
||||||
|
'total_volume_cny' => (float) ($globalData['total_volume']['cny'] ?? 0),
|
||||||
|
],
|
||||||
|
'coins' => $coins,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function coinConfig(): array
|
||||||
|
{
|
||||||
|
$config = ConfigService::get('crypto_market', 'coins', []);
|
||||||
|
if (!is_array($config)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$coins = [];
|
||||||
|
foreach ($config as $item) {
|
||||||
|
if (!is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$id = strtolower(trim((string) ($item['id'] ?? '')));
|
||||||
|
$symbol = strtoupper(trim((string) ($item['symbol'] ?? '')));
|
||||||
|
if (!preg_match('/^[a-z0-9-]+$/', $id) || !preg_match('/^[A-Z0-9]{2,20}$/', $symbol)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$coins[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'name_zh' => trim((string) ($item['name_zh'] ?? '')),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $coins;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function requestJson(string $url): array
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 15,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
CURLOPT_ENCODING => '',
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Accept: application/json',
|
||||||
|
'User-Agent: SportEra/1.0',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($error !== '') {
|
||||||
|
throw new RuntimeException($error);
|
||||||
|
}
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
throw new RuntimeException('CoinGecko HTTP ' . $httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode((string) $body, true);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
throw new RuntimeException('CoinGecko 返回格式异常');
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isValidSnapshot($snapshot): bool
|
||||||
|
{
|
||||||
|
return is_array($snapshot)
|
||||||
|
&& !empty($snapshot['coins'])
|
||||||
|
&& is_array($snapshot['coins'])
|
||||||
|
&& is_array($snapshot['overview'] ?? null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import appConfig from '@/config'
|
import appConfig from '@/config'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
const PROXY_BASE = `${appConfig.baseUrl}${appConfig.urlPrefix}/crypto/proxy`
|
const PROXY_BASE = `${appConfig.baseUrl}${appConfig.urlPrefix}/crypto/proxy`
|
||||||
|
|
||||||
@@ -165,6 +166,46 @@ export interface CoinTicker {
|
|||||||
lowPrice: string
|
lowPrice: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CryptoMarketCoin {
|
||||||
|
id: string
|
||||||
|
symbol: string
|
||||||
|
name: string
|
||||||
|
name_zh: string
|
||||||
|
image: string
|
||||||
|
current_price: number
|
||||||
|
market_cap: number
|
||||||
|
market_cap_rank: number
|
||||||
|
total_volume: number
|
||||||
|
price_change_percentage_24h: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CryptoMarketSnapshot {
|
||||||
|
updated_at: number
|
||||||
|
updated_time: string
|
||||||
|
stale: boolean
|
||||||
|
overview: {
|
||||||
|
btc_market_cap_percentage: number
|
||||||
|
total_volume_cny: number
|
||||||
|
}
|
||||||
|
coins: CryptoMarketCoin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCryptoMarket() {
|
||||||
|
return request.get<CryptoMarketSnapshot>({ url: '/crypto/market' }, { ignoreCancel: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCryptoFavorites() {
|
||||||
|
return request.get<{ symbols: string[] }>({ url: '/crypto/favorites' }, { isAuth: true, ignoreCancel: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCryptoFavorite(symbol: string) {
|
||||||
|
return request.post({ url: '/crypto/addFavorite', data: { symbol } }, { isAuth: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelCryptoFavorite(symbol: string) {
|
||||||
|
return request.post({ url: '/crypto/cancelFavorite', data: { symbol } }, { isAuth: true })
|
||||||
|
}
|
||||||
|
|
||||||
export function formatPrice(price: string | number): string {
|
export function formatPrice(price: string | number): string {
|
||||||
const p = typeof price === 'string' ? parseFloat(price) : price
|
const p = typeof price === 'string' ? parseFloat(price) : price
|
||||||
if (isNaN(p)) return '0.00'
|
if (isNaN(p)) return '0.00'
|
||||||
|
|||||||
@@ -518,7 +518,7 @@
|
|||||||
"iconPath": "static/images/tabbar/crypto.png",
|
"iconPath": "static/images/tabbar/crypto.png",
|
||||||
"selectedIconPath": "static/images/tabbar/crypto_s.png",
|
"selectedIconPath": "static/images/tabbar/crypto_s.png",
|
||||||
"pagePath": "pages/crypto/crypto",
|
"pagePath": "pages/crypto/crypto",
|
||||||
"text": "加密行情"
|
"text": "行情"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"iconPath": "static/images/tabbar/lottery.png",
|
"iconPath": "static/images/tabbar/lottery.png",
|
||||||
|
|||||||
+645
-208
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user