feat(stock): ️完成公告板查询

This commit is contained in:
LittleBoy 2025-04-02 23:01:50 +08:00
parent ccd19641d8
commit ab234cd9e4
11 changed files with 257 additions and 15 deletions

View File

@ -1,6 +1,6 @@
{
"useTabs": true,
"tabWidth": 4,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100

@ -1 +1 @@
Subproject commit f968447dd61331eda162ca8e8fb8d2307f532139
Subproject commit 38ffc419475dd2f68b0b82acaef60af4e8b4785f

View File

@ -1,22 +1,18 @@
import ReactDOM from "react-dom/client";
import createApp from "../package/package/index.tsx"
import createApp from "@package/index.tsx"
import StockQuery from '@/stock-query';
import PageSetting from '@package/pages/setting';
const HelloWorld = ()=><h1>Hello world</h1>
console.log(createApp)
const App = createApp([
{
path: "hello-world",
element: <HelloWorld/>
path: "stock-query",
element: <StockQuery/>
},
{
path: "hello-2",
element: <HelloWorld/>
path: "setting",
element: <PageSetting settingKeys={['lexicon']}/>
},
{
path: "hello-3",
element: <HelloWorld/>
}
])

134
src/stock-query/index.tsx Normal file
View File

@ -0,0 +1,134 @@
import { AutoComplete, AutoCompleteProps, Button, Empty, Space } from 'antd';
import React from 'react';
import { useSetState, useMount } from 'ahooks';
import { AppTitleBar } from '@package/components/app/title-bar.tsx';
import { queryStockInfo, searchStockList } from '@/stock-query/service/api.ts';
import { SecuritiesInfo } from '@/stock-query/service/types.ts';
import styles from './style.module.less';
import { formatMoney } from '@package/utils/strings.ts';
/**
* - *10%10001000
* - *10%100100
* - *10%10001000
* -
* @param amount
* @param minValue
*/
function formatStockMoney(amount: number, minValue = 1000) {
if (amount < 0) amount = Math.abs(amount);
return formatMoney(amount < minValue ? minValue : amount);
}
export default function StockQuery() {
const [options, setOptions] = React.useState<AutoCompleteProps['options']>([]);
const [state, setState] = useSetState<{
companyName: string;
result?: SecuritiesInfo;
queried?: boolean;
}>({
companyName: ''
});
// 模糊查询证券列表
const handleFilter = async (name: string) => {
if (!name) {
setOptions(() => []);
return;
}
const list = await searchStockList(name);
setOptions(list.security_names.map((name) => ({
label: name,
value: name
})));
};
// 查询证券营收信息
const handleSearch = async () => {
if (!state.companyName) return;
try {
const result = await queryStockInfo(state.companyName);
if (!result) return;
setState({ result, queried: true });
} catch (e) {
console.log('query error:', e);
setState({ result: undefined, queried: true });
}
};
useMount(() => {
chrome.webview?.hostObjects.host.SetTitle('交易权限速查');
});
return <>
<AppTitleBar title="交易权限速查" backgroundColor={'#eddcb9'} />
<div className={styles.stockQuery}>
<Space size={20}>
<AutoComplete
style={{ width: 200 }}
onSearch={handleFilter}
placeholder="请输入证券名称"
options={options}
value={state.companyName}
onChange={(companyName: string) => setState({ companyName })}
/>
<Button type="primary" onClick={handleSearch}></Button>
</Space>
<div style={{ minHeight: 200 }}>
{state.queried && !state.result && <div className={styles.emptyQuery}>
</div>}
{state.queried && state.result && (
<div className="result">
<div className={styles.stockInfo}>
<div>
<span></span>
<span>{state.result.securities_code}</span>
</div>
<div>
<span></span>
<span>{state.result.security_name}</span>
</div>
</div>
<div className={styles.stockResult}>
<table>
<thead>
<tr>
<th className={styles.slashed}></th>
<th>10%</th>
<th>50%</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>{formatStockMoney(state.result.percent10.operating_income)}</td>
<td>{formatStockMoney(state.result.percent50.operating_income)}</td>
</tr>
<tr>
<td></td>
<td>{formatStockMoney(state.result.percent10.net_profit,100)}</td>
<td>{formatStockMoney(state.result.percent50.net_profit,100)}</td>
</tr>
<tr>
<td></td>
<td>{formatMoney(state.result.percent10.total_assets)}</td>
<td>{formatMoney(state.result.percent50.total_assets)}</td>
</tr>
<tr>
<td></td>
<td>{formatStockMoney(state.result.percent10.net_assets)}</td>
<td>{formatStockMoney(state.result.percent50.net_assets)}</td>
</tr>
</tbody>
</table>
</div>
</div>
)}
</div>
<div className={styles.stockTips}>*(10)</div>
</div>
</>;
}

View File

@ -0,0 +1,26 @@
import { post } from '@package/service/request.ts';
import { SecuritiesInfo, SecuritiesInfoDto } from '@/stock-query/service/types.ts';
/**
*
* @param stockCompanyName
*/
export async function queryStockInfo(stockCompanyName: string) {
const info = await post<{
securities_info: SecuritiesInfoDto
}>('/api/announce/trade', { security_name: stockCompanyName });
return {
securities_code: info.securities_info.securities_code,
security_name: info.securities_info.security_name,
percent10: info.securities_info['10%'],
percent50: info.securities_info['50%']
} as SecuritiesInfo;
}
/**
*
* @param stockCompanyName
*/
export function searchStockList(stockCompanyName: string) {
return post<{security_names:string[]}>('/api/announce/search_name', { security_name: stockCompanyName });
}

View File

@ -0,0 +1,23 @@
interface CompanyRevenueDto {
// 营业收入
operating_income: number;
// 净利润
net_profit: number;
// 资产总额
total_assets: number;
// 净资产
net_assets: number;
}
export interface SecuritiesInfoDto {
securities_code: string;
security_name: string;
'10%': CompanyRevenueDto
'50%': CompanyRevenueDto
}
export interface SecuritiesInfo {
securities_code: string;
security_name: string;
percent10: CompanyRevenueDto;
percent50: CompanyRevenueDto;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,54 @@
.stockQuery{
padding: 30px;
font-size: 14px;
}
.emptyQuery{
padding: 15px 0;
}
.stockInfo{
line-height: 1.2em;
margin: 15px 0;
}
.stockResult{
table{
text-align: center;
border-left: 1px #999 solid;
border-top: 1px #999 solid;
border-collapse: collapse;
td,th{
border-right: 1px #999 solid;
border-bottom: 1px #999 solid;
padding: 6px 15px;
}
}
}
.stockTips{
font-size: 12px;
color: #999;
margin: 15px 0;
}
// 单元格斜线
.slashed{
position: relative;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%; /* 或者具体数值 */
height: 100%; /* 或者具体数值 */
background-image: linear-gradient(20deg, transparent 49%, #666 50%,transparent 51%,);
}
//&::after {
// content: "";
// position: absolute;
// left: 0;
// top: 50%;
// width: 100%;
// height: 1px; /* 或者使用 border-bottom */
// background-color: black; /* 斜线颜色 */
// transform: rotate(-45deg); /* -45度斜线 */
// z-index: -1; /* 确保内容在上面 */
//}
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -16,6 +16,9 @@
"paths": {
"@/*": [
"./src/*"
],
"@package/*": [
"./package/package/*"
]
},
/* Linting */
@ -25,6 +28,9 @@
"noImplicitAny": false,
"noFallthroughCasesInSwitch": false
},
"include": ["src"],
"include": [
"src",
"package/package"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -47,7 +47,7 @@ export default defineConfig(({mode}) => {
server: {
proxy: {
'/api': {
target: "http://192.168.0.114:9892", // https://gm.gachafun.com http://gm.zverse.group http://192.168.0.231:9892 192.168.0.114:9892 pre-gm-plugin.gachafun.com
target: "https://gm-plugin-fn.gachafun.com", // https://gm.gachafun.com http://gm.zverse.group http://192.168.0.231:9892 192.168.0.114:9892 pre-gm-plugin.gachafun.com
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ""),
}