package main
import (
"encoding/json"
"fmt"
"time"
"github.com/go-via/via"
"github.com/go-via/via/h"
)
// 股票数据结构
type Stock struct {
Code string `json:"code"`
Name string `json:"name"`
Price float64 `json:"price"`
Change float64 `json:"change"`
ChangePercent float64 `json:"changePercent"`
}
// 股票数据包含状态和时间戳
type StockData struct {
Stocks []Stock `json:"stocks"`
UpdateAt string `json:"updateAt"`
}
// toJSON 将数据转换为JSON字符串
func toJSON(v interface{}) string {
data, err := json.Marshal(v)
if err != nil {
return "{}"
}
return string(data)
}
func main() {
app := via.New(
via.WithTitle("股票实时行情"),
via.WithAddr(":8080"),
)
// 初始股票数据
initialStocks := []Stock{
{Code: "600000", Name: "浦发银行", Price: 8.62, Change: 0, ChangePercent: 0},
{Code: "600036", Name: "招商银行", Price: 35.45, Change: 0, ChangePercent: 0},
{Code: "000858", Name: "五粮液", Price: 178.20, Change: 0, ChangePercent: 0},
{Code: "002594", Name: "比亚迪", Price: 235.80, Change: 0, ChangePercent: 0},
{Code: "601318", Name: "中国平安", Price: 42.65, Change: 0, ChangePercent: 0},
{Code: "600519", Name: "贵州茅台", Price: 1890.50, Change: 0, ChangePercent: 0},
{Code: "300750", Name: "宁德时代", Price: 185.60, Change: 0, ChangePercent: 0},
{Code: "601689", Name: "拓普集团", Price: 56.80, Change: 0, ChangePercent: 0},
}
// 添加AG-Grid库到head
app.AppendToHead(h.Script(h.Src("https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js")))
// 将主要JavaScript逻辑添加到head,完全用JavaScript更新数据
app.AppendToHead(h.Script(h.Raw(`
// 全局AG-Grid管理器 - 完全JavaScript更新
window.AGGridManager = (function() {
let gridApi = null;
let gridInitialized = false;
let dataUpdateInterval = null;
let initializationPromise = null;
let currentStocksData = [];
// AG-Grid配置
const gridOptions = {
columnDefs: [
{headerName: '代码', field: 'code', width: 100, pinned: 'left'},
{headerName: '名称', field: 'name', width: 120, pinned: 'left'},
{
headerName: '价格',
field: 'price',
width: 100,
type: 'numericColumn',
valueFormatter: params => params.value.toFixed(2),
cellStyle: {textAlign: 'right'},
cellClass: params => params.data.change > 0 ? 'up' : (params.data.change < 0 ? 'down' : 'neutral')
},
{
headerName: '涨跌',
field: 'change',
width: 100,
type: 'numericColumn',
valueFormatter: params => (params.value >= 0 ? '+' : '') + params.value.toFixed(2),
cellStyle: {textAlign: 'right'},
cellClass: params => params.value > 0 ? 'up' : (params.value < 0 ? 'down' : 'neutral')
},
{
headerName: '涨跌幅',
field: 'changePercent',
width: 100,
type: 'numericColumn',
valueFormatter: params => (params.value >= 0 ? '+' : '') + params.value.toFixed(2) + '%',
cellStyle: {textAlign: 'right'},
cellClass: params => params.value > 0 ? 'up' : (params.value < 0 ? 'down' : 'neutral')
}
],
rowData: [],
defaultColDef: {sortable: true, filter: true, resizable: true, flex:1},
rowHeight: 40,
headerHeight: 50,
animateRows: true,
onGridReady: function(params) {
gridApi = params.api;
gridInitialized = true;
loadInitialData();
startDataUpdates();
}
};
// 初始化AG-Grid
function initialize() {
if (initializationPromise) {
return initializationPromise;
}
initializationPromise = new Promise(function(resolve, reject) {
const gridContainer = document.getElementById('grid-container');
if (!gridContainer) {
setTimeout(function() {
initialize().then(resolve).catch(reject);
}, 100);
return;
}
try {
// agGrid.createGrid 直接返回 GridAPI,不是 Promise
gridApi = agGrid.createGrid(gridContainer, gridOptions);
gridInitialized = true;
resolve();
} catch (error) {
reject(error);
}
});
return initializationPromise;
}
// 更新股票数据(完全在前端)
function updateStockPrices(stocks) {
return stocks.map(function(stock) {
const oldPrice = stock.price;
const fluctuation = (Math.random() - 0.5) * 0.01;
const newPrice = oldPrice * (1 + fluctuation);
const change = newPrice - oldPrice;
const changePercent = (change / oldPrice) * 100;
return {
...stock,
price: newPrice,
change: change,
changePercent: changePercent
};
});
}
// 加载初始数据
function loadInitialData() {
const gridContainer = document.getElementById('grid-container');
if (!gridContainer) {
return;
}
const stocksData = gridContainer.getAttribute('data-stocks');
const updateTime = gridContainer.getAttribute('data-updateAt');
if (stocksData && updateTime) {
try {
const stocks = JSON.parse(stocksData);
currentStocksData = stocks;
if (gridApi && stocks && stocks.length > 0) {
gridApi.setGridOption('rowData', stocks);
const updateTimeElement = document.getElementById('updateTime');
if (updateTimeElement) {
updateTimeElement.textContent = updateTime;
}
}
} catch (error) {
}
}
}
// 开始数据更新
function startDataUpdates() {
// 每2秒更新一次数据
dataUpdateInterval = setInterval(function() {
updateStockData();
}, 2000);
}
// 更新股票数据
function updateStockData() {
if (!gridApi) {
return;
}
try {
// 更新股票价格
const updatedStocks = updateStockPrices(currentStocksData);
const updateTime = new Date().toLocaleTimeString('zh-CN', {hour12: false});
// 更新grid
gridApi.setGridOption('rowData', updatedStocks);
// 更新显示时间
const updateTimeElement = document.getElementById('updateTime');
if (updateTimeElement) {
updateTimeElement.textContent = updateTime;
}
// 保存更新后的数据
currentStocksData = updatedStocks;
} catch (error) {
}
}
// 启动函数
function start() {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(function() {
initialize().then(function() {
}).catch(function(error) {
});
}, 100);
} else {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
initialize().then(function() {
}).catch(function(error) {
});
}, 100);
});
}
}
// 清理函数
function cleanup() {
if (dataUpdateInterval) {
clearInterval(dataUpdateInterval);
dataUpdateInterval = null;
}
}
// 返回公共API
return {
start: start,
cleanup: cleanup
};
})();
// 页面加载完成后启动
window.AGGridManager.start();
// 窗口关闭时清理
window.addEventListener('beforeunload', function() {
window.AGGridManager.cleanup();
});
`)))
// 添加自定义样式到head
app.AppendToHead(h.StyleEl(
h.Raw(`
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 10px;
}
.subtitle {
color: #666;
margin-bottom: 20px;
font-size: 14px;
}
#grid-container {
height: 500px;
width: 100%;
}
.ag-theme-alpine {
--ag-header-height: 50px;
--ag-header-foreground-color: #333;
--ag-header-background-color: #f0f0f0;
--ag-odd-row-background-color: #f9f9f9;
}
.update-time {
margin-top: 10px;
font-size: 12px;
color: #999;
}
.up { color: #ff4d4f; }
.down { color: #52c41a; }
.neutral { color: #999; }
`),
))
// 主页路由 - 完全使用JavaScript更新,避免重新渲染
app.Page("/", func(cmp *via.Cmp) {
// 渲染静态视图 - 不使用via.State,避免重新渲染
cmp.View(func(ctx *via.Ctx) h.H {
return h.Div(
h.Class("page-container"),
h.Div(
h.Class("container"),
h.H1(h.Text("股票实时行情")),
h.P(
h.Class("subtitle"),
h.Text("数据每2秒自动更新 | AG-Grid"),
),
// AG-Grid容器 - 初始数据在data属性中
h.Div(
h.ID("grid-container"),
h.Class("ag-theme-alpine"),
h.Data("stocks", toJSON(initialStocks)),
h.Data("updateAt", time.Now().Format("15:04:05")),
),
h.Div(
h.Class("update-time"),
h.Text("最后更新: "),
h.Span(
h.ID("updateTime"),
h.Text(time.Now().Format("15:04:05")),
),
),
),
)
})
})
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("访问 http://localhost:8080 查看AG-Grid版本")
if err := app.Start(); err != nil {
fmt.Printf("服务器错误: %v\n", err)
}
}