diff --git a/accounting/init.py b/accounting/init.py index a47ac48..74fbbfc 100644 --- a/accounting/init.py +++ b/accounting/init.py @@ -7,6 +7,7 @@ from .accounting_config import Accounting from .bill import write_bill from .openaccount import openOwnerAccounts, openProviderAccounts, openResellerAccounts, openCustomerAccounts, openRetailRelationshipAccounts from .getaccount import getAccountBalance, getCustomerBalance, getAccountByName, get_account_total_amount +from .stats import get_accounting_stats from .recharge import RechargeBiz, recharge_accounting from .consume import consume_accounting @@ -19,16 +20,10 @@ async def all_my_accounts(request): b.id, a.name, b.balance_at, -case when c.balance is null then 0.00 else c.balance end as balance +b.balance from subject a, - account b left join - ( - select a.* - from acc_balance a, - (select accountid, max(acc_date) max_date from acc_balance group by accountid) b - where a.accountid=b.accountid and a.acc_date=b.max_date - ) c on c.accountid = b.id + account b where b.subjectid = a.id and b.orgid = ${orgid}$ """ @@ -77,3 +72,4 @@ def load_accounting(): g.get_accdetail = get_accdetail g.all_my_accounts = all_my_accounts g.openRetailRelationshipAccounts = openRetailRelationshipAccounts + g.get_accounting_stats = get_accounting_stats diff --git a/accounting/stats.py b/accounting/stats.py new file mode 100644 index 0000000..27f5afc --- /dev/null +++ b/accounting/stats.py @@ -0,0 +1,77 @@ +from appPublic.log import debug, exception +from sqlor.dbpools import get_sor_context +from datetime import datetime, timedelta + +async def get_accounting_stats(request): + """Get accounting statistics for the current user's organization""" + env = request._run_ns + userorgid = await env.get_userorgid() + + now = datetime.now() + today_start = now.strftime('%Y-%m-%d') + month_start = now.strftime('%Y-%m-01') + tomorrow = (now + timedelta(days=1)).strftime('%Y-%m-%d') + + stats = { + 'total_balance': 0, + 'today_amount': 0, + 'month_amount': 0, + 'account_count': 0 + } + + async with get_sor_context(request._run_ns, 'accounting') as sor: + # Total balance across all accounts + sql_balance = """ + SELECT COALESCE(SUM(CASE WHEN balance_at = '1' THEN balance ELSE -balance END), 0) as total + FROM account + WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql_balance, {'orgid': userorgid}) + if recs: + stats['total_balance'] = float(recs[0].total or 0) + + # Account count + sql_count = """ + SELECT COUNT(*) as cnt FROM account WHERE orgid = ${orgid}$ + """ + recs = await sor.sqlExe(sql_count, {'orgid': userorgid}) + if recs: + stats['account_count'] = int(recs[0].cnt or 0) + + # Today's consumption (acc_dir=1 means debit/consumption) + sql_today = """ + SELECT COALESCE(SUM(amount), 0) as total + FROM acc_detail a + JOIN account b ON a.accountid = b.id + WHERE b.orgid = ${orgid}$ + AND a.acc_dir = 1 + AND a.acc_date >= ${from_date}$ + AND a.acc_date < ${to_date}$ + """ + recs = await sor.sqlExe(sql_today, { + 'orgid': userorgid, + 'from_date': today_start, + 'to_date': tomorrow + }) + if recs: + stats['today_amount'] = float(recs[0].total or 0) + + # This month's consumption + sql_month = """ + SELECT COALESCE(SUM(amount), 0) as total + FROM acc_detail a + JOIN account b ON a.accountid = b.id + WHERE b.orgid = ${orgid}$ + AND a.acc_dir = 1 + AND a.acc_date >= ${from_date}$ + AND a.acc_date < ${to_date}$ + """ + recs = await sor.sqlExe(sql_month, { + 'orgid': userorgid, + 'from_date': month_start, + 'to_date': tomorrow + }) + if recs: + stats['month_amount'] = float(recs[0].total or 0) + + return stats diff --git a/models/acc_balance.json b/models/acc_balance.json new file mode 100644 index 0000000..14d8d0a --- /dev/null +++ b/models/acc_balance.json @@ -0,0 +1,37 @@ +{ + "summary": [ + { + "name": "acc_balance", + "title": "账户余额表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "balance", + "title": "账户余额", + "type": "float", + "length": 20, + "dec": 2 + } + ] +} \ No newline at end of file diff --git a/models/acc_detail.json b/models/acc_detail.json new file mode 100644 index 0000000..b0d687b --- /dev/null +++ b/models/acc_detail.json @@ -0,0 +1,72 @@ +{ + "summary": [ + { + "name": "acc_detail", + "title": "账户明细表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_no", + "title": "明细顺序号", + "type": "short" + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "acc_timestamp", + "title": "记账时间戳", + "type": "timestamp" + }, + { + "name": "acc_dir", + "title": "记账方向", + "type": "str", + "length": 1 + }, + { + "name": "summary", + "title": "摘要", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "记账金额", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "balance", + "title": "账户余额", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "acclogid", + "title": "账务流水id", + "type": "str", + "length": 32 + } + ] +} \ No newline at end of file diff --git a/models/account.json b/models/account.json new file mode 100644 index 0000000..859b82a --- /dev/null +++ b/models/account.json @@ -0,0 +1,100 @@ +{ + "summary": [ + { + "name": "account", + "title": "机构账户表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账本机构", + "type": "str", + "length": 32 + }, + { + "name": "orgid", + "title": "主机构id", + "type": "str", + "length": 32 + }, + { + "name": "org1id", + "title": "从机构id", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目号", + "type": "str", + "length": 21 + }, + { + "name": "balance_at", + "title": "余额方向", + "type": "str", + "length": 1 + }, + { + "name": "max_detailno", + "title": "最大明细顺序号", + "type": "short" + }, + { + "name": "balance", + "title": "余额", + "type": "float", + "length": 20, + "dec": 2 + } + ], + "indexes": [ + { + "name": "idx1", + "idxtype": "unique", + "idxfields": [ + "accounting_orgid", + "orgid", + "subjectid", + "org1id" + ] + } + ], + "codes": [ + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "orgid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "balance_at", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + }, + { + "field": "org1id", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} \ No newline at end of file diff --git a/models/account_config.json b/models/account_config.json new file mode 100644 index 0000000..3709c3b --- /dev/null +++ b/models/account_config.json @@ -0,0 +1,61 @@ +{ + "summary": [ + { + "name": "account_config", + "title": "账户配置表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目id", + "type": "str", + "length": 32 + }, + { + "name": "partytype", + "title": "主参与方类型", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "party1type", + "title": "从参与方类型", + "type": "str", + "length": 255, + "nullable": "yes" + } + ], + "codes": [ + { + "field": "party1type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "partytype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + } + ] +} \ No newline at end of file diff --git a/models/accounting_config.json b/models/accounting_config.json new file mode 100644 index 0000000..35de6f4 --- /dev/null +++ b/models/accounting_config.json @@ -0,0 +1,98 @@ +{ + "summary": [ + { + "name": "accounting_config", + "title": "记账配置表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "action", + "title": "交易", + "type": "str", + "length": 255 + }, + { + "name": "accounting_orgtype", + "title": "账务机构", + "type": "str", + "length": 256 + }, + { + "name": "accounting_dir", + "title": "记账方向", + "type": "str", + "length": 255 + }, + { + "name": "orgtype", + "title": "机构类型", + "type": "str", + "length": 32 + }, + { + "name": "org1type", + "title": "从机构类型", + "type": "str", + "length": 32, + "nullable": "yes" + }, + { + "name": "subjectid", + "title": "科目id", + "type": "str", + "length": 21 + }, + { + "name": "amt_pattern", + "title": "金额模板", + "type": "str", + "length": 255 + } + ], + "codes": [ + { + "field": "accounting_orgtype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "accounting_dir", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + }, + { + "field": "orgtype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "org1type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='partytype'" + }, + { + "field": "subjectid", + "table": "subject", + "valuefield": "id", + "textfield": "name" + } + ] +} \ No newline at end of file diff --git a/models/accounting_log.json b/models/accounting_log.json new file mode 100644 index 0000000..a7c5fea --- /dev/null +++ b/models/accounting_log.json @@ -0,0 +1,69 @@ +{ + "summary": [ + { + "name": "accounting_log", + "title": "账务流水表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accountid", + "title": "账户id", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "记账日期", + "type": "date" + }, + { + "name": "acc_timestamp", + "title": "记账时间戳", + "type": "timestamp" + }, + { + "name": "acc_dir", + "title": "记账方向", + "type": "str", + "length": 1 + }, + { + "name": "summary", + "title": "摘要", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "记账金额", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "billid", + "title": "账单id", + "type": "str", + "length": 32 + } + ], + "codes": [ + { + "field": "acc_dir", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='accounting_dir'" + } + ] +} \ No newline at end of file diff --git a/models/bill.json b/models/bill.json new file mode 100644 index 0000000..30f57fd --- /dev/null +++ b/models/bill.json @@ -0,0 +1,78 @@ +{ + "summary": [ + { + "name": "bill", + "title": "账单", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "customerid", + "title": "客户编号", + "type": "str", + "length": 32 + }, + { + "name": "resellerid", + "title": "商户id", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品id", + "type": "str", + "length": 32 + }, + { + "name": "resourceid", + "title": "资源id", + "type": "str", + "length": 32 + }, + { + "name": "orderid", + "title": "订单编号", + "type": "str", + "length": 32 + }, + { + "name": "business_op", + "title": "业务操作", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "金额", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "bill_date", + "title": "账单日期", + "type": "date" + }, + { + "name": "bill_timestamp", + "title": "账单时间戳", + "type": "timestamp" + }, + { + "name": "bill_state", + "title": "账单状态", + "type": "str", + "length": 1 + } + ] +} \ No newline at end of file diff --git a/models/bill_detail.json b/models/bill_detail.json new file mode 100644 index 0000000..ea799c0 --- /dev/null +++ b/models/bill_detail.json @@ -0,0 +1,68 @@ +{ + "summary": [ + { + "name": "bill_detail", + "title": "账单明细", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账务机构id", + "type": "str", + "length": 32 + }, + { + "name": "billid", + "title": "账单ID", + "type": "str", + "length": 32 + }, + { + "name": "description", + "title": "账务说明", + "type": "str", + "length": 255 + }, + { + "name": "participantid", + "title": "记账方id", + "type": "str", + "length": 32 + }, + { + "name": "participanttype", + "title": "记账方类型", + "type": "str", + "length": 255 + }, + { + "name": "subjectname", + "title": "科目名称", + "type": "str", + "length": 255 + }, + { + "name": "accounting_dir", + "title": "记账方向", + "type": "str", + "length": 255 + }, + { + "name": "amount", + "title": "账单金额", + "type": "float", + "length": 18, + "dec": 2 + } + ] +} \ No newline at end of file diff --git a/models/ledger.json b/models/ledger.json new file mode 100644 index 0000000..f3d3b16 --- /dev/null +++ b/models/ledger.json @@ -0,0 +1,50 @@ +{ + "summary": [ + { + "name": "ledger", + "title": "总账表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "accounting_orgid", + "title": "账本机构", + "type": "str", + "length": 32 + }, + { + "name": "subjectid", + "title": "科目号", + "type": "str", + "length": 32 + }, + { + "name": "acc_date", + "title": "账务日期", + "type": "date" + }, + { + "name": "d_balance", + "title": "借方余额", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "c_balance", + "title": "贷方余额", + "type": "float", + "length": 18, + "dec": 2 + } + ] +} \ No newline at end of file diff --git a/models/subject.json b/models/subject.json new file mode 100644 index 0000000..f660d9c --- /dev/null +++ b/models/subject.json @@ -0,0 +1,53 @@ +{ + "summary": [ + { + "name": "subject", + "title": "科目表", + "primary": [ + "id" + ] + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "科目名称", + "type": "str", + "length": 255 + }, + { + "name": "balance_side", + "title": "余额方向", + "type": "str", + "length": 1 + }, + { + "name": "subjecttype", + "title": "科目类别", + "type": "str", + "length": 30 + } + ], + "codes": [ + { + "field": "balance_side", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='balance_side'" + }, + { + "field": "subjecttype", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='subjecttype'" + } + ] +} \ No newline at end of file diff --git a/scripts/load_path.py b/scripts/load_path.py new file mode 100644 index 0000000..fdd29a7 --- /dev/null +++ b/scripts/load_path.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +accounting 模块 RBAC 权限管理脚本 + +使用方法: + cd ~/repos/sage + ./py3/bin/python ~/accounting/scripts/load_path.py + +每次代码变更如有新 path 出现,需同步更新此脚本。 +""" + +import subprocess +import os +import sys + +def find_sage_root(): + candidates = [ + os.path.expanduser("~/repos/sage"), + os.path.expanduser("~/sage"), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), + ] + for c in candidates: + if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")): + return c + return None + +SAGE_ROOT = find_sage_root() +if not SAGE_ROOT: + print("ERROR: Cannot find Sage root directory") + sys.exit(1) + +PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python") +SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py") + +MOD = "accounting" + +# ============================================================ +# 权限路径定义 — 每次新增页面或API时同步更新 +# ============================================================ + +# any — 无需登录(菜单、登录页等) +PATHS_ANY = [ + f"/accounting/usermenu.ui",] + +# logined — 需要认证的页面和 API +PATHS_LOGINED = [ + f"/accounting", + f"/accounting/acc_balance", + f"/accounting/acc_detail", + f"/accounting/accdetail.dspy", + f"/accounting/accdetail.ui", + f"/accounting/account", + f"/accounting/account_config", + f"/accounting/accounting_config", + f"/accounting/accounting_log", + f"/accounting/get_user_balance.dspy", + f"/accounting/index.ui", + f"/accounting/myaccounts.dspy", + f"/accounting/myaccounts.ui", + f"/accounting/mybalance.dspy", + f"/accounting/oca.dspy", + f"/accounting/open_customer_accounts.dspy", + f"/accounting/open_owner_accounts.dspy", + f"/accounting/open_provider_accounts.dspy", + f"/accounting/open_reseller_accounts.dspy", + f"/accounting/open_reseller_provider_accounts.dspy", + f"/accounting/stat_account_count.ui", + f"/accounting/stat_month_consumption.ui", + f"/accounting/stat_today_consumption.ui", + f"/accounting/stat_total_balance.ui", + f"/accounting/subject",] + +# ============================================================ +# 执行注册 +# ============================================================ + +def run_set_perm(role, path): + cmd = [PYTHON, SET_PERM_SCRIPT, role, path] + result = subprocess.run(cmd, capture_output=True, text=True) + return result.returncode == 0 + +def register_role_paths(role, paths): + count = 0 + for p in paths: + if run_set_perm(role, p): + count += 1 + print(f" {role}: {count}/{len(paths)} paths registered") + return count + +def main(): + print(f"Sage root: {SAGE_ROOT}") + total = 0 + total += register_role_paths("any", PATHS_ANY) + total += register_role_paths("logined", PATHS_LOGINED) + print(f"\nDone. Total {total} permission entries registered.") + print("NOTE: Restart Sage after permission changes to reload RBAC cache.") + +if __name__ == "__main__": + main() diff --git a/wwwroot/stat_account_count.ui b/wwwroot/stat_account_count.ui new file mode 100644 index 0000000..d2b49a3 --- /dev/null +++ b/wwwroot/stat_account_count.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "{{stats.account_count}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "账户数量", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_month_consumption.ui b/wwwroot/stat_month_consumption.ui new file mode 100644 index 0000000..8dcb266 --- /dev/null +++ b/wwwroot/stat_month_consumption.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.month_amount}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "本月消费", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_today_consumption.ui b/wwwroot/stat_today_consumption.ui new file mode 100644 index 0000000..4f4f5ee --- /dev/null +++ b/wwwroot/stat_today_consumption.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.today_amount}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "今日消费", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +} diff --git a/wwwroot/stat_total_balance.ui b/wwwroot/stat_total_balance.ui new file mode 100644 index 0000000..4687c21 --- /dev/null +++ b/wwwroot/stat_total_balance.ui @@ -0,0 +1,53 @@ +{% set stats = get_accounting_stats(request) %} +{ + "widgettype": "VBox", + "options": { + "bgcolor": "#1E293B", + "padding": "20px", + "borderRadius": "12px", + "border": "1px solid #334155", + "flex": "1", + "minHeight": "110px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "marginBottom": "12px" + }, + "subwidgets": [ + { + "widgettype": "Svg", + "options": { + "svg": "", + "width": "24px", + "height": "24px" + } + }, + { + "widgettype": "Filler" + } + ] + }, + { + "widgettype": "Text", + "options": { + "text": "¥{{'%.2f' % stats.total_balance}}", + "fontSize": "32px", + "fontWeight": "700", + "color": "#F1F5F9", + "lineHeight": "1.1" + } + }, + { + "widgettype": "Text", + "options": { + "text": "账户总余额", + "fontSize": "14px", + "color": "#94A3B8", + "marginTop": "4px" + } + } + ] +}