feat: Company 页面搜索框支持拼音缩写搜索

- 新增 useStockSearch Hook,提取通用股票搜索能力
  - 支持代码、名称、拼音缩写模糊搜索
  - 内置 300ms 防抖,避免频繁 API 调用
  - 使用 useRef 存储回调,防止防抖函数重建
- Company/index.js 使用新 Hook 替换本地搜索
  - 搜索结果显示拼音缩写 (如 GZMT)
  - 搜索框宽度调整为 280px
- Mock handler 添加拼音缩写支持
  - 新增 PINYIN_MAP 字符映射表
  - 搜索逻辑支持拼音匹配和排序

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-10 15:45:30 +08:00
parent fbeb66fb39
commit b151400c65
3 changed files with 189 additions and 48 deletions

View File

@@ -7,6 +7,42 @@ import { generateTimelineData, generateDailyData } from '../data/kline';
// 模拟延迟
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 常用字拼音首字母映射(简化版)
const PINYIN_MAP = {
'平': 'p', '安': 'a', '银': 'y', '行': 'h', '浦': 'p', '发': 'f',
'招': 'z', '商': 's', '兴': 'x', '业': 'y', '北': 'b', '京': 'j',
'农': 'n', '交': 'j', '通': 't', '工': 'g', '光': 'g', '大': 'd',
'建': 'j', '设': 's', '中': 'z', '信': 'x', '证': 'z', '券': 'q',
'国': 'g', '金': 'j', '海': 'h', '华': 'h', '泰': 't', '方': 'f',
'正': 'z', '新': 'x', '保': 'b', '险': 'x', '太': 't', '人': 'r',
'寿': 's', '泸': 'l', '州': 'z', '老': 'l', '窖': 'j', '古': 'g',
'井': 'j', '贡': 'g', '酒': 'j', '五': 'w', '粮': 'l', '液': 'y',
'贵': 'g', '茅': 'm', '台': 't', '青': 'q', '岛': 'd', '啤': 'p',
'水': 's', '坊': 'f', '今': 'j', '世': 's', '缘': 'y', '云': 'y',
'南': 'n', '白': 'b', '药': 'y', '长': 'c', '春': 'c', '高': 'g',
'科': 'k', '伦': 'l', '比': 'b', '亚': 'y', '迪': 'd', '恒': 'h',
'瑞': 'r', '医': 'y', '片': 'p', '仔': 'z', '癀': 'h', '明': 'm',
'康': 'k', '德': 'd', '讯': 'x', '东': 'd', '威': 'w', '视': 's',
'立': 'l', '精': 'j', '密': 'm', '电': 'd', '航': 'h',
'动': 'd', '力': 'l', '韦': 'w', '尔': 'e', '股': 'g', '份': 'f',
'万': 'w', '赣': 'g', '锋': 'f', '锂': 'l', '宁': 'n', '时': 's',
'代': 'd', '隆': 'l', '基': 'j', '绿': 'l', '能': 'n',
'筑': 'z', '汽': 'q', '车': 'c', '宇': 'y', '客': 'k', '上': 's',
'集': 'j', '团': 't', '广': 'g', '城': 'c', '侨': 'q', '夏': 'x',
'幸': 'x', '福': 'f', '地': 'd', '控': 'k', '美': 'm', '格': 'g',
'苏': 's', '智': 'z', '家': 'j', '易': 'y', '购': 'g',
'轩': 'x', '财': 'c', '富': 'f', '石': 's', '化': 'h', '学': 'x',
'山': 's', '黄': 'h', '螺': 'l', '泥': 'n', '神': 's', '油': 'y',
'联': 'l', '移': 'y', '伊': 'y', '利': 'l', '紫': 'z', '矿': 'k',
'天': 't', '味': 'w', '港': 'g', '微': 'w',
'技': 'j', '的': 'd', '器': 'q', '泊': 'b', '铁': 't',
};
// 生成拼音缩写
const generatePinyinAbbr = (name) => {
return name.split('').map(char => PINYIN_MAP[char] || '').join('');
};
// 生成A股主要股票数据包含各大指数成分股
const generateStockList = () => {
const stocks = [
@@ -118,7 +154,11 @@ const generateStockList = () => {
{ code: '603288', name: '海天味业' },
];
return stocks;
// 添加拼音缩写
return stocks.map(s => ({
...s,
pinyin_abbr: generatePinyinAbbr(s.name)
}));
};
// 股票相关的 Handlers
@@ -143,11 +183,12 @@ export const stockHandlers = [
});
}
// 模糊搜索:代码 + 名称(不区分大小写)
// 模糊搜索:代码 + 名称 + 拼音缩写(不区分大小写)
const results = stocks.filter(s => {
const code = s.code.toLowerCase();
const name = s.name.toLowerCase();
return code.includes(query) || name.includes(query);
const pinyin = (s.pinyin_abbr || '').toLowerCase();
return code.includes(query) || name.includes(query) || pinyin.includes(query);
});
// 按相关性排序:完全匹配 > 开头匹配 > 包含匹配
@@ -156,18 +197,22 @@ export const stockHandlers = [
const bCode = b.code.toLowerCase();
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
const aPinyin = (a.pinyin_abbr || '').toLowerCase();
const bPinyin = (b.pinyin_abbr || '').toLowerCase();
// 计算匹配分数
const getScore = (code, name) => {
if (code === query || name === query) return 100; // 完全匹配
// 计算匹配分数(包含拼音匹配)
const getScore = (code, name, pinyin) => {
if (code === query || name === query || pinyin === query) return 100; // 完全匹配
if (code.startsWith(query)) return 80; // 代码开头
if (pinyin.startsWith(query)) return 70; // 拼音开头
if (name.startsWith(query)) return 60; // 名称开头
if (code.includes(query)) return 40; // 代码包含
if (pinyin.includes(query)) return 30; // 拼音包含
if (name.includes(query)) return 20; // 名称包含
return 0;
};
return getScore(bCode, bName) - getScore(aCode, aName);
return getScore(bCode, bName, bPinyin) - getScore(aCode, aName, aPinyin);
});
// 返回格式化数据
@@ -176,6 +221,7 @@ export const stockHandlers = [
data: results.slice(0, limit).map(s => ({
stock_code: s.code,
stock_name: s.name,
pinyin_abbr: s.pinyin_abbr,
market: s.code.startsWith('6') ? 'SH' : 'SZ',
industry: ['银行', '证券', '保险', '白酒', '医药', '科技', '新能源', '汽车', '地产', '家电'][Math.floor(Math.random() * 10)],
change_pct: parseFloat((Math.random() * 10 - 3).toFixed(2)),