feat(credit): redesign credit limit UI with dashboard, overview and management views

- Add hub.ui as main entry with stat cards (total/used/available/usage%)
- Add credit_overview.ui for user's own credit visualization with progress bars
- Add credit_manage.ui for distributor sales to manage customer credits
- Add set_credit_form.ui and set_customer_credit.dspy for credit adjustment
- Add credit_summary.dspy API for stats data
- Enhance creditlimit.py with get_credit_stats, get_my_credit_list, get_all_customer_credits
- Register new functions in init.py with ServerEnv
This commit is contained in:
yumoqing 2026-05-30 21:00:27 +08:00
parent 195a7bfb46
commit 78ff190789
8 changed files with 1206 additions and 3 deletions

View File

@ -1,6 +1,103 @@
from appPublic.log import debug, exception
from appPublic.uniqueID import getID
from datetime import datetime
async def get_credit_stats(sor, orgid):
"""
Get credit summary statistics for an organization.
Returns total_credit, total_used, total_available, usage_pct, customer_count.
"""
sql = """
SELECT
COALESCE(SUM(credit_limit), 0) as total_credit,
COALESCE(SUM(used_credit), 0) as total_used,
COALESCE(SUM(available_credit), 0) as total_available,
COUNT(*) as customer_count,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count,
COUNT(CASE WHEN status = 'expired' THEN 1 END) as expired_count
FROM credit_limit
WHERE orgid = ${orgid}$
"""
recs = await sor.sqlExe(sql, {'orgid': orgid})
if recs and len(recs) > 0:
r = recs[0]
total_credit = float(r.total_credit or 0)
total_used = float(r.total_used or 0)
total_available = float(r.total_available or 0)
usage_pct = round((total_used / total_credit * 100), 1) if total_credit > 0 else 0
return {
'total_credit': total_credit,
'total_used': total_used,
'total_available': total_available,
'usage_pct': usage_pct,
'customer_count': int(r.customer_count or 0),
'active_count': int(r.active_count or 0),
'expired_count': int(r.expired_count or 0)
}
return {
'total_credit': 0, 'total_used': 0, 'total_available': 0,
'usage_pct': 0, 'customer_count': 0, 'active_count': 0, 'expired_count': 0
}
async def get_my_credit_list(sor, orgid):
"""
Get all credit limit records for the current user's organization,
with organization name and account info for display.
"""
sql = """
SELECT
cl.*,
org.orgname as orgname_text,
sub.name as subject_name,
CASE
WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1)
ELSE 0
END as usage_pct
FROM credit_limit cl
LEFT JOIN organization org ON cl.orgid = org.id
LEFT JOIN account acc ON cl.accountid = acc.id
LEFT JOIN subject sub ON acc.subjectid = sub.id
WHERE cl.orgid = ${orgid}$
ORDER BY cl.created_at DESC
"""
recs = await sor.sqlExe(sql, {'orgid': orgid})
return recs
async def get_all_customer_credits(sor, orgid, status_filter=None):
"""
Get all customer credit limits for management view.
For distributor sales to see all their customers' credit status.
"""
where_clause = "WHERE cl.orgid = ${orgid}$"
params = {'orgid': orgid}
if status_filter and status_filter != 'all':
where_clause += " AND cl.status = ${status}$"
params['status'] = status_filter
sql = f"""
SELECT
cl.*,
org.orgname as orgname_text,
sub.name as subject_name,
acc.balance as account_balance,
CASE
WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1)
ELSE 0
END as usage_pct
FROM credit_limit cl
LEFT JOIN organization org ON cl.orgid = org.id
LEFT JOIN account acc ON cl.accountid = acc.id
LEFT JOIN subject sub ON acc.subjectid = sub.id
{where_clause}
ORDER BY cl.updated_at DESC
"""
recs = await sor.sqlExe(sql, params)
return recs
async def get_credit_limit_for_account(sor, accid):
"""
Get active credit limit for an account.
@ -52,8 +149,6 @@ async def set_credit_limit(sor, accountid, orgid, credit_limit_amount,
Set or update credit limit for an account.
If a credit limit already exists, update it; otherwise create new.
"""
from appPublic.uniqueID import getID
# Check if credit limit exists
existing = await get_credit_limit_for_account(sor, accountid)

View File

@ -10,7 +10,7 @@ from .getaccount import getAccountBalance, getCustomerBalance, getAccountByName,
from .stats import get_accounting_stats
from .recharge import RechargeBiz, recharge_accounting
from .consume import consume_accounting
from .creditlimit import get_credit_limit_for_account, update_used_credit, set_credit_limit
from .creditlimit import get_credit_limit_for_account, update_used_credit, set_credit_limit, get_credit_stats, get_my_credit_list, get_all_customer_credits
async def all_my_accounts(request):
env = request._run_ns
@ -77,3 +77,34 @@ def load_accounting():
g.get_credit_limit_for_account = get_credit_limit_for_account
g.update_used_credit = update_used_credit
g.set_credit_limit = set_credit_limit
g.get_credit_stats = get_credit_stats
g.get_my_credit_list = get_my_credit_list
g.get_all_customer_credits = get_all_customer_credits
g.get_credit_stats_web = get_credit_stats_web
g.get_my_credits_web = get_my_credits_web
g.get_all_credits_web = get_all_credits_web
async def get_credit_stats_web(request):
"""Web wrapper for get_credit_stats - used in Jinja2 .ui templates"""
env = request._run_ns
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'accounting') as sor:
return await get_credit_stats(sor, userorgid)
async def get_my_credits_web(request):
"""Web wrapper for get_my_credit_list - used in Jinja2 .ui templates"""
env = request._run_ns
userorgid = await env.get_userorgid()
async with get_sor_context(env, 'accounting') as sor:
return await get_my_credit_list(sor, userorgid)
async def get_all_credits_web(request):
"""Web wrapper for get_all_customer_credits - used in Jinja2 .ui templates"""
env = request._run_ns
userorgid = await env.get_userorgid()
status_filter = getattr(request, '_params_kw', {}).get('status', None) if hasattr(request, '_params_kw') else None
async with get_sor_context(env, 'accounting') as sor:
return await get_all_customer_credits(sor, userorgid, status_filter)

View File

@ -0,0 +1,43 @@
orgid = await get_userorgid()
db = DBPools()
dbname = get_module_dbname('accounting')
async with db.sqlorContext(dbname) as sor:
sql = """
SELECT
COALESCE(SUM(credit_limit), 0) as total_credit,
COALESCE(SUM(used_credit), 0) as total_used,
COALESCE(SUM(available_credit), 0) as total_available,
COUNT(*) as customer_count,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count,
COUNT(CASE WHEN status = 'expired' THEN 1 END) as expired_count
FROM credit_limit
WHERE orgid = ${orgid}$
"""
recs = await sor.sqlExe(sql, {'orgid': orgid})
if recs and len(recs) > 0:
r = recs[0]
total_credit = float(r.total_credit or 0)
total_used = float(r.total_used or 0)
total_available = float(r.total_available or 0)
usage_pct = round((total_used / total_credit * 100), 1) if total_credit > 0 else 0
return json.dumps({
"status": "ok",
"data": {
"total_credit": total_credit,
"total_used": total_used,
"total_available": total_available,
"usage_pct": usage_pct,
"customer_count": int(r.customer_count or 0),
"active_count": int(r.active_count or 0),
"expired_count": int(r.expired_count or 0)
}
})
return json.dumps({
"status": "ok",
"data": {
"total_credit": 0, "total_used": 0, "total_available": 0,
"usage_pct": 0, "customer_count": 0, "active_count": 0, "expired_count": 0
}
})

View File

@ -0,0 +1,63 @@
{
"widgettype": "Form",
"options": {
"width": "100%",
"padding": "16px",
"url": "{{entire_url('/accounting/credit_limit/api/set_customer_credit.dspy')}}",
"method": "POST",
"fields": [
{
"name": "id",
"label": "id",
"uitype": "hidden",
"value": "{{params_kw.get('id', '')}}"
},
{
"name": "accountid",
"label": "账户ID",
"uitype": "str",
"required": true,
"value": "{{params_kw.get('accountid', '')}}"
},
{
"name": "credit_limit",
"label": "授信额度",
"uitype": "float",
"required": true,
"value": "{{params_kw.get('credit_limit', '0')}}"
},
{
"name": "valid_from",
"label": "生效日期",
"uitype": "date",
"value": "{{params_kw.get('valid_from', '')}}"
},
{
"name": "valid_to",
"label": "失效日期",
"uitype": "date",
"value": "{{params_kw.get('valid_to', '')}}"
},
{
"name": "remark",
"label": "备注",
"uitype": "str",
"value": "{{params_kw.get('remark', '')}}"
}
],
"buttons": [
{
"label": "保存",
"actiontype": "submit",
"bgcolor": "#3B82F6",
"color": "#FFFFFF"
},
{
"label": "取消",
"actiontype": "close",
"bgcolor": "#475569",
"color": "#FFFFFF"
}
]
}
}

View File

@ -0,0 +1,105 @@
ns = params_kw.copy()
for k, v in ns.items():
if v == 'NaN' or v == 'null' or v == '':
ns[k] = None
accountid = ns.get('accountid')
if not accountid:
return {
"widgettype": "Error",
"options": {
"title": "参数错误",
"cwidth": 16,
"cheight": 9,
"timeout": 3,
"message": "账户ID不能为空"
}
}
credit_limit_amount = float(ns.get('credit_limit', 0) or 0)
if credit_limit_amount <= 0:
return {
"widgettype": "Error",
"options": {
"title": "参数错误",
"cwidth": 16,
"cheight": 9,
"timeout": 3,
"message": "授信额度必须大于0"
}
}
valid_from = ns.get('valid_from')
valid_to = ns.get('valid_to')
remark = ns.get('remark')
record_id = ns.get('id')
user_id = await get_user()
orgid = await get_userorgid()
db = DBPools()
dbname = get_module_dbname('accounting')
async with db.sqlorContext(dbname) as sor:
if record_id:
sql = """
UPDATE credit_limit
SET credit_limit = ${credit_limit}$,
available_credit = ${credit_limit}$ - used_credit,
valid_from = ${valid_from}$,
valid_to = ${valid_to}$,
remark = ${remark}$,
updated_at = CURRENT_TIMESTAMP
WHERE id = ${id}$ AND orgid = ${orgid}$
"""
await sor.sqlExe(sql, {
'credit_limit': credit_limit_amount,
'valid_from': valid_from,
'valid_to': valid_to,
'remark': remark,
'id': record_id,
'orgid': orgid
})
debug(f'Updated credit limit {record_id} to {credit_limit_amount}')
else:
new_id = uuid()
now = datetime.now()
data = {
'id': new_id,
'accountid': accountid,
'orgid': orgid,
'credit_limit': credit_limit_amount,
'used_credit': 0,
'available_credit': credit_limit_amount,
'valid_from': valid_from,
'valid_to': valid_to,
'status': 'active',
'created_at': now,
'updated_at': now,
'created_by': user_id,
'remark': remark
}
await sor.C('credit_limit', data)
debug(f'Created credit limit for {accountid}: {credit_limit_amount}')
return {
"widgettype": "Message",
"options": {
"cwidth": 16,
"cheight": 9,
"title": "授信额度设置成功",
"timeout": 3,
"message": "ok"
}
}
return {
"widgettype": "Error",
"options": {
"title": "设置失败",
"cwidth": 16,
"cheight": 9,
"timeout": 3,
"message": "failed"
}
}

View File

@ -0,0 +1,276 @@
{% set credits = get_all_credits_web(request) %}
{
"widgettype": "VBox",
"options": {
"width": "100%",
"gap": "12px",
"padding": "4px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"justifyContent": "space-between"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "客户额度管理 (共{{credits|length}}条)",
"fontSize": "16px",
"fontWeight": "600",
"color": "#F1F5F9"
}
},
{
"widgettype": "Button",
"options": {
"label": "新增授信",
"bgcolor": "#3B82F6",
"color": "#FFFFFF",
"borderRadius": "6px",
"padding": "6px 14px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": "新增客户授信",
"width": "480px",
"height": "520px"
},
"options": {
"url": "{{entire_url('/accounting/credit_limit/api/set_credit_form.ui')}}"
}
}]
}
]
},
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"borderRadius": "10px",
"border": "1px solid #334155",
"width": "100%"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"bgcolor": "#0F172A",
"padding": "10px 16px",
"borderRadius": "10px 10px 0 0",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {"text": "客户名称", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 10}
},
{
"widgettype": "Text",
"options": {"text": "授信额度", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 8}
},
{
"widgettype": "Text",
"options": {"text": "已用/剩余", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 10}
},
{
"widgettype": "Text",
"options": {"text": "使用率", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 6}
},
{
"widgettype": "Text",
"options": {"text": "状态", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 4}
},
{
"widgettype": "Text",
"options": {"text": "操作", "fontSize": "12px", "fontWeight": "600", "color": "#94A3B8", "cwidth": 6}
}
]
},
{% if credits|length == 0 %}
{
"widgettype": "VBox",
"options": {
"padding": "30px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "暂无客户授信记录",
"fontSize": "14px",
"color": "#64748B"
}
}
]
}
{% else %}
{% for c in credits %}
{
"widgettype": "HBox",
"options": {
"padding": "12px 16px",
"alignItems": "center",
"border": "0 0 1px 0",
"borderColor": "#334155"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {"cwidth": 10, "gap": "2px"},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "{{c.orgname_text or c.accountid}}",
"fontSize": "13px",
"fontWeight": "500",
"color": "#F1F5F9"
}
},
{
"widgettype": "Text",
"options": {
"text": "{{c.subject_name or ''}}",
"fontSize": "11px",
"color": "#64748B"
}
}
]
},
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % c.credit_limit}}",
"fontSize": "13px",
"fontWeight": "600",
"color": "#3B82F6",
"cwidth": 8
}
},
{
"widgettype": "VBox",
"options": {"cwidth": 10, "gap": "2px"},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "已用 ¥{{'%.2f' % c.used_credit}}",
"fontSize": "12px",
"color": "#F59E0B"
}
},
{
"widgettype": "Text",
"options": {
"text": "剩余 ¥{{'%.2f' % c.available_credit}}",
"fontSize": "12px",
"color": "#22C55E"
}
}
]
},
{
"widgettype": "HBox",
"options": {"cwidth": 6, "alignItems": "center", "gap": "4px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"bgcolor": "#334155",
"borderRadius": "3px",
"height": "6px",
"width": "50px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"bgcolor": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}",
"borderRadius": "3px",
"height": "6px",
"width": "{{c.usage_pct}}%"
},
"subwidgets": []
}
]
},
{
"widgettype": "Text",
"options": {
"text": "{{c.usage_pct}}%",
"fontSize": "11px",
"color": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}"
}
}
]
},
{
"widgettype": "VBox",
"options": {"cwidth": 4},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "{{'生效' if c.status == 'active' else ('停用' if c.status == 'inactive' else '过期')}}",
"fontSize": "11px",
"fontWeight": "600",
"color": "{{'#22C55E' if c.status == 'active' else '#EF4444'}}"
}
}
]
},
{
"widgettype": "HBox",
"options": {"cwidth": 6, "gap": "4px"},
"subwidgets": [
{
"widgettype": "Button",
"options": {
"label": "调整",
"bgcolor": "#475569",
"color": "#FFFFFF",
"borderRadius": "4px",
"padding": "4px 8px",
"fontSize": "11px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": "调整授信额度",
"width": "480px",
"height": "520px"
},
"options": {
"url": "{{entire_url('/accounting/credit_limit/api/set_credit_form.ui')}}",
"params_kw": {
"id": "{{c.id}}",
"accountid": "{{c.accountid}}",
"credit_limit": "{{c.credit_limit}}",
"valid_from": "{{c.valid_from or ''}}",
"valid_to": "{{c.valid_to or ''}}",
"remark": "{{c.remark or ''}}"
}
}
}]
}
]
}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}
]
}
]
}

View File

@ -0,0 +1,289 @@
{% set credits = get_my_credits_web(request) %}
{
"widgettype": "VBox",
"options": {
"width": "100%",
"gap": "12px",
"padding": "4px"
},
"subwidgets": [
{% if credits|length == 0 %}
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "40px",
"borderRadius": "10px",
"border": "1px solid #334155",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "暂无信用额度记录",
"fontSize": "16px",
"color": "#94A3B8"
}
},
{
"widgettype": "Text",
"options": {
"text": "请联系您的分销商销售人员为您设置信用额度",
"fontSize": "13px",
"color": "#64748B",
"marginTop": "8px"
}
}
]
}
{% else %}
{% for c in credits %}
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "16px",
"borderRadius": "10px",
"border": "1px solid #334155",
"gap": "12px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"justifyContent": "space-between"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {"gap": "2px"},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "{{c.orgname_text or '未知客户'}}",
"fontSize": "16px",
"fontWeight": "600",
"color": "#F1F5F9"
}
},
{
"widgettype": "Text",
"options": {
"text": "{{c.subject_name or ''}} | 账户: {{c.accountid}}",
"fontSize": "12px",
"color": "#64748B"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"bgcolor": "{{'#16A34A22' if c.status == 'active' else '#EF444422'}}",
"padding": "4px 12px",
"borderRadius": "12px"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "{{'生效' if c.status == 'active' else ('停用' if c.status == 'inactive' else '已过期')}}",
"fontSize": "12px",
"fontWeight": "600",
"color": "{{'#22C55E' if c.status == 'active' else '#EF4444'}}"
}
}
]
}
]
},
{
"widgettype": "HBox",
"options": {
"gap": "12px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {"flex": "1", "gap": "4px"},
"subwidgets": [
{
"widgettype": "HBox",
"options": {"justifyContent": "space-between"},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "额度使用",
"fontSize": "12px",
"color": "#94A3B8"
}
},
{
"widgettype": "Text",
"options": {
"text": "{{c.usage_pct}}%",
"fontSize": "12px",
"fontWeight": "600",
"color": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}"
}
}
]
},
{
"widgettype": "HBox",
"options": {
"bgcolor": "#334155",
"borderRadius": "4px",
"height": "8px",
"width": "100%"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"bgcolor": "{{'#22C55E' if c.usage_pct < 60 else ('#F59E0B' if c.usage_pct < 85 else '#EF4444')}}",
"borderRadius": "4px",
"height": "8px",
"width": "{{c.usage_pct}}%"
},
"subwidgets": []
}
]
}
]
}
]
},
{
"widgettype": "HBox",
"options": {
"gap": "8px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"flex": "1",
"bgcolor": "#0F172A",
"padding": "10px",
"borderRadius": "6px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % c.credit_limit}}",
"fontSize": "16px",
"fontWeight": "700",
"color": "#3B82F6"
}
},
{
"widgettype": "Text",
"options": {
"text": "授信额度",
"fontSize": "11px",
"color": "#64748B",
"marginTop": "2px"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"flex": "1",
"bgcolor": "#0F172A",
"padding": "10px",
"borderRadius": "6px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % c.used_credit}}",
"fontSize": "16px",
"fontWeight": "700",
"color": "#F59E0B"
}
},
{
"widgettype": "Text",
"options": {
"text": "已用额度",
"fontSize": "11px",
"color": "#64748B",
"marginTop": "2px"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"flex": "1",
"bgcolor": "#0F172A",
"padding": "10px",
"borderRadius": "6px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % c.available_credit}}",
"fontSize": "16px",
"fontWeight": "700",
"color": "#22C55E"
}
},
{
"widgettype": "Text",
"options": {
"text": "剩余额度",
"fontSize": "11px",
"color": "#64748B",
"marginTop": "2px"
}
}
]
}
]
},
{
"widgettype": "HBox",
"options": {
"justifyContent": "space-between"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "有效期: {{c.valid_from or '不限'}} ~ {{c.valid_to or '不限'}}",
"fontSize": "11px",
"color": "#64748B"
}
},
{
"widgettype": "Text",
"options": {
"text": "更新于 {{c.updated_at}}",
"fontSize": "11px",
"color": "#475569"
}
}
]
}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}
]
}

301
wwwroot/credit_limit/hub.ui Normal file
View File

@ -0,0 +1,301 @@
{% set cstats = get_credit_stats_web(request) %}
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "16px",
"gap": "16px"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "信用额度管理",
"fontSize": "22px",
"fontWeight": "700",
"color": "#F1F5F9"
}
},
{
"widgettype": "ResponsableBox",
"options": {
"gap": "12px",
"minWidth": "200px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "16px",
"borderRadius": "10px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "100px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "8px"
},
"subwidgets": [
{
"widgettype": "Svg",
"options": {
"svg": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M12 2v20M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6\"/></svg>",
"width": "20px",
"height": "20px"
}
},
{"widgettype": "Filler"}
]
},
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % cstats.total_credit}}",
"fontSize": "26px",
"fontWeight": "700",
"color": "#3B82F6",
"lineHeight": "1.2"
}
},
{
"widgettype": "Text",
"options": {
"text": "授信总额度",
"fontSize": "13px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "16px",
"borderRadius": "10px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "100px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "8px"
},
"subwidgets": [
{
"widgettype": "Svg",
"options": {
"svg": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F59E0B\" stroke-width=\"2\"><path d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/><path d=\"M9 12l2 2 4-4\"/></svg>",
"width": "20px",
"height": "20px"
}
},
{"widgettype": "Filler"}
]
},
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % cstats.total_used}}",
"fontSize": "26px",
"fontWeight": "700",
"color": "#F59E0B",
"lineHeight": "1.2"
}
},
{
"widgettype": "Text",
"options": {
"text": "已用额度",
"fontSize": "13px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "16px",
"borderRadius": "10px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "100px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "8px"
},
"subwidgets": [
{
"widgettype": "Svg",
"options": {
"svg": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125H3.375a.75.75 0 01-.75-.75V4.5\"/></svg>",
"width": "20px",
"height": "20px"
}
},
{"widgettype": "Filler"}
]
},
{
"widgettype": "Text",
"options": {
"text": "¥{{'%.2f' % cstats.total_available}}",
"fontSize": "26px",
"fontWeight": "700",
"color": "#22C55E",
"lineHeight": "1.2"
}
},
{
"widgettype": "Text",
"options": {
"text": "剩余额度",
"fontSize": "13px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
},
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "16px",
"borderRadius": "10px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "100px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "8px"
},
"subwidgets": [
{
"widgettype": "Svg",
"options": {
"svg": "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#A78BFA\" stroke-width=\"2\"><path d=\"M3 3v18h18\"/><path d=\"M18.7 8l-5.1 5.2-2.8-2.7L7 14.3\"/></svg>",
"width": "20px",
"height": "20px"
}
},
{"widgettype": "Filler"}
]
},
{
"widgettype": "Text",
"options": {
"text": "{{cstats.usage_pct}}%",
"fontSize": "26px",
"fontWeight": "700",
"color": "#A78BFA",
"lineHeight": "1.2"
}
},
{
"widgettype": "Text",
"options": {
"text": "额度使用率 ({{cstats.active_count}}/{{cstats.customer_count}}户)",
"fontSize": "13px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
}
]
},
{
"widgettype": "HBox",
"options": {
"gap": "8px",
"alignItems": "center"
},
"subwidgets": [
{
"widgettype": "Button",
"options": {
"label": "我的额度",
"bgcolor": "#3B82F6",
"color": "#FFFFFF",
"borderRadius": "6px",
"padding": "8px 16px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.credit_content",
"options": {"url": "{{entire_url('/accounting/credit_limit/credit_overview.ui')}}"},
"mode": "replace"
}]
},
{
"widgettype": "Button",
"options": {
"label": "客户额度管理",
"bgcolor": "#475569",
"color": "#FFFFFF",
"borderRadius": "6px",
"padding": "8px 16px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.credit_content",
"options": {"url": "{{entire_url('/accounting/credit_limit/credit_manage.ui')}}"},
"mode": "replace"
}]
},
{
"widgettype": "Button",
"options": {
"label": "全部客户查询",
"bgcolor": "#475569",
"color": "#FFFFFF",
"borderRadius": "6px",
"padding": "8px 16px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.credit_content",
"options": {"url": "{{entire_url('/accounting/credit_limit/index.ui')}}"},
"mode": "replace"
}]
}
]
},
{
"widgettype": "VBox",
"id": "credit_content",
"options": {
"width": "100%",
"flex": "1"
}
}
]
}