1
This commit is contained in:
parent
8ece78a3dc
commit
d34e3cc998
|
|
@ -26,6 +26,42 @@ async def list_users(_admin: Dict[str, Any] = Depends(get_admin_user)):
|
||||||
return User.list_all()
|
return User.list_all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/detailed")
|
||||||
|
async def list_users_with_accounts(_admin: Dict[str, Any] = Depends(get_admin_user)):
|
||||||
|
"""获取所有用户及其关联账号列表"""
|
||||||
|
users = User.list_all()
|
||||||
|
out = []
|
||||||
|
|
||||||
|
# 获取所有授权关系
|
||||||
|
# 优化:一次性查询所有 memberships 并在内存中分组,避免 N+1 查询
|
||||||
|
# 但由于 UserAccountMembership 没有 list_all 方法,暂时循环查询或添加 list_all
|
||||||
|
# 考虑到用户量不大,循环查询尚可接受。
|
||||||
|
|
||||||
|
for u in users:
|
||||||
|
uid = u['id']
|
||||||
|
memberships = UserAccountMembership.list_for_user(uid)
|
||||||
|
user_accounts = []
|
||||||
|
for m in memberships or []:
|
||||||
|
aid = int(m.get("account_id"))
|
||||||
|
a = Account.get(aid)
|
||||||
|
if a:
|
||||||
|
user_accounts.append({
|
||||||
|
"id": aid,
|
||||||
|
"name": a.get("name"),
|
||||||
|
"status": a.get("status"),
|
||||||
|
"role": m.get("role")
|
||||||
|
})
|
||||||
|
|
||||||
|
out.append({
|
||||||
|
"id": uid,
|
||||||
|
"username": u['username'],
|
||||||
|
"role": u['role'],
|
||||||
|
"status": u['status'],
|
||||||
|
"accounts": user_accounts
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
@router.post("/users")
|
@router.post("/users")
|
||||||
async def create_user(payload: UserCreateReq, _admin: Dict[str, Any] = Depends(get_admin_user)):
|
async def create_user(payload: UserCreateReq, _admin: Dict[str, Any] = Depends(get_admin_user)):
|
||||||
exists = User.get_by_username(payload.username)
|
exists = User.get_by_username(payload.username)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ const TradeList = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出当前订单数据(含入场/离场原因、入场思路等完整字段,便于后续分析)
|
// 导出当前订单数据(含入场/离场原因、入场思路等完整字段,便于后续分析)
|
||||||
const handleExport = () => {
|
// type: 'csv' | 'json'
|
||||||
|
const handleExport = (type = 'csv') => {
|
||||||
if (trades.length === 0) {
|
if (trades.length === 0) {
|
||||||
alert('暂无数据可导出')
|
alert('暂无数据可导出')
|
||||||
return
|
return
|
||||||
|
|
@ -137,6 +138,21 @@ const TradeList = () => {
|
||||||
return row
|
return row
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')
|
||||||
|
|
||||||
|
if (type === 'json') {
|
||||||
|
const filename = `交易记录_${timestamp}.json`
|
||||||
|
const dataStr = JSON.stringify(exportData, null, 2)
|
||||||
|
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(dataBlob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = filename
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
} else {
|
||||||
// 转换为CSV格式 helper
|
// 转换为CSV格式 helper
|
||||||
const convertToCSV = (objArray) => {
|
const convertToCSV = (objArray) => {
|
||||||
const array = typeof objArray !== 'object' ? JSON.parse(objArray) : objArray;
|
const array = typeof objArray !== 'object' ? JSON.parse(objArray) : objArray;
|
||||||
|
|
@ -157,6 +173,8 @@ const TradeList = () => {
|
||||||
let value = array[i][index];
|
let value = array[i][index];
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
value = '';
|
value = '';
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
value = JSON.stringify(value);
|
||||||
} else {
|
} else {
|
||||||
value = String(value);
|
value = String(value);
|
||||||
}
|
}
|
||||||
|
|
@ -173,10 +191,7 @@ const TradeList = () => {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成文件名
|
|
||||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')
|
|
||||||
const filename = `交易记录_${timestamp}.csv`
|
const filename = `交易记录_${timestamp}.csv`
|
||||||
|
|
||||||
// 创建并下载文件 (CSV with BOM for Excel)
|
// 创建并下载文件 (CSV with BOM for Excel)
|
||||||
const csvStr = convertToCSV(exportData)
|
const csvStr = convertToCSV(exportData)
|
||||||
const bom = '\uFEFF'
|
const bom = '\uFEFF'
|
||||||
|
|
@ -190,6 +205,7 @@ const TradeList = () => {
|
||||||
document.body.removeChild(link)
|
document.body.removeChild(link)
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 复制统计数据到剪贴板
|
// 复制统计数据到剪贴板
|
||||||
const handleCopyStats = (statsData) => {
|
const handleCopyStats = (statsData) => {
|
||||||
|
|
@ -373,9 +389,14 @@ const TradeList = () => {
|
||||||
重置
|
重置
|
||||||
</button>
|
</button>
|
||||||
{trades.length > 0 && (
|
{trades.length > 0 && (
|
||||||
<button className="btn-export" onClick={handleExport} title="导出Excel/CSV(含入场/离场原因、入场思路等),便于后续分析">
|
<>
|
||||||
|
<button className="btn-export" onClick={() => handleExport('csv')} title="导出Excel/CSV(含入场/离场原因、入场思路等),便于后续分析">
|
||||||
导出 Excel ({trades.length})
|
导出 Excel ({trades.length})
|
||||||
</button>
|
</button>
|
||||||
|
<button className="btn-export" onClick={() => handleExport('json')} style={{ backgroundColor: '#607D8B' }} title="导出JSON数据,方便程序处理">
|
||||||
|
导出 JSON ({trades.length})
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user